From 02d2a8c1f875cc2dd4c15a0e3f1079658d9ef76e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 16:02:32 +0200 Subject: [PATCH] Add metadata processing for C# 12 'ref readonly' parameters --- .../Helpers/Tester.cs | 2 +- .../TestCases/Pretty/RefLocalsAndReturns.cs | 18 ++++++++++++++ .../CSharp/CSharpLanguageVersion.cs | 1 + .../CSharp/ExpressionBuilder.cs | 8 ++++++- .../CSharp/Transforms/DeclareVariables.cs | 6 ++++- .../Transforms/EscapeInvalidIdentifiers.cs | 1 + ICSharpCode.Decompiler/DecompilerSettings.cs | 24 +++++++++++++++++++ .../TypeSystem/DecompilerTypeSystem.cs | 2 ++ .../Implementation/AttributeListBuilder.cs | 3 +++ .../Implementation/KnownAttributes.cs | 2 ++ .../Implementation/MetadataParameter.cs | 8 +++++++ ILSpy/Languages/CSharpLanguage.cs | 1 + ILSpy/Properties/Resources.Designer.cs | 18 +++++++------- ILSpy/Properties/Resources.resx | 3 +++ 14 files changed, 85 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 92eb339f6..709bf4cae 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -596,7 +596,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6, CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3, CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0, - _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp11_0, + _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp12_0, }; DecompilerSettings settings = new(langVersion) { // Never use file-scoped namespaces diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index f4c0f1187..d3e2f75c6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -320,5 +320,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty LastOrDefault() = 10000; Console.WriteLine(ElementAtOrDefault(-5)); } + +#if CS120 + public ref readonly int M(in int x) + { + return ref x; + } + public ref readonly int M2(ref readonly int x) + { + return ref x; + } + + public void Test() + { + int x = 32; + M(in x); + M2(in x); } +#endif +} } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs index 87314b0c4..019774580 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs @@ -34,6 +34,7 @@ namespace ICSharpCode.Decompiler.CSharp CSharp9_0 = 900, CSharp10_0 = 1000, CSharp11_0 = 1100, + CSharp12_0 = 1200, Preview = 1100, Latest = 0x7FFFFFFF } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index ce410ab49..a5127c6fb 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4188,7 +4188,13 @@ namespace ICSharpCode.Decompiler.CSharp { if (!(input.Expression is DirectionExpression dirExpr && input.ResolveResult is ByReferenceResolveResult brrr)) return input; - dirExpr.FieldDirection = (FieldDirection)kind; + dirExpr.FieldDirection = kind switch { + ReferenceKind.Ref => FieldDirection.Ref, + ReferenceKind.Out => FieldDirection.Out, + ReferenceKind.In => FieldDirection.In, + ReferenceKind.RefReadOnly => FieldDirection.In, + _ => throw new NotSupportedException("Unsupported reference kind: " + kind) + }; dirExpr.RemoveAnnotations(); if (brrr.ElementResult == null) brrr = new ByReferenceResolveResult(brrr.ElementType, kind); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index b17960e36..c84d9e81a 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -205,7 +205,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { foreach (var stmt in rootNode.DescendantsAndSelf.OfType()) { - if (!IsValidInStatementExpression(stmt.Expression)) + if (stmt.Expression is DirectionExpression dir && IsValidInStatementExpression(dir.Expression)) + { + stmt.Expression = dir.Expression.Detach(); + } + else if (!IsValidInStatementExpression(stmt.Expression)) { // fetch ILFunction var function = stmt.Ancestors.SelectMany(a => a.Annotations.OfType()).First(f => f.Parent == null); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 2b1dc7f1d..828d220af 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -168,6 +168,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms "System.Runtime.CompilerServices.NativeIntegerAttribute", "System.Runtime.CompilerServices.RefSafetyRulesAttribute", "System.Runtime.CompilerServices.ScopedRefAttribute", + "System.Runtime.CompilerServices.RequiresLocationAttribute", "Microsoft.CodeAnalysis.EmbeddedAttribute", }; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 533eb1ed3..c9d2e77ab 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -159,10 +159,16 @@ namespace ICSharpCode.Decompiler unsignedRightShift = false; checkedOperators = false; } + if (languageVersion < CSharp.LanguageVersion.CSharp12_0) + { + refReadOnlyParameters = false; + } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { + if (refReadOnlyParameters) + return CSharp.LanguageVersion.CSharp12_0; if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) @@ -1991,6 +1997,24 @@ namespace ICSharpCode.Decompiler } } + bool refReadOnlyParameters = true; + + /// + /// Gets/sets whether RequiresLocationAttribute on parameters should be replaced with 'ref readonly' modifiers. + /// + [Category("C# 12.0 / VS 2022.8")] + [Description("DecompilerSettings.RefReadOnlyParameters")] + public bool RefReadOnlyParameters { + get { return refReadOnlyParameters; } + set { + if (refReadOnlyParameters != value) + { + refReadOnlyParameters = value; + OnPropertyChanged(); + } + } + } + bool separateLocalVariableDeclarations = false; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 5906c97ea..896f9e802 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -183,6 +183,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.ScopedRef; if (settings.NumericIntPtr) typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute; + if (settings.RefReadOnlyParameters) + typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index cea813250..66d77a735 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -255,6 +255,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation case "ScopedRefAttribute": return (options & TypeSystemOptions.ScopedRef) != 0 && (target == SymbolKind.Parameter); + case "RequiresLocationAttribute": + return (options & TypeSystemOptions.RefReadOnlyParameters) != 0 + && (target == SymbolKind.Parameter); default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 59dfc5863..59d2c19eb 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -95,6 +95,7 @@ namespace ICSharpCode.Decompiler.TypeSystem CallerFilePath, CallerLineNumber, ScopedRef, + RequiresLocation, // Type parameter attributes: IsUnmanaged, @@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerFilePathAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerLineNumberAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", "ScopedRefAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "RequiresLocationAttribute"), // 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 294d8d124..40b4e1f06 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -105,6 +105,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly)) return ReferenceKind.In; } + if ((module.TypeSystemOptions & TypeSystemOptions.RefReadOnlyParameters) != 0 + && (attributes & inOut) == ParameterAttributes.In) + { + var metadata = module.metadata; + var parameterDef = metadata.GetParameter(handle); + if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.RequiresLocation)) + return ReferenceKind.RefReadOnly; + } return ReferenceKind.Ref; } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index a7fc1cb55..212d4f870 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -114,6 +114,7 @@ namespace ICSharpCode.ILSpy 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.4"), + new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"), }; } return versions; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 1866a7b29..54ec14ad5 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1154,15 +1154,6 @@ namespace ICSharpCode.ILSpy.Properties { } } - /// - /// Looks up a localized string similar to Use parameter null checking. - /// - public static string DecompilerSettings_ParameterNullCheck { - get { - return ResourceManager.GetString("DecompilerSettings.ParameterNullCheck", resourceCulture); - } - } - /// /// Looks up a localized string similar to Pattern combinators (and, or, not). /// @@ -1235,6 +1226,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to 'ref readonly' parameters. + /// + public static string DecompilerSettings_RefReadOnlyParameters { + get { + return ResourceManager.GetString("DecompilerSettings.RefReadOnlyParameters", resourceCulture); + } + } + /// /// Looks up a localized string similar to Relational patterns. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 19b1cc2b8..096c9d74b 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -432,6 +432,9 @@ Are you sure you want to continue? Recursive pattern matching + + 'ref readonly' parameters + Relational patterns