diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs index e8bc2be3a8445..77a181093c2fc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs @@ -315,13 +315,36 @@ private Tuple> MakeDeclaredBase baseType = partBase; baseTypeLocation = decl.NameLocation; } - else if ((object)partBase != null && !TypeSymbol.Equals(partBase, baseType, TypeCompareKind.ConsiderEverything2) && partBase.TypeKind != TypeKind.Error) + else if ((object)partBase != null && !TypeSymbol.Equals(partBase, baseType, TypeCompareKind.ConsiderEverything) && partBase.TypeKind != TypeKind.Error) { // the parts do not agree + if (partBase.Equals(baseType, TypeCompareKind.ObliviousNullableModifierMatchesAny)) + { + if (containsOnlyOblivious(baseType)) + { + baseType = partBase; + baseTypeLocation = decl.NameLocation; + continue; + } + else if (containsOnlyOblivious(partBase)) + { + continue; + } + } + var info = diagnostics.Add(ErrorCode.ERR_PartialMultipleBases, Locations[0], this); baseType = new ExtendedErrorTypeSymbol(baseType, LookupResultKind.Ambiguous, info); baseTypeLocation = decl.NameLocation; reportedPartialConflict = true; + + static bool containsOnlyOblivious(TypeSymbol type) + { + return TypeWithAnnotations.Create(type).VisitType( + type: null, + static (type, arg, flag) => !type.Type.IsValueType && !type.NullableAnnotation.IsOblivious(), + typePredicate: null, + arg: (object)null) is null; + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 2f0f4b7ab8281..41801dbdd0d15 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -131349,6 +131349,598 @@ partial interface I4 where T : I1 ); } + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_01() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); +Base base2 = new C(); // 1 + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable disable +partial class C : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,23): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base2 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(5, 23) + ); + + var cClass = comp.GetMember("C"); + var @base = cClass.BaseTypeNoUseSiteDiagnostics; + // note: the symbol model doesn't include a nullable annotation for base types or implemented interfaces. + Assert.Equal("Base", @base.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_02() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); // 1 +Base base2 = new C(); + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable disable +partial class C : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (4,22): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base1 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(4, 22) + ); + + var cClass = comp.GetMember("C"); + var @base = cClass.BaseTypeNoUseSiteDiagnostics; + // note: the symbol model doesn't include a nullable annotation for base types or implemented interfaces. + Assert.Equal("Base", @base.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_03() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); +Base base2 = new C(); // 1 + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable enable +partial class C : Base< +#nullable disable + string +#nullable enable +> { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,23): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base2 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(5, 23) + ); + + var cClass = comp.GetMember("C"); + var @base = cClass.BaseTypeNoUseSiteDiagnostics; + Assert.Equal("Base", @base.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_04() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); // 1 +Base base2 = new C(); + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable enable +partial class C : Base< +#nullable disable + string +#nullable enable +> { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (4,22): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base1 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(4, 22) + ); + + var cClass = comp.GetMember("C"); + var @base = cClass.BaseTypeNoUseSiteDiagnostics; + Assert.Equal("Base", @base.ToTestDisplayString(includeNonNullable: true)); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_05() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); +Base base2 = new C(); // 1 + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable enable +partial class C : Base< +#nullable disable + string +> { } // Note: the top-level nullability is oblivious here. +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,23): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base2 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(5, 23) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_06() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); +Base base2 = new C(); // 1 +Base base3 = new C(); // 2 + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } +"; + var source2 = @" +#nullable disable +partial class C : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,30): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base2 = new C(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(5, 30), + // (6,32): warning CS8619: Nullability of reference types in value of type 'C' doesn't match target type 'Base'. + // Base base3 = new C(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C()").WithArguments("C", "Base").WithLocation(6, 32) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_07() + { + var source0 = @" +#nullable enable + +Base base1 = new C(); // 1 +Base base2 = new C(); // 2 +Base base3 = new C(); // 3 + +class Base { } +"; + var source1 = @" +#nullable enable +partial class C : Base { } // 4 +"; + var source2 = @" +#nullable disable +partial class C : Base { } +"; + var source3 = @" +#nullable enable +partial class C : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2, source3 }); + verify(); + + comp = CreateCompilation(new[] { source0, source3, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (3,15): error CS0263: Partial declarations of 'C' must not specify different base classes + // partial class C : Base { } // 4 + Diagnostic(ErrorCode.ERR_PartialMultipleBases, "C").WithArguments("C").WithLocation(3, 15), + // (4,31): error CS0029: Cannot implicitly convert type 'C' to 'Base' + // Base base1 = new C(); // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C()").WithArguments("C", "Base").WithLocation(4, 31), + // (5,30): error CS0029: Cannot implicitly convert type 'C' to 'Base' + // Base base2 = new C(); // 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C()").WithArguments("C", "Base").WithLocation(5, 30), + // (6,32): error CS0029: Cannot implicitly convert type 'C' to 'Base' + // Base base3 = new C(); // 3 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C()").WithArguments("C", "Base").WithLocation(6, 32) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_08() + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); +Base base2 = new C1(); // 1 + +Base base3 = new C2(); // 2 +Base base4 = new C2(); + +class Base { } + +struct S { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var source2 = @" +#nullable disable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,25): warning CS8619: Nullability of reference types in value of type 'C1' doesn't match target type 'Base'. + // Base base2 = new C1(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C1()").WithArguments("C1", "Base").WithLocation(5, 25), + // (7,26): warning CS8619: Nullability of reference types in value of type 'C2' doesn't match target type 'Base'. + // Base base3 = new C2(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C2()").WithArguments("C2", "Base").WithLocation(7, 26) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_09() + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); +Base base2 = new C1(); // 1 + +Base base3 = new C2(); // 2 +Base base4 = new C2(); + +class Base { } + +struct S { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var source2 = @" +#nullable disable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,26): warning CS8619: Nullability of reference types in value of type 'C1' doesn't match target type 'Base'. + // Base base2 = new C1(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C1()").WithArguments("C1", "Base").WithLocation(5, 26), + // (7,27): warning CS8619: Nullability of reference types in value of type 'C2' doesn't match target type 'Base'. + // Base base3 = new C2(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C2()").WithArguments("C2", "Base").WithLocation(7, 27) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_10() + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); +Base base2 = new C1(); // 1 + +Base base3 = new C2(); // 2 +Base base4 = new C2(); + +class Base { } + +struct S { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var source2 = @" +#nullable enable +partial class C1 : Base< + S, +#nullable disable + object> { } + +#nullable enable +partial class C2 : Base< + object, +#nullable disable + S> { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,25): warning CS8619: Nullability of reference types in value of type 'C1' doesn't match target type 'Base'. + // Base base2 = new C1(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C1()").WithArguments("C1", "Base").WithLocation(5, 25), + // (7,26): warning CS8619: Nullability of reference types in value of type 'C2' doesn't match target type 'Base'. + // Base base3 = new C2(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C2()").WithArguments("C2", "Base").WithLocation(7, 26) + ); + } + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_11() + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); +Base base2 = new C1(); // 1 + +Base base3 = new C2(); // 2 +Base base4 = new C2(); + +class Base { } + +struct S { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base { } +partial class C2 : Base { } +"; + var source2 = @" +#nullable enable +partial class C1 : Base< + S?, +#nullable disable + object> { } + +#nullable enable +partial class C2 : Base< + object, +#nullable disable + S?> { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + comp.VerifyDiagnostics( + // (5,26): warning CS8619: Nullability of reference types in value of type 'C1' doesn't match target type 'Base'. + // Base base2 = new C1(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C1()").WithArguments("C1", "Base").WithLocation(5, 26), + // (7,27): warning CS8619: Nullability of reference types in value of type 'C2' doesn't match target type 'Base'. + // Base base3 = new C2(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C2()").WithArguments("C2", "Base").WithLocation(7, 27) + ); + } + + [Fact, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + public void PartialBaseTypeDifference_12() + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); +Base base2 = new C1(); // 1 + +Base base3 = new C2(); // 2 +Base base4 = new C2(); + +class Base { } + +struct S { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base where TStruct : struct { } +partial class C2 : Base where TStruct : struct { } +"; + var source2 = @" +#nullable enable +partial class C1 : Base< + TStruct, +#nullable disable + object> where TStruct : struct { } + +#nullable enable +partial class C2 : Base< + object, +#nullable disable + TStruct> where TStruct : struct { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + verify(); + + comp = CreateCompilation(new[] { source0, source2, source1 }); + verify(); + + void verify() + { + comp.VerifyDiagnostics( + // (5,25): warning CS8619: Nullability of reference types in value of type 'C1' doesn't match target type 'Base'. + // Base base2 = new C1(); // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C1()").WithArguments("C1", "Base").WithLocation(5, 25), + // (7,26): warning CS8619: Nullability of reference types in value of type 'C2' doesn't match target type 'Base'. + // Base base3 = new C2(); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new C2()").WithArguments("C2", "Base").WithLocation(7, 26) + ); + } + } + + [Theory, WorkItem(45960, "https://github.com/dotnet/roslyn/issues/45960")] + [InlineData("")] + [InlineData("where T : class")] + public void PartialBaseTypeDifference_13(string constraints) + { + var source0 = @" +#nullable enable + +Base base1 = new C1(); // 1 +Base base2 = new C1(); // 2 + +Base base3 = new C2(); // 3 +Base base4 = new C2(); // 4 + +class Base { } + +class C { } +"; + var source1 = @" +#nullable enable +partial class C1 : Base " + constraints + @" { } // 5 +partial class C2 : Base " + constraints + @" { } // 6 +"; + var source2 = @" +#nullable enable +partial class C1 : Base< + T, +#nullable disable + object> " + constraints + @" { } + +#nullable enable +partial class C2 : Base< + object, +#nullable disable + T> " + constraints + @" { } +"; + var comp = CreateCompilation(new[] { source0, source1, source2 }); + comp.VerifyDiagnostics( + // (3,15): error CS0263: Partial declarations of 'C1' must not specify different base classes + // partial class C1 : Base { } // 5 + Diagnostic(ErrorCode.ERR_PartialMultipleBases, "C1").WithArguments("C1").WithLocation(3, 15), + // (4,15): error CS0263: Partial declarations of 'C2' must not specify different base classes + // partial class C2 : Base { } // 6 + Diagnostic(ErrorCode.ERR_PartialMultipleBases, "C2").WithArguments("C2").WithLocation(4, 15), + // (4,26): error CS0029: Cannot implicitly convert type 'C1' to 'Base' + // Base base1 = new C1(); // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C1()").WithArguments("C1", "Base").WithLocation(4, 26), + // (5,25): error CS0029: Cannot implicitly convert type 'C1' to 'Base' + // Base base2 = new C1(); // 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C1()").WithArguments("C1", "Base").WithLocation(5, 25), + // (7,26): error CS0029: Cannot implicitly convert type 'C2' to 'Base' + // Base base3 = new C2(); // 3 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C2()").WithArguments("C2", "Base").WithLocation(7, 26), + // (8,25): error CS0029: Cannot implicitly convert type 'C2' to 'Base' + // Base base4 = new C2(); // 4 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C2()").WithArguments("C2", "Base").WithLocation(8, 25) + ); + } + [Fact] public void PartialMethodsWithConstraints_01() {