diff --git a/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs b/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs index be77e9a27411126e2279778b8d1a31f10fa90937..92ba362e6e0f4515a44c58f5a3aa145179d97ba3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AbstractTypeMap.cs @@ -325,12 +325,16 @@ internal ImmutableArray SubstituteTypes(ImmutableArray /// Substitute types, and return the results without duplicates, preserving the original order. + /// Note, all occurrences of 'dynamic' in resulting types will be replaced with 'object'. /// internal void SubstituteConstraintTypesDistinctWithoutModifiers( + TypeParameterSymbol owner, ImmutableArray original, ArrayBuilder result, HashSet ignoreTypesDependentOnTypeParametersOpt) { + DynamicTypeEraser dynamicEraser = null; + if (original.Length == 0) { return; @@ -341,7 +345,7 @@ internal ImmutableArray SubstituteTypes(ImmutableArray SubstituteTypes(ImmutableArray SubstituteTypes(ImmutableArray SubstituteTypeParameters(ImmutableArray original) diff --git a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs index 682889d0f2b5425ef7926bd7deea436315608045..53d97bbfab722a33924f6e3d26123c5bd1393efd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs @@ -108,7 +108,6 @@ internal static class ConstraintsHelper NamedTypeSymbol effectiveBaseClass = corLibrary.GetSpecialType(typeParameter.HasValueTypeConstraint ? SpecialType.System_ValueType : SpecialType.System_Object); TypeSymbol deducedBaseType = effectiveBaseClass; - DynamicTypeEraser dynamicEraser = null; if (constraintTypes.Length == 0) { @@ -125,15 +124,13 @@ internal static class ConstraintsHelper // interfaces, and filter out any constraint types that cause cycles. foreach (var constraintType in constraintTypes) { + Debug.Assert(!constraintType.Type.ContainsDynamic()); + NamedTypeSymbol constraintEffectiveBase; TypeSymbol constraintDeducedBase; switch (constraintType.TypeKind) { - case TypeKind.Dynamic: - Debug.Assert(inherited || currentCompilation == null); - continue; - case TypeKind.TypeParameter: { var constraintTypeParameter = (TypeParameterSymbol)constraintType.Type; @@ -188,35 +185,18 @@ internal static class ConstraintsHelper case TypeKind.Interface: case TypeKind.Class: case TypeKind.Delegate: - NamedTypeSymbol erasedConstraintType; - if (inherited || currentCompilation == null) - { - // only inherited constraints may contain dynamic - if (dynamicEraser == null) - { - dynamicEraser = new DynamicTypeEraser(corLibrary.GetSpecialType(SpecialType.System_Object)); - } - - erasedConstraintType = (NamedTypeSymbol)dynamicEraser.EraseDynamic(constraintType.Type); - } - else - { - Debug.Assert(!constraintType.Type.ContainsDynamic()); - Debug.Assert(constraintType.TypeKind != TypeKind.Delegate); - - erasedConstraintType = (NamedTypeSymbol)constraintType.Type; - } + Debug.Assert(inherited || currentCompilation == null || constraintType.TypeKind != TypeKind.Delegate); if (constraintType.Type.IsInterfaceType()) { - AddInterface(interfacesBuilder, erasedConstraintType); + AddInterface(interfacesBuilder, (NamedTypeSymbol)constraintType.Type); constraintTypesBuilder.Add(constraintType); continue; } else { - constraintEffectiveBase = erasedConstraintType; + constraintEffectiveBase = (NamedTypeSymbol)constraintType.Type; constraintDeducedBase = constraintType.Type; break; } @@ -920,7 +900,7 @@ private static bool HasDuplicateInterfaces(NamedTypeSymbol type, ConsList.GetInstance(); HashSet useSiteDiagnostics = null; - substitution.SubstituteConstraintTypesDistinctWithoutModifiers(typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), constraintTypes, + substitution.SubstituteConstraintTypesDistinctWithoutModifiers(typeParameter, typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), constraintTypes, ignoreTypeConstraintsDependentOnTypeParametersOpt); bool hasError = false; diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedTypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedTypeParameterSymbol.cs index d4271f503b255927051460cbd10376bc9018a39a..6e70af594e8dd565b674786273b4be6c1a3464d6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedTypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedTypeParameterSymbol.cs @@ -100,7 +100,7 @@ public override ImmutableArray GetAttributes() internal override ImmutableArray GetConstraintTypes(ConsList inProgress) { var constraintTypes = ArrayBuilder.GetInstance(); - _map.SubstituteConstraintTypesDistinctWithoutModifiers(_underlyingTypeParameter.GetConstraintTypes(inProgress), constraintTypes, null); + _map.SubstituteConstraintTypesDistinctWithoutModifiers(_underlyingTypeParameter, _underlyingTypeParameter.GetConstraintTypes(inProgress), constraintTypes, null); TypeWithAnnotations bestObjectConstraint = default; @@ -160,7 +160,7 @@ internal override ImmutableArray GetConstraintTypes(ConsLis else if (!HasNotNullConstraint && !HasValueTypeConstraint && !HasReferenceTypeConstraint) { var constraintTypes = ArrayBuilder.GetInstance(); - _map.SubstituteConstraintTypesDistinctWithoutModifiers(_underlyingTypeParameter.GetConstraintTypes(ConsList.Empty), constraintTypes, null); + _map.SubstituteConstraintTypesDistinctWithoutModifiers(_underlyingTypeParameter, _underlyingTypeParameter.GetConstraintTypes(ConsList.Empty), constraintTypes, null); return IsNotNullableIfReferenceTypeFromConstraintTypes(constraintTypes.ToImmutableAndFree()); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index e23407cd7e3c035145e1ee4dd4d482a5a4770154..e700958e54ff90f11d4f0af1e8cae40c3528278d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -70210,6 +70210,219 @@ void symbolValidator(ModuleSymbol m) } } + [Fact] + public void DynamicConstraint_01() + { + var source = +@" +class B +{ + public static void F1(T1 t1) where T1 : dynamic + { + } + + public static void F2(T2 t2) where T2 : B + { + } +}"; + var comp = CreateCompilation(new[] { source }); + comp.VerifyDiagnostics( + // (4,49): error CS1967: Constraint cannot be the dynamic type + // public static void F1(T1 t1) where T1 : dynamic + Diagnostic(ErrorCode.ERR_DynamicTypeAsBound, "dynamic").WithLocation(4, 49), + // (8,49): error CS1968: Constraint cannot be a dynamic type 'B' + // public static void F2(T2 t2) where T2 : B + Diagnostic(ErrorCode.ERR_ConstructedDynamicTypeAsBound, "B").WithArguments("B").WithLocation(8, 49) + ); + + var m = comp.SourceModule; + + var f1 = (MethodSymbol)m.GlobalNamespace.GetMember("B.F1"); + Assert.Equal("void B.F1(T1 t1)", f1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + + var f2 = (MethodSymbol)m.GlobalNamespace.GetMember("B.F2"); + Assert.Equal("void B.F2(T2 t2)", f2.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + } + + [Fact] + [WorkItem(36276, "https://github.com/dotnet/roslyn/issues/36276")] + public void DynamicConstraint_02() + { + var source1 = + @" +#nullable enable +class Test1 +{ + public virtual void M1() where S : T + { + } +} + +class Test2 : Test1 +{ + public override void M1() + { + } + + void Test() + { + base.M1(); + this.M1(); + } +} +"; + var comp1 = CreateCompilation(new[] { source1 }); + comp1.VerifyDiagnostics( + // (18,9): warning CS8631: The type 'object?' cannot be used as type parameter 'S' in the generic type or method 'Test1.M1()'. Nullability of type argument 'object?' doesn't match constraint type 'object'. + // base.M1(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "base.M1").WithArguments("Test1.M1()", "object", "S", "object?").WithLocation(18, 9), + // (19,9): warning CS8631: The type 'object?' cannot be used as type parameter 'S' in the generic type or method 'Test2.M1()'. Nullability of type argument 'object?' doesn't match constraint type 'object'. + // this.M1(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "this.M1").WithArguments("Test2.M1()", "object", "S", "object?").WithLocation(19, 9) + ); + + CompileAndVerify(comp1, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator); + void symbolValidator(ModuleSymbol m) + { + var m1 = (MethodSymbol)m.GlobalNamespace.GetMember("Test2.M1"); + Assert.Equal("void Test2.M1() where S : System.Object!", m1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(m1.TypeParameters[0].IsNotNullableIfReferenceType); + + var baseM1 = m1.OverriddenMethod; + Assert.Equal("void Test1.M1() where S : System.Object!", baseM1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(baseM1.TypeParameters[0].IsNotNullableIfReferenceType); + } + } + + [Fact] + [WorkItem(36276, "https://github.com/dotnet/roslyn/issues/36276")] + public void DynamicConstraint_03() + { + var source1 = + @" +#nullable disable +class Test1 +{ + public virtual void M1() where S : T + { + } +} + +class Test2 : Test1 +{ + public override void M1() + { + } + + void Test() + { +#nullable enable + base.M1(); + this.M1(); + } +} +"; + var comp1 = CreateCompilation(new[] { source1 }); + comp1.VerifyDiagnostics(); + + CompileAndVerify(comp1, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator); + void symbolValidator(ModuleSymbol m) + { + var m1 = (MethodSymbol)m.GlobalNamespace.GetMember("Test2.M1"); + Assert.Equal("void Test2.M1()", m1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.Null(m1.TypeParameters[0].IsNotNullableIfReferenceType); + + var baseM1 = m1.OverriddenMethod; + Assert.Equal("void Test1.M1()", baseM1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.Null(baseM1.TypeParameters[0].IsNotNullableIfReferenceType); + } + } + + [Fact] + public void DynamicConstraint_04() + { + var source1 = + @" +#nullable enable +class Test1 +{ + public virtual void M1() where S : T + { + } +} + +class Test2 : Test1> +{ + public override void M1() + { + } + + void Test() + { + base.M1>(); + this.M1>(); + } +} +"; + var comp1 = CreateCompilation(new[] { source1 }); + comp1.VerifyDiagnostics( + // (18,9): warning CS8631: The type 'Test1' cannot be used as type parameter 'S' in the generic type or method 'Test1>.M1()'. Nullability of type argument 'Test1' doesn't match constraint type 'Test1'. + // base.M1>(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "base.M1>").WithArguments("Test1>.M1()", "Test1", "S", "Test1").WithLocation(18, 9), + // (19,9): warning CS8631: The type 'Test1' cannot be used as type parameter 'S' in the generic type or method 'Test2.M1()'. Nullability of type argument 'Test1' doesn't match constraint type 'Test1'. + // this.M1>(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "this.M1>").WithArguments("Test2.M1()", "Test1", "S", "Test1").WithLocation(19, 9) + ); + + CompileAndVerify(comp1, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator); + void symbolValidator(ModuleSymbol m) + { + var m1 = (MethodSymbol)m.GlobalNamespace.GetMember("Test2.M1"); + Assert.Equal("void Test2.M1() where S : Test1!", m1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(m1.TypeParameters[0].IsNotNullableIfReferenceType); + + var baseM1 = m1.OverriddenMethod; + Assert.Equal("void Test1!>.M1() where S : Test1!", baseM1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(baseM1.TypeParameters[0].IsNotNullableIfReferenceType); + } + } + + [Fact] + public void DynamicConstraint_05() + { + var source1 = + @" +#nullable enable +public class Test1 +{ + public virtual void M1(S x) where S : I1?, T {} +} + +public interface I1 {} + +public class Test2 : Test1 +{ + public override void M1(S x) + { + } +} +"; + var comp1 = CreateCompilation(new[] { source1 }); + comp1.VerifyDiagnostics(); + + CompileAndVerify(comp1, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator); + void symbolValidator(ModuleSymbol m) + { + var m1 = (MethodSymbol)m.GlobalNamespace.GetMember("Test2.M1"); + Assert.Equal("void Test2.M1(S x) where S : System.Object!, I1?", m1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(m1.TypeParameters[0].IsNotNullableIfReferenceType); + + var baseM1 = m1.OverriddenMethod; + Assert.Equal("void Test1.M1(S x) where S : System.Object!, I1?", baseM1.ToDisplayString(SymbolDisplayFormat.TestFormatWithConstraints)); + Assert.True(baseM1.TypeParameters[0].IsNotNullableIfReferenceType); + } + } + [Fact] public void NotNullConstraint_01() {