diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 65c70668a8..b1d70dfda3 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -99,6 +99,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 07a3fa84e8..3bce841c01 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -569,6 +569,12 @@ public void CS9_ExtensionGetEnumerator([ValueSource(nameof(dotnetCoreOnlyOptions 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 0000000000..11987dde5c --- /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 4fc6c16432..ccad740d69 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1215,9 +1215,24 @@ EntityDeclaration DoDecompile(Decompiler.TypeSystem.IMethod method, DecompileRun if (method.SymbolKind == SymbolKind.Method && !method.IsExplicitInterfaceImplementation && methodDefinition.IsVirtual == methodDefinition.IsNewSlot) { 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(ICSharpCode.Decompiler.TypeSystem.IMethod method) { return method.ReturnType.Kind == TypeKind.Void && method.Name == "InitializeComponent" && method.DeclaringTypeDefinition.GetNonInterfaceBaseTypes().Any(t => t.FullName == "System.Windows.Forms.Control"); @@ -1470,7 +1485,15 @@ EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITy } var accessor = (MethodDef)(property.Getter ?? property.Setter).MetadataToken; if (!accessor.HasOverrides && accessor.IsVirtual == accessor.IsNewSlot) + { 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)) { throw new DecompilerException(property.MetadataToken, innerException); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 7fa9cfcb31..3dec9c5da8 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -127,12 +127,14 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) 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) return CSharp.LanguageVersion.CSharp8_0; @@ -175,6 +177,24 @@ public bool NativeIntegers { } } + 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 d45109c934..b6be4954a0 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -102,11 +102,12 @@ public enum KnownAttribute // 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 @@ static class KnownAttributes 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)