diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 0c18b39a3..40517bfc8 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -405,6 +405,7 @@ namespace ICSharpCode.Decompiler.CSharp typeSystemAstBuilder.AlwaysUseShortTypeNames = true; typeSystemAstBuilder.AddResolveResultAnnotations = true; typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; + typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors; return typeSystemAstBuilder; } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 81f3f0c6e..1fc2b44bd 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -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); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs index dd41e83b7..345aa9aca 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs @@ -72,13 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } /// - /// Gets the 'get'/'set'/'add'/'remove' keyword + /// Gets the 'get'/'set'/'init'/'add'/'remove' keyword /// 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; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs index 00f163122..34ac8039a 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs @@ -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 GetterRole = new Role("Getter", Accessor.Null); public static readonly Role SetterRole = new Role("Setter", Accessor.Null); public static readonly Role ExpressionBodyRole = new Role("ExpressionBody", Expression.Null); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 33464c99a..1f944a664 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -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 /// The default value is . /// public bool PrintIntegralValuesAsHex { get; set; } + + /// + /// Controls whether C# 9 "init;" accessors are supported. + /// If disabled, emits "set /*init*/;" instead. + /// + public bool SupportInitAccessors { get; set; } #endregion #region Convert Type @@ -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 } } - 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 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 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 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 } 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; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index e1b158b0a..26bd075b0 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -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 } } + bool initAccessors = true; + + /// + /// Use C# 9 init; property accessors. + /// + [Category("C# 9.0 (experimental)")] + [Description("DecompilerSettings.InitAccessors")] + public bool InitAccessors { + get { return initAccessors; } + set { + if (initAccessors != value) { + initAccessors = value; + OnPropertyChanged(); + } + } + } + bool anonymousMethods = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index ad1c177d6..b12b4f064 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.TypeSystem /// bool ReturnTypeIsRefReadOnly { get; } + /// + /// Gets whether this method may only be called on fresh instances. + /// Used with C# 9 `init;` property setters. + /// + bool IsInitOnly { get; } + /// /// 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' diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 2a11babee..837539720 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -134,6 +134,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance; bool IMethod.ReturnTypeIsRefReadOnly => false; bool IMethod.ThisIsRefReadOnly => false; + bool IMethod.IsInitOnly => false; public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 1139366f1..1e14fa85b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -147,6 +147,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => baseMethod.IsInitOnly; /// /// We consider local functions as always static, because they do not have a "this parameter". /// Even local functions in instance methods capture this. diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index ab02b8252..a015d7d3d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -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 } } + 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 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.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 signature, ParameterHandleCollection? parameterHandles, Nullability nullableContext, TypeSystemOptions typeSystemOptions, CustomAttributeHandleCollection? returnTypeAttributes = null) @@ -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 #endregion public Accessibility Accessibility => GetAccessibility(attributes); - + internal static Accessibility GetAccessibility(MethodAttributes attr) { switch (attr & MethodAttributes.MemberAccessMask) { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs index 83596cb51..2f33e3d17 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs @@ -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) { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index 58d1e2f56..7ec70de21 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -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 TypeParameters { get { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs index 9d79e1b79..0acaf9d28 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs @@ -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 IMethod.TypeParameters => EmptyList.Instance; IReadOnlyList IMethod.TypeArguments => EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index ecc0cbf22..6b53caa88 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -115,6 +115,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => baseMethod.IsInitOnly; public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; } diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index 6845088a3..f588cd153 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -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;