diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/CovariantReturnTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/CovariantReturnTests.cs index a9dabdba4b18f..1062ee1b19da1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/CovariantReturnTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/CovariantReturnTests.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols @@ -413,18 +414,278 @@ public class Derived : Base ); } + [Fact] + public void NonOverrideTests_01() + { + var source = @" +public class Base +{ + public virtual object M1 => null; + public virtual object M2 => null; +} +public class Derived : Base +{ + public new string M1 => null; + public string M2 => null; +} +public class Derived2 : Derived +{ + public new string M1 => null; + public string M2 => null; +} +public class Derived3 : Derived +{ + public new object M1 => null; + public object M2 => null; +} +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (10,19): warning CS0114: 'Derived.M2' hides inherited member 'Base.M2'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. + // public string M2 => null; + Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "M2").WithArguments("Derived.M2", "Base.M2").WithLocation(10, 19), + // (15,19): warning CS0108: 'Derived2.M2' hides inherited member 'Derived.M2'. Use the new keyword if hiding was intended. + // public string M2 => null; + Diagnostic(ErrorCode.WRN_NewRequired, "M2").WithArguments("Derived2.M2", "Derived.M2").WithLocation(15, 19), + // (20,19): warning CS0108: 'Derived3.M2' hides inherited member 'Derived.M2'. Use the new keyword if hiding was intended. + // public object M2 => null; + Diagnostic(ErrorCode.WRN_NewRequired, "M2").WithArguments("Derived3.M2", "Derived.M2").WithLocation(20, 19) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + // (10,19): warning CS0114: 'Derived.M2' hides inherited member 'Base.M2'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. + // public string M2 => null; + Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "M2").WithArguments("Derived.M2", "Base.M2").WithLocation(10, 19), + // (15,19): warning CS0108: 'Derived2.M2' hides inherited member 'Derived.M2'. Use the new keyword if hiding was intended. + // public string M2 => null; + Diagnostic(ErrorCode.WRN_NewRequired, "M2").WithArguments("Derived2.M2", "Derived.M2").WithLocation(15, 19), + // (20,19): warning CS0108: 'Derived3.M2' hides inherited member 'Derived.M2'. Use the new keyword if hiding was intended. + // public object M2 => null; + Diagnostic(ErrorCode.WRN_NewRequired, "M2").WithArguments("Derived3.M2", "Derived.M2").WithLocation(20, 19) + ); + } + + [Fact] + public void ChainedOverrides_01() + { + var source = @" +public class Base +{ + public virtual object M1 => null; + public virtual object M2 => null; + public virtual object M3 => null; +} +public class Derived : Base +{ + public override string M1 => null; + public override string M2 => null; + public override string M3 => null; +} +public class Derived2 : Derived +{ + public override string M1 => null; + public override object M2 => null; + public override Base M3 => null; +} +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (10,28): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override string M1 => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M1").WithArguments("covariant returns").WithLocation(10, 28), + // (11,28): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override string M2 => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M2").WithArguments("covariant returns").WithLocation(11, 28), + // (12,28): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override string M3 => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M3").WithArguments("covariant returns").WithLocation(12, 28), + // (17,28): error CS1715: 'Derived2.M2': type must be 'string' to match overridden member 'Derived.M2' + // public override object M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived2.M2", "Derived.M2", "string").WithLocation(17, 28), + // (18,26): error CS1715: 'Derived2.M3': type must be 'string' to match overridden member 'Derived.M3' + // public override Base M3 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M3").WithArguments("Derived2.M3", "Derived.M3", "string").WithLocation(18, 26) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + // (17,28): error CS1715: 'Derived2.M2': type must be 'string' to match overridden member 'Derived.M2' + // public override object M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived2.M2", "Derived.M2", "string").WithLocation(17, 28), + // (18,26): error CS1715: 'Derived2.M3': type must be 'string' to match overridden member 'Derived.M3' + // public override Base M3 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M3").WithArguments("Derived2.M3", "Derived.M3", "string").WithLocation(18, 26) + ); + } + + [Fact] + public void NestedVariance_01() + { + var source = @" +public class Base +{ + public virtual IIn M1 => null; + public virtual IOut M2 => null; +} +public class Derived : Base +{ + public override IIn M1 => null; + public override IOut M2 => null; +} +public interface IOut { } +public interface IIn { } +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (9,33): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override IIn M1 => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M1").WithArguments("covariant returns").WithLocation(9, 33), + // (10,34): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override IOut M2 => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M2").WithArguments("covariant returns").WithLocation(10, 34) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + ); + } - // PROTOTYPE: Future tests to be added: - // - What is expected for public Derived : Base { public new string M => null; - // - What is expected for public Derived : Base { public string M => null; - // - What is expected for public Derived2: Derived { public new object/string M => null; - // - Please add a test where Base has a property, Derived hides it with a different return type, and Derived2 tries to override with either return type. - // - These are also applicable to virtual methods. - // - Please add a test with nested variance involved (returning CIn vs.CIn, or COut vs.COut). Also consider nullability variance (COutvs.COut` and some permutations). - // - Test with an override that doesn't have an implicit reference conversion from base. For instance, numeric types, types convertible via user-defined operators, etc. - // - Test other implicit reference conversion scenarios, such as Interface Base.Method() and TypeThatImplementsInterface Derived.Method(), to lock-in the proper check - // - Test some DIM scenarios (no changed behavior) - // - Test that UD conversions don't count (not an implicit reference conversion) - // - Test three levels of inheritance - overriding one, both, neither, correctly, incorrectly. https://github.com/dotnet/roslyn/pull/43576#discussion_r414074476 + [Fact] + public void NestedVariance_02() + { + var source = @" +public class Base +{ + public virtual IIn M1 => null; + public virtual IOut M2 => null; +} +public class Derived : Base +{ + public override IIn M1 => null; + public override IOut M2 => null; +} +public interface IOut { } +public interface IIn { } +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (9,33): error CS1715: 'Derived.M1': type must be 'IIn' to match overridden member 'Base.M1' + // public override IIn M1 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M1").WithArguments("Derived.M1", "Base.M1", "IIn").WithLocation(9, 33), + // (10,34): error CS1715: 'Derived.M2': type must be 'IOut' to match overridden member 'Base.M2' + // public override IOut M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived.M2", "Base.M2", "IOut").WithLocation(10, 34) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + // (9,33): error CS1715: 'Derived.M1': type must be 'IIn' to match overridden member 'Base.M1' + // public override IIn M1 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M1").WithArguments("Derived.M1", "Base.M1", "IIn").WithLocation(9, 33), + // (10,34): error CS1715: 'Derived.M2': type must be 'IOut' to match overridden member 'Base.M2' + // public override IOut M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived.M2", "Base.M2", "IOut").WithLocation(10, 34) + ); + } + + [Fact] + public void BadCovariantReturnType_01() + { + var source = @" +public class Base +{ + public virtual int M1 => 1; + public virtual A M2 => null; +} +public class Derived : Base +{ + public override short M1 => 1; + public override B M2 => null; +} +public class A { } +public class B +{ + public static implicit operator A(B b) => null; +} +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (9,27): error CS1715: 'Derived.M1': type must be 'int' to match overridden member 'Base.M1' + // public override short M1 => 1; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M1").WithArguments("Derived.M1", "Base.M1", "int").WithLocation(9, 27), + // (10,23): error CS1715: 'Derived.M2': type must be 'A' to match overridden member 'Base.M2' + // public override B M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived.M2", "Base.M2", "A").WithLocation(10, 23) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + // (9,27): error CS1715: 'Derived.M1': type must be 'int' to match overridden member 'Base.M1' + // public override short M1 => 1; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M1").WithArguments("Derived.M1", "Base.M1", "int").WithLocation(9, 27), + // (10,23): error CS1715: 'Derived.M2': type must be 'A' to match overridden member 'Base.M2' + // public override B M2 => null; + Diagnostic(ErrorCode.ERR_CantChangeTypeOnOverride, "M2").WithArguments("Derived.M2", "Base.M2", "A").WithLocation(10, 23) + ); + } + + [Fact] + public void CovariantReturns_12() + { + var source = @" +public class Base +{ + public virtual System.IComparable M => null; +} +public class Derived : Base +{ + public override string M => null; +} +"; + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns).VerifyDiagnostics( + // (8,28): error CS8652: The feature 'covariant returns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public override string M => null; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M").WithArguments("covariant returns").WithLocation(8, 28) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns).VerifyDiagnostics( + ); + } + + [Fact] + public void NoCovariantImplementations_01() + { + var source = @" +public interface Base +{ + public virtual object M1 => null; + public virtual object M2() => null; +} +public interface Derived : Base +{ + string Base.M1 => null; + string Base.M2() => null; +} +public class C : Base +{ + string Base.M1 => null; + string Base.M2() => null; +} +"; + // these are poor diagnostics; see https://github.com/dotnet/roslyn/issues/43719 + CreateCompilation(source, parseOptions: TestOptions.WithoutCovariantReturns, targetFramework: TargetFramework.NetStandardLatest).VerifyDiagnostics( + // (9,17): error CS0539: 'Derived.M1' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M1 => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M1").WithArguments("Derived.M1").WithLocation(9, 17), + // (10,17): error CS0539: 'Derived.M2()' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M2() => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M2").WithArguments("Derived.M2()").WithLocation(10, 17), + // (14,17): error CS0539: 'C.M1' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M1 => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M1").WithArguments("C.M1").WithLocation(14, 17), + // (15,17): error CS0539: 'C.M2()' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M2() => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M2").WithArguments("C.M2()").WithLocation(15, 17) + ); + CreateCompilation(source, parseOptions: TestOptions.WithCovariantReturns, targetFramework: TargetFramework.NetStandardLatest).VerifyDiagnostics( + // (9,17): error CS0539: 'Derived.M1' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M1 => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M1").WithArguments("Derived.M1").WithLocation(9, 17), + // (10,17): error CS0539: 'Derived.M2()' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M2() => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M2").WithArguments("Derived.M2()").WithLocation(10, 17), + // (14,17): error CS0539: 'C.M1' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M1 => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M1").WithArguments("C.M1").WithLocation(14, 17), + // (15,17): error CS0539: 'C.M2()' in explicit interface declaration is not found among members of the interface that can be implemented + // string Base.M2() => null; + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M2").WithArguments("C.M2()").WithLocation(15, 17) + ); + } } }