Skip to content

Commit

Permalink
Avoid AmbigMember in lookup of interfaces with nullability differences
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Nov 13, 2020
1 parent f609480 commit 7ad43bd
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 9 deletions.
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,10 @@ private static void MergeHidingLookupResults(LookupResult resultHiding, LookupRe
// SPEC: interface type, the base types of T are the base interfaces
// SPEC: of T and the class type object.

if (hiddenContainer.Equals(hidingSym.ContainingType, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
{
goto symIsHidden; // discard the new candidate, as it is not really different
}
if (!IsDerivedType(baseType: hiddenContainer, derivedType: hidingSym.ContainingType, basesBeingResolved, useSiteDiagnostics: ref useSiteDiagnostics) &&
hiddenContainer.SpecialType != SpecialType.System_Object)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141927,5 +141927,376 @@ static T GetValue2<T>(Collection c, object key)
// return s2; // 4
Diagnostic(ErrorCode.WRN_NullReferenceReturn, "s2").WithLocation(47, 20));
}

[Fact]
public void AmbigMember_DynamicDifferences()
{
var src = @"
interface I<T> { T Item { get; } }

interface I2<T> : I<T> { }

interface I3 : I<dynamic>, I2<object> { }

public class C
{
void M(I3 i)
{
_ = i.Item;
}
}
";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (6,11): error CS8779: 'I<object>' is already listed in the interface list on type 'I3' as 'I<dynamic>'.
// interface I3 : I<dynamic>, I2<object> { }
Diagnostic(ErrorCode.ERR_DuplicateInterfaceWithDifferencesInBaseList, "I3").WithArguments("I<object>", "I<dynamic>", "I3").WithLocation(6, 11),
// (6,16): error CS1966: 'I3': cannot implement a dynamic interface 'I<dynamic>'
// interface I3 : I<dynamic>, I2<object> { }
Diagnostic(ErrorCode.ERR_DeriveFromConstructedDynamic, "I<dynamic>").WithArguments("I3", "I<dynamic>").WithLocation(6, 16),
// (12,15): error CS0229: Ambiguity between 'I<dynamic>.Item' and 'I<object>.Item'
// _ = i.Item;
Diagnostic(ErrorCode.ERR_AmbigMember, "Item").WithArguments("I<dynamic>.Item", "I<object>.Item").WithLocation(12, 15)
);
var i3 = comp.GetTypeByMetadataName("I3");
AssertEx.Equal(new string[] { "I<dynamic>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I<dynamic>", "I2<object>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_TupleDifferences()
{
var src = @"
interface I<T> { T Item { get; } }

interface I2<T> : I<T> { }

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> { T Item { get; } }

#nullable enable
interface I2<T> : I<T> { }

#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> { T Item { get; } }

interface I2<T> : I<T> { }

interface I3 : I<object>, I2<object> { }

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<object>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I2<object>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint()
{
var src = @"
#nullable disable
interface I<T> { T Item { get; set; } }

#nullable enable
interface I2<T> : I<T> { }

#nullable disable
interface I3 : I<object>, I2<object> { }

#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<object>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I2<object>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithoutConstraint_ReverseOrder()
{
var src = @"
#nullable disable
interface I<T> { T Item { get; set; } }

#nullable enable
interface I2<T> : I<T> { }

#nullable disable
interface I3 : I2<object>, I<object> { }

#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<object>", "I<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I2<object>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint()
{
var src = @"
#nullable disable
interface I<T> where T : class { T Item { get; set; } }

#nullable enable
interface I2<T> : I<T> where T : class { }

#nullable disable
interface I3 : I<object>, I2<object> { }

#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<object>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I<object>", "I2<object>", "I<object!>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNonnullableAndOblivious_WithConstraint_ReverseOrder()
{
var src = @"
#nullable disable
interface I<T> where T : class { T Item { get; set; } }

#nullable enable
interface I2<T> : I<T> where T : class { }

#nullable disable
interface I3 : I2<object>, I<object> { }

#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<object>", "I<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I2<object>", "I<object!>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNullableAndOblivious_WithConstraint()
{
var src = @"
#nullable disable
interface I<T> where T : class { T Item { get; } }

#nullable enable
interface I2<T> : I<T> where T : class { }

interface I3 : I<object?>,
#nullable disable
I2<object> { }

#nullable enable
public class C
{
void M(I3 i)
{
_ = i.Item;
}
}
";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (8,11): warning CS8645: 'I<object>' is already listed in the interface list on type 'I3' with different nullability of reference types.
// interface I3 : I<object?>,
Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "I3").WithArguments("I<object>", "I3").WithLocation(8, 11)
);
var i3 = comp.GetTypeByMetadataName("I3");
AssertEx.Equal(new string[] { "I<object?>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I<object?>", "I2<object>", "I<object!>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNullableAndOblivious_WithoutConstraint()
{
var src = @"
#nullable disable
interface I<T> { T Item { get; } }

#nullable enable
interface I2<T> : I<T> { }

interface I3 : I<object?>,
#nullable disable
I2<object> { }

#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<object?>", "I2<object>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I<object?>", "I2<object>", "I<object>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}

[Fact]
public void AmbigMember_DifferenceBetweenNullableAndNonnullable()
{
var src = @"
#nullable disable
interface I<T> where T : class { T Item { get; } }

#nullable enable
interface I2<T> : I<T> where T : class { }

interface I3 : I<object?>, I2<object> { }

#nullable enable
public class C
{
void M(I3 i)
{
_ = i.Item;
}
}
";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (8,11): warning CS8645: 'I<object>' is already listed in the interface list on type 'I3' with different nullability of reference types.
// interface I3 : I<object?>, I2<object> { }
Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "I3").WithArguments("I<object>", "I3").WithLocation(8, 11)
);
var i3 = comp.GetTypeByMetadataName("I3");
AssertEx.Equal(new string[] { "I<object?>", "I2<object!>" },
i3.Interfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
AssertEx.Equal(new string[] { "I<object?>", "I2<object!>", "I<object!>" },
i3.AllInterfaces().ToTestDisplayStrings(TypeWithAnnotations.TestDisplayFormat));
}
}
}
Loading

0 comments on commit 7ad43bd

Please sign in to comment.