diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - Milestone 16.md b/docs/compilers/CSharp/Compiler Breaking Changes - Milestone 16.md index ffe7209480fdec131eff17dae6733c07b029aab4..000a4e44503899d8ea2d7e1f8aa8f31cb3e8a8d3 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - Milestone 16.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - Milestone 16.md @@ -22,7 +22,17 @@ Each entry should include a short description of the break, followed by either a ``` Due to this the `OpenParenToken` and `CloseParenToken` fields of a `SwitchStatementSyntax` node may now sometimes be empty. -4. https://github.com/dotnet/roslyn/issues/26098 In C# 8, we give a warning when an is-type expression is always `false` because the input type is an open class type and the type it is tested against is a value type: +4. In an *is-pattern-expression*, a warning is now issued when a constant expression does not match the provided pattern because of its value. Such code was previously accepted but gave no warning. For example + ``` c# + if (3 is 4) // warning: the given expression never matches the provided pattern. + ``` + We also issue a warning when a constant expression *always* matches a constant pattern in an *is-pattern-expression*. For example + ``` c# + if (3 is 3) // warning: the given expression always matches the provided constant. + ``` + Other cases of the pattern always matching (e.g. `e is var t`) do not trigger a warning, even when they are known by the compiler to produce an invariant result. + +5. https://github.com/dotnet/roslyn/issues/26098 In C# 8, we give a warning when an is-type expression is always `false` because the input type is an open class type and the type it is tested against is a value type: ``` c# class C { } void M(C x) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 7f9c272eba11b6f4f55f9bf0baee6d51dd45de37..922749602ee930349e989c5d19f2ba6fce2e9b88 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -2641,9 +2641,9 @@ private bool IsOperandErrors(CSharpSyntaxNode node, ref BoundExpression operand, if (!operand.HasAnyErrors) { Error(diagnostics, ErrorCode.ERR_LambdaInIsAs, node); - operand = BadExpression(node, operand).MakeCompilerGenerated(); } + operand = BadExpression(node, operand).MakeCompilerGenerated(); return true; default: @@ -2655,6 +2655,7 @@ private bool IsOperandErrors(CSharpSyntaxNode node, ref BoundExpression operand, Error(diagnostics, ErrorCode.ERR_BadUnaryOp, node, SyntaxFacts.GetText(SyntaxKind.IsKeyword), operand.Display); } + operand = BadExpression(node, operand).MakeCompilerGenerated(); return true; } @@ -2724,7 +2725,7 @@ private BoundExpression BindIsOperator(BinaryExpressionSyntax node, DiagnosticBa { isTypeDiagnostics.Free(); diagnostics.AddRangeAndFree(isPatternDiagnostics); - return new BoundIsPatternExpression(node, operand, boundConstantPattern, resultType, operandHasErrors); + return MakeIsPatternExpression(node, operand, boundConstantPattern, resultType, operandHasErrors, diagnostics); } isPatternDiagnostics.Free(); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 68ee8784f441254dfdfa0e3c1b755b711af08f5a..74c88fc4030fc811c16e01d564e57e011fdd43fe 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -20,23 +20,53 @@ private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node, TypeSymbol expressionType = expression.Type; if ((object)expressionType == null || expressionType.SpecialType == SpecialType.System_Void) { - expressionType = CreateErrorType(); if (!hasErrors) { // value expected diagnostics.Add(ErrorCode.ERR_BadPatternExpression, node.Expression.Location, expression.Display); hasErrors = true; } + + expression = BadExpression(expression.Syntax, expression); } - BoundPattern pattern = BindPattern(node.Pattern, expressionType, hasErrors, diagnostics); - if (!hasErrors && pattern is BoundDeclarationPattern p && !p.IsVar && expression.ConstantValue == ConstantValue.Null) + BoundPattern pattern = BindPattern(node.Pattern, expression.Type, hasErrors, diagnostics); + hasErrors |= pattern.HasErrors; + return MakeIsPatternExpression( + node, expression, pattern, GetSpecialType(SpecialType.System_Boolean, diagnostics, node), + hasErrors, diagnostics); + } + + private BoundExpression MakeIsPatternExpression(SyntaxNode node, BoundExpression expression, BoundPattern pattern, TypeSymbol boolType, bool hasErrors, DiagnosticBag diagnostics) + { + // Note that these labels are for the convenience of the compilation of patterns, and are not actually emitted into the lowered code. + LabelSymbol whenTrueLabel = new GeneratedLabelSymbol("isPatternSuccess"); + LabelSymbol whenFalseLabel = new GeneratedLabelSymbol("isPatternFailure"); + BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern( + this.Compilation, pattern.Syntax, expression, pattern, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, diagnostics); + if (!hasErrors && !decisionDag.ReachableLabels.Contains(whenTrueLabel)) + { + diagnostics.Add(ErrorCode.ERR_IsPatternImpossible, node.Location, expression.Type); + hasErrors = true; + } + + if (expression.ConstantValue != null) { - diagnostics.Add(ErrorCode.WRN_IsAlwaysFalse, node.Location, p.DeclaredType.Type); + decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(expression); + if (!hasErrors) + { + if (!decisionDag.ReachableLabels.Contains(whenTrueLabel)) + { + diagnostics.Add(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, node.Location); + } + else if (!decisionDag.ReachableLabels.Contains(whenFalseLabel) && pattern.Kind == BoundKind.ConstantPattern) + { + diagnostics.Add(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, node.Location); + } + } } - return new BoundIsPatternExpression( - node, expression, pattern, GetSpecialType(SpecialType.System_Boolean, diagnostics, node), hasErrors); + return new BoundIsPatternExpression(node, expression, pattern, decisionDag, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, boolType, hasErrors); } private BoundExpression BindSwitchExpression(SwitchExpressionSyntax node, DiagnosticBag diagnostics) @@ -726,14 +756,16 @@ private BoundPattern BindPropertyPattern(PropertyPatternSyntax node, TypeSymbol { Symbol symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics); - if (inputType.IsErrorType() || hasErrors) + if (inputType.IsErrorType() || hasErrors || symbol == (object)null) { memberType = CreateErrorType(); return null; } - - memberType = symbol.GetTypeOrReturnType(); - return symbol; + else + { + memberType = symbol.GetTypeOrReturnType(); + return symbol; + } } private Symbol BindPropertyPatternMember( @@ -794,16 +826,18 @@ private BoundPattern BindPropertyPattern(PropertyPatternSyntax node, TypeSymbol default: Error(diagnostics, ErrorCode.ERR_PropertyLacksGet, memberName, name); - hasErrors = true; break; } } + + hasErrors = true; return null; } if (hasErrors || !CheckValueKind(node: memberName.Parent, expr: boundMember, valueKind: BindValueKind.RValue, checkingReceiver: false, diagnostics: diagnostics)) { + hasErrors = true; return null; } diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index ca7a1c3a05c398c9ffee7de05122980917515135..df264d66b29512fb5882d236ca8844a765d19d0e 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -107,23 +107,22 @@ private DecisionDagBuilder(CSharpCompilation compilation, LabelSymbol defaultLab SyntaxNode syntax, BoundExpression inputExpression, BoundPattern pattern, - LabelSymbol defaultLabel, - DiagnosticBag diagnostics, - out LabelSymbol successLabel) + LabelSymbol whenTrueLabel, + LabelSymbol whenFalseLabel, + DiagnosticBag diagnostics) { - var builder = new DecisionDagBuilder(compilation, defaultLabel, diagnostics); - return builder.CreateDecisionDagForIsPattern(syntax, inputExpression, pattern, out successLabel); + var builder = new DecisionDagBuilder(compilation, defaultLabel: whenFalseLabel, diagnostics); + return builder.CreateDecisionDagForIsPattern(syntax, inputExpression, pattern, whenTrueLabel); } private BoundDecisionDag CreateDecisionDagForIsPattern( SyntaxNode syntax, BoundExpression inputExpression, BoundPattern pattern, - out LabelSymbol successLabel) + LabelSymbol whenTrueLabel) { - successLabel = new GeneratedLabelSymbol("success"); var rootIdentifier = new BoundDagTemp(inputExpression.Syntax, inputExpression.Type, source: null, index: 0); - return MakeDecisionDag(syntax, ImmutableArray.Create(MakeTestsForPattern(index: 1, pattern.Syntax, rootIdentifier, pattern, whenClause: null, successLabel))); + return MakeDecisionDag(syntax, ImmutableArray.Create(MakeTestsForPattern(index: 1, pattern.Syntax, rootIdentifier, pattern, whenClause: null, whenTrueLabel))); } private BoundDecisionDag CreateDecisionDagForSwitchStatement( @@ -402,10 +401,14 @@ private static void Assert(bool condition, string message = null) ArrayBuilder bindings) { Debug.Assert(input.Type.IsErrorType() || input.Type == recursive.InputType); - if (recursive.DeclaredType != null && recursive.DeclaredType.Type != input.Type) + if (recursive.DeclaredType != null) { input = MakeConvertToType(input, recursive.Syntax, recursive.DeclaredType.Type, tests); } + else + { + MakeCheckNotNull(input, recursive.Syntax, tests); + } if (!recursive.Deconstruction.IsDefault) { diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index dce2bbcca7ab0af25393b7bc9e54c4a5e7434330..fa87266b7333132a6e0b9386e7e3a0a50e7ddbd7 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -826,6 +826,7 @@ + @@ -862,6 +863,7 @@ + @@ -1720,6 +1722,9 @@ + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundPatternSwitchStatement.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundPatternSwitchStatement.cs deleted file mode 100644 index b331556133a3258e004240e76a7e167b986e41f0..0000000000000000000000000000000000000000 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundPatternSwitchStatement.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Symbols; - -namespace Microsoft.CodeAnalysis.CSharp -{ - internal partial class BoundPatternSwitchStatement - { - public BoundDecisionDag DecisionDag { get; } - - public BoundPatternSwitchStatement(SyntaxNode syntax, BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel, BoundDecisionDag decisionDag, bool hasErrors = false) - : this(syntax, expression, innerLocals, innerLocalFunctions, switchSections, defaultLabel, breakLabel, hasErrors) - { - - this.DecisionDag = decisionDag; - } - - public BoundPatternSwitchStatement Update(BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel, BoundDecisionDag decisionDag) - { - if (expression != this.Expression || innerLocals != this.InnerLocals || innerLocalFunctions != this.InnerLocalFunctions || switchSections != this.SwitchSections || defaultLabel != this.DefaultLabel || breakLabel != this.BreakLabel || decisionDag != this.DecisionDag) - { - var result = new BoundPatternSwitchStatement(this.Syntax, expression, innerLocals, innerLocalFunctions, switchSections, defaultLabel, breakLabel, decisionDag, this.HasErrors); - result.WasCompilerGenerated = this.WasCompilerGenerated; - return result; - } - return this; - } - } - -} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundSwitchExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundSwitchExpression.cs deleted file mode 100644 index babbb161cab0b8557d4c09f67223e823933195a0..0000000000000000000000000000000000000000 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundSwitchExpression.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Symbols; - -namespace Microsoft.CodeAnalysis.CSharp -{ - internal partial class BoundSwitchExpression - { - public BoundDecisionDag DecisionDag { get; } - - public BoundSwitchExpression(SyntaxNode syntax, BoundExpression expression, ImmutableArray switchArms, BoundDecisionDag decisionDag, LabelSymbol defaultLabel, TypeSymbol type, bool hasErrors = false) - : base(BoundKind.SwitchExpression, syntax, type, hasErrors || expression.HasErrors() || switchArms.HasErrors() || decisionDag.HasErrors()) - { - - Debug.Assert(expression != null, "Field 'expression' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); - Debug.Assert(!switchArms.IsDefault, "Field 'switchArms' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); - - this.Expression = expression; - this.SwitchArms = switchArms; - this.DecisionDag = decisionDag; - this.DefaultLabel = defaultLabel; - } - - public BoundSwitchExpression Update(BoundExpression expression, ImmutableArray switchArms, BoundDecisionDag decisionDag, LabelSymbol defaultLabel, TypeSymbol type) - { - if (expression != this.Expression || switchArms != this.SwitchArms || decisionDag != this.DecisionDag || defaultLabel != this.DefaultLabel || type != this.Type) - { - var result = new BoundSwitchExpression(this.Syntax, expression, switchArms, decisionDag, defaultLabel, type, this.HasErrors); - result.WasCompilerGenerated = this.WasCompilerGenerated; - return result; - } - return this; - } - } - -} diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index f945ea04d393335b2451742ccbf882333f9eef81..0950d72bf509610fa49016e3a081d1cf847d412b 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -6109,6 +6109,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to An expression of type '{0}' can never match the provided pattern.. + /// + internal static string ERR_IsPatternImpossible { + get { + return ResourceManager.GetString("ERR_IsPatternImpossible", resourceCulture); + } + } + /// /// Looks up a localized string similar to Yield statements may not appear at the top level in interactive code.. /// @@ -13573,6 +13582,42 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to The given expression always matches the provided constant.. + /// + internal static string WRN_GivenExpressionAlwaysMatchesConstant { + get { + return ResourceManager.GetString("WRN_GivenExpressionAlwaysMatchesConstant", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given expression always matches the provided constant.. + /// + internal static string WRN_GivenExpressionAlwaysMatchesConstant_Title { + get { + return ResourceManager.GetString("WRN_GivenExpressionAlwaysMatchesConstant_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given expression never matches the provided pattern.. + /// + internal static string WRN_GivenExpressionNeverMatchesPattern { + get { + return ResourceManager.GetString("WRN_GivenExpressionNeverMatchesPattern", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given expression never matches the provided pattern.. + /// + internal static string WRN_GivenExpressionNeverMatchesPattern_Title { + get { + return ResourceManager.GetString("WRN_GivenExpressionNeverMatchesPattern_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Defining an alias named 'global' is ill-advised since 'global::' always references the global namespace and not an alias. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2e836de42e236e865b1a79ff07c3efd881aaf9b7..ccf83c83036c8fce3b48a56bf1a5384bcc80a64f 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5381,4 +5381,19 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + + + The given expression never matches the provided pattern. + + + The given expression never matches the provided pattern. + + + The given expression always matches the provided constant. + + + The given expression always matches the provided constant. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 9396e37526651caa7c9fd9cd770d9dbe4baf22f9..380acab6a436e4b2f15a209f9c97419edbb2ac6f 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1599,6 +1599,9 @@ internal enum ErrorCode ERR_SwitchGoverningExpressionRequiresParens = 8415, ERR_TupleElementNameMismatch = 8416, ERR_DeconstructParameterNameMismatch = 8417, + ERR_IsPatternImpossible = 8418, + WRN_GivenExpressionNeverMatchesPattern = 8419, + WRN_GivenExpressionAlwaysMatchesConstant = 8420, #endregion diagnostics introduced for recursive patterns } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 16fbb2e3ddc3da8a0ae8c9e89435ebba2f79257d..af353c1f17a7650febda0ccc6b9559b08e15450f 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -325,6 +325,8 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_TupleBinopLiteralNameMismatch: case ErrorCode.WRN_SwitchExpressionNotExhaustive: case ErrorCode.WRN_IsTypeNamedUnderscore: + case ErrorCode.WRN_GivenExpressionNeverMatchesPattern: + case ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant: return 1; default: return 0; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index 0815bd949786b1c0501c8579146b9797e7826f08..2b0dcde04ff00ee537fadb9fb31d5b03a47efa1d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -1512,9 +1512,9 @@ protected override LocalState UnreachableState() #region Visitors - public override void VisitPattern(BoundExpression expression, BoundPattern pattern) + public override void VisitPattern(BoundPattern pattern) { - base.VisitPattern(expression, pattern); + base.VisitPattern(pattern); var whenFail = StateWhenFalse; SetState(StateWhenTrue); AssignPatternVariables(pattern); @@ -1560,7 +1560,7 @@ private void AssignPatternVariables(BoundPattern pattern) break; } default: - break; + throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 7b71c3ab3df0402a85ae8c8252c3d934b5bcde44..d7ac306ae1d228e2b206a39e874f6cd19465476e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -923,30 +923,25 @@ public override BoundNode VisitPassByCopy(BoundPassByCopy node) public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { VisitRvalue(node.Expression); - VisitPattern(node.Expression, node.Pattern); + VisitPattern(node.Pattern); + var reachableLabels = node.DecisionDag.ReachableLabels; + if (!reachableLabels.Contains(node.WhenTrueLabel)) + { + SetState(this.StateWhenFalse); + SetConditionalState(UnreachableState(), this.State); + } + else if (!reachableLabels.Contains(node.WhenFalseLabel)) + { + SetState(this.StateWhenTrue); + SetConditionalState(this.State, UnreachableState()); + } + return null; } - public virtual void VisitPattern(BoundExpression expression, BoundPattern pattern) + public virtual void VisitPattern(BoundPattern pattern) { Split(); - if (expression != null) - { - bool? knownMatch = CheckRefutations(expression, pattern); - switch (knownMatch) - { - case true: - SetState(StateWhenTrue); - SetConditionalState(this.State, UnreachableState()); - break; - case false: - SetState(StateWhenFalse); - SetConditionalState(UnreachableState(), this.State); - break; - case null: - break; - } - } } public override BoundNode VisitConstantPattern(BoundConstantPattern node) @@ -955,42 +950,6 @@ public override BoundNode VisitConstantPattern(BoundConstantPattern node) throw ExceptionUtilities.Unreachable; } - /// - /// Check if the given expression is known to *always* match, or *always* fail against the given pattern. - /// Return true for known match, false for known fail, and null otherwise. Used for the "is pattern" expression. - /// - private bool? CheckRefutations(BoundExpression expression, BoundPattern pattern) - { - Debug.Assert(expression != null); - switch (pattern.Kind) - { - case BoundKind.DeclarationPattern: - { - var declPattern = (BoundDeclarationPattern)pattern; - if (declPattern.IsVar || // var pattern always matches - declPattern.DeclaredType?.Type?.IsValueType == true && declPattern.DeclaredType.Type == (object)expression.Type) // exact match - { - return true; - } - Debug.Assert(!declPattern.IsVar); - switch (expression.ConstantValue?.IsNull) - { - case true: return false; - case false: return true; - default: return null; - } - } - case BoundKind.ConstantPattern: - { - var constPattern = (BoundConstantPattern)pattern; - if (expression.ConstantValue == null || constPattern.ConstantValue == null) return null; - return Equals(expression.ConstantValue.Value, constPattern.ConstantValue.Value); - } - } - - return null; - } - public override BoundNode VisitTupleLiteral(BoundTupleLiteral node) { return VisitTupleExpression(node); @@ -2767,16 +2726,17 @@ public override BoundNode VisitSwitchExpression(BoundSwitchExpression node) VisitRvalue(node.Expression); var dispatchState = this.State; var endState = UnreachableState(); + var reachableLabels = node.DecisionDag.ReachableLabels; foreach (var arm in node.SwitchArms) { SetState(dispatchState.Clone()); - VisitPattern(node.Expression, arm.Pattern); + VisitPattern(arm.Pattern); SetState(StateWhenTrue); - if (arm.Pattern.HasErrors) + if (!reachableLabels.Contains(arm.Label) || arm.Pattern.HasErrors) { - // suppress definite assignment errors on broken switch arms SetUnreachable(); } + if (arm.WhenClause != null) { VisitCondition(arm.WhenClause); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass_Switch.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass_Switch.cs index 4c80a932c9d41cde2ca3a0c9eee627c6268c1156..9820976fe269f7e9b4c2f0f3bc13173fd975a7a9 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass_Switch.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass_Switch.cs @@ -44,7 +44,7 @@ private void VisitPatternSwitchBlock(BoundPatternSwitchStatement node) SetUnreachable(); } - VisitPattern(null, label.Pattern); + VisitPattern(label.Pattern); SetState(StateWhenTrue); if (label.WhenClause != null) { diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs index 7c74d3f138a66611ab3c91b5ea0e7195cea09a1f..c6b6c0dc5e05c81a151f63339f3bf4ce426d762d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs @@ -43,9 +43,9 @@ protected override void Free() _variablesDeclared = null; } - public override void VisitPattern(BoundExpression expression, BoundPattern pattern) + public override void VisitPattern(BoundPattern pattern) { - base.VisitPattern(expression, pattern); + base.VisitPattern(pattern); NoteDeclaredPatternVariables(pattern); } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index a19b1fc471f617bf14a4f15bf29d6e6ab85fdc4a..a64edb58b38b3fbdf8e47ffe3308d2d202bcc0cf 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -2843,20 +2843,22 @@ public BoundContinueStatement Update(GeneratedLabelSymbol label) internal sealed partial class BoundPatternSwitchStatement : BoundStatement { - public BoundPatternSwitchStatement(SyntaxNode syntax, BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel, bool hasErrors = false) - : base(BoundKind.PatternSwitchStatement, syntax, hasErrors || expression.HasErrors() || switchSections.HasErrors() || defaultLabel.HasErrors()) + public BoundPatternSwitchStatement(SyntaxNode syntax, BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundDecisionDag decisionDag, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel, bool hasErrors = false) + : base(BoundKind.PatternSwitchStatement, syntax, hasErrors || expression.HasErrors() || switchSections.HasErrors() || decisionDag.HasErrors() || defaultLabel.HasErrors()) { Debug.Assert(expression != null, "Field 'expression' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(!innerLocals.IsDefault, "Field 'innerLocals' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(!innerLocalFunctions.IsDefault, "Field 'innerLocalFunctions' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(!switchSections.IsDefault, "Field 'switchSections' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(decisionDag != null, "Field 'decisionDag' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(breakLabel != null, "Field 'breakLabel' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); this.Expression = expression; this.InnerLocals = innerLocals; this.InnerLocalFunctions = innerLocalFunctions; this.SwitchSections = switchSections; + this.DecisionDag = decisionDag; this.DefaultLabel = defaultLabel; this.BreakLabel = breakLabel; } @@ -2870,6 +2872,8 @@ public BoundPatternSwitchStatement(SyntaxNode syntax, BoundExpression expression public ImmutableArray SwitchSections { get; } + public BoundDecisionDag DecisionDag { get; } + public BoundPatternSwitchLabel DefaultLabel { get; } public GeneratedLabelSymbol BreakLabel { get; } @@ -2879,11 +2883,11 @@ public override BoundNode Accept(BoundTreeVisitor visitor) return visitor.VisitPatternSwitchStatement(this); } - public BoundPatternSwitchStatement Update(BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel) + public BoundPatternSwitchStatement Update(BoundExpression expression, ImmutableArray innerLocals, ImmutableArray innerLocalFunctions, ImmutableArray switchSections, BoundDecisionDag decisionDag, BoundPatternSwitchLabel defaultLabel, GeneratedLabelSymbol breakLabel) { - if (expression != this.Expression || innerLocals != this.InnerLocals || innerLocalFunctions != this.InnerLocalFunctions || switchSections != this.SwitchSections || defaultLabel != this.DefaultLabel || breakLabel != this.BreakLabel) + if (expression != this.Expression || innerLocals != this.InnerLocals || innerLocalFunctions != this.InnerLocalFunctions || switchSections != this.SwitchSections || decisionDag != this.DecisionDag || defaultLabel != this.DefaultLabel || breakLabel != this.BreakLabel) { - var result = new BoundPatternSwitchStatement(this.Syntax, expression, innerLocals, innerLocalFunctions, switchSections, defaultLabel, breakLabel, this.HasErrors); + var result = new BoundPatternSwitchStatement(this.Syntax, expression, innerLocals, innerLocalFunctions, switchSections, decisionDag, defaultLabel, breakLabel, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -2935,15 +2939,17 @@ public BoundSwitchDispatch Update(BoundExpression expression, ImmutableArray<(Co internal sealed partial class BoundSwitchExpression : BoundExpression { - public BoundSwitchExpression(SyntaxNode syntax, BoundExpression expression, ImmutableArray switchArms, LabelSymbol defaultLabel, TypeSymbol type, bool hasErrors = false) - : base(BoundKind.SwitchExpression, syntax, type, hasErrors || expression.HasErrors() || switchArms.HasErrors()) + public BoundSwitchExpression(SyntaxNode syntax, BoundExpression expression, ImmutableArray switchArms, BoundDecisionDag decisionDag, LabelSymbol defaultLabel, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.SwitchExpression, syntax, type, hasErrors || expression.HasErrors() || switchArms.HasErrors() || decisionDag.HasErrors()) { Debug.Assert(expression != null, "Field 'expression' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(!switchArms.IsDefault, "Field 'switchArms' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(decisionDag != null, "Field 'decisionDag' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); this.Expression = expression; this.SwitchArms = switchArms; + this.DecisionDag = decisionDag; this.DefaultLabel = defaultLabel; } @@ -2952,6 +2958,8 @@ public BoundSwitchExpression(SyntaxNode syntax, BoundExpression expression, Immu public ImmutableArray SwitchArms { get; } + public BoundDecisionDag DecisionDag { get; } + public LabelSymbol DefaultLabel { get; } public override BoundNode Accept(BoundTreeVisitor visitor) @@ -2959,11 +2967,11 @@ public override BoundNode Accept(BoundTreeVisitor visitor) return visitor.VisitSwitchExpression(this); } - public BoundSwitchExpression Update(BoundExpression expression, ImmutableArray switchArms, LabelSymbol defaultLabel, TypeSymbol type) + public BoundSwitchExpression Update(BoundExpression expression, ImmutableArray switchArms, BoundDecisionDag decisionDag, LabelSymbol defaultLabel, TypeSymbol type) { - if (expression != this.Expression || switchArms != this.SwitchArms || defaultLabel != this.DefaultLabel || type != this.Type) + if (expression != this.Expression || switchArms != this.SwitchArms || decisionDag != this.DecisionDag || defaultLabel != this.DefaultLabel || type != this.Type) { - var result = new BoundSwitchExpression(this.Syntax, expression, switchArms, defaultLabel, type, this.HasErrors); + var result = new BoundSwitchExpression(this.Syntax, expression, switchArms, decisionDag, defaultLabel, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -6524,15 +6532,21 @@ public BoundStringInsert Update(BoundExpression value, BoundExpression alignment internal sealed partial class BoundIsPatternExpression : BoundExpression { - public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, BoundPattern pattern, TypeSymbol type, bool hasErrors = false) - : base(BoundKind.IsPatternExpression, syntax, type, hasErrors || expression.HasErrors() || pattern.HasErrors()) + public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, BoundPattern pattern, BoundDecisionDag decisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.IsPatternExpression, syntax, type, hasErrors || expression.HasErrors() || pattern.HasErrors() || decisionDag.HasErrors()) { Debug.Assert(expression != null, "Field 'expression' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(pattern != null, "Field 'pattern' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(decisionDag != null, "Field 'decisionDag' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(whenTrueLabel != null, "Field 'whenTrueLabel' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(whenFalseLabel != null, "Field 'whenFalseLabel' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); this.Expression = expression; this.Pattern = pattern; + this.DecisionDag = decisionDag; + this.WhenTrueLabel = whenTrueLabel; + this.WhenFalseLabel = whenFalseLabel; } @@ -6540,16 +6554,22 @@ public BoundIsPatternExpression(SyntaxNode syntax, BoundExpression expression, B public BoundPattern Pattern { get; } + public BoundDecisionDag DecisionDag { get; } + + public LabelSymbol WhenTrueLabel { get; } + + public LabelSymbol WhenFalseLabel { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitIsPatternExpression(this); } - public BoundIsPatternExpression Update(BoundExpression expression, BoundPattern pattern, TypeSymbol type) + public BoundIsPatternExpression Update(BoundExpression expression, BoundPattern pattern, BoundDecisionDag decisionDag, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel, TypeSymbol type) { - if (expression != this.Expression || pattern != this.Pattern || type != this.Type) + if (expression != this.Expression || pattern != this.Pattern || decisionDag != this.DecisionDag || whenTrueLabel != this.WhenTrueLabel || whenFalseLabel != this.WhenFalseLabel || type != this.Type) { - var result = new BoundIsPatternExpression(this.Syntax, expression, pattern, type, this.HasErrors); + var result = new BoundIsPatternExpression(this.Syntax, expression, pattern, decisionDag, whenTrueLabel, whenFalseLabel, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -9979,8 +9999,9 @@ public override BoundNode VisitPatternSwitchStatement(BoundPatternSwitchStatemen { BoundExpression expression = (BoundExpression)this.Visit(node.Expression); ImmutableArray switchSections = (ImmutableArray)this.VisitList(node.SwitchSections); + BoundDecisionDag decisionDag = node.DecisionDag; BoundPatternSwitchLabel defaultLabel = (BoundPatternSwitchLabel)this.Visit(node.DefaultLabel); - return node.Update(expression, node.InnerLocals, node.InnerLocalFunctions, switchSections, defaultLabel, node.BreakLabel); + return node.Update(expression, node.InnerLocals, node.InnerLocalFunctions, switchSections, decisionDag, defaultLabel, node.BreakLabel); } public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node) { @@ -9991,8 +10012,9 @@ public override BoundNode VisitSwitchExpression(BoundSwitchExpression node) { BoundExpression expression = (BoundExpression)this.Visit(node.Expression); ImmutableArray switchArms = (ImmutableArray)this.VisitList(node.SwitchArms); + BoundDecisionDag decisionDag = node.DecisionDag; TypeSymbol type = this.VisitType(node.Type); - return node.Update(expression, switchArms, node.DefaultLabel, type); + return node.Update(expression, switchArms, decisionDag, node.DefaultLabel, type); } public override BoundNode VisitSwitchExpressionArm(BoundSwitchExpressionArm node) { @@ -10524,8 +10546,9 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node { BoundExpression expression = (BoundExpression)this.Visit(node.Expression); BoundPattern pattern = (BoundPattern)this.Visit(node.Pattern); + BoundDecisionDag decisionDag = node.DecisionDag; TypeSymbol type = this.VisitType(node.Type); - return node.Update(expression, pattern, type); + return node.Update(expression, pattern, decisionDag, node.WhenTrueLabel, node.WhenFalseLabel, type); } public override BoundNode VisitConstantPattern(BoundConstantPattern node) { @@ -11287,6 +11310,7 @@ public override TreeDumperNode VisitPatternSwitchStatement(BoundPatternSwitchSta new TreeDumperNode("innerLocals", node.InnerLocals, null), new TreeDumperNode("innerLocalFunctions", node.InnerLocalFunctions, null), new TreeDumperNode("switchSections", null, from x in node.SwitchSections select Visit(x, null)), + new TreeDumperNode("decisionDag", null, new TreeDumperNode[] { Visit(node.DecisionDag, null) }), new TreeDumperNode("defaultLabel", null, new TreeDumperNode[] { Visit(node.DefaultLabel, null) }), new TreeDumperNode("breakLabel", node.BreakLabel, null) } @@ -11309,6 +11333,7 @@ public override TreeDumperNode VisitSwitchExpression(BoundSwitchExpression node, { new TreeDumperNode("expression", null, new TreeDumperNode[] { Visit(node.Expression, null) }), new TreeDumperNode("switchArms", null, from x in node.SwitchArms select Visit(x, null)), + new TreeDumperNode("decisionDag", null, new TreeDumperNode[] { Visit(node.DecisionDag, null) }), new TreeDumperNode("defaultLabel", node.DefaultLabel, null), new TreeDumperNode("type", node.Type, null) } @@ -12250,6 +12275,9 @@ public override TreeDumperNode VisitIsPatternExpression(BoundIsPatternExpression { new TreeDumperNode("expression", null, new TreeDumperNode[] { Visit(node.Expression, null) }), new TreeDumperNode("pattern", null, new TreeDumperNode[] { Visit(node.Pattern, null) }), + new TreeDumperNode("decisionDag", null, new TreeDumperNode[] { Visit(node.DecisionDag, null) }), + new TreeDumperNode("whenTrueLabel", node.WhenTrueLabel, null), + new TreeDumperNode("whenFalseLabel", node.WhenFalseLabel, null), new TreeDumperNode("type", node.Type, null) } ); diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index af7412fa10251e41d1c8e0861432abaad6ea0ea5..f8592d46c8ae0a2331f5d01cf40a59d0e6040d10 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -181,6 +181,8 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_TupleBinopLiteralNameMismatch: case ErrorCode.WRN_SwitchExpressionNotExhaustive: case ErrorCode.WRN_IsTypeNamedUnderscore: + case ErrorCode.WRN_GivenExpressionNeverMatchesPattern: + case ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index bb39812d75a4f589e48f8ffb67460d1e09019f85..89505714e12de1d49bf21ec0d798886356e7a9d6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -13,8 +13,7 @@ internal sealed partial class LocalRewriter public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { var isPatternRewriter = new IsPatternExpressionLocalRewriter(node.Syntax, this); - BoundExpression loweredExpression = VisitExpression(node.Expression); - BoundExpression result = isPatternRewriter.LowerIsPattern(loweredExpression, node.Pattern, this._compilation, this._diagnostics); + BoundExpression result = isPatternRewriter.LowerIsPattern(node, node.Pattern, this._compilation, this._diagnostics); isPatternRewriter.Free(); return result; } @@ -85,11 +84,13 @@ private void LowerOneTest(BoundDagTest test) } } - public BoundExpression LowerIsPattern(BoundExpression loweredInput, BoundPattern pattern, CSharpCompilation compilation, DiagnosticBag diagnostics) + public BoundExpression LowerIsPattern( + BoundIsPatternExpression isPatternExpression, BoundPattern pattern, CSharpCompilation compilation, DiagnosticBag diagnostics) { - LabelSymbol failureLabel = new GeneratedLabelSymbol("failure"); - BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern(compilation, pattern.Syntax, loweredInput, pattern, failureLabel, diagnostics, out LabelSymbol successLabel); - decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(loweredInput); + BoundDecisionDag decisionDag = isPatternExpression.DecisionDag; + LabelSymbol whenTrueLabel = isPatternExpression.WhenTrueLabel; + LabelSymbol whenFalseLabel = isPatternExpression.WhenFalseLabel; + BoundExpression loweredInput = _localRewriter.VisitExpression(isPatternExpression.Expression); // The optimization of sharing pattern-matching temps with user variables can always apply to // an is-pattern expression because there is no when clause that could possibly intervene during @@ -111,7 +112,7 @@ public BoundExpression LowerIsPattern(BoundExpression loweredInput, BoundPattern break; case BoundTestDecisionDagNode testNode: { - Debug.Assert(testNode.WhenFalse is BoundLeafDecisionDagNode x && x.Label == failureLabel); + Debug.Assert(testNode.WhenFalse is BoundLeafDecisionDagNode x && x.Label == whenFalseLabel); if (testNode.WhenTrue is BoundEvaluationDecisionDagNode e && TryLowerTypeTestAndCast(testNode.Test, e.Evaluation, out BoundExpression sideEffect, out BoundExpression testExpression)) { @@ -134,14 +135,14 @@ public BoundExpression LowerIsPattern(BoundExpression loweredInput, BoundPattern { case BoundLeafDecisionDagNode leafNode: { - if (leafNode.Label == failureLabel) + if (leafNode.Label == whenFalseLabel) { // It is not clear that this can occur given the dag "optimizations" we performed earlier. AddConjunct(_factory.Literal(false)); } else { - Debug.Assert(leafNode.Label == successLabel); + Debug.Assert(leafNode.Label == whenTrueLabel); } } @@ -150,7 +151,7 @@ public BoundExpression LowerIsPattern(BoundExpression loweredInput, BoundPattern case BoundWhenDecisionDagNode whenNode: { Debug.Assert(whenNode.WhenExpression == null); - Debug.Assert(whenNode.WhenTrue is BoundLeafDecisionDagNode d && d.Label == successLabel); + Debug.Assert(whenNode.WhenTrue is BoundLeafDecisionDagNode d && d.Label == whenTrueLabel); foreach (BoundPatternBinding binding in whenNode.Bindings) { BoundExpression left = _localRewriter.VisitExpression(binding.VariableAccess); diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index eab0dfa4875fffc589044c98229cc56741582004..1275921cdd867511b815049f6853b36f35f1308b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -8825,6 +8825,31 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 2ca35ff9c0b0c6c6f55ab9a8f50cb81eba4edc92..68d6293d362be4089478f7542d83d45d5b5e3920 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -8825,6 +8825,31 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 3c29d64ca5e5b6b0d0dcf5b881fcff33593384b0..ded2f58e5e525cb9e5d18352e56280272d338636 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -8825,6 +8825,31 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 8319278bbda4a252ba60dc721c8a7e37c61d8804..41bc6845de69099b7ee2515dec99d7592d137732 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -8825,6 +8825,31 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 0e53b594f45f249061dd87db2cae4c08c84996a1..5d97eb4979b99f0f6ce7ad279670b9bd1de47d46 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -8825,6 +8825,31 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index fd83290d67a54af01a69827d470412a49db6c34f..5900fdd4d465d1d69ccd315375b4261f64c375b7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -8825,6 +8825,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 9611dce5183de382617d39c1fb8e53bdcae916f5..c2d11d7cf9111f8a4b44e3d7811eba1e9be7be7f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -8825,6 +8825,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index d570df0ff81f9df75be5d77f93c596f7562f55f7..3618f0d1cbc30895978af3a1c39f410489cdbd09 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -8825,6 +8825,31 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 4032abfdacbb513473c9195e85e051a8d3853413..0e06c363e6c3c857e9bf9d49fc66d9951314540b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -8825,6 +8825,31 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 0f066cc01039e2ef1d6b3546872cde168912dc25..dc5dd5b59e18c4310a434bb0676529a94b38166e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -8825,6 +8825,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index b82e5ee644c00c22cfc477838aada3cdc2001f05..764c05204c9bb00ab5657feb18f4cf634f65ed70 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -8825,6 +8825,31 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 484ad702cd9c63888d5cd7b26673f72110b353cf..91cda4718f296b60a1d75dc9021cee05d50a6d7d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -8825,6 +8825,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index f1971b52fc42bd72651da143f5959e4cb73b0aba..afaaf71a6cc50d5a66a9287213e36c4025248ce6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -8825,6 +8825,31 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The name '{0}' does not match the corresponding 'Deconstruct' parameter '{1}'. + + An expression of type '{0}' can never match the provided pattern. + An expression of type '{0}' can never match the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression never matches the provided pattern. + The given expression never matches the provided pattern. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + + + The given expression always matches the provided constant. + The given expression always matches the provided constant. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 11f7b9802a0ac369873fff62cba6b2178250fcbd..1ea0bba509153d1c670964bfe7b878fb4dc7de5e 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -1398,6 +1398,86 @@ .maxstack 2 }"); } + [Fact, WorkItem(17266, "https://github.com/dotnet/roslyn/issues/17266")] + public void IrrefutablePatternInIs01() + { + var source = +@"using System; +public class C +{ + public static void Main() + { + if (Get() is int index) { } + Console.WriteLine(index); + } + + public static int Get() + { + Console.WriteLine(""eval""); + return 1; + } +}"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe); + compilation.VerifyDiagnostics(); + var expectedOutput = @"eval +1"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact, WorkItem(17266, "https://github.com/dotnet/roslyn/issues/17266")] + public void IrrefutablePatternInIs02() + { + var source = +@"using System; +public class C +{ + public static void Main() + { + if (Get() is Assignment(int left, var right)) { } + Console.WriteLine(left); + Console.WriteLine(right); + } + + public static Assignment Get() + { + Console.WriteLine(""eval""); + return new Assignment(1, 2); + } +} +public struct Assignment +{ + public int Left, Right; + public Assignment(int left, int right) => (Left, Right) = (left, right); + public void Deconstruct(out int left, out int right) => (left, right) = (Left, Right); +} +"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularWithRecursivePatterns); + compilation.VerifyDiagnostics(); + var expectedOutput = @"eval +1 +2"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + public void MissingNullCheck01() + { + var source = +@"class Program +{ + public static void Main() + { + string s = null; + System.Console.WriteLine(s is string { Length: 3 }); + } +} +"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularWithRecursivePatterns); + compilation.VerifyDiagnostics(); + var expectedOutput = @"False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + [Fact] [WorkItem(24550, "https://github.com/dotnet/roslyn/issues/24550")] [WorkItem(1284, "https://github.com/dotnet/csharplang/issues/1284")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs index 2ee2e70684f14685e58883c62ec463df75daef10..8cd9c99224e3dff7d65599c13c3647f8e300d383 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs @@ -201,7 +201,7 @@ public static void Main() var s = nameof(Main); byte b = 1; if (s is string t) { } else Console.WriteLine(t); - if (null is dynamic t) { } // null not allowed + if (null is dynamic t2) { } // null not allowed if (s is NullableInt x) { } // error: cannot use nullable type if (s is long l) { } // error: cannot convert string to long if (b is 1000) { } // error: cannot convert 1000 to byte @@ -210,15 +210,12 @@ public static void Main() var compilation = CreateCompilation(source, options: TestOptions.DebugExe); compilation.VerifyDiagnostics( // (10,13): error CS8117: Invalid operand for pattern match; value required, but found ''. - // if (null is dynamic t) { } // null not allowed + // if (null is dynamic t2) { } // null not allowed Diagnostic(ErrorCode.ERR_BadPatternExpression, "null").WithArguments("").WithLocation(10, 13), - // (10,29): error CS0128: A local variable named 't' is already defined in this scope - // if (null is dynamic t) { } // null not allowed - Diagnostic(ErrorCode.ERR_LocalDuplicate, "t").WithArguments("t").WithLocation(10, 29), // (11,18): error CS8116: It is not legal to use nullable type 'int?' in a pattern; use the underlying type 'int' instead. // if (s is NullableInt x) { } // error: cannot use nullable type Diagnostic(ErrorCode.ERR_PatternNullableType, "NullableInt").WithArguments("int?", "int").WithLocation(11, 18), - // (12,18): error CS8121: An expression of type string cannot be handled by a pattern of type long. + // (12,18): error CS8121: An expression of type 'string' cannot be handled by a pattern of type 'long'. // if (s is long l) { } // error: cannot convert string to long Diagnostic(ErrorCode.ERR_PatternWrongType, "long").WithArguments("string", "long").WithLocation(12, 18), // (13,18): error CS0031: Constant value '1000' cannot be converted to a 'byte' @@ -3118,10 +3115,10 @@ public static void Main() // (8,27): warning CS0184: The given expression is never of the provided ('int[]') type // Console.WriteLine(1 is int[]); // warning: expression is never of the provided type Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "1 is int[]").WithArguments("int[]").WithLocation(8, 27), - // (10,33): error CS8121: An expression of type long cannot be handled by a pattern of type string. + // (10,33): error CS8121: An expression of type 'long' cannot be handled by a pattern of type 'string'. // Console.WriteLine(1L is string s); // error: type mismatch Diagnostic(ErrorCode.ERR_PatternWrongType, "string").WithArguments("long", "string").WithLocation(10, 33), - // (11,32): error CS8121: An expression of type int cannot be handled by a pattern of type int[]. + // (11,32): error CS8121: An expression of type 'int' cannot be handled by a pattern of type 'int[]'. // Console.WriteLine(1 is int[] a); // error: expression is never of the provided type Diagnostic(ErrorCode.ERR_PatternWrongType, "int[]").WithArguments("int", "int[]").WithLocation(11, 32) ); @@ -3162,9 +3159,24 @@ public static void Main() "; var compilation = CreateCompilation(source, options: TestOptions.DebugExe); compilation.VerifyDiagnostics( + // (7,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(1 is 1); // true + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "1 is 1").WithLocation(7, 27), + // (8,27): warning CS8416: The given expression never matches the provided pattern. + // Console.WriteLine(1L is int.MaxValue); // OK, but false + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "1L is int.MaxValue").WithLocation(8, 27), + // (9,27): warning CS8416: The given expression never matches the provided pattern. + // Console.WriteLine(1 is int.MaxValue); // false + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "1 is int.MaxValue").WithLocation(9, 27), + // (10,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(int.MaxValue is int.MaxValue); // true + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "int.MaxValue is int.MaxValue").WithLocation(10, 27), // (11,27): warning CS0183: The given expression is always of the provided ('string') type // Console.WriteLine("goo" is System.String); // true - Diagnostic(ErrorCode.WRN_IsAlwaysTrue, @"""goo"" is System.String").WithArguments("string").WithLocation(11, 27) + Diagnostic(ErrorCode.WRN_IsAlwaysTrue, @"""goo"" is System.String").WithArguments("string").WithLocation(11, 27), + // (12,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(Int32.MaxValue is Int32.MaxValue); // true + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "Int32.MaxValue is Int32.MaxValue").WithLocation(12, 27) ); CompileAndVerify(compilation, expectedOutput: @"True @@ -4062,7 +4074,13 @@ class B Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "3 is One + 2").WithArguments("pattern matching", "7.0").WithLocation(15, 27), // (16,27): error CS8059: Feature 'pattern matching' is not available in C# 6. Please use language version 7.0 or greater. // Console.WriteLine(One + 2 is 3); // should print True - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "One + 2 is 3").WithArguments("pattern matching", "7.0").WithLocation(16, 27) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "One + 2 is 3").WithArguments("pattern matching", "7.0").WithLocation(16, 27), + // (15,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(3 is One + 2); // should print True + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "3 is One + 2").WithLocation(15, 27), + // (16,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(One + 2 is 3); // should print True + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "One + 2 is 3").WithLocation(16, 27) ); var expectedOutput = @"5 @@ -4071,7 +4089,14 @@ class B True True"; compilation = CreateCompilation(source, options: TestOptions.DebugExe); - compilation.VerifyDiagnostics(); + compilation.VerifyDiagnostics( + // (15,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(3 is One + 2); // should print True + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "3 is One + 2").WithLocation(15, 27), + // (16,27): warning CS8417: The given expression always matches the provided constant. + // Console.WriteLine(One + 2 is 3); // should print True + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "One + 2 is 3").WithLocation(16, 27) + ); var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput); } @@ -5192,7 +5217,7 @@ public static void Main() compilation.VerifyDiagnostics( // (7,32): error CS0266: Cannot implicitly convert type 'long' to 'byte'. An explicit conversion exists (are you missing a cast?) // Console.WriteLine(b is 12L); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "12L").WithArguments("long", "byte"), + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "12L").WithArguments("long", "byte").WithLocation(7, 32), // (8,32): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type // Console.WriteLine(1 is null); Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(8, 32) @@ -5892,9 +5917,9 @@ void M1(int? i) // (8,13): warning CS0184: The given expression is never of the provided ('string') type // if (s is string) { } Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "s is string").WithArguments("string").WithLocation(8, 13), - // (9,13): warning CS0184: The given expression is never of the provided ('string') type + // (9,13): warning CS8416: The given expression never matches the provided pattern. // if (s is string s2) { } - Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "s is string s2").WithArguments("string").WithLocation(9, 13), + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "s is string s2").WithLocation(9, 13), // (14,13): warning CS0184: The given expression is never of the provided ('long') type // if (i is long) { } Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "i is long").WithArguments("long").WithLocation(14, 13), @@ -6299,9 +6324,9 @@ static void Main() // (7,13): warning CS0184: The given expression is never of the provided ('string') type // if (s is string) {} else { Console.Write("Hello "); } Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "s is string").WithArguments("string").WithLocation(7, 13), - // (8,13): warning CS0184: The given expression is never of the provided ('string') type + // (8,13): warning CS8416: The given expression never matches the provided pattern. // if (s is string t) {} else { Console.WriteLine("World"); } - Diagnostic(ErrorCode.WRN_IsAlwaysFalse, "s is string t").WithArguments("string").WithLocation(8, 13) + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "s is string t").WithLocation(8, 13) ); CompileAndVerify(compilation, expectedOutput: expectedOutput); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs index 1a06289c893ee2ae46c9aad880f13cd6dc4e246a..6dce50658d160eea07b62318d056a1913abcd780 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs @@ -154,6 +154,47 @@ private static bool Check(T expected, T actual) if (!object.Equals(expected, actual)) throw new Exception($""expected: {expected}; actual: {actual}""); return true; } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (9,22): error CS8415: An expression of type '(int x, int y)' can never match the provided pattern. + // Check(false, p is (1, 4) { x: 3 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "p is (1, 4) { x: 3 }").WithArguments("(int x, int y)").WithLocation(9, 22), + // (10,22): error CS8415: An expression of type '(int x, int y)' can never match the provided pattern. + // Check(false, p is (3, 1) { y: 4 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "p is (3, 1) { y: 4 }").WithArguments("(int x, int y)").WithLocation(10, 22), + // (11,22): error CS8415: An expression of type '(int x, int y)' can never match the provided pattern. + // Check(false, p is (3, 4) { x: 1 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "p is (3, 4) { x: 1 }").WithArguments("(int x, int y)").WithLocation(11, 22), + // (13,22): error CS8415: An expression of type '(int x, int y)' can never match the provided pattern. + // Check(false, p is (1, 4) { x: 3 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "p is (1, 4) { x: 3 }").WithArguments("(int x, int y)").WithLocation(13, 22), + // (15,22): error CS8415: An expression of type '(int x, int y)' can never match the provided pattern. + // Check(false, p is (3, 4) { x: 1 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "p is (3, 4) { x: 1 }").WithArguments("(int x, int y)").WithLocation(15, 22) + ); + } + + [Fact] + public void Patterns2_04b() + { + var source = +@" +using System; +class Program +{ + public static void Main() + { + var p = (x: 3, y: 4); + Check(true, p is (3, 4) q1 && Check(p, q1)); + Check(true, p is (3, 4) { x: 3 } q2 && Check(p, q2)); + Check(false, p is (3, 1) { x: 3 }); + } + private static bool Check(T expected, T actual) + { + if (!object.Equals(expected, actual)) throw new Exception($""expected: {expected}; actual: {actual}""); + return true; + } }"; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( @@ -1220,6 +1261,164 @@ void Test5() ); } + [Fact] + public void ERR_IsPatternImpossible() + { + var source = +@"class Program +{ + public static void Main() + { + System.Console.WriteLine(""frog"" is string { Length: 4, Length: 5 }); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (5,34): error CS8415: An expression of type 'string' can never match the provided pattern. + // System.Console.WriteLine("frog" is string { Length: 4, Length: 5 }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, @"""frog"" is string { Length: 4, Length: 5 }").WithArguments("string").WithLocation(5, 34) + ); + } + + [Fact] + public void WRN_GivenExpressionNeverMatchesPattern01() + { + var source = +@"class Program +{ + public static void Main() + { + System.Console.WriteLine(3 is 4); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (5,34): warning CS8416: The given expression never matches the provided pattern. + // System.Console.WriteLine(3 is 4); + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "3 is 4").WithLocation(5, 34) + ); + } + + [Fact] + public void WRN_GivenExpressionNeverMatchesPattern02() + { + var source = +@"class Program +{ + public static void Main() + { + const string s = null; + System.Console.WriteLine(s is string { Length: 3 }); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (6,34): warning CS8416: The given expression never matches the provided pattern. + // System.Console.WriteLine(s is string { Length: 3 }); + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "s is string { Length: 3 }").WithLocation(6, 34) + ); + } + + [Fact] + public void DefiniteAssignmentForIsPattern01() + { + var source = +@"class Program +{ + public static void Main() + { + string s = 300.ToString(); + System.Console.WriteLine(s is string { Length: int j }); + System.Console.WriteLine(j); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (7,34): error CS0165: Use of unassigned local variable 'j' + // System.Console.WriteLine(j); + Diagnostic(ErrorCode.ERR_UseDefViolation, "j").WithArguments("j").WithLocation(7, 34) + ); + } + + [Fact] + public void DefiniteAssignmentForIsPattern02() + { + var source = +@"class Program +{ + public static void Main() + { + const string s = ""300""; + System.Console.WriteLine(s is string { Length: int j }); + System.Console.WriteLine(j); + } +} +"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularWithRecursivePatterns); + compilation.VerifyDiagnostics(); + var expectedOutput = @"True +3"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + public void DefiniteAssignmentForIsPattern03() + { + var source = +@"class Program +{ + public static void Main() + { + int j; + const string s = null; + if (s is string { Length: 3 }) + { + System.Console.WriteLine(j); + } + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (7,13): warning CS8416: The given expression never matches the provided pattern. + // if (s is string { Length: 3 }) + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "s is string { Length: 3 }").WithLocation(7, 13) + ); + } + + [Fact] + public void RefutableConstantPattern01() + { + var source = +@"class Program +{ + public static void Main() + { + int j; + const int N = 3; + const int M = 3; + if (N is M) + { + } + else + { + System.Console.WriteLine(j); + } + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (8,13): warning CS8417: The given expression always matches the provided constant. + // if (N is M) + Diagnostic(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, "N is M").WithLocation(8, 13) + ); + } + [Fact, WorkItem(25591, "https://github.com/dotnet/roslyn/issues/25591")] public void TupleSubsumptionError() { @@ -1560,6 +1759,116 @@ static void Main() ); } + [Fact] + public void PropertyPatternMemberMissing01() + { + var source = +@"class Program +{ + static void Main(string[] args) + { + Blah b = null; + if (b is Blah { X: int i }) + { + } + } +} + +class Blah +{ +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (6,25): error CS0117: 'Blah' does not contain a definition for 'X' + // if (b is Blah { X: int i }) + Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("Blah", "X").WithLocation(6, 25) + ); + } + + [Fact] + public void PropertyPatternMemberMissing02() + { + var source = +@"class Program +{ + static void Main(string[] args) + { + Blah b = null; + if (b is Blah { X: int i }) + { + } + } +} + +class Blah +{ + public int X { set {} } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (6,25): error CS0154: The property or indexer 'Blah.X' cannot be used in this context because it lacks the get accessor + // if (b is Blah { X: int i }) + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "X:").WithArguments("Blah.X").WithLocation(6, 25) + ); + } + + [Fact] + public void PropertyPatternMemberMissing03() + { + var source = +@"class Program +{ + static void Main(string[] args) + { + Blah b = null; + switch (b) + { + case Blah { X: int i }: + break; + } + } +} + +class Blah +{ +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (8,25): error CS0117: 'Blah' does not contain a definition for 'X' + // case Blah { X: int i }: + Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("Blah", "X").WithLocation(8, 25) + ); + } + + [Fact] + public void PropertyPatternMemberMissing04() + { + var source = +@"class Program +{ + static void Main(string[] args) + { + Blah b = null; + switch (b) + { + case Blah { X: int i }: + break; + } + } +} + +class Blah +{ + public int X { set {} } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (8,25): error CS0154: The property or indexer 'Blah.X' cannot be used in this context because it lacks the get accessor + // case Blah { X: int i }: + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "X:").WithArguments("Blah.X").WithLocation(8, 25) + ); + } + [Fact] [WorkItem(24550, "https://github.com/dotnet/roslyn/issues/24550")] [WorkItem(1284, "https://github.com/dotnet/csharplang/issues/1284")] diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index 4144a636d698ac52db909bfba0ddc68da3b18a89..7318caf3ca9c3b3477d300cf77640c04830a087a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -246,6 +246,8 @@ public void WarningLevel_2() case ErrorCode.WRN_TupleBinopLiteralNameMismatch: case ErrorCode.WRN_SwitchExpressionNotExhaustive: case ErrorCode.WRN_IsTypeNamedUnderscore: + case ErrorCode.WRN_GivenExpressionNeverMatchesPattern: + case ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_MainIgnored: