From 932e0f15a7be82ff3ee7e829615f026fdda1d9ee Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 18 Dec 2020 15:08:16 -0800 Subject: [PATCH] Avoid AmbigMember in lookup of interfaces with nullability differences only (#49338) --- .../CSharp/Portable/Binder/Binder_Lookup.cs | 17 +- .../Semantics/NullableReferenceTypesTests.cs | 868 +++++++++++++++++- .../DefaultInterfaceImplementationTests.cs | 8 +- .../Test/Utilities/CSharp/SymbolUtilities.cs | 5 +- 4 files changed, 855 insertions(+), 43 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index 1091ee6000a..e0159958fdf 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -1058,11 +1058,20 @@ static void addAllInterfaces(NamedTypeSymbol @interface, HashSet 0) { var tmp = LookupResult.GetInstance(); - foreach (TypeSymbol baseInterface in interfaces) + HashSet seenInterfaces = null; + if (interfaces.Length > 1) { - LookupMembersWithoutInheritance(tmp, baseInterface, name, arity, options, originalBinder, accessThroughType, diagnose, ref useSiteDiagnostics, basesBeingResolved); - MergeHidingLookupResults(current, tmp, basesBeingResolved, ref useSiteDiagnostics); - tmp.Clear(); + seenInterfaces = new HashSet(Symbols.SymbolEqualityComparer.IgnoringNullable); + } + + foreach (NamedTypeSymbol baseInterface in interfaces) + { + if (seenInterfaces is null || seenInterfaces.Add(baseInterface)) + { + LookupMembersWithoutInheritance(tmp, baseInterface, name, arity, options, originalBinder, accessThroughType, diagnose, ref useSiteDiagnostics, basesBeingResolved); + MergeHidingLookupResults(current, tmp, basesBeingResolved, ref useSiteDiagnostics); + tmp.Clear(); + } } tmp.Free(); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 9d507ec4ea4..a724862fbc2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -16856,14 +16856,14 @@ public class A {} public class C1 : I1 { - public void M1() => System.Console.WriteLine(""C1.M1""); - void I1.M2() => System.Console.WriteLine(""C1.M2""); + public void M1() => System.Console.Write(""C1.M1 ""); + void I1.M2() => System.Console.Write(""C1.M2 ""); } public class C2 : C1, I1 { - new public void M1() => System.Console.WriteLine(""C2.M1""); - void I1.M2() => System.Console.WriteLine(""C2.M2""); + new public void M1() => System.Console.Write(""C2.M1 ""); + void I1.M2() => System.Console.Write(""C2.M2 ""); static void Main() { @@ -16916,10 +16916,7 @@ static void Main() c2.FindImplementationForInterfaceMember(((TypeSymbol)c1Interfaces[0]).GetMember("M2"))); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C2.M1 -C2.M2 -"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C2.M1 C2.M2"); } [Fact] @@ -16936,8 +16933,8 @@ public class A {} public class C1 : I1 { - public void M1() => System.Console.WriteLine(""C1.M1""); - void I1.M2() => System.Console.WriteLine(""C1.M2""); + public void M1() => System.Console.Write(""C1.M1 ""); + void I1.M2() => System.Console.Write(""C1.M2 ""); public virtual void M2() {} } @@ -16998,10 +16995,7 @@ static void Main() c2.FindImplementationForInterfaceMember(((TypeSymbol)c1Interfaces[0]).GetMember("M2"))); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C1.M1 -C1.M2 -"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C1.M1 C1.M2"); } [Fact] @@ -17273,7 +17267,7 @@ public class C3 : I1 { void I1.M() { - System.Console.WriteLine(""C3.M""); + System.Console.Write(""C3.M ""); } static void Main() @@ -17328,9 +17322,7 @@ public class C4 : I1 c3.FindImplementationForInterfaceMember(m.GlobalNamespace.GetTypeMember("C4").InterfacesNoUseSiteDiagnostics()[0].GetMember("M"))); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C3.M -C3.M"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C3.M C3.M"); } [Fact] @@ -17348,7 +17340,7 @@ public class C3 : I1, I1 { void I1.M() { - System.Console.WriteLine(""C3.M""); + System.Console.Write(""C3.M ""); } static void Main() @@ -17408,9 +17400,7 @@ static void Main() c3.FindImplementationForInterfaceMember(mImplementations[0])); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C3.M -C3.M"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C3.M C3.M"); } [Fact] @@ -17431,7 +17421,7 @@ public class C3 : I2, I1 { void I1.M() { - System.Console.WriteLine(""C3.M""); + System.Console.Write(""C3.M ""); } static void Main() @@ -17494,9 +17484,7 @@ static void Main() c3.FindImplementationForInterfaceMember(mImplementations[0])); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C3.M -C3.M"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C3.M C3.M"); } [Fact] @@ -17514,7 +17502,7 @@ public partial class C3 : I1 { void I1.M() { - System.Console.WriteLine(""C3.M""); + System.Console.Write(""C3.M ""); } static void Main() @@ -17577,9 +17565,7 @@ public partial class C3 : I1 {} c3.FindImplementationForInterfaceMember(mImplementations[0])); }; - CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: -@"C3.M -C3.M"); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate, expectedOutput: @"C3.M C3.M"); } [Fact] @@ -143137,5 +143123,827 @@ partial class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnPartial, "F").WithLocation(10, 23) ); } + + [Fact] + public void AmbigMember_DynamicDifferences() + { + var src = @" +interface I { T Item { get; } } + +interface I2 : I { } + +interface I3 : I, I2 { } + +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (6,11): error CS8779: 'I' is already listed in the interface list on type 'I3' as 'I'. + // interface I3 : I, I2 { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithDifferencesInBaseList, "I3").WithArguments("I", "I", "I3").WithLocation(6, 11), + // (6,16): error CS1966: 'I3': cannot implement a dynamic interface 'I' + // interface I3 : I, I2 { } + Diagnostic(ErrorCode.ERR_DeriveFromConstructedDynamic, "I").WithArguments("I3", "I").WithLocation(6, 16), + // (12,15): error CS0229: Ambiguity between 'I.Item' and 'I.Item' + // _ = i.Item; + Diagnostic(ErrorCode.ERR_AmbigMember, "Item").WithArguments("I.Item", "I.Item").WithLocation(12, 15) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_TupleDifferences() + { + var src = @" +interface I { T Item { get; } } + +interface I2 : I { } + +interface I3 : I<(int a, int b)>, I2<(int notA, int notB)> { } + +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (6,11): error CS8140: 'I<(int notA, int notB)>' is already listed in the interface list on type 'I3' with different tuple element names, as 'I<(int a, int b)>'. + // interface I3 : I<(int a, int b)>, I2<(int notA, int notB)> { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithTupleNamesInBaseList, "I3").WithArguments("I<(int notA, int notB)>", "I<(int a, int b)>", "I3").WithLocation(6, 11), + // (12,15): error CS0229: Ambiguity between 'I<(int a, int b)>.Item' and 'I<(int notA, int notB)>.Item' + // _ = i.Item; + Diagnostic(ErrorCode.ERR_AmbigMember, "Item").WithArguments("I<(int a, int b)>.Item", "I<(int notA, int notB)>.Item").WithLocation(12, 15) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I<(int a, int b)>", "I2<(int notA, int notB)>" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I<(int a, int b)>", "I2<(int notA, int notB)>", "I<(int notA, int notB)>" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_TupleAndNullabilityDifferences() + { + var src = @" +#nullable disable +interface I { T Item { get; } } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I<(object a, object b)>, I2<(object notA, object notB)> { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,11): error CS8140: 'I<(object notA, object notB)>' is already listed in the interface list on type 'I3' with different tuple element names, as 'I<(object a, object b)>'. + // interface I3 : I<(object a, object b)>, I2<(object notA, object notB)> { } + Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithTupleNamesInBaseList, "I3").WithArguments("I<(object notA, object notB)>", "I<(object a, object b)>", "I3").WithLocation(9, 11), + // (16,15): error CS0229: Ambiguity between 'I<(object a, object b)>.Item' and 'I<(object notA, object notB)>.Item' + // _ = i.Item; + Diagnostic(ErrorCode.ERR_AmbigMember, "Item").WithArguments("I<(object a, object b)>.Item", "I<(object notA, object notB)>.Item").WithLocation(16, 15) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I<(object a, object b)>", "I2<(object notA, object notB)>" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I<(object a, object b)>", "I2<(object notA, object notB)>", "I<(object notA, object notB)>" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_NoDifference() + { + var src = @" +interface I { T Item { get; } } + +interface I2 : I { } + +interface I3 : I, I2 { } + +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint() + { + var src = @" +#nullable disable +interface I { T Item { get; set; } } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i.Item", item.ToString()); + Assert.Equal("object", ((IPropertySymbol)model.GetSymbolInfo(item).Symbol).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + + var found = model.LookupSymbols(item.SpanStart, i3.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_Indexer() + { + var src = @" +#nullable disable +interface I { T this[int i] { get; set; } } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i[0]; + i[0] = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i[0]", item.ToString()); + Assert.Equal("object", ((IPropertySymbol)model.GetSymbolInfo(item).Symbol).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_Event() + { + var src = @" +using System; + +#nullable disable +interface I { event Func Event; } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i, Func f1, Func f2) + { + i.Event += f1; + i.Event += f2; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_Nested() + { + var src = @" +#nullable disable +interface I +{ + interface Inner + { + static T Item { get; set; } + } +} + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M() + { + _ = I3.Inner.Item; + I3.Inner.Item = null; + } +} +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.NetStandardLatest); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_Method() + { + var src = @" +#nullable disable +interface I { ref T Get(); } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Get(); + i.Get() = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i.Get()", item.ToString()); + Assert.Equal("object", ((IMethodSymbol)model.GetSymbolInfo(item).Symbol).ReturnType.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_ReverseOrder() + { + var src = @" +#nullable disable +interface I { T Item { get; set; } } + +#nullable enable +interface I2 : I { } + +#nullable disable +interface I3 : I2, I { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i.Item", item.ToString()); + Assert.Equal("object", ((IPropertySymbol)model.GetSymbolInfo(item).Symbol).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + + var found = model.LookupSymbols(item.SpanStart, i3.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.True(model.LookupNames(item.SpanStart, i3.GetPublicSymbol()).Contains("Item")); + + var found = model.LookupSymbols(item.SpanStart, i3.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_OnTypeParameter() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(T i) where T : I3 + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.True(model.LookupNames(item.SpanStart, i3.GetPublicSymbol()).Contains("Item")); + + var t = ((MethodSymbol)comp.GetMember("C.M")).TypeParameters.Single(); + var found = model.LookupSymbols(item.SpanStart, t.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_OnTypeParameterImplementingBothInterfaces() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +public class C +{ +#nullable disable + void M(T i) where T : I, I2 + { +#nullable enable + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + + var t = ((MethodSymbol)comp.GetMember("C.M")).TypeParameters.Single(); + Assert.True(model.LookupNames(item.SpanStart, t.GetPublicSymbol()).Contains("Item")); + + var found = model.LookupSymbols(item.SpanStart, t.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_Indexer() + { + var src = @" +#nullable disable +interface I where T : class { T this[int i] { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i[0]; + i[0] = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i[0]", item.ToString()); + Assert.Equal("object", ((IPropertySymbol)model.GetSymbolInfo(item).Symbol).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_Event() + { + var src = @" +using System; + +#nullable disable +interface I where T : class { event Func Event; } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i, Func f1, Func f2) + { + i.Event += f1; + i.Event += f2; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_Nested() + { + var src = @" +#nullable disable +interface I where T : class +{ + interface Inner + { + static T Item { get; set; } + } +} + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M() + { + _ = I3.Inner.Item; + I3.Inner.Item = null; + } +} +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.NetStandardLatest); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_Method() + { + var src = @" +#nullable disable +interface I where T : class { ref T Get(); } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Get(); + i.Get() = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal("i.Get()", item.ToString()); + Assert.Equal("object", ((IMethodSymbol)model.GetSymbolInfo(item).Symbol).ReturnType.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + + var i3 = comp.GetTypeByMetadataName("I3"); + Assert.True(model.LookupNames(item.SpanStart, i3.GetPublicSymbol()).Contains("Get")); + + var found = model.LookupSymbols(item.SpanStart, i3.GetPublicSymbol()).Single(s => s.Name == "Get"); + Assert.Equal("object", ((IMethodSymbol)found).ReturnType.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_ReverseOrder() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I2, I { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (17,18): warning CS8625: Cannot convert null literal to non-nullable reference type. + // i.Item = null; + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(17, 18) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I2", "I", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + var found = model.LookupSymbols(item.SpanStart, i3.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object!", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_ReverseOrder_OnTypeParameter() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +#nullable disable +interface I3 : I2, I { } + +#nullable enable +public class C +{ + void M(T i) where T : I3 + { + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (17,18): warning CS8625: Cannot convert null literal to non-nullable reference type. + // i.Item = null; + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(17, 18) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I2", "I" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I2", "I", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + + var t = ((MethodSymbol)comp.GetMember("C.M")).TypeParameters.Single(); + var found = model.LookupSymbols(item.SpanStart, t.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object!", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_ReverseOrder_OnTypeParameterImplementingBothInterfaces() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; set; } } + +#nullable enable +interface I2 : I where T : class { } + +public class C +{ +#nullable disable + void M(T i) where T : I2, I + { +#nullable enable + _ = i.Item; + i.Item = null; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (15,18): warning CS8625: Cannot convert null literal to non-nullable reference type. + // i.Item = null; + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(15, 18) + ); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var item = tree.GetRoot().DescendantNodes().OfType().First(); + + var t = ((MethodSymbol)comp.GetMember("C.M")).TypeParameters.Single(); + var found = model.LookupSymbols(item.SpanStart, t.GetPublicSymbol()).Single(s => s.Name == "Item"); + Assert.Equal("object!", ((IPropertySymbol)found).Type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNullableAndOblivious_WithConstraint() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; } } + +#nullable enable +interface I2 : I where T : class { } + +interface I3 : I, +#nullable disable + I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,11): warning CS8645: 'I' is already listed in the interface list on type 'I3' with different nullability of reference types. + // interface I3 : I, + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "I3").WithArguments("I", "I3").WithLocation(8, 11) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNullableAndOblivious_WithoutConstraint() + { + var src = @" +#nullable disable +interface I { T Item { get; } } + +#nullable enable +interface I2 : I { } + +interface I3 : I, +#nullable disable + I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } + + [Fact] + public void AmbigMember_DifferenceBetweenNullableAndNonnullable() + { + var src = @" +#nullable disable +interface I where T : class { T Item { get; } } + +#nullable enable +interface I2 : I where T : class { } + +interface I3 : I, I2 { } + +#nullable enable +public class C +{ + void M(I3 i) + { + _ = i.Item; + } +} +"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (8,11): warning CS8645: 'I' is already listed in the interface list on type 'I3' with different nullability of reference types. + // interface I3 : I, I2 { } + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "I3").WithArguments("I", "I3").WithLocation(8, 11) + ); + var i3 = comp.GetTypeByMetadataName("I3"); + AssertEx.Equal(new string[] { "I", "I2" }, + i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + AssertEx.Equal(new string[] { "I", "I2", "I" }, + i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat)); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index a6434f9c9b5..fdb317f184b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -57708,13 +57708,7 @@ interface D : A.E compilation1.VerifyDiagnostics( // (4,11): warning CS8645: 'C' is already listed in the interface list on type 'A' with different nullability of reference types. // interface A : B, C - Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "A").WithArguments("C", "A").WithLocation(4, 11), - // (18,17): error CS0104: 'E' is an ambiguous reference between 'C.E' and 'C.E' - // interface D : A.E - Diagnostic(ErrorCode.ERR_AmbigContext, "E").WithArguments("E", "C.E", "C.E").WithLocation(18, 17), - // (20,7): error CS0104: 'E' is an ambiguous reference between 'C.E' and 'C.E' - // A.E Test(); - Diagnostic(ErrorCode.ERR_AmbigContext, "E").WithArguments("E", "C.E", "C.E").WithLocation(20, 7) + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "A").WithArguments("C", "A").WithLocation(4, 11) ); } diff --git a/src/Compilers/Test/Utilities/CSharp/SymbolUtilities.cs b/src/Compilers/Test/Utilities/CSharp/SymbolUtilities.cs index 0ec9a63d30e..b45b9209c0d 100644 --- a/src/Compilers/Test/Utilities/CSharp/SymbolUtilities.cs +++ b/src/Compilers/Test/Utilities/CSharp/SymbolUtilities.cs @@ -118,9 +118,10 @@ public static string[] ToTestDisplayStrings(this IEnumerable symbols) return symbols.Select(s => s.ToTestDisplayString()).ToArray(); } - public static string[] ToTestDisplayStrings(this IEnumerable symbols) + public static string[] ToTestDisplayStrings(this IEnumerable symbols, SymbolDisplayFormat format = null) { - return symbols.Select(s => s.ToTestDisplayString()).ToArray(); + format ??= SymbolDisplayFormat.TestFormat; + return symbols.Select(s => s.ToDisplayString(format)).ToArray(); } public static string ToTestDisplayString(this ISymbol symbol, bool includeNonNullable) -- GitLab