Browse Source

Add support for C# 9 init accessors.

pull/2087/head
Daniel Grunwald 5 years ago
parent
commit
d388319cba
  1. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 6
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  3. 5
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs
  4. 1
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs
  5. 55
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  6. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  7. 6
      ICSharpCode.Decompiler/TypeSystem/IMethod.cs
  8. 1
      ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs
  9. 1
      ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs
  10. 22
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
  11. 3
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs
  12. 1
      ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs
  13. 1
      ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
  14. 1
      ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs
  15. 2
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -405,6 +405,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -405,6 +405,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.AlwaysUseShortTypeNames = true;
typeSystemAstBuilder.AddResolveResultAnnotations = true;
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors;
return typeSystemAstBuilder;
}

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

@ -1927,7 +1927,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1927,7 +1927,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword("get", PropertyDeclaration.GetKeywordRole);
style = policy.PropertyGetBraceStyle;
} else if (accessor.Role == PropertyDeclaration.SetterRole) {
WriteKeyword("set", PropertyDeclaration.SetKeywordRole);
if (accessor.Keyword.Role == PropertyDeclaration.InitKeywordRole) {
WriteKeyword("init", PropertyDeclaration.InitKeywordRole);
} else {
WriteKeyword("set", PropertyDeclaration.SetKeywordRole);
}
style = policy.PropertySetBraceStyle;
} else if (accessor.Role == CustomEventDeclaration.AddAccessorRole) {
WriteKeyword("add", CustomEventDeclaration.AddKeywordRole);

5
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs

@ -72,13 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -72,13 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
/// <summary>
/// Gets the 'get'/'set'/'add'/'remove' keyword
/// Gets the 'get'/'set'/'init'/'add'/'remove' keyword
/// </summary>
public CSharpTokenNode Keyword {
get {
for (AstNode child = this.FirstChild; child != null; child = child.NextSibling) {
if (child.Role == PropertyDeclaration.GetKeywordRole || child.Role == PropertyDeclaration.SetKeywordRole
|| child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole)
|| child.Role == PropertyDeclaration.InitKeywordRole
|| child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole)
{
return (CSharpTokenNode)child;
}

1
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs

@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public static readonly TokenRole GetKeywordRole = new TokenRole ("get");
public static readonly TokenRole SetKeywordRole = new TokenRole ("set");
public static readonly TokenRole InitKeywordRole = new TokenRole ("init");
public static readonly Role<Accessor> GetterRole = new Role<Accessor>("Getter", Accessor.Null);
public static readonly Role<Accessor> SetterRole = new Role<Accessor>("Setter", Accessor.Null);
public static readonly Role<Expression> ExpressionBodyRole = new Role<Expression>("ExpressionBody", Expression.Null);

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

@ -21,6 +21,7 @@ using System.Collections.Generic; @@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.Semantics;
@ -205,6 +206,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -205,6 +206,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// The default value is <see langword="false" />.
/// </summary>
public bool PrintIntegralValuesAsHex { get; set; }
/// <summary>
/// Controls whether C# 9 "init;" accessors are supported.
/// If disabled, emits "set /*init*/;" instead.
/// </summary>
public bool SupportInitAccessors { get; set; }
#endregion
#region Convert Type
@ -1363,7 +1370,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1363,7 +1370,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ConvertDestructor((IMethod)entity);
case SymbolKind.Accessor:
IMethod accessor = (IMethod)entity;
return ConvertAccessor(accessor, accessor.AccessorOwner != null ? accessor.AccessorOwner.Accessibility : Accessibility.None, false);
Accessibility ownerAccessibility = accessor.AccessorOwner?.Accessibility ?? Accessibility.None;
return ConvertAccessor(accessor, accessor.AccessorKind, ownerAccessibility, false);
default:
throw new ArgumentException("Invalid value for SymbolKind: " + entity.SymbolKind);
}
@ -1554,15 +1562,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1554,15 +1562,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
Accessor ConvertAccessor(IMethod accessor, Accessibility ownerAccessibility, bool addParameterAttribute)
Accessor ConvertAccessor(IMethod accessor, MethodSemanticsAttributes kind, Accessibility ownerAccessibility, bool addParameterAttribute)
{
if (accessor == null)
return Accessor.Null;
Accessor decl = new Accessor();
if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
if (accessor.HasReadonlyModifier())
decl.Modifiers |= Modifiers.Readonly;
if (ShowAttributes) {
decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes()));
decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return"));
@ -1570,10 +1574,35 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1570,10 +1574,35 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
decl.Attributes.AddRange(ConvertAttributes(accessor.Parameters.Last().GetAttributes(), "param"));
}
}
if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility)
decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility);
if (accessor.HasReadonlyModifier())
decl.Modifiers |= Modifiers.Readonly;
TokenRole keywordRole = kind switch
{
MethodSemanticsAttributes.Getter => PropertyDeclaration.GetKeywordRole,
MethodSemanticsAttributes.Setter => PropertyDeclaration.SetKeywordRole,
MethodSemanticsAttributes.Adder => CustomEventDeclaration.AddKeywordRole,
MethodSemanticsAttributes.Remover => CustomEventDeclaration.RemoveKeywordRole,
_ => null
};
if (kind == MethodSemanticsAttributes.Setter && SupportInitAccessors && accessor.IsInitOnly) {
keywordRole = PropertyDeclaration.InitKeywordRole;
}
if (keywordRole != null) {
decl.AddChild(new CSharpTokenNode(TextLocation.Empty, keywordRole), keywordRole);
}
if (accessor.IsInitOnly && keywordRole != PropertyDeclaration.InitKeywordRole) {
decl.AddChild(new Comment("init", CommentType.MultiLine), Roles.Comment);
}
if (AddResolveResultAnnotations) {
decl.AddAnnotation(new MemberResolveResult(null, accessor));
}
decl.Body = GenerateBodyBlock();
if (GenerateBody) {
decl.Body = GenerateBodyBlock();
} else {
decl.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.Semicolon), Roles.Semicolon);
}
return decl;
}
@ -1592,8 +1621,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1592,8 +1621,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ct.HasReadOnlySpecifier = true;
}
decl.Name = property.Name;
decl.Getter = ConvertAccessor(property.Getter, property.Accessibility, false);
decl.Setter = ConvertAccessor(property.Setter, property.Accessibility, true);
decl.Getter = ConvertAccessor(property.Getter, MethodSemanticsAttributes.Getter, property.Accessibility, false);
decl.Setter = ConvertAccessor(property.Setter, MethodSemanticsAttributes.Setter, property.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (property);
MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
@ -1624,8 +1653,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1624,8 +1653,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
foreach (IParameter p in indexer.Parameters) {
decl.Parameters.Add(ConvertParameter(p));
}
decl.Getter = ConvertAccessor(indexer.Getter, indexer.Accessibility, false);
decl.Setter = ConvertAccessor(indexer.Setter, indexer.Accessibility, true);
decl.Getter = ConvertAccessor(indexer.Getter, MethodSemanticsAttributes.Getter, indexer.Accessibility, false);
decl.Setter = ConvertAccessor(indexer.Setter, MethodSemanticsAttributes.Setter, indexer.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (indexer);
MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter);
return decl;
@ -1644,8 +1673,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1644,8 +1673,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
decl.ReturnType = ConvertType(ev.ReturnType);
decl.Name = ev.Name;
decl.AddAccessor = ConvertAccessor(ev.AddAccessor, ev.Accessibility, true);
decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, ev.Accessibility, true);
decl.AddAccessor = ConvertAccessor(ev.AddAccessor, MethodSemanticsAttributes.Adder, ev.Accessibility, true);
decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, MethodSemanticsAttributes.Remover, ev.Accessibility, true);
decl.PrivateImplementationType = GetExplicitInterfaceType (ev);
MergeReadOnlyModifiers(decl, decl.AddAccessor, decl.RemoveAccessor);
return decl;

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -116,12 +116,13 @@ namespace ICSharpCode.Decompiler @@ -116,12 +116,13 @@ namespace ICSharpCode.Decompiler
}
if (languageVersion < CSharp.LanguageVersion.Preview) {
nativeIntegers = false;
initAccessors = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (nativeIntegers)
if (nativeIntegers || initAccessors)
return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0;
@ -163,6 +164,23 @@ namespace ICSharpCode.Decompiler @@ -163,6 +164,23 @@ namespace ICSharpCode.Decompiler
}
}
bool initAccessors = true;
/// <summary>
/// Use C# 9 <c>init;</c> property accessors.
/// </summary>
[Category("C# 9.0 (experimental)")]
[Description("DecompilerSettings.InitAccessors")]
public bool InitAccessors {
get { return initAccessors; }
set {
if (initAccessors != value) {
initAccessors = value;
OnPropertyChanged();
}
}
}
bool anonymousMethods = true;
/// <summary>

6
ICSharpCode.Decompiler/TypeSystem/IMethod.cs

@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
bool ReturnTypeIsRefReadOnly { get; }
/// <summary>
/// Gets whether this method may only be called on fresh instances.
/// Used with C# 9 `init;` property setters.
/// </summary>
bool IsInitOnly { get; }
/// <summary>
/// Gets whether the method accepts the 'this' reference as ref readonly.
/// This can be either because the method is C# 8.0 'readonly', or because it is within a C# 7.2 'readonly struct'

1
ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs

@ -134,6 +134,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -134,6 +134,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IEnumerable<IAttribute> IMethod.GetReturnTypeAttributes() => EmptyList<IAttribute>.Instance;
bool IMethod.ReturnTypeIsRefReadOnly => false;
bool IMethod.ThisIsRefReadOnly => false;
bool IMethod.IsInitOnly => false;
public IReadOnlyList<ITypeParameter> TypeParameters { get; set; } = EmptyList<ITypeParameter>.Instance;

1
ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs

@ -147,6 +147,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -147,6 +147,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IEnumerable<IAttribute> IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes();
bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly;
bool IMethod.IsInitOnly => baseMethod.IsInitOnly;
/// <summary>
/// We consider local functions as always static, because they do not have a "this parameter".
/// Even local functions in instance methods capture this.

22
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs

@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IType returnType;
byte returnTypeIsRefReadonly = ThreeState.Unknown;
byte thisIsRefReadonly = ThreeState.Unknown;
bool isInitOnly;
internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle)
{
@ -150,6 +151,15 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -150,6 +151,15 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
}
}
public bool IsInitOnly {
get {
var returnType = LazyInit.VolatileRead(ref this.returnType);
if (returnType == null)
DecodeSignature();
return this.isInitOnly;
}
}
internal Nullability NullableContext {
get {
var methodDef = module.metadata.GetMethodDefinition(handle);
@ -163,19 +173,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -163,19 +173,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
var genericContext = new GenericContext(DeclaringType.TypeParameters, this.TypeParameters);
IType returnType;
IParameter[] parameters;
ModifiedType mod;
try {
var nullableContext = methodDef.GetCustomAttributes().GetNullableContext(module.metadata) ?? DeclaringTypeDefinition.NullableContext;
var signature = methodDef.DecodeSignature(module.TypeProvider, genericContext);
(returnType, parameters) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this));
(returnType, parameters, mod) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this));
} catch (BadImageFormatException) {
returnType = SpecialType.UnknownType;
parameters = Empty<IParameter>.Array;
mod = null;
}
this.isInitOnly = mod is { Modifier: { Name: "IsExternalInit", Namespace: "System.Runtime.CompilerServices" } };
LazyInit.GetOrSet(ref this.returnType, returnType);
LazyInit.GetOrSet(ref this.parameters, parameters);
}
internal static (IType, IParameter[]) DecodeSignature(MetadataModule module, IParameterizedMember owner,
internal static (IType returnType, IParameter[] parameters, ModifiedType returnTypeModifier) DecodeSignature(
MetadataModule module, IParameterizedMember owner,
MethodSignature<IType> signature, ParameterHandleCollection? parameterHandles,
Nullability nullableContext, TypeSystemOptions typeSystemOptions,
CustomAttributeHandleCollection? returnTypeAttributes = null)
@ -231,7 +245,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -231,7 +245,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
Debug.Assert(i == parameters.Length);
var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType,
module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext);
return (returnType, parameters);
return (returnType, parameters, signature.ReturnType as ModifiedType);
}
#endregion
@ -453,7 +467,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -453,7 +467,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
#endregion
public Accessibility Accessibility => GetAccessibility(attributes);
internal static Accessibility GetAccessibility(MethodAttributes attr)
{
switch (attr & MethodAttributes.MemberAccessMask) {

3
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs

@ -153,7 +153,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -153,7 +153,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
// Roslyn uses the same workaround (see the NullableTypeDecoder.TransformType
// call in PEPropertySymbol).
var typeOptions = module.OptionsForEntity(declTypeDef);
(returnType, parameters) = MetadataMethod.DecodeSignature(module, this, signature,
(returnType, parameters, _) = MetadataMethod.DecodeSignature(
module, this, signature,
parameterHandles, nullableContext, typeOptions,
returnTypeAttributes: propertyDef.GetCustomAttributes());
} catch (BadImageFormatException) {

1
ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs

@ -98,6 +98,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -98,6 +98,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public bool ReturnTypeIsRefReadOnly => methodDefinition.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => methodDefinition.ThisIsRefReadOnly;
bool IMethod.IsInitOnly => methodDefinition.IsInitOnly;
public IReadOnlyList<ITypeParameter> TypeParameters {
get {

1
ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs

@ -62,6 +62,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -62,6 +62,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly;
bool IMethod.IsInitOnly => underlyingMethod.IsInitOnly;
IReadOnlyList<ITypeParameter> IMethod.TypeParameters => EmptyList<ITypeParameter>.Instance;
IReadOnlyList<IType> IMethod.TypeArguments => EmptyList<IType>.Instance;

1
ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs

@ -115,6 +115,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -115,6 +115,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
IEnumerable<IAttribute> IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes();
bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly;
bool IMethod.IsInitOnly => baseMethod.IsInitOnly;
public IReadOnlyList<ITypeParameter> TypeParameters {
get { return baseMethod.TypeParameters; }

2
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -184,8 +184,10 @@ namespace ICSharpCode.ILSpy @@ -184,8 +184,10 @@ namespace ICSharpCode.ILSpy
case "set":
case "add":
case "remove":
case "init":
if (role == PropertyDeclaration.GetKeywordRole ||
role == PropertyDeclaration.SetKeywordRole ||
role == PropertyDeclaration.InitKeywordRole ||
role == CustomEventDeclaration.AddKeywordRole ||
role == CustomEventDeclaration.RemoveKeywordRole)
color = accessorKeywordsColor;

Loading…
Cancel
Save