diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 60bf9101b01d3..5bdf09d1c377c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -417,7 +417,7 @@ private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSy convertedType = inputType; } - if ((constantValueOpt?.IsNumeric == true) && HasBlockedINumberConversion(patternConversion, inputType)) + if ((constantValueOpt?.IsNumeric == true) && ShouldBlockINumberBaseConversion(patternConversion, inputType)) { // Cannot use a numeric constant or relational pattern on '{0}' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. diagnostics.Add(ErrorCode.ERR_CannotMatchOnINumberBase, node.Location, inputType); @@ -437,7 +437,7 @@ private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSy } } - private bool HasBlockedINumberConversion(Conversion patternConversion, TypeSymbol inputType) + private bool ShouldBlockINumberBaseConversion(Conversion patternConversion, TypeSymbol inputType) { // We want to block constant and relation patterns that have an input type constrained to or inherited from INumberBase, if we don't // know how to represent the constant being matched against in the input type. For example, `1.0 is 1` will work when written inline, but @@ -449,16 +449,8 @@ private bool HasBlockedINumberConversion(Conversion patternConversion, TypeSymbo return false; } - var iNumberBase = Compilation.GetWellKnownType(WellKnownType.System_Numerics_INumberBase_T); - - if (iNumberBase.IsErrorType()) - { - return false; - } - var interfaces = inputType is TypeParameterSymbol typeParam ? typeParam.EffectiveInterfacesNoUseSiteDiagnostics : inputType.AllInterfacesNoUseSiteDiagnostics; - return interfaces.Any(static (i, iNumberBase) => i.OriginalDefinition.Equals(iNumberBase, TypeCompareKind.AllIgnoreOptions), iNumberBase) - || inputType.OriginalDefinition.Equals(iNumberBase, TypeCompareKind.AllIgnoreOptions); + return interfaces.Any(static (i, _) => i.IsWellKnownINumberBaseType(), 0) || inputType.IsWellKnownINumberBaseType(); } private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e, BindingDiagnosticBag diagnostics, ref bool hasErrors) @@ -1663,7 +1655,7 @@ void addSubpatternsForTuple(ImmutableArray elementTypes) constantValueOpt = ConstantValue.Bad; } - if (!hasErrors && HasBlockedINumberConversion(patternConversion, inputType)) + if (!hasErrors && ShouldBlockINumberBaseConversion(patternConversion, inputType)) { // Cannot use a numeric constant or relational pattern on '{0}' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. diagnostics.Add(ErrorCode.ERR_CannotMatchOnINumberBase, node.Location, inputType); diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 16f7cd6b8144a..7cff3652c3bc6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -2060,18 +2060,34 @@ internal static bool IsCompilerServicesTopLevelType(this TypeSymbol typeSymbol) internal static bool IsWellKnownSetsRequiredMembersAttribute(this TypeSymbol type) => type.Name == "SetsRequiredMembersAttribute" && type.IsWellKnownDiagnosticsCodeAnalysisTopLevelType(); + internal static bool IsWellKnownINumberBaseType(this TypeSymbol type) + { + type = type.OriginalDefinition; + return type is NamedTypeSymbol { Name: "INumberBase", IsInterface: true, Arity: 1, ContainingType: null } && + IsContainedInNamespace(type, "System", "Numerics"); + } + private static bool IsWellKnownDiagnosticsCodeAnalysisTopLevelType(this TypeSymbol typeSymbol) => typeSymbol.ContainingType is null && IsContainedInNamespace(typeSymbol, "System", "Diagnostics", "CodeAnalysis"); - private static bool IsContainedInNamespace(this TypeSymbol typeSymbol, string outerNS, string midNS, string innerNS) + private static bool IsContainedInNamespace(this TypeSymbol typeSymbol, string outerNS, string midNS, string? innerNS = null) { - var innerNamespace = typeSymbol.ContainingNamespace; - if (innerNamespace?.Name != innerNS) + NamespaceSymbol? midNamespace; + + if (innerNS != null) { - return false; + var innerNamespace = typeSymbol.ContainingNamespace; + if (innerNamespace?.Name != innerNS) + { + return false; + } + midNamespace = innerNamespace.ContainingNamespace; + } + else + { + midNamespace = typeSymbol.ContainingNamespace; } - var midNamespace = innerNamespace.ContainingNamespace; if (midNamespace?.Name != midNS) { return false; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs index 907a57f2db5e7..204b383c6179d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; @@ -2046,11 +2047,108 @@ public void ForbiddenOnTypeParametersConstrainedToINumberBase_02() ); } + [Fact] + public void ForbiddenOnTypeParametersConstrainedToINumberBase_MultipleReferences_01() + { + var source = """ + #pragma warning disable 8321 // Unused local function + using System.Numerics; + + void M(T t) where T : struct, INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + int => 3, // OK + [] => 4, // 3 + (_) => 5, // OK + "" => 6, // 4 + { } => 7, // OK + var x => 8, // OK + _ => 9 // Ok + }; + } + """; + + var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(); + var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); + + var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); + comp.VerifyDiagnostics( + // (4,34): error CS0433: The type 'INumberBase' exists in both 'A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' + // void M(T t) where T : struct, INumberBase + Diagnostic(ErrorCode.ERR_SameFullNameAggAgg, "INumberBase").WithArguments("A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "System.Numerics.INumberBase", "B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(4, 34), + // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), + // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), + // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9), + // (13,9): error CS8121: An expression of type 'T' cannot be handled by a pattern of type 'string'. + // "" => 6, // 4 + Diagnostic(ErrorCode.ERR_PatternWrongType, @"""""").WithArguments("T", "string").WithLocation(13, 9) + ); + } + + [Fact] + public void ForbiddenOnTypeParametersConstrainedToINumberBase_MultipleReferences_02() + { + var source = """ + extern alias A; + #pragma warning disable 8321 // Unused local function + + void M(T t) where T : struct, A::System.Numerics.INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + int => 3, // OK + [] => 4, // 3 + (_) => 5, // OK + "" => 6, // 4 + { } => 7, // OK + var x => 8, // OK + _ => 9 // Ok + }; + } + """; + + var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(aliases: ImmutableArray.Create("A")); + var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); + + var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); + comp.VerifyDiagnostics( + // (8,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase'. Consider using a type pattern to narrow to a specifc numeric type. + // 1 => 1, // 1 + Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("T").WithLocation(8, 9), + // (9,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase'. Consider using a type pattern to narrow to a specifc numeric type. + // > 1 => 2, // 2 + Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("T").WithLocation(9, 9), + // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), + // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), + // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' + // [] => 4, // 3 + Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9), + // (13,9): error CS8121: An expression of type 'T' cannot be handled by a pattern of type 'string'. + // "" => 6, // 4 + Diagnostic(ErrorCode.ERR_PatternWrongType, @"""""").WithArguments("T", "string").WithLocation(13, 9) + ); + } + [Theory] [InlineData("class")] [InlineData("struct")] [InlineData("interface")] - public void ForbiddenOnTypeParametersInheritingFromINumberBase(string type) + public void ForbiddenOnTypesInheritingFromINumberBase(string type) { var source = $$""" #pragma warning disable 8321 // Unused local function @@ -2104,6 +2202,132 @@ public void ForbiddenOnTypeParametersInheritingFromINumberBase(string type) ); } + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + public void ForbiddenOnTypesInheritingFromINumberBase_MultipleReferences01(string type) + { + var source = $$""" + #pragma warning disable 8321 // Unused local function + using System.Numerics; + + C c = default(C); + int o = c switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + int => 3, // 3 + [] => 4, // 4 + (_) => 5, // OK + "" => 6, // 5 + { } => 7, // OK + var x => 8, // OK + _ => 9 // Ok + }; + + {{type}} C : + INumberBase + { + } + """; + + var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(); + var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); + + var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); + comp.VerifyDiagnostics( + // (7,5): error CS0029: Cannot implicitly convert type 'int' to 'C' + // 1 => 1, // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(7, 5), + // (8,5): error CS8781: Relational patterns may not be used for a value of type 'C'. + // > 1 => 2, // 2 + Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments("C").WithLocation(8, 5), + // (8,7): error CS0029: Cannot implicitly convert type 'int' to 'C' + // > 1 => 2, // 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(8, 7), + // (9,5): error CS8121: An expression of type 'C' cannot be handled by a pattern of type 'int'. + // int => 3, // 3 + Diagnostic(ErrorCode.ERR_PatternWrongType, "int").WithArguments("C", "int").WithLocation(9, 5), + // (10,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(10, 5), + // (10,5): error CS0518: Predefined type 'System.Index' is not defined or imported + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(10, 5), + // (10,5): error CS0021: Cannot apply indexing with [] to an expression of type 'C' + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("C").WithLocation(10, 5), + // (12,5): error CS0029: Cannot implicitly convert type 'string' to 'C' + // "" => 6, // 5 + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""""").WithArguments("string", "C").WithLocation(12, 5), + // (19,5): error CS0433: The type 'INumberBase' exists in both 'A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' + // INumberBase + Diagnostic(ErrorCode.ERR_SameFullNameAggAgg, "INumberBase").WithArguments("A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "System.Numerics.INumberBase", "B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(19, 5) + ); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + public void ForbiddenOnTypeParametersInheritingFromINumberBase_MultipleReferences02(string type) + { + var source = $$""" + extern alias A; + #pragma warning disable 8321 // Unused local function + + C c = default(C); + int o = c switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + int => 3, // 3 + [] => 4, // 4 + (_) => 5, // OK + "" => 6, // 5 + { } => 7, // OK + var x => 8, // OK + _ => 9 // Ok + }; + + {{type}} C : A::System.Numerics.INumberBase + { + } + """; + + var ref1 = CreateCompilation(INumberBaseDefinition).EmitToImageReference(aliases: ImmutableArray.Create("A")); + var ref2 = CreateCompilation(INumberBaseDefinition).EmitToImageReference(); + + var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); + comp.VerifyDiagnostics( + // (7,5): error CS0029: Cannot implicitly convert type 'int' to 'C' + // 1 => 1, // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(7, 5), + // (8,5): error CS8781: Relational patterns may not be used for a value of type 'C'. + // > 1 => 2, // 2 + Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments("C").WithLocation(8, 5), + // (8,7): error CS0029: Cannot implicitly convert type 'int' to 'C' + // > 1 => 2, // 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(8, 7), + // (9,5): error CS8121: An expression of type 'C' cannot be handled by a pattern of type 'int'. + // int => 3, // 3 + Diagnostic(ErrorCode.ERR_PatternWrongType, "int").WithArguments("C", "int").WithLocation(9, 5), + // (10,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(10, 5), + // (10,5): error CS0518: Predefined type 'System.Index' is not defined or imported + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(10, 5), + // (10,5): error CS0021: Cannot apply indexing with [] to an expression of type 'C' + // [] => 4, // 4 + Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("C").WithLocation(10, 5), + // (12,5): error CS0029: Cannot implicitly convert type 'string' to 'C' + // "" => 6, // 5 + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""""").WithArguments("string", "C").WithLocation(12, 5) + ); + } + private const string INumberBaseBCL = """ namespace System { @@ -2256,5 +2480,138 @@ public void MatchingOnConstantConversionsWithINumberBaseIsAllowed_TypePatternToI Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", $"System.Numerics.INumberBase<{inputType}>").WithLocation(6, 7) ); } + + [Fact] + public void MatchingOnINumberBaseIsAllowed_ClassNotInterface() + { + var source = """ + #pragma warning disable 8321 // Unused local function + using System.Numerics; + + void M(T t) where T : INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + _ => 3 + }; + } + + namespace System.Numerics + { + public class INumberBase where T : INumberBase + { + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MatchingOnINumberBaseIsAllowed_WrongArity() + { + var source = """ + #pragma warning disable 8321 // Unused local function + using System.Numerics; + + void M1(T1 t) where T1 : INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + _ => 3 + }; + } + + void M2(T t) where T : INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + _ => 3 + }; + } + + namespace System.Numerics + { + public class INumberBase where T1 : INumberBase + { + } + + public class INumberBase + { + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MatchingOnINumberBaseIsAllowed_WrongNamespace() + { + var source = """ + #pragma warning disable 8321 // Unused local function + using System; + + void M(T t) where T : INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + _ => 3 + }; + } + + namespace System + { + public interface INumberBase where T : INumberBase + { + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MatchingOnINumberBaseIsAllowed_NestedType() + { + var source = """ + #pragma warning disable 8321 // Unused local function + using static System.Numerics.Outer; + + void M(T t) where T : INumberBase + { + int o = t switch + { + 1 => 1, // 1 + > 1 => 2, // 2 + _ => 3 + }; + } + + namespace System.Numerics + { + public interface Outer + { + public interface INumberBase where T : INumberBase + { + } + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } } } diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 187199184300e..8f8b77a97f155 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -328,8 +328,6 @@ internal enum WellKnownType System_GC, - System_Numerics_INumberBase_T, - NextAvailable, // Remember to update the AllWellKnownTypes tests when making changes here } @@ -646,8 +644,6 @@ internal static class WellKnownTypes "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", "System.MissingMethodException", "System.GC", - - "System.Numerics.INumberBase`1", }; private static readonly Dictionary s_nameToTypeIdMap = new Dictionary((int)Count);