From 84704a7452f23ba2d8def22e3a8157b177b825d7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 31 Jul 2021 22:42:49 +0200 Subject: [PATCH] Adds support for C# 9 covariant return types in methods and getter-only properties and indexers. --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 +++ .../TestCases/Pretty/CovariantReturns.cs | 50 +++++++++++++++++++ .../CSharp/CSharpDecompiler.cs | 23 +++++++++ ICSharpCode.Decompiler/DecompilerSettings.cs | 22 +++++++- .../Implementation/KnownAttributes.cs | 4 +- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/CovariantReturns.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index e0334ffcb..71edc5762 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -107,6 +107,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 49d82222c..30ee7b18d 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -573,6 +573,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } + [Test] + public void CovariantReturns([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + } + void RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) { Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CovariantReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CovariantReturns.cs new file mode 100644 index 000000000..11987dde5 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CovariantReturns.cs @@ -0,0 +1,50 @@ +namespace ICSharpCode.Decompiler.Tests.TestCases.CovariantReturns +{ + public abstract class Base + { + public abstract Base Instance { get; } + + public abstract Base this[int index] { get; } + + public virtual Base Build() + { + throw null; + } + + protected abstract Base SetParent(object parent); + } + + public class Derived : Base + { + public override Derived Instance { get; } + + public override Derived this[int index] { + get { + throw null; + } + } + + public override Derived Build() + { + throw null; + } + + protected override Derived SetParent(object parent) + { + throw null; + } + } + + public class UseSites + { + public Base Test(Base x) + { + return x.Build(); + } + + public Derived Test(Derived x) + { + return x.Build(); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 676c924d4..fe1ea0ee9 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1451,9 +1451,24 @@ namespace ICSharpCode.Decompiler.CSharp { SetNewModifier(methodDecl); } + if (IsCovariantReturnOverride(method)) + { + RemoveAttribute(methodDecl, KnownAttribute.PreserveBaseOverrides); + methodDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual); + methodDecl.Modifiers |= Modifiers.Override; + } return methodDecl; } + private bool IsCovariantReturnOverride(IEntity entity) + { + if (!settings.CovariantReturns) + return false; + if (!entity.HasAttribute(KnownAttribute.PreserveBaseOverrides)) + return false; + return true; + } + internal static bool IsWindowsFormsInitializeComponentMethod(IMethod method) { return method.ReturnType.Kind == TypeKind.Void && method.Name == "InitializeComponent" && method.DeclaringTypeDefinition.GetNonInterfaceBaseTypes().Any(t => t.FullName == "System.Windows.Forms.Control"); @@ -1766,7 +1781,15 @@ namespace ICSharpCode.Decompiler.CSharp var accessorHandle = (MethodDefinitionHandle)(property.Getter ?? property.Setter).MetadataToken; var accessor = metadata.GetMethodDefinition(accessorHandle); if (!accessorHandle.GetMethodImplementations(metadata).Any() && accessor.HasFlag(System.Reflection.MethodAttributes.Virtual) == accessor.HasFlag(System.Reflection.MethodAttributes.NewSlot)) + { SetNewModifier(propertyDecl); + } + if (IsCovariantReturnOverride(property.Getter)) + { + RemoveAttribute(getter, KnownAttribute.PreserveBaseOverrides); + propertyDecl.Modifiers &= ~(Modifiers.New | Modifiers.Virtual); + propertyDecl.Modifiers |= Modifiers.Override; + } return propertyDecl; } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index b7c467223..b456b3203 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -139,12 +139,14 @@ namespace ICSharpCode.Decompiler recordClasses = false; withExpressions = false; usePrimaryConstructorSyntax = false; + covariantReturns = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension || recordClasses) + if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension + || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns) return CSharp.LanguageVersion.Preview; if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges || switchExpressions) @@ -193,6 +195,24 @@ namespace ICSharpCode.Decompiler } } + bool covariantReturns = true; + + /// + /// Decompile C# 9 covariant return types. + /// + [Category("C# 9.0 / VS 2019.8")] + [Description("DecompilerSettings.CovariantReturns")] + public bool CovariantReturns { + get { return covariantReturns; } + set { + if (covariantReturns != value) + { + covariantReturns = value; + OnPropertyChanged(); + } + } + } + bool initAccessors = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index aae66c064..2ce7b2b62 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -102,11 +102,12 @@ namespace ICSharpCode.Decompiler.TypeSystem // C# 9 attributes: NativeInteger, + PreserveBaseOverrides, } static class KnownAttributes { - internal const int Count = (int)KnownAttribute.NativeInteger + 1; + internal const int Count = (int)KnownAttribute.PreserveBaseOverrides + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, @@ -167,6 +168,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Security.Permissions", "PermissionSetAttribute"), // C# 9 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "PreserveBaseOverridesAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr)