From 2ed9ad6b51110acfa5430a50f34b985c9c4eb1ac Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 16 Jul 2022 16:20:18 +0200 Subject: [PATCH] Add support for C# 11 scoped parameter modifier. --- .../OutputVisitor/CSharpOutputVisitor.cs | 10 ++++++ .../TypeMembers/ParameterDeclaration.cs | 32 ++++++++++++++----- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 2 ++ ICSharpCode.Decompiler/DecompilerSettings.cs | 22 ++++++++++++- .../TypeSystem/DecompilerTypeSystem.cs | 9 +++++- .../TypeSystem/IParameter.cs | 13 +++++++- .../Implementation/AttributeListBuilder.cs | 3 ++ .../Implementation/DefaultParameter.cs | 2 ++ .../Implementation/KnownAttributes.cs | 2 ++ .../Implementation/MetadataParameter.cs | 32 +++++++++++++++++++ .../Implementation/SpecializedParameter.cs | 2 ++ .../CSharpHighlightingTokenWriter.cs | 1 + ILSpy/Languages/CSharpLanguage.cs | 2 +- ILSpy/Properties/Resources.Designer.cs | 20 +++++++++++- ILSpy/Properties/Resources.resx | 8 ++++- 15 files changed, 146 insertions(+), 14 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 4b7bcd93f..5cd4bd8da 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2565,6 +2565,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteKeyword(ParameterDeclaration.ThisModifierRole); Space(); } + if (parameterDeclaration.IsRefScoped) + { + WriteKeyword(ParameterDeclaration.RefScopedRole); + Space(); + } switch (parameterDeclaration.ParameterModifier) { case ParameterModifier.Ref: @@ -2584,6 +2589,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor Space(); break; } + if (parameterDeclaration.IsValueScoped) + { + WriteKeyword(ParameterDeclaration.ValueScopedRole); + Space(); + } parameterDeclaration.Type.AcceptVisitor(this); if (!parameterDeclaration.Type.IsNull && !string.IsNullOrEmpty(parameterDeclaration.Name)) { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs index 98ae6bd75..8e1dca003 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs @@ -34,17 +34,20 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Ref, Out, Params, - In + In, + Scoped } public class ParameterDeclaration : AstNode { public static readonly Role AttributeRole = EntityDeclaration.AttributeRole; + public static readonly TokenRole ThisModifierRole = new TokenRole("this"); + public static readonly TokenRole RefScopedRole = new TokenRole("scoped"); public static readonly TokenRole RefModifierRole = new TokenRole("ref"); public static readonly TokenRole OutModifierRole = new TokenRole("out"); - public static readonly TokenRole ParamsModifierRole = new TokenRole("params"); - public static readonly TokenRole ThisModifierRole = new TokenRole("this"); public static readonly TokenRole InModifierRole = new TokenRole("in"); + public static readonly TokenRole ValueScopedRole = new TokenRole("scoped"); + public static readonly TokenRole ParamsModifierRole = new TokenRole("params"); #region PatternPlaceholder public static implicit operator ParameterDeclaration?(PatternMatching.Pattern pattern) @@ -92,17 +95,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } #endregion - public override NodeType NodeType { - get { - return NodeType.Unknown; - } - } + public override NodeType NodeType => NodeType.Unknown; public AstNodeCollection Attributes { get { return GetChildrenByRole(AttributeRole); } } bool hasThisModifier; + bool isRefScoped, isValueScoped; public CSharpTokenNode ThisKeyword { get { @@ -122,6 +122,22 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + public bool IsRefScoped { + get { return isRefScoped; } + set { + ThrowIfFrozen(); + isRefScoped = value; + } + } + + public bool IsValueScoped { + get { return isValueScoped; } + set { + ThrowIfFrozen(); + isValueScoped = value; + } + } + ParameterModifier parameterModifier; public ParameterModifier ParameterModifier { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 9106e7850..dc2dd2aee 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1649,6 +1649,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { decl.ParameterModifier = ParameterModifier.Params; } + decl.IsRefScoped = parameter.Lifetime.RefScoped; + decl.IsValueScoped = parameter.Lifetime.ValueScoped; if (ShowAttributes) { decl.Attributes.AddRange(ConvertAttributes(parameter.GetAttributes())); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index e4316ce1f..74d48ce30 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -150,12 +150,13 @@ namespace ICSharpCode.Decompiler if (languageVersion < CSharp.LanguageVersion.CSharp11_0) { parameterNullCheck = false; + lifetimeAnnotations = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck) + if (parameterNullCheck || lifetimeAnnotations) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -336,6 +337,25 @@ namespace ICSharpCode.Decompiler } } + bool lifetimeAnnotations = true; + + /// + /// Use C# 9 delegate* unmanaged types. + /// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.LifetimeAnnotations")] + public bool LifetimeAnnotations { + get { return lifetimeAnnotations; } + set { + if (lifetimeAnnotations != value) + { + lifetimeAnnotations = value; + OnPropertyChanged(); + } + } + } + bool switchExpressions = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 234b8be09..b20d1bb0e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -120,11 +120,16 @@ namespace ICSharpCode.Decompiler.TypeSystem /// FunctionPointers = 0x2000, /// + /// Allow C# 11 scoped annotation. If this option is not enabled, LifetimeAnnotationAttribute + /// will be reported as custom attribute. + /// + LifetimeAnnotations = 0x4000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods - | NativeIntegers | FunctionPointers + | NativeIntegers | FunctionPointers | LifetimeAnnotations } /// @@ -160,6 +165,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.NativeIntegers; if (settings.FunctionPointers) typeSystemOptions |= TypeSystemOptions.FunctionPointers; + if (settings.LifetimeAnnotations) + typeSystemOptions |= TypeSystemOptions.LifetimeAnnotations; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index b28b4db09..59025af0f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// /// Should match order in . /// - public enum ReferenceKind + public enum ReferenceKind : byte { None, Out, @@ -34,6 +34,12 @@ namespace ICSharpCode.Decompiler.TypeSystem In } + public struct LifetimeAnnotation + { + public bool RefScoped; + public bool ValueScoped; + } + public interface IParameter : IVariable { /// @@ -46,6 +52,11 @@ namespace ICSharpCode.Decompiler.TypeSystem /// ReferenceKind ReferenceKind { get; } + /// + /// C# 11 scoped annotation. + /// + LifetimeAnnotation Lifetime { get; } + /// /// Gets whether this parameter is a C# 'ref' parameter. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index d60aa4bac..e5b62ad9b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -246,6 +246,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation case "NullableContextAttribute": return (options & TypeSystemOptions.NullabilityAnnotations) != 0 && (target == SymbolKind.TypeDefinition || IsMethodLike(target)); + case "LifetimeAnnotationAttribute": + return (options & TypeSystemOptions.LifetimeAnnotations) != 0 + && (target == SymbolKind.Parameter); default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs index b03fcb3f8..e3a1a8a25 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs @@ -100,6 +100,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation get { return IsOptional; } } + public LifetimeAnnotation Lifetime => default; + public object GetConstantValue(bool throwOnInvalidMetadata) { return defaultValue; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index e54b32e0b..21f805500 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -91,6 +91,7 @@ namespace ICSharpCode.Decompiler.TypeSystem CallerMemberName, CallerFilePath, CallerLineNumber, + LifetimeAnnotation, // Type parameter attributes: IsUnmanaged, @@ -162,6 +163,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerMemberNameAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerFilePathAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerLineNumberAttribute)), + new TopLevelTypeName("System.Runtime.CompilerServices", "LifetimeAnnotationAttribute"), // Type parameter attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "IsUnmanagedAttribute"), // Marshalling attributes: diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index 5820d9c21..adcf82540 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -106,6 +106,38 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return ReferenceKind.Ref; } + public LifetimeAnnotation Lifetime { + get { + if ((module.TypeSystemOptions & TypeSystemOptions.LifetimeAnnotations) == 0) + { + return default; + } + + var metadata = module.metadata; + var parameterDef = metadata.GetParameter(handle); + foreach (var h in parameterDef.GetCustomAttributes()) + { + var custom = metadata.GetCustomAttribute(h); + if (!custom.IsKnownAttribute(metadata, KnownAttribute.LifetimeAnnotation)) + continue; + + var value = custom.DecodeValue(module.TypeProvider); + if (value.FixedArguments.Length != 2) + continue; + if (value.FixedArguments[0].Value is bool refScoped + && value.FixedArguments[1].Value is bool valueScoped) + { + return new LifetimeAnnotation { + RefScoped = refScoped, + ValueScoped = valueScoped + }; + } + } + + return default; + } + } + public bool IsParams { get { if (Type.Kind != TypeKind.Array) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs index 2c3fa6a90..a05340d3a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs @@ -51,6 +51,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation object IVariable.GetConstantValue(bool throwOnInvalidMetadata) => baseParameter.GetConstantValue(throwOnInvalidMetadata); SymbolKind ISymbol.SymbolKind => SymbolKind.Parameter; + public LifetimeAnnotation Lifetime => baseParameter.Lifetime; + public override string ToString() { return DefaultParameter.ToString(this); diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index c3ca4c165..e4ce9eba6 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -262,6 +262,7 @@ namespace ICSharpCode.ILSpy case "params": case "ref": case "out": + case "scoped": color = parameterModifierColor; break; case "break": diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index fdbccd101..2e6f3343f 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -115,7 +115,7 @@ namespace ICSharpCode.ILSpy new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp8_0.ToString(), "C# 8.0 / VS 2019"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp9_0.ToString(), "C# 9.0 / VS 2019.8"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp10_0.ToString(), "C# 10.0 / VS 2022"), - new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.1"), + new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"), }; } return versions; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index be034c88e..2394d593c 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1073,6 +1073,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to 'scoped' lifetime annotation. + /// + public static string DecompilerSettings_LifetimeAnnotations { + get { + return ResourceManager.GetString("DecompilerSettings.LifetimeAnnotations", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use nint/nuint types. /// @@ -1164,7 +1173,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Records. + /// Looks up a localized string similar to Record classes. /// public static string DecompilerSettings_RecordClasses { get { @@ -1172,6 +1181,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Record structs. + /// + public static string DecompilerSettings_RecordStructs { + get { + return ResourceManager.GetString("DecompilerSettings.RecordStructs", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove dead and side effect free code (use with caution!). /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index cda648ceb..a446434e6 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -381,6 +381,9 @@ Are you sure you want to continue? IsUnmanagedAttribute on type parameters should be replaced with 'unmanaged' constraints + + 'scoped' lifetime annotation + Use nint/nuint types @@ -412,7 +415,10 @@ Are you sure you want to continue? Read-only methods - Records + Record classes + + + Record structs Remove dead and side effect free code (use with caution!)