Browse Source

Use "record" instead of "class" for C# 9 record class types.

pull/2251/head
Daniel Grunwald 5 years ago
parent
commit
f869756fed
  1. 4
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 3
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  3. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  4. 8
      ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
  6. 9
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  7. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  8. 5
      ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
  9. 36
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
  10. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs
  11. 2
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  12. 9
      ILSpy/Properties/Resources.Designer.cs
  13. 3
      ILSpy/Properties/Resources.resx

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -18,14 +18,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.OutputVisitor;
@ -415,6 +412,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.AddResolveResultAnnotations = true; typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors; typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors;
typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses;
return typeSystemAstBuilder; return typeSystemAstBuilder;
} }

3
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -80,6 +80,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case ClassType.Enum: case ClassType.Enum:
writer.WriteKeyword(Roles.EnumKeyword, "enum"); writer.WriteKeyword(Roles.EnumKeyword, "enum");
break; break;
case ClassType.RecordClass:
writer.WriteKeyword(Roles.RecordKeyword, "record");
break;
default: default:
throw new Exception("Invalid value for ClassType"); throw new Exception("Invalid value for ClassType");
} }

4
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1480,6 +1480,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword(Roles.StructKeyword); WriteKeyword(Roles.StructKeyword);
braceStyle = policy.StructBraceStyle; braceStyle = policy.StructBraceStyle;
break; break;
case ClassType.RecordClass:
WriteKeyword(Roles.RecordKeyword);
braceStyle = policy.ClassBraceStyle;
break;
default: default:
WriteKeyword(Roles.ClassKeyword); WriteKeyword(Roles.ClassKeyword);
braceStyle = policy.ClassBraceStyle; braceStyle = policy.ClassBraceStyle;

8
ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs

@ -33,7 +33,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Class, Class,
Struct, Struct,
Interface, Interface,
Enum Enum,
/// <summary>
/// C# 9 'record'
/// </summary>
RecordClass,
} }
/// <summary> /// <summary>
@ -63,6 +67,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return GetChildByRole(Roles.InterfaceKeyword); return GetChildByRole(Roles.InterfaceKeyword);
case ClassType.Enum: case ClassType.Enum:
return GetChildByRole(Roles.EnumKeyword); return GetChildByRole(Roles.EnumKeyword);
case ClassType.RecordClass:
return GetChildByRole(Roles.RecordKeyword);
default: default:
return CSharpTokenNode.Null; return CSharpTokenNode.Null;
} }

2
ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs

@ -87,7 +87,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly TokenRole InterfaceKeyword = new TokenRole("interface"); public static readonly TokenRole InterfaceKeyword = new TokenRole("interface");
public static readonly TokenRole StructKeyword = new TokenRole("struct"); public static readonly TokenRole StructKeyword = new TokenRole("struct");
public static readonly TokenRole ClassKeyword = new TokenRole("class"); public static readonly TokenRole ClassKeyword = new TokenRole("class");
public static readonly TokenRole RecordKeyword = new TokenRole("record");
} }
} }

9
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -213,6 +213,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// If disabled, emits "set /*init*/;" instead. /// If disabled, emits "set /*init*/;" instead.
/// </summary> /// </summary>
public bool SupportInitAccessors { get; set; } public bool SupportInitAccessors { get; set; }
/// <summary>
/// Controls whether C# 9 "record" class types are supported.
/// </summary>
public bool SupportRecordClasses { get; set; }
#endregion #endregion
#region Convert Type #region Convert Type
@ -1744,6 +1749,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
default: default:
classType = ClassType.Class; classType = ClassType.Class;
if (SupportRecordClasses && typeDefinition.IsRecord)
{
classType = ClassType.RecordClass;
}
break; break;
} }

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -135,12 +135,13 @@ namespace ICSharpCode.Decompiler
initAccessors = false; initAccessors = false;
functionPointers = false; functionPointers = false;
forEachWithGetEnumeratorExtension = false; forEachWithGetEnumeratorExtension = false;
recordClasses = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension) if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension || recordClasses)
return CSharp.LanguageVersion.Preview; return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement
|| staticLocalFunctions || ranges || switchExpressions) || staticLocalFunctions || ranges || switchExpressions)
@ -206,6 +207,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool recordClasses = true;
/// <summary>
/// Use C# 9 <c>init;</c> property accessors.
/// </summary>
[Category("C# 9.0 (experimental)")]
[Description("DecompilerSettings.RecordClasses")]
public bool RecordClasses {
get { return recordClasses; }
set {
if (recordClasses != value)
{
recordClasses = value;
OnPropertyChanged();
}
}
}
bool functionPointers = true; bool functionPointers = true;
/// <summary> /// <summary>

5
ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs

@ -73,5 +73,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// This serves as default nullability for members of the type that do not have a [Nullable] attribute. /// This serves as default nullability for members of the type that do not have a [Nullable] attribute.
/// </summary> /// </summary>
Nullability NullableContext { get; } Nullability NullableContext { get; }
/// <summary>
/// Gets whether the type has the necessary members to be considered a C# 9 record type.
/// </summary>
bool IsRecord { get; }
} }
} }

36
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs

@ -24,11 +24,8 @@ using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.TypeSystem.Implementation namespace ICSharpCode.Decompiler.TypeSystem.Implementation
@ -738,5 +735,38 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
return false; return false;
} }
#endregion #endregion
#region IsRecord
byte isRecord = ThreeState.Unknown;
public bool IsRecord {
get {
if (isRecord == ThreeState.Unknown)
{
isRecord = ThreeState.From(ComputeIsRecord());
}
return isRecord == ThreeState.True;
}
}
private bool ComputeIsRecord()
{
if (Kind != TypeKind.Class)
return false;
var metadata = module.metadata;
var typeDef = metadata.GetTypeDefinition(handle);
bool opEquality = false;
bool opInequality = false;
bool clone = false;
foreach (var methodHandle in typeDef.GetMethods())
{
var method = metadata.GetMethodDefinition(methodHandle);
opEquality |= metadata.StringComparer.Equals(method.Name, "op_Equality");
opInequality |= metadata.StringComparer.Equals(method.Name, "op_Inequality");
clone |= metadata.StringComparer.Equals(method.Name, "<Clone>$");
}
return opEquality & opInequality & clone;
}
#endregion
} }
} }

2
ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs

@ -302,6 +302,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
return EmptyList<IProperty>.Instance; return EmptyList<IProperty>.Instance;
} }
bool ITypeDefinition.IsRecord => false;
ITypeDefinition IType.GetDefinition() => this; ITypeDefinition IType.GetDefinition() => this;
TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity; TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity;

2
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -228,6 +227,7 @@ namespace ICSharpCode.ILSpy
case "class": case "class":
case "interface": case "interface":
case "delegate": case "delegate":
case "record":
color = referenceTypeKeywordsColor; color = referenceTypeKeywordsColor;
break; break;
case "select": case "select":

9
ILSpy/Properties/Resources.Designer.cs generated

@ -1064,6 +1064,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Records.
/// </summary>
public static string DecompilerSettings_RecordClasses {
get {
return ResourceManager.GetString("DecompilerSettings.RecordClasses", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Remove dead and side effect free code (use with caution!). /// Looks up a localized string similar to Remove dead and side effect free code (use with caution!).
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -384,6 +384,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.ReadOnlyMethods" xml:space="preserve"> <data name="DecompilerSettings.ReadOnlyMethods" xml:space="preserve">
<value>Read-only methods</value> <value>Read-only methods</value>
</data> </data>
<data name="DecompilerSettings.RecordClasses" xml:space="preserve">
<value>Records</value>
</data>
<data name="DecompilerSettings.RemoveDeadAndSideEffectFreeCodeUseWithCaution" xml:space="preserve"> <data name="DecompilerSettings.RemoveDeadAndSideEffectFreeCodeUseWithCaution" xml:space="preserve">
<value>Remove dead and side effect free code (use with caution!)</value> <value>Remove dead and side effect free code (use with caution!)</value>
</data> </data>

Loading…
Cancel
Save