From d056317cde4be8e3af4c310ddf3e0721236a553b Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 25 Jul 2022 15:09:53 -0700 Subject: [PATCH] Block constant and relational pattern matching on types inheriting from or constrained to INumberBase (#62653) To ensure we have a good user experience and avoid any potential breaking changes later, we block this scenario for types that don't have known conversions from patterns. Spec change: https://github.com/dotnet/csharplang/pull/6273. --- .../Portable/Binder/Binder_Operators.cs | 2 +- .../CSharp/Portable/Binder/Binder_Patterns.cs | 70 +- .../Portable/Binder/Binder_Statements.cs | 5 +- .../CSharp/Portable/Binder/SwitchBinder.cs | 2 +- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 6 +- .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Portable/Symbols/TypeSymbolExtensions.cs | 26 +- .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Semantics/PatternMatchingTests5.cs | 664 ++++++++++++++++++ .../WellKnownTypeValidationTests.vb | 2 +- 23 files changed, 823 insertions(+), 23 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 0de1a60a39fba..88c32506faebe 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -3187,7 +3187,7 @@ private BoundExpression BindIsOperator(BinaryExpressionSyntax node, BindingDiagn } bool hasErrors = node.Right.HasErrors; - var convertedExpression = BindExpressionForPattern(operand.Type, node.Right, ref hasErrors, isPatternDiagnostics, out var constantValueOpt, out var wasExpression); + var convertedExpression = BindExpressionForPattern(operand.Type, node.Right, ref hasErrors, isPatternDiagnostics, out var constantValueOpt, out var wasExpression, out _); if (wasExpression) { hasErrors |= constantValueOpt is null; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index e4dc68b4cff54..5bdf09d1c377c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -408,7 +408,7 @@ private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSy BindingDiagnosticBag diagnostics) { ExpressionSyntax innerExpression = SkipParensAndNullSuppressions(expression, diagnostics, ref hasErrors); - var convertedExpression = BindExpressionOrTypeForPattern(inputType, innerExpression, ref hasErrors, diagnostics, out var constantValueOpt, out bool wasExpression); + var convertedExpression = BindExpressionOrTypeForPattern(inputType, innerExpression, ref hasErrors, diagnostics, out var constantValueOpt, out bool wasExpression, out Conversion patternConversion); if (wasExpression) { var convertedType = convertedExpression.Type ?? inputType; @@ -417,6 +417,12 @@ private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSy convertedType = 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); + } + return new BoundConstantPattern( node, convertedExpression, constantValueOpt ?? ConstantValue.Bad, inputType, convertedType, hasErrors || constantValueOpt is null); } @@ -431,6 +437,22 @@ private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSy } } + 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 + // will fail if the input type is `INumberBase`. We block this now so that we can make make it work as expected in the future without + // being a breaking change. + + if (patternConversion.IsIdentity || patternConversion.IsConstantExpression || patternConversion.IsNumeric) + { + return false; + } + + var interfaces = inputType is TypeParameterSymbol typeParam ? typeParam.EffectiveInterfacesNoUseSiteDiagnostics : inputType.AllInterfacesNoUseSiteDiagnostics; + return interfaces.Any(static (i, _) => i.IsWellKnownINumberBaseType(), 0) || inputType.IsWellKnownINumberBaseType(); + } + private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e, BindingDiagnosticBag diagnostics, ref bool hasErrors) { while (true) @@ -465,19 +487,21 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e ref bool hasErrors, BindingDiagnosticBag diagnostics, out ConstantValue? constantValueOpt, - out bool wasExpression) + out bool wasExpression, + out Conversion patternExpressionConversion) { constantValueOpt = null; BoundExpression expression = BindTypeOrRValue(patternExpression, diagnostics); wasExpression = expression.Kind != BoundKind.TypeExpression; if (wasExpression) { - return BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt); + return BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt, out patternExpressionConversion); } else { Debug.Assert(expression is { Kind: BoundKind.TypeExpression, Type: { } }); hasErrors |= CheckValidPatternType(patternExpression, inputType, expression.Type, diagnostics: diagnostics); + patternExpressionConversion = Conversion.NoConversion; return expression; } } @@ -491,13 +515,15 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e ref bool hasErrors, BindingDiagnosticBag diagnostics, out ConstantValue? constantValueOpt, - out bool wasExpression) + out bool wasExpression, + out Conversion patternExpressionConversion) { constantValueOpt = null; var expression = BindExpression(patternExpression, diagnostics: diagnostics, invoked: false, indexed: false); expression = CheckValue(expression, BindValueKind.RValue, diagnostics); wasExpression = expression.Kind switch { BoundKind.BadExpression => false, BoundKind.TypeExpression => false, _ => true }; - return wasExpression ? BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt) : expression; + patternExpressionConversion = Conversion.NoConversion; + return wasExpression ? BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt, out patternExpressionConversion) : expression; } private BoundExpression BindExpressionForPatternContinued( @@ -506,10 +532,11 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e ExpressionSyntax patternExpression, ref bool hasErrors, BindingDiagnosticBag diagnostics, - out ConstantValue? constantValueOpt) + out ConstantValue? constantValueOpt, + out Conversion patternExpressionConversion) { BoundExpression convertedExpression = ConvertPatternExpression( - inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics); + inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics, out patternExpressionConversion); ConstantValueUtils.CheckLangVersionForConstantValue(convertedExpression, diagnostics); @@ -541,7 +568,8 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e BoundExpression expression, out ConstantValue? constantValue, bool hasErrors, - BindingDiagnosticBag diagnostics) + BindingDiagnosticBag diagnostics, + out Conversion patternExpressionConversion) { BoundExpression convertedExpression; @@ -577,16 +605,24 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e if (!hasErrors) { var requiredVersion = MessageID.IDS_FeatureRecursivePatterns.RequiredVersion(); - if (Compilation.LanguageVersion < requiredVersion && - !this.Conversions.ClassifyConversionFromExpression(expression, inputType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo).IsImplicit) + patternExpressionConversion = this.Conversions.ClassifyConversionFromExpression(expression, inputType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); + if (Compilation.LanguageVersion < requiredVersion && !patternExpressionConversion.IsImplicit) { diagnostics.Add(ErrorCode.ERR_ConstantPatternVsOpenType, expression.Syntax.Location, inputType, expression.Display, new CSharpRequiredLanguageVersion(requiredVersion)); } } + else + { + patternExpressionConversion = Conversion.NoConversion; + } diagnostics.Add(node, useSiteInfo); } + else + { + patternExpressionConversion = Conversion.NoConversion; + } } else { @@ -613,13 +649,16 @@ private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e { diagnostics.Add(ErrorCode.ERR_PatternSpanCharCannotBeStringNull, convertedExpression.Syntax.Location, inputType); } + + patternExpressionConversion = Conversion.NoConversion; + return convertedExpression; } // This will allow user-defined conversions, even though they're not permitted here. This is acceptable // because the result of a user-defined conversion does not have a ConstantValue. A constant pattern // requires a constant value so we'll report a diagnostic to that effect later. - convertedExpression = GenerateConversionForAssignment(inputType, expression, diagnostics); + convertedExpression = GenerateConversionForAssignment(inputType, expression, diagnostics, out patternExpressionConversion); if (convertedExpression.Kind == BoundKind.Conversion) { @@ -1578,7 +1617,7 @@ void addSubpatternsForTuple(ImmutableArray elementTypes) bool hasErrors, BindingDiagnosticBag diagnostics) { - BoundExpression value = BindExpressionForPattern(inputType, node.Expression, ref hasErrors, diagnostics, out var constantValueOpt, out _); + BoundExpression value = BindExpressionForPattern(inputType, node.Expression, ref hasErrors, diagnostics, out var constantValueOpt, out _, out Conversion patternConversion); ExpressionSyntax innerExpression = SkipParensAndNullSuppressions(node.Expression, diagnostics, ref hasErrors); RoslynDebug.Assert(value.Type is { }); BinaryOperatorKind operation = tokenKindToBinaryOperatorKind(node.OperatorToken.Kind()); @@ -1616,6 +1655,13 @@ void addSubpatternsForTuple(ImmutableArray elementTypes) constantValueOpt = ConstantValue.Bad; } + 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); + hasErrors = true; + } + return new BoundRelationalPattern(node, operation | opType, value, constantValueOpt, inputType, value.Type, hasErrors); static BinaryOperatorKind tokenKindToBinaryOperatorKind(SyntaxKind kind) => kind switch diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 645d243b43017..cbc0f2996f113 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1897,6 +1897,9 @@ internal enum ConversionForAssignmentFlags } internal BoundExpression GenerateConversionForAssignment(TypeSymbol targetType, BoundExpression expression, BindingDiagnosticBag diagnostics, ConversionForAssignmentFlags flags = ConversionForAssignmentFlags.None) + => GenerateConversionForAssignment(targetType, expression, diagnostics, out _, flags); + + internal BoundExpression GenerateConversionForAssignment(TypeSymbol targetType, BoundExpression expression, BindingDiagnosticBag diagnostics, out Conversion conversion, ConversionForAssignmentFlags flags = ConversionForAssignmentFlags.None) { Debug.Assert((object)targetType != null); Debug.Assert(expression != null); @@ -1916,7 +1919,7 @@ internal BoundExpression GenerateConversionForAssignment(TypeSymbol targetType, CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); - var conversion = (flags & ConversionForAssignmentFlags.IncrementAssignment) == 0 ? + conversion = (flags & ConversionForAssignmentFlags.IncrementAssignment) == 0 ? this.Conversions.ClassifyConversionFromExpression(expression, targetType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo) : this.Conversions.ClassifyConversionFromType(expression.Type, targetType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs index 6e8fbb7f207e6..5d193953c36c0 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs @@ -268,7 +268,7 @@ protected BoundExpression ConvertCaseExpression(CSharpSyntaxNode node, BoundExpr caseExpression = CreateConversion(caseExpression, conversion, SwitchGoverningType, diagnostics); } - return ConvertPatternExpression(SwitchGoverningType, node, caseExpression, out constantValueOpt, hasErrors, diagnostics); + return ConvertPatternExpression(SwitchGoverningType, node, caseExpression, out constantValueOpt, hasErrors, diagnostics, out _); } private static readonly object s_nullKey = new object(); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index dd4aefb0a2b62..0a6a100427231 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7187,6 +7187,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ file types + + 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 specifc numeric type. + array access diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 361dac4b52014..c63f4f30774e2 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2108,11 +2108,13 @@ internal enum ErrorCode WRN_AnalyzerReferencesNewerCompiler = 9057, ERR_FeatureNotAvailableInVersion11 = 9058, ERR_RefFieldInNonRefStruct = 9059, + ERR_CannotMatchOnINumberBase = 9060, #endregion - // Note: you will need to do the following after adding warnings: + // Note: you will need to do the following after adding any code: + // 1) Update ErrorFacts.IsBuildOnlyDiagnostic to handle the new error code. + // Additionally, after adding a new warning you will need to do the following: // 1) Re-generate compiler code (eng\generate-compiler-code.cmd). - // 2) Update ErrorFacts.IsBuildOnlyDiagnostic to handle the new error code. } } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 087b043224ac4..f698f7992c4d2 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2208,6 +2208,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_FeatureNotAvailableInVersion11: case ErrorCode.ERR_RefFieldInNonRefStruct: case ErrorCode.WRN_AnalyzerReferencesNewerCompiler: + case ErrorCode.ERR_CannotMatchOnINumberBase: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement 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/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 938e31049dea5..18c8f90fa6d38 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -257,6 +257,11 @@ Nedal se odvodit typ delegáta. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Konvence volání managed se nedá kombinovat se specifikátory konvence nespravovaného volání. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 1697de3432f8d..4cbfe2a32b57f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -257,6 +257,11 @@ Der Delegattyp konnte nicht abgeleitet werden. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Die Aufrufkonvention "managed" kann nicht mit Spezifizierern für nicht verwaltete Aufrufkonventionen kombiniert werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 5778f00a7de72..b1307c073c71d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -257,6 +257,11 @@ El tipo de delegado no se puede deducir. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. La convención de llamada "managed" no se puede combinar con especificadores de convención de llamada no administrados. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 4f0faabd94fac..89fa98732c820 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -257,6 +257,11 @@ Impossible de déduire le type délégué. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Impossible d'associer la convention d'appel 'managed' à des spécificateurs de convention d'appel non managés. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 6875dba223f14..51ba85d5ebaff 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -257,6 +257,11 @@ Non è possibile dedurre il tipo di delegato. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Non è possibile combinare la convenzione di chiamata 'managed' con identificatori di convenzione di chiamata non gestita. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 4b140b9d62eb0..d619d868fefc7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -257,6 +257,11 @@ デリゲート型を推論できませんでした。 + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'マネージド' 呼び出し規則をアンマネージド呼び出し規則指定子と組み合わせることはできません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 3d333a7816e3e..545bddc8d7767 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -257,6 +257,11 @@ 대리자 형식을 유추할 수 없습니다. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. '관리되는' 호출 규칙은 관리되지 않는 호출 규칙 지정자와 함께 사용할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 07300af03d9ca..7d576fc7ed819 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -257,6 +257,11 @@ Nie można wywnioskować typu delegowania. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Konwencji wywoływania „managed” nie można łączyć z niezarządzanymi specyfikatorami konwencji wywoływania. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index fbe4cbc964cd6..b46fbf8b5ac1c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -257,6 +257,11 @@ O tipo de representante não pôde ser inferido. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. A convenção de chamada 'managed' não pode ser combinada com especificadores de convenção de chamada não gerenciados. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 318531f293d5f..0c8c489247d4f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -257,6 +257,11 @@ Не удалось вывести тип делегата. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. Соглашение о вызовах "managed" невозможно использовать вместе с спецификаторами неуправляемых соглашений о вызовах. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index ba836b0208bbd..9d5dc7e699959 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -257,6 +257,11 @@ Temsilci türü çıkarsanamadı. + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'managed' çağırma kuralı, yönetilmeyen çağırma kuralı tanımlayıcılarla birleştirilemez. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index e14aebea2fc44..877727a5c0317 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -257,6 +257,11 @@ 无法推断委托类型。 + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. "managed" 调用约定不能与非托管调用约定说明符一起使用。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 0fcfdca7b253e..742862248650c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -257,6 +257,11 @@ 無法推斷委派類型。 + + 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 specifc numeric type. + 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 specifc numeric type. + + 'managed' calling convention cannot be combined with unmanaged calling convention specifiers. 'managed' 呼叫慣例不得與未受控的呼叫慣例指定名稱並用。 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs index 2eb874bb6b89b..4cca81c72e802 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; @@ -1949,5 +1950,668 @@ public void Repro55184() Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("int", "Error").WithLocation(7, 19) ); } + + private const string INumberBaseDefinition = """ + namespace System.Numerics; + public interface INumberBase where T : INumberBase {} + """; + + [Fact] + public void ForbiddenOnTypeParametersConstrainedToINumberBase_01() + { + 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 + int => 3, // OK + [] => 4, // 3 + (_) => 5, // OK + "" => 6, // OK + { } => 7, // OK + var x => 8, // OK + _ => 9 // Ok + }; + } + """; + + var comp = CreateCompilation(new[] { source, INumberBaseDefinition }); + 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) + ); + } + + [Fact] + public void ForbiddenOnTypeParametersConstrainedToINumberBase_02() + { + 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 comp = CreateCompilation(new[] { source, INumberBaseDefinition }); + 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) + ); + } + + [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 ForbiddenOnTypesInheritingFromINumberBase(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 comp = CreateCompilation(new[] { source, INumberBaseDefinition }); + 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) + ); + } + + [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 + { + using System.Numerics; + + public class Object {} + public class Void {} + public class ValueType {} + public class String {} + public class Enum {} + public struct Nullable where T : struct {} + public struct Byte : INumberBase {} + public struct SByte : INumberBase {} + public struct Int16 : INumberBase {} + public struct Char : INumberBase {} + public struct UInt16 : INumberBase {} + public struct Int32 : INumberBase {} + public struct UInt32 : INumberBase {} + public struct Int64 : INumberBase {} + public struct UInt64 : INumberBase {} + public struct Single : INumberBase {} + public struct Double : INumberBase {} + public struct Decimal : INumberBase { public Decimal(int value) {} } + public struct IntPtr : INumberBase {} + public struct UIntPtr : INumberBase {} + } + """; + + [Theory] + [InlineData("byte")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("ushort")] + [InlineData("int")] + [InlineData("uint")] + [InlineData("nint")] + [InlineData("nuint")] + [InlineData("long")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("decimal")] + public void MatchingOnConstantConversionsWithINumberBaseIsAllowed(string inputType) + { + var source = $$""" + {{inputType}} i = 1; + _ = i switch + { + 1 => 1, + > 1 => 2, + _ => 3 + }; + """; + + var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); + comp.VerifyDiagnostics(); + } + + [Theory] + [InlineData("byte")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("ushort")] + [InlineData("int")] + [InlineData("uint")] + [InlineData("nint")] + [InlineData("nuint")] + [InlineData("long")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("decimal")] + public void MatchingOnConstantConversionsWithINumberBaseIsAllowed_Nullable(string inputType) + { + var source = $$""" + {{inputType}}? i = 1; + _ = i switch + { + 1 => 1, + > 1 => 2, + _ => 3 + }; + """; + + var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MatchingOnConstantConversionsWithINumberBaseIsDisallowed_TypePatternToINumberBaseInt() + { + var source = """ + using System.Numerics; + int i = 1; + _ = ((INumberBase)i) switch + { + 1 => 1, + > 1 => 2, + _ => 3 + }; + """; + + var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); + comp.VerifyDiagnostics( + // (5,5): error CS9060: Cannot use a numeric constant or relational pattern on 'INumberBase' because it inherits from or extends 'INumberBase'. Consider using a type pattern to narrow to a specifc numeric type. + // 1 => 1, + Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("System.Numerics.INumberBase").WithLocation(5, 5), + // (6,5): error CS9060: Cannot use a numeric constant or relational pattern on 'INumberBase' because it inherits from or extends 'INumberBase'. Consider using a type pattern to narrow to a specifc numeric type. + // > 1 => 2, + Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("System.Numerics.INumberBase").WithLocation(6, 5) + ); + } + + [Theory] + [InlineData("byte")] + [InlineData("sbyte")] + [InlineData("short")] + [InlineData("ushort")] + [InlineData("uint")] + [InlineData("nint")] + [InlineData("nuint")] + [InlineData("long")] + [InlineData("ulong")] + [InlineData("float")] + [InlineData("double")] + [InlineData("decimal")] + public void MatchingOnConstantConversionsWithINumberBaseIsAllowed_TypePatternToINumberBaseT(string inputType) + { + var source = $$""" + using System.Numerics; + {{inputType}} i = 1; + _ = ((INumberBase<{{inputType}}>)i) switch + { + 1 => 1, + > 1 => 2, + _ => 3 + }; + """; + + var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); + comp.VerifyDiagnostics( + // (5,5): error CS0029: Cannot implicitly convert type 'int' to 'System.Numerics.INumberBase' + // 1 => 1, + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", $"System.Numerics.INumberBase<{inputType}>").WithLocation(5, 5), + // (6,5): error CS8781: Relational patterns may not be used for a value of type 'System.Numerics.INumberBase'. + // > 1 => 2, + Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments($"System.Numerics.INumberBase<{inputType}>").WithLocation(6, 5), + // (6,7): error CS0029: Cannot implicitly convert type 'int' to 'System.Numerics.INumberBase' + // > 1 => 2, + 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 => 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 => 2, + _ => 3 + }; + } + + void M2(T t) where T : INumberBase + { + int o = t switch + { + 1 => 1, + > 1 => 2, + _ => 3 + }; + } + + namespace System.Numerics + { + public interface INumberBase where T1 : INumberBase + { + } + + public interface 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 => 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 => 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/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 77ead726d1968..200cfb8e6ecf0 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -551,7 +551,7 @@ End Namespace WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, WellKnownType.System_MemoryExtensions, WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, - WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute, + WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute, WellKnownType.System_MemoryExtensions ' Not available on all platforms. Continue For