diff --git a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs index adda0ec4d4d9e..b3f9110017ea3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs @@ -9,11 +9,11 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; -using System.Runtime.CompilerServices; -using System.Threading; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -751,6 +751,11 @@ public static bool CheckConstraints(this NamedTypeSymbol type, in CheckConstrain // metadata and be instantiated in C#. We check to see if that's happened. private static bool HasDuplicateInterfaces(NamedTypeSymbol type, ConsList basesBeingResolved) { + if (type.OriginalDefinition is not PENamedTypeSymbol) + { + return false; + } + // PERF: avoid instantiating all interfaces here // Ex: if class implements just IEnumerable<> and IComparable<> it cannot have conflicting implementations var array = type.OriginalDefinition.InterfacesNoUseSiteDiagnostics(basesBeingResolved); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 4d3054f38cf56..4b416fa579944 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -11,6 +11,7 @@ using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -136032,6 +136033,307 @@ partial class C2 : Base< ); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_01() + { + var source = """ + #nullable enable + var c = new C(); + + partial class C : I { } + + partial class C : + #nullable disable + I + #nullable enable + { + } + + public interface I { } + """; + + CompileAndVerify(CreateCompilationWithMscorlib40(source), sourceSymbolValidator: validate, symbolValidator: validate).VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + if (module is SourceModuleSymbol) + { + var interfaces = c.AllInterfacesNoUseSiteDiagnostics; + Assert.Equal(2, interfaces.Length); + AssertEx.Equal("I", interfaces[0].ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(NullableAnnotation.NotAnnotated, interfaces[0].TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().NullableAnnotation); + AssertEx.Equal("I", interfaces[1].ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(NullableAnnotation.Oblivious, interfaces[1].TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().NullableAnnotation); + } + else + { + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_02() + { + var source = """ + #nullable enable + var c = new C(); + + partial class C : I { } + + partial class C : I + #nullable enable + { + } + + public interface I { } + """; + CompileAndVerify(source, symbolValidator: validate).VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_03() + { + var source = """ + #nullable enable + var c1 = new C1(); + var c2 = new C2(); + + partial class C1 : I; + + partial class C1 : I< + T, + #nullable disable + object>; + #nullable enable + + partial class C2 : I; + + partial class C2 : I< + object, + #nullable disable + T>; + #nullable enable + + interface I { } + """; + CompileAndVerify(source, symbolValidator: validate).VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C1"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + + c = module.GlobalNamespace.GetTypeMember("C2"); + cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_03b() + { + var source = """ + #nullable enable + var c1 = new C1(); + var c2 = new C2(); + + partial class C1 : I< + T, + #nullable disable + object>; + #nullable enable + + partial class C1 : I; + + partial class C2 : I< + object, + #nullable disable + T>; + #nullable enable + + partial class C2 : I; + + interface I { } + """; + CompileAndVerify(source, symbolValidator: validate).VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C1"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + + c = module.GlobalNamespace.GetTypeMember("C2"); + cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_04() + { + var source = """ + #nullable enable + var c = new C(); + + interface I { } + + class C : I, I { } + """; + + CompileAndVerify(CreateCompilationWithMscorlib40(source), symbolValidator: validate).VerifyDiagnostics( + // (6,7): warning CS8645: 'I' is already listed in the interface list on type 'C' with different nullability of reference types. + // class C : I, I { } + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "C").WithArguments("I", "C").WithLocation(6, 7)); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_05() + { + var source = """ + #nullable enable + var c = new C(); + + interface I { } + + partial class C : I { } + + partial class C : I { } + """; + CompileAndVerify(source, symbolValidator: validate).VerifyDiagnostics( + // (6,15): warning CS8645: 'I' is already listed in the interface list on type 'C' with different nullability of reference types. + // partial class C : I { } + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "C").WithArguments("I", "C").WithLocation(6, 15)); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_06() + { + var source = """ + #nullable enable + var c = new C(); + + interface I { } + + partial class C : I { } + + partial class C : I, I { } + """; + CompileAndVerify(source, symbolValidator: validate).VerifyDiagnostics( + // (6,15): warning CS8645: 'I' is already listed in the interface list on type 'C' with different nullability of reference types. + // partial class C : I { } + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "C").WithArguments("I", "C").WithLocation(6, 15)); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40538")] + public void AnnotationMismatch_Interface_GenericClass_Retargeting() + { + var source1 = """ + #nullable enable + interface I { } + + public class C : I, I { } + """; + + var verifier1 = CompileAndVerify(source1, symbolValidator: validate, targetFramework: TargetFramework.Mscorlib40); + verifier1.VerifyDiagnostics( + // (4,14): warning CS8645: 'I' is already listed in the interface list on type 'C' with different nullability of reference types. + // public class C : I, I { } + Diagnostic(ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList, "C").WithArguments("I", "C").WithLocation(4, 14)); + + static void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var cT = c.TypeParameters.Single(); + AssertEx.Equal("T", cT.ToTestDisplayString(includeNonNullable: true)); + Assert.Equal(CodeAnalysis.NullableAnnotation.None, cT.GetPublicSymbol().ReferenceTypeConstraintNullableAnnotation); + + var i = c.AllInterfacesNoUseSiteDiagnostics.Single(); + AssertEx.Equal("I", i.ToTestDisplayString(includeNonNullable: true)); + } + + var source2 = """ + #nullable enable + var c = new C(); + """; + var comp2 = CreateCompilation(source2, [verifier1.Compilation.ToMetadataReference()]); + comp2.VerifyEmitDiagnostics(); + + var c = comp2.GlobalNamespace.GetMember("C"); + Assert.IsType(c); + Assert.False(c.HasUnsupportedMetadata); + } + [Fact] public void PartialMethodsWithConstraints_01() {