diff --git a/docs/features/nullable-reference-types.md b/docs/features/nullable-reference-types.md index 8e2e034cf83cb9352e2dbf4ece9e17a05df62e41..61e094383145e2c5e5382d86fd8fdc2c8f228ebd 100644 --- a/docs/features/nullable-reference-types.md +++ b/docs/features/nullable-reference-types.md @@ -179,15 +179,23 @@ A warning is reported for inconsistent top-level nullability of constraint types static void F4 where T : class, Stream? { } // warning static void F5 where T : Stream?, IDisposable { } // warning ``` -An error is reported for duplicate constraints. _Should the error be reported for duplicates that differ by top-level or nested nullability?_ +An error is reported for duplicate constraints where constraints are compared ignoring top-level and nested nullability. ```c# class C where T : class { - static void F1() where U : T?, T? { } // error: duplicate constraint - static void F2() where U : I, I { } // error: duplicate constraint - static void F3() where U : T, T? { } // error? - static void F4() where U : I, I { } // error? + static void F1() where U : T, T? { } // error: duplicate constraint + static void F2() where U : I, I { } // error: duplicate constraint } ``` +_What are the rules for annotated (unannotated) type arguments for generic type parameters from unannotated (annotated) types and methods?_ +```c# +[NotNullTypes(false)] List F1(T t) where T : class { ... } +[NotNullTypes(true)] List F2(T t) where T : class { ... } +[NotNullTypes(true)] List F3(T? t) where T : class { ... } +var x = F1(notNullString); // List or List ? +var y = F1(maybeNullString); // List or List ? +var z = F2(obliviousString); // List! or List! ? +var w = F3(obliviousString); // List! or List! ? +``` ## Compiler switch _Describe behavior when feature is disabled._ diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs index 3eb67cfcf0a0fd7b15bdbf23af10eb26d94f16d0..3f94fb87b75994ed13be32f4924ccb6ab897b828 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs @@ -209,12 +209,11 @@ internal partial class Binder return false; } - // PROTOTYPE(NullableReferenceTypes): Report ERR_DuplicateBound for - // duplicates that differ by top-level or nested nullability as well? - if (constraintTypes.Contains(c => type.Equals(c, TypeCompareKind.AllAspects))) + // Ignore nullability when comparing constraints. + if (constraintTypes.Contains(c => type.Equals(c, TypeCompareKind.ConsiderEverything))) { // "Duplicate constraint '{0}' for type parameter '{1}'" - Error(diagnostics, ErrorCode.ERR_DuplicateBound, syntax, type, typeParameterName); + Error(diagnostics, ErrorCode.ERR_DuplicateBound, syntax, type.TypeSymbol.SetUnknownNullabilityForReferenceTypes(), typeParameterName); return false; } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 082a1485b98477b16f51b66cf9f8a458ccb0ef72..6c7217549740cca9f26572236a3175a1946be92e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -34612,24 +34612,33 @@ class C where V : V?, V? { } where U3 : T3, T3?;"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( - // (3,26): error CS0405: Duplicate constraint 'V?' for type parameter 'V' - // class C where V : V?, V? { } - Diagnostic(ErrorCode.ERR_DuplicateBound, "V?").WithArguments("V?", "V").WithLocation(3, 26), - // (3,9): error CS0454: Circular constraint dependency involving 'V' and 'V' - // class C where V : V?, V? { } - Diagnostic(ErrorCode.ERR_CircularConstraint, "V").WithArguments("V", "V").WithLocation(3, 9), - // (1,9): error CS0454: Circular constraint dependency involving 'T' and 'T' + // (1,25): error CS0405: Duplicate constraint 'T' for type parameter 'T' // class A where T : T, T? { } - Diagnostic(ErrorCode.ERR_CircularConstraint, "T").WithArguments("T", "T").WithLocation(1, 9), + Diagnostic(ErrorCode.ERR_DuplicateBound, "T?").WithArguments("T", "T").WithLocation(1, 25), // (1,9): error CS0454: Circular constraint dependency involving 'T' and 'T' // class A where T : T, T? { } Diagnostic(ErrorCode.ERR_CircularConstraint, "T").WithArguments("T", "T").WithLocation(1, 9), - // (2,9): error CS0454: Circular constraint dependency involving 'U' and 'U' + // (2,26): error CS0405: Duplicate constraint 'U' for type parameter 'U' // class B where U : U?, U { } - Diagnostic(ErrorCode.ERR_CircularConstraint, "U").WithArguments("U", "U").WithLocation(2, 9), + Diagnostic(ErrorCode.ERR_DuplicateBound, "U").WithArguments("U", "U").WithLocation(2, 26), // (2,9): error CS0454: Circular constraint dependency involving 'U' and 'U' // class B where U : U?, U { } - Diagnostic(ErrorCode.ERR_CircularConstraint, "U").WithArguments("U", "U").WithLocation(2, 9)); + Diagnostic(ErrorCode.ERR_CircularConstraint, "U").WithArguments("U", "U").WithLocation(2, 9), + // (3,26): error CS0405: Duplicate constraint 'V' for type parameter 'V' + // class C where V : V?, V? { } + Diagnostic(ErrorCode.ERR_DuplicateBound, "V?").WithArguments("V", "V").WithLocation(3, 26), + // (3,9): error CS0454: Circular constraint dependency involving 'V' and 'V' + // class C where V : V?, V? { } + Diagnostic(ErrorCode.ERR_CircularConstraint, "V").WithArguments("V", "V").WithLocation(3, 9), + // (5,20): error CS0405: Duplicate constraint 'T1' for type parameter 'U1' + // where U1 : T1, T1?; + Diagnostic(ErrorCode.ERR_DuplicateBound, "T1?").WithArguments("T1", "U1").WithLocation(5, 20), + // (7,28): error CS0405: Duplicate constraint 'T2' for type parameter 'U2' + // where U2 : class, T2?, T2; + Diagnostic(ErrorCode.ERR_DuplicateBound, "T2").WithArguments("T2", "U2").WithLocation(7, 28), + // (10,20): error CS0405: Duplicate constraint 'T3' for type parameter 'U3' + // where U3 : T3, T3?; + Diagnostic(ErrorCode.ERR_DuplicateBound, "T3?").WithArguments("T3", "U3").WithLocation(10, 20)); } [Fact] @@ -34870,21 +34879,31 @@ class C where T : class static void F8() where U : I, I { } }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); - // PROTOTYPE(NullableReferenceTypes): Report ERR_DuplicateBound for - // duplicates that differ by top-level or nested nullability as well? comp.VerifyDiagnostics( // (4,38): error CS0405: Duplicate constraint 'T' for type parameter 'U' // static void F1() where U : T, T { } Diagnostic(ErrorCode.ERR_DuplicateBound, "T").WithArguments("T", "U").WithLocation(4, 38), - // (7,39): error CS0405: Duplicate constraint 'T?' for type parameter 'U' + // (5,38): error CS0405: Duplicate constraint 'T' for type parameter 'U' + // static void F2() where U : T, T? { } + Diagnostic(ErrorCode.ERR_DuplicateBound, "T?").WithArguments("T", "U").WithLocation(5, 38), + // (6,39): error CS0405: Duplicate constraint 'T' for type parameter 'U' + // static void F3() where U : T?, T { } + Diagnostic(ErrorCode.ERR_DuplicateBound, "T").WithArguments("T", "U").WithLocation(6, 39), + // (7,39): error CS0405: Duplicate constraint 'T' for type parameter 'U' // static void F4() where U : T?, T? { } - Diagnostic(ErrorCode.ERR_DuplicateBound, "T?").WithArguments("T?", "U").WithLocation(7, 39), + Diagnostic(ErrorCode.ERR_DuplicateBound, "T?").WithArguments("T", "U").WithLocation(7, 39), // (8,41): error CS0405: Duplicate constraint 'I' for type parameter 'U' // static void F5() where U : I, I { } Diagnostic(ErrorCode.ERR_DuplicateBound, "I").WithArguments("I", "U").WithLocation(8, 41), - // (11,42): error CS0405: Duplicate constraint 'I' for type parameter 'U' + // (9,41): error CS0405: Duplicate constraint 'I' for type parameter 'U' + // static void F6() where U : I, I { } + Diagnostic(ErrorCode.ERR_DuplicateBound, "I").WithArguments("I", "U").WithLocation(9, 41), + // (10,42): error CS0405: Duplicate constraint 'I' for type parameter 'U' + // static void F7() where U : I, I { } + Diagnostic(ErrorCode.ERR_DuplicateBound, "I").WithArguments("I", "U").WithLocation(10, 42), + // (11,42): error CS0405: Duplicate constraint 'I' for type parameter 'U' // static void F8() where U : I, I { } - Diagnostic(ErrorCode.ERR_DuplicateBound, "I").WithArguments("I", "U").WithLocation(11, 42)); + Diagnostic(ErrorCode.ERR_DuplicateBound, "I").WithArguments("I", "U").WithLocation(11, 42)); } [Fact] @@ -34908,7 +34927,7 @@ public void TypeUnification_01() @"interface I { } class C1 : I, I { } class C2 : I, I { } -class C3 : I, I { } +class C3 : I, I { } class C4 : I, I { }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( @@ -34919,8 +34938,8 @@ class C4 : I, I { }"; // class C2 : I, I { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C2").WithArguments("C2", "I", "I").WithLocation(3, 7), // (4,7): error CS0695: 'C3' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions - // class C3 : I, I { } - Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), + // class C3 : I, I { } + Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), // (5,7): error CS0695: 'C4' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C4 : I, I { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C4").WithArguments("C4", "I", "I").WithLocation(5, 7)); @@ -34933,7 +34952,7 @@ public void TypeUnification_02() @"interface I { } class C1 : I, I where T : struct { } class C2 : I, I where T : struct { } -class C3 : I, I where T : struct { } +class C3 : I, I where T : struct { } class C4 : I, I where T : struct { }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( @@ -34944,8 +34963,8 @@ class C4 : I, I where T : struct { }"; // class C2 : I, I where T : struct { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C2").WithArguments("C2", "I", "I").WithLocation(3, 7), // (4,7): error CS0695: 'C3' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions - // class C3 : I, I where T : struct { } - Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), + // class C3 : I, I where T : struct { } + Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), // (5,7): error CS0695: 'C4' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C4 : I, I where T : struct { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C4").WithArguments("C4", "I", "I").WithLocation(5, 7)); @@ -34958,7 +34977,7 @@ public void TypeUnification_03() @"interface I { } class C1 : I, I where T : class { } class C2 : I, I where T : class { } -class C3 : I, I where T : class { } +class C3 : I, I where T : class { } class C4 : I, I where T : class { }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( @@ -34969,8 +34988,8 @@ class C4 : I, I where T : class { }"; // class C2 : I, I where T : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C2").WithArguments("C2", "I", "I").WithLocation(3, 7), // (4,7): error CS0695: 'C3' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions - // class C3 : I, I where T : class { } - Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), + // class C3 : I, I where T : class { } + Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), // (5,7): error CS0695: 'C4' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C4 : I, I where T : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C4").WithArguments("C4", "I", "I").WithLocation(5, 7)); @@ -34983,9 +35002,10 @@ public void TypeUnification_04() @"interface I { } class C1 : I, I where T : struct where U : class { } class C2 : I, I where T : struct where U : class { } -class C3 : I, I where T : struct where U : class { } +class C3 : I, I where T : struct where U : class { } class C4 : I, I where T : struct where U : class { }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); + // Constraints are ignored when unifying types. comp.VerifyDiagnostics( // (2,7): error CS0695: 'C1' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C1 : I, I where T : struct where U : class { } @@ -34994,8 +35014,8 @@ class C4 : I, I where T : struct where U : class { }"; // class C2 : I, I where T : struct where U : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C2").WithArguments("C2", "I", "I").WithLocation(3, 7), // (4,7): error CS0695: 'C3' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions - // class C3 : I, I where T : struct where U : class { } - Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), + // class C3 : I, I where T : struct where U : class { } + Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), // (5,7): error CS0695: 'C4' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C4 : I, I where T : struct where U : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C4").WithArguments("C4", "I", "I").WithLocation(5, 7)); @@ -35008,7 +35028,7 @@ public void TypeUnification_05() @"interface I where T : class { } class C1 : I, I where T : class where U : class { } class C2 : I, I where T : class where U : class { } -class C3 : I, I where T : class where U : class { } +class C3 : I, I where T : class where U : class { } class C4 : I, I where T : class where U : class { }"; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular8); comp.VerifyDiagnostics( @@ -35019,8 +35039,8 @@ class C4 : I, I where T : class where U : class { }"; // class C2 : I, I where T : class where U : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C2").WithArguments("C2", "I", "I").WithLocation(3, 7), // (4,7): error CS0695: 'C3' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions - // class C3 : I, I where T : class where U : class { } - Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), + // class C3 : I, I where T : class where U : class { } + Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C3").WithArguments("C3", "I", "I").WithLocation(4, 7), // (5,7): error CS0695: 'C4' cannot implement both 'I' and 'I' because they may unify for some type parameter substitutions // class C4 : I, I where T : class where U : class { } Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C4").WithArguments("C4", "I", "I").WithLocation(5, 7)); @@ -35311,6 +35331,92 @@ static void Main() Assert.Equal("A2!", typeParameters[1].ConstraintTypesNoUseSiteDiagnostics[0].ToTestDisplayString(true)); } + [Fact] + public void UnannotatedConstraint_Override() + { + var source0 = +@"using System.Runtime.CompilerServices; +public interface I { } +public abstract class A where T : class +{ + [NonNullTypes(false)] public abstract void F1() where U : T, I; + [NonNullTypes(false)] public abstract void F2() where U : T?, I; + [NonNullTypes(true)] public abstract void F3() where U : T, I; + [NonNullTypes(true)] public abstract void F4() where U : T?, I; +}"; + var comp0 = CreateCompilation(new[] { source0, NonNullTypesAttributesDefinition }, parseOptions: TestOptions.Regular8); + comp0.VerifyDiagnostics(); + var ref0 = comp0.EmitToImageReference(); + + var source = +@"using System.Runtime.CompilerServices; +[NonNullTypes(false)] +class B1 : A +{ + public override void F1() { } + public override void F2() { } + public override void F3() { } + public override void F4() { } +} +[NonNullTypes(false)] +class B2 : A +{ + public override void F1() { } + public override void F2() { } + public override void F3() { } + public override void F4() { } +} +[NonNullTypes(true)] +class B3 : A +{ + public override void F1() { } + public override void F2() { } + public override void F3() { } + public override void F4() { } +} +[NonNullTypes(true)] +class B4 : A +{ + public override void F1() { } + public override void F2() { } + public override void F3() { } + public override void F4() { } +}"; + var comp = CreateCompilation(source, references: new[] { new CSharpCompilationReference(comp0) }, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics(); + verifyAllConstraintTypes(); + + comp = CreateCompilation(source, references: new[] { comp0.EmitToImageReference() }, parseOptions: TestOptions.Regular8); + comp.VerifyDiagnostics(); + verifyAllConstraintTypes(); + + void verifyAllConstraintTypes() + { + verifyConstraintTypes("B1.F1", "System.String", "I"); + verifyConstraintTypes("B1.F2", "System.String?", "I"); + verifyConstraintTypes("B1.F3", "System.String", "I"); + verifyConstraintTypes("B1.F4", "System.String?", "I"); + verifyConstraintTypes("B2.F1", "System.String?", "I"); + verifyConstraintTypes("B2.F2", "System.String?", "I"); + verifyConstraintTypes("B2.F3", "System.String?", "I"); + verifyConstraintTypes("B2.F4", "System.String?", "I"); + verifyConstraintTypes("B3.F1", "System.String!", "I"); + verifyConstraintTypes("B3.F2", "System.String?", "I"); + verifyConstraintTypes("B3.F3", "System.String!", "I"); + verifyConstraintTypes("B3.F4", "System.String?", "I"); + verifyConstraintTypes("B4.F1", "System.String?", "I"); + verifyConstraintTypes("B4.F2", "System.String?", "I"); + verifyConstraintTypes("B4.F3", "System.String?", "I"); + verifyConstraintTypes("B4.F4", "System.String?", "I"); + } + + void verifyConstraintTypes(string methodName, params string[] expectedTypes) + { + var constraintTypes = comp.GetMember(methodName).TypeParameters[0].ConstraintTypesNoUseSiteDiagnostics; + AssertEx.Equal(expectedTypes, constraintTypes.SelectAsArray(t => t.ToTestDisplayString(true))); + } + } + [Fact] public void Constraint_Oblivious_01() {