未验证 提交 ede3ecf9 编写于 作者: A Andy Gocke 提交者: GitHub

Make IEquatable contravariant for the purpose of nullability (#37215)

Fixes #37187
上级 33cdfbfd
......@@ -2712,6 +2712,7 @@ private bool HasVariantConversionNoCycleCheck(NamedTypeSymbol source, NamedTypeS
source.GetAllTypeArguments(sourceTypeArguments, ref useSiteDiagnostics);
destination.GetAllTypeArguments(destinationTypeArguments, ref useSiteDiagnostics);
Debug.Assert(TypeSymbol.Equals(source.OriginalDefinition, destination.OriginalDefinition, TypeCompareKind.AllIgnoreOptions));
Debug.Assert(typeParameters.Count == sourceTypeArguments.Count);
Debug.Assert(typeParameters.Count == destinationTypeArguments.Count);
......@@ -2732,6 +2733,19 @@ private bool HasVariantConversionNoCycleCheck(NamedTypeSymbol source, NamedTypeS
switch (typeParameterSymbol.Variance)
{
case VarianceKind.None:
// System.IEquatable<T> is invariant for back compat reasons (dynamic type checks could start
// to succeed where they previously failed, creating different runtime behavior), but the uses
// require treatment specifically of nullability as contravariant, so we special case the
// behavior here. Normally we use GetWellKnownType for these kinds of checks, but in this
// case we don't want just the canonical IEquatable to be special-cased, we want all definitions
// to be treated as contravariant, in case there are other definitions in metadata that were
// compiled with that expectation.
if (isTypeIEquatable(destination.OriginalDefinition) &&
TypeSymbol.Equals(destinationTypeArgument.Type, sourceTypeArgument.Type, TypeCompareKind.AllNullableIgnoreOptions) &&
HasAnyNullabilityImplicitConversion(destinationTypeArgument, sourceTypeArgument))
{
return true;
}
return false;
case VarianceKind.Out:
......@@ -2761,6 +2775,19 @@ private bool HasVariantConversionNoCycleCheck(NamedTypeSymbol source, NamedTypeS
}
return true;
static bool isTypeIEquatable(NamedTypeSymbol type)
{
return type is
{
IsInterface: true,
Name: "IEquatable",
ContainingNamespace: { Name: "System", ContainingNamespace: { IsGlobalNamespace: true } },
ContainingSymbol: { Kind: SymbolKind.Namespace },
TypeParameters: { Length: 1 }
};
}
}
// Spec 6.1.10
......
......@@ -102101,14 +102101,7 @@ public class Broken<T> : IEnumerable<IEquatable<T>> where T : class
}
";
var comp = CreateCompilation(source, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (8,40): warning CS8613: Nullability of reference types in return type of 'IEnumerator<IEquatable<T?>> Working<T>.GetEnumerator()' doesn't match implicitly implemented member 'IEnumerator<IEquatable<T>> IEnumerable<IEquatable<T>>.GetEnumerator()'.
// public IEnumerator<IEquatable<T?>> GetEnumerator() => throw null!;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnImplicitImplementation, "GetEnumerator").WithArguments("IEnumerator<IEquatable<T?>> Working<T>.GetEnumerator()", "IEnumerator<IEquatable<T>> IEnumerable<IEquatable<T>>.GetEnumerator()").WithLocation(8, 40),
// (15,60): warning CS8616: Nullability of reference types in return type doesn't match implemented member 'IEnumerator<IEquatable<T>> IEnumerable<IEquatable<T>>.GetEnumerator()'.
// IEnumerator<IEquatable<T?>> IEnumerable<IEquatable<T>>.GetEnumerator() => throw null!;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOnExplicitImplementation, "GetEnumerator").WithArguments("IEnumerator<IEquatable<T>> IEnumerable<IEquatable<T>>.GetEnumerator()").WithLocation(15, 60)
);
comp.VerifyDiagnostics();
}
[WorkItem(30140, "https://github.com/dotnet/roslyn/issues/30140")]
......@@ -112197,5 +112190,209 @@ void M()
// _ = location.ToString(); // 1
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "location").WithLocation(19, 13));
}
[Fact]
[WorkItem(37187, "https://github.com/dotnet/roslyn/issues/37187")]
public void IEquatableContravariantNullability()
{
var def = @"
using System;
namespace System
{
public interface IEquatable<T>
{
bool Equals(T other);
}
}
public class A : IEquatable<A?>
{
public bool Equals(A? a) => false;
public static void M<T>(Span<T> s) where T : IEquatable<T>?
{
s[0]?.Equals(null);
s[0]?.Equals(s[1]);
}
}
public class B : IEquatable<(B?, B?)>
{
public bool Equals((B?, B?) l) => false;
}
";
var spanRef = CreateCompilation(SpanSource, options: TestOptions.UnsafeReleaseDll)
.EmitToImageReference();
var comp = CreateCompilation(def + @"
class C
{
static void Main()
{
var x = new Span<A?>();
var y = new Span<A>();
A.M(x);
A.M(y);
IEquatable<A?> i1 = new A();
IEquatable<A> i2 = i1;
IEquatable<A> i3 = new A();
IEquatable<A?> i4 = i3; // warn
_ = i4;
IEquatable<(B?, B?)> i5 = new B();
IEquatable<(B, B)> i6 = i5;
IEquatable<(B, B)> i7 = new B();
IEquatable<(B?, B?)> i8 = i7; // warn
_ = i8;
}
}",
new[] { spanRef },
options: WithNonNullTypesTrue().WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress));
comp.VerifyDiagnostics(
// (34,29): warning CS8619: Nullability of reference types in value of type 'IEquatable<A>' doesn't match target type 'IEquatable<A?>'.
// IEquatable<A?> i4 = i3; // warn
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "i3").WithArguments("System.IEquatable<A>", "System.IEquatable<A?>").WithLocation(41, 29),
// (40,35): warning CS8619: Nullability of reference types in value of type 'IEquatable<(B, B)>' doesn't match target type 'IEquatable<(B?, B?)>'.
// IEquatable<(B?, B?)> i8 = i7; // warn
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "i7").WithArguments("System.IEquatable<(B, B)>", "System.IEquatable<(B?, B?)>").WithLocation(47, 35)
);
// Test with non-wellknown type
var defComp = CreateCompilation(def,
references: new[] { spanRef },
options: WithNonNullTypesTrue().WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress));
defComp.VerifyDiagnostics();
var useComp = CreateCompilation(@"
using System;
public interface IEquatable<T>
{
bool Equals(T other);
}
class C
{
static void Main()
{
var x = new Span<A?>();
var y = new Span<A>();
A.M(x);
A.M(y);
}
}",
references: new[] { spanRef, defComp.ToMetadataReference() },
options: WithNonNullTypesTrue());
useComp.VerifyDiagnostics();
}
[Fact]
public void IEquatableNotContravariantExceptNullability()
{
var src = @"
using System;
class A : IEquatable<A?>
{
public bool Equals(A? a) => false;
}
class B : A {}
class C
{
static void Main()
{
var x = new Span<B?>();
var y = new Span<B>();
M(x);
M(y);
}
static void M<T>(Span<T> s) where T : IEquatable<T>? { }
}";
var comp = CreateCompilationWithSpan(src + @"
namespace System
{
public interface IEquatable<T>
{
bool Equals(T other);
}
}
", options: WithNonNullTypesTrue().WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress));
comp.VerifyDiagnostics(
// (25,9): error CS0311: The type 'B' cannot be used as type parameter 'T' in the generic type or method 'C.M<T>(Span<T>)'. There is no implicit reference conversion from 'B' to 'System.IEquatable<B>'.
// M(x);
Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M").WithArguments("C.M<T>(System.Span<T>)", "System.IEquatable<B>", "T", "B").WithLocation(18, 9),
// (26,9): error CS0311: The type 'B' cannot be used as type parameter 'T' in the generic type or method 'C.M<T>(Span<T>)'. There is no implicit reference conversion from 'B' to 'System.IEquatable<B>'.
// M(y);
Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M").WithArguments("C.M<T>(System.Span<T>)", "System.IEquatable<B>", "T", "B").WithLocation(19, 9));
// If IEquatable is actually marked contravariant this is fine
comp = CreateCompilationWithSpan(src + @"
namespace System
{
public interface IEquatable<in T>
{
bool Equals(T other);
}
}
", options: WithNonNullTypesTrue().WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress));
comp.VerifyDiagnostics();
}
[Fact]
public void IEquatableInWrongNamespace()
{
var comp = CreateCompilation(@"
public interface IEquatable<T>
{
bool Equals(T other);
}
public class A : IEquatable<A?>
{
public bool Equals(A? a) => false;
public static void Main()
{
IEquatable<A?> i1 = new A();
IEquatable<A> i2 = i1;
IEquatable<A?> i3 = i2;
_ = i3;
}
}
", options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (14,28): warning CS8619: Nullability of reference types in value of type 'IEquatable<A?>' doesn't match target type 'IEquatable<A>'.
// IEquatable<A> i2 = i1;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "i1").WithArguments("IEquatable<A?>", "IEquatable<A>").WithLocation(14, 28),
// (15,29): warning CS8619: Nullability of reference types in value of type 'IEquatable<A>' doesn't match target type 'IEquatable<A?>'.
// IEquatable<A?> i3 = i2;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "i2").WithArguments("IEquatable<A>", "IEquatable<A?>").WithLocation(15, 29));
}
[Fact]
public void IEquatableNullableVarianceOutParameters()
{
var comp = CreateCompilation(@"
using System;
class C
{
void M<T>(IEquatable<T?> input, out IEquatable<T> output) where T: class
{
output = input;
}
}
namespace System
{
public interface IEquatable<T>
{
bool Equals(T other);
}
}
", options: WithNonNullTypesTrue().WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress));
comp.VerifyDiagnostics();
}
}
}
......@@ -1842,6 +1842,9 @@ protected static CSharpCompilation CreateCompilationWithSpan(SyntaxTree tree, CS
return comp;
}
protected static CSharpCompilation CreateCompilationWithSpan(string s, CSharpCompilationOptions options = null)
=> CreateCompilationWithSpan(SyntaxFactory.ParseSyntaxTree(s), options);
protected static CSharpCompilation CreateCompilationWithMscorlibAndSpan(string text, CSharpCompilationOptions options = null, CSharpParseOptions parseOptions = null)
{
var reference = CreateEmptyCompilation(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册