diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md index 48f544a359ebc..773805bc5f26f 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md @@ -13,7 +13,7 @@ might be raised because a new overload is applicable but there is no single best The following example shows some ambiguities and possible workarounds. Note that another workaround is for API authors to use the `OverloadResolutionPriorityAttribute`. -```cs +```csharp var x = new long[] { 1 }; Assert.Equal([2], x); // previously Assert.Equal(T[], T[]), now ambiguous with Assert.Equal(ReadOnlySpan, Span) Assert.Equal([2], x.AsSpan()); // workaround @@ -27,7 +27,7 @@ Assert.Equal(y.AsSpan(), s); // workaround A `Span` overload might be chosen in C# 14 where an overload taking an interface implemented by `T[]` (such as `IEnumerable`) was chosen in C# 13, and that can lead to an `ArrayTypeMismatchException` at runtime if used with a covariant array: -```cs +```csharp string[] s = new[] { "a" }; object[] o = s; // array variance @@ -48,7 +48,7 @@ When using C# 14 or newer and targeting a .NET older than `net10.0` or .NET Framework with `System.Memory` reference, there is a breaking change with `Enumerable.Reverse` and arrays: -```cs +```csharp int[] x = new[] { 1, 2, 3 }; var y = x.Reverse(); // previously Enumerable.Reverse, now MemoryExtensions.Reverse ``` @@ -59,7 +59,7 @@ than `Enumerable.Reverse(this IEnumerable)` (which used to be resolved in C# Specifically, the `Span` extension does the reversal in place and returns `void`. As a workaround, one can define their own `Enumerable.Reverse(this T[])` or use `Enumerable.Reverse` explicitly: -```cs +```csharp int[] x = new[] { 1, 2, 3 }; var y = Enumerable.Reverse(x); // instead of 'x.Reverse();' ``` @@ -118,3 +118,21 @@ class C } ``` + +## Warn for redundant pattern in simple `or` patterns + +***Introduced in Visual Studio 2022 version 17.13*** + +TODO2 update description + +In a disjunctive `or` pattern such as `is not null or 42` or `is not int or string` +the second pattern is redundant and likely results from misunderstanding the precedence order +of `not` and `or` pattern combinators. +The compiler provides a warning in common cases of this mistake: + +```csharp +_ = o is not null or 42; // warning: pattern "42" is redundant +_ = o is not int or string; // warning: pattern "string" is redundant +``` +It is likely that the user meant `is not (null or 42)` or `is not (int or string)` instead. + diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 860fbe3e56536..f9f86f7bb7b8d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -58,6 +58,7 @@ private BoundExpression MakeIsPatternExpression( BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern( this.Compilation, pattern.Syntax, expression, innerPattern, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, diagnostics); + bool wasReported = false; if (!hasErrors && getConstantResult(decisionDag, negated, whenTrueLabel, whenFalseLabel) is { } constantResult) { if (!constantResult) @@ -65,6 +66,7 @@ private BoundExpression MakeIsPatternExpression( Debug.Assert(expression.Type is object); diagnostics.Add(ErrorCode.ERR_IsPatternImpossible, node.Location, expression.Type); hasErrors = true; + wasReported = true; } else { @@ -81,6 +83,7 @@ private BoundExpression MakeIsPatternExpression( case BoundListPattern: Debug.Assert(expression.Type is object); diagnostics.Add(ErrorCode.WRN_IsPatternAlways, node.Location, expression.Type); + wasReported = true; break; case BoundDiscardPattern _: // we do not give a warning on this because it is an existing scenario, and it should @@ -101,6 +104,7 @@ private BoundExpression MakeIsPatternExpression( if (!simplifiedResult) { diagnostics.Add(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, node.Location); + wasReported = true; } else { @@ -108,6 +112,7 @@ private BoundExpression MakeIsPatternExpression( { case BoundConstantPattern _: diagnostics.Add(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, node.Location); + wasReported = true; break; case BoundRelationalPattern _: case BoundTypePattern _: @@ -115,12 +120,18 @@ private BoundExpression MakeIsPatternExpression( case BoundBinaryPattern _: case BoundDiscardPattern _: diagnostics.Add(ErrorCode.WRN_GivenExpressionAlwaysMatchesPattern, node.Location); + wasReported = true; break; } } } } + if (!wasReported && diagnostics.AccumulatesDiagnostics) + { + DecisionDagBuilder.CheckRedundantPatternsForIsPattern(this.Compilation, pattern.Syntax, expression, pattern, diagnostics); + } + // decisionDag, whenTrueLabel, and whenFalseLabel represent the decision DAG for the inner pattern, // after removing any outer 'not's, so consumers will need to compensate for negated patterns. return new BoundIsPatternExpression( @@ -1796,58 +1807,11 @@ static BoundPattern bindBinaryPattern( TypeSymbol? leastSpecificType(SyntaxNode node, ArrayBuilder candidates, BindingDiagnosticBag diagnostics) { - Debug.Assert(candidates.Count >= 2); CompoundUseSiteInfo useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics); - TypeSymbol? bestSoFar = candidates[0]; - // first pass: select a candidate for which no other has been shown to be an improvement. - for (int i = 1, n = candidates.Count; i < n; i++) - { - TypeSymbol candidate = candidates[i]; - bestSoFar = lessSpecificCandidate(bestSoFar, candidate, ref useSiteInfo) ?? bestSoFar; - } - // second pass: check that it is no more specific than any candidate. - for (int i = 0, n = candidates.Count; i < n; i++) - { - TypeSymbol candidate = candidates[i]; - TypeSymbol? spoiler = lessSpecificCandidate(candidate, bestSoFar, ref useSiteInfo); - if (spoiler is null) - { - bestSoFar = null; - break; - } - - // Our specificity criteria are transitive - Debug.Assert(spoiler.Equals(bestSoFar, TypeCompareKind.ConsiderEverything)); - } - + TypeSymbol? bestSoFar = LeastSpecificType(binder.Conversions, candidates, ref useSiteInfo); diagnostics.Add(node, useSiteInfo); return bestSoFar; } - - // Given a candidate least specific type so far, attempt to refine it with a possibly less specific candidate. - TypeSymbol? lessSpecificCandidate(TypeSymbol bestSoFar, TypeSymbol possiblyLessSpecificCandidate, ref CompoundUseSiteInfo useSiteInfo) - { - if (bestSoFar.Equals(possiblyLessSpecificCandidate, TypeCompareKind.AllIgnoreOptions)) - { - // When the types are equivalent, merge them. - return bestSoFar.MergeEquivalentTypes(possiblyLessSpecificCandidate, VarianceKind.Out); - } - else if (binder.Conversions.HasImplicitReferenceConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo)) - { - // When there is an implicit reference conversion from T to U, U is less specific - return possiblyLessSpecificCandidate; - } - else if (binder.Conversions.HasBoxingConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo)) - { - // when there is a boxing conversion from T to U, U is less specific. - return possiblyLessSpecificCandidate; - } - else - { - // We have no improved candidate to offer. - return null; - } - } } else { @@ -1872,7 +1836,58 @@ static void collectCandidates(BoundPattern pat, ArrayBuilder candida candidates.Add(pat.NarrowedType); } } + } + + internal static TypeSymbol? LeastSpecificType(Conversions conversions, ArrayBuilder candidates, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(candidates.Count >= 2); + TypeSymbol? bestSoFar = candidates[0]; + // first pass: select a candidate for which no other has been shown to be an improvement. + for (int i = 1, n = candidates.Count; i < n; i++) + { + TypeSymbol candidate = candidates[i]; + bestSoFar = lessSpecificCandidate(bestSoFar, candidate, ref useSiteInfo, conversions) ?? bestSoFar; + } + // second pass: check that it is no more specific than any candidate. + for (int i = 0, n = candidates.Count; i < n; i++) + { + TypeSymbol candidate = candidates[i]; + TypeSymbol? spoiler = lessSpecificCandidate(candidate, bestSoFar, ref useSiteInfo, conversions); + if (spoiler is null) + { + bestSoFar = null; + break; + } + + // Our specificity criteria are transitive + Debug.Assert(spoiler.Equals(bestSoFar, TypeCompareKind.ConsiderEverything)); + } + + return bestSoFar; + static TypeSymbol? lessSpecificCandidate(TypeSymbol bestSoFar, TypeSymbol possiblyLessSpecificCandidate, ref CompoundUseSiteInfo useSiteInfo, Conversions conversions) + { + if (bestSoFar.Equals(possiblyLessSpecificCandidate, TypeCompareKind.AllIgnoreOptions)) + { + // When the types are equivalent, merge them. + return bestSoFar.MergeEquivalentTypes(possiblyLessSpecificCandidate, VarianceKind.Out); + } + else if (conversions.HasImplicitReferenceConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo)) + { + // When there is an implicit reference conversion from T to U, U is less specific + return possiblyLessSpecificCandidate; + } + else if (conversions.HasBoxingConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo)) + { + // when there is a boxing conversion from T to U, U is less specific. + return possiblyLessSpecificCandidate; + } + else + { + // We have no improved candidate to offer. + return null; + } + } } } } diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.CheckOrReachability.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.CheckOrReachability.cs new file mode 100644 index 0000000000000..7af71e8586b5b --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.CheckOrReachability.cs @@ -0,0 +1,1102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + // TODO2 after review, rename file to use underscore separator + internal sealed partial class DecisionDagBuilder + { + /// + /// For patterns that contain a disjunction `... or ...` we're going to perform reachability analysis for each branch of the `or`. + /// We effectively pick each analyzable `or` sequence in turn and expand it to top-level cases. + /// + /// For example, `A and (B or C)` is expanded to two cases: `A and B` and `A and C`. + /// + /// Similarly, `(A or B) and (C or D)` is expanded to two sets of two cases: + /// 1. { `case A`, `case B` } (we can truncate later test since we only care about the reachability of `A` and `B` here) + /// 2. { `case (A or B) and C`, `case (A or B) and D` } + /// We then check the reachability for each of those cases in different sets. + /// + internal static void CheckRedundantPatternsForIsPattern( + CSharpCompilation compilation, + SyntaxNode syntax, + BoundExpression inputExpression, + BoundPattern pattern, + BindingDiagnosticBag diagnostics) + { + if (pattern.HasErrors) + { + return; + } + + LabelSymbol defaultLabel = new GeneratedLabelSymbol("isPatternFailure"); + var builder = new DecisionDagBuilder(compilation, defaultLabel: defaultLabel, forLowering: false, BindingDiagnosticBag.Discarded); + BoundDagTemp rootIdentifier = BoundDagTemp.ForOriginalInput(inputExpression); + + var noPreviousCases = ArrayBuilder.GetInstance(0); + CheckOrAndAndReachability(noPreviousCases, patternIndex: 0, pattern: pattern, builder: builder, rootIdentifier: rootIdentifier, defaultLabel: defaultLabel, syntax: syntax, diagnostics: diagnostics); + noPreviousCases.Free(); + } + + /// + /// + /// + internal static void CheckRedundantPatternsForSwitchExpression( + CSharpCompilation compilation, + SyntaxNode syntax, + BoundExpression inputExpression, + ImmutableArray switchArms, + BindingDiagnosticBag diagnostics) + { + LabelSymbol defaultLabel = new GeneratedLabelSymbol("isPatternFailure"); + var builder = new DecisionDagBuilder(compilation, defaultLabel: defaultLabel, forLowering: false, BindingDiagnosticBag.Discarded); + BoundDagTemp rootIdentifier = BoundDagTemp.ForOriginalInput(inputExpression); + + var existingCases = ArrayBuilder.GetInstance(switchArms.Length); + int index = 0; + foreach (var switchArm in switchArms) + { + if (switchArm.Pattern.HasErrors) + { + return; + } + + existingCases.Add(builder.MakeTestsForPattern(++index, switchArm.Syntax, rootIdentifier, switchArm.Pattern, whenClause: switchArm.WhenClause, label: switchArm.Label)); + } + + for (int patternIndex = 0; patternIndex < switchArms.Length; patternIndex++) + { + CheckOrAndAndReachability(existingCases, patternIndex, switchArms[patternIndex].Pattern, builder, rootIdentifier, defaultLabel, syntax, diagnostics); + } + + existingCases.Free(); + } + + /// + /// + /// + internal static void CheckRedundantPatternsForSwitchStatement( + CSharpCompilation compilation, + SyntaxNode syntax, + BoundExpression inputExpression, + ImmutableArray switchSections, + BindingDiagnosticBag diagnostics) + { + LabelSymbol defaultLabel = new GeneratedLabelSymbol("isPatternFailure"); + var builder = new DecisionDagBuilder(compilation, defaultLabel: defaultLabel, forLowering: false, BindingDiagnosticBag.Discarded); + BoundDagTemp rootIdentifier = BoundDagTemp.ForOriginalInput(inputExpression); + + var existingCases = ArrayBuilder.GetInstance(); + int index = 0; + foreach (BoundSwitchSection section in switchSections) + { + foreach (BoundSwitchLabel label in section.SwitchLabels) + { + if (label.Syntax.Kind() != SyntaxKind.DefaultSwitchLabel) + { + if (label.Pattern.HasErrors) + { + return; + } + + existingCases.Add(builder.MakeTestsForPattern(++index, label.Syntax, rootIdentifier, label.Pattern, label.WhenClause, label.Label)); + } + } + } + + int patternIndex = 0; + foreach (BoundSwitchSection section in switchSections) + { + foreach (BoundSwitchLabel label in section.SwitchLabels) + { + if (label.Syntax.Kind() != SyntaxKind.DefaultSwitchLabel) + { + CheckOrAndAndReachability(existingCases, patternIndex, label.Pattern, builder, rootIdentifier, defaultLabel, syntax, diagnostics); + patternIndex++; + } + } + } + + existingCases.Free(); + } + + private static void CheckOrAndAndReachability( + ArrayBuilder previousCases, + int patternIndex, + BoundPattern pattern, + DecisionDagBuilder builder, + BoundDagTemp rootIdentifier, + LabelSymbol defaultLabel, + SyntaxNode syntax, + BindingDiagnosticBag diagnostics) + { + CheckOrReachability(previousCases, patternIndex, pattern, + builder, rootIdentifier, defaultLabel, syntax, diagnostics); + + var negated = new BoundNegatedPattern(pattern.Syntax, negated: pattern, pattern.InputType, narrowedType: pattern.InputType); + CheckOrReachability(previousCases, patternIndex, negated, + builder, rootIdentifier, defaultLabel, syntax, diagnostics); + } + + private static void CheckOrReachability( + ArrayBuilder previousCases, + int patternIndex, + BoundPattern pattern, + DecisionDagBuilder builder, + BoundDagTemp rootIdentifier, + LabelSymbol defaultLabel, + SyntaxNode syntax, + BindingDiagnosticBag diagnostics) + { + var normalizedPattern = PatternNormalizer.Rewrite(pattern, rootIdentifier.Type); + + SetsOfOrCases setOfOrCases = RewriteToSetsOfOrCases(normalizedPattern, builder._conversions); + if (setOfOrCases.IsDefault) + { + return; + } + + // We construct a DAG and analyze reachability of branches once per `or` sequence + Debug.Assert(!setOfOrCases.IsDefault); + foreach (OrCases orCases in setOfOrCases.Set) + { + using var casesBuilder = TemporaryArray.GetInstance(orCases.Cases.Count); + var labelsToIgnore = PooledHashSet.GetInstance(); + populateStateForCases(builder, rootIdentifier, previousCases, patternIndex, orCases, labelsToIgnore, defaultLabel, syntax, ref casesBuilder.AsRef()); + BoundDecisionDag dag = builder.MakeBoundDecisionDag(syntax, ref casesBuilder.AsRef()); + + foreach (StateForCase @case in casesBuilder) + { + if (!dag.ReachableLabels.Contains(@case.CaseLabel) && !labelsToIgnore.Contains(@case.CaseLabel)) + { + diagnostics.Add(ErrorCode.WRN_RedundantPattern, @case.Syntax); + } + } + + labelsToIgnore.Free(); + } + + return; + + static void populateStateForCases(DecisionDagBuilder builder, BoundDagTemp rootIdentifier, ArrayBuilder previousCases, int patternIndex, + OrCases set, PooledHashSet labelsToIgnore, LabelSymbol defaultLabel, SyntaxNode nodeSyntax, ref TemporaryArray casesBuilder) + { + for (int i = 0; i < patternIndex; i++) + { + casesBuilder.Add(previousCases[i]); + } + + int index = patternIndex; + foreach ((BoundPattern pattern, SyntaxNode? syntax) in set.Cases) + { + var label = new GeneratedLabelSymbol("orCase"); + SyntaxNode? diagSyntax = syntax; + if (diagSyntax is null) + { + labelsToIgnore.Add(label); + diagSyntax = nodeSyntax; + } + + Debug.Assert(diagSyntax is not null); + casesBuilder.Add(builder.MakeTestsForPattern(++index, diagSyntax, rootIdentifier, pattern, whenClause: null, label: label)); + } + } + } + + /// + /// The purpose of this method is to bring `or` sequences to the top-level (so they can be used as separate cases). + /// Each set handles one `or`. + /// + private static SetsOfOrCases RewriteToSetsOfOrCases(BoundPattern? pattern, Conversions conversions) + { + return pattern switch + { + BoundBinaryPattern binary => rewriteBinary(binary, conversions), + BoundRecursivePattern => default, + BoundListPattern => default, + BoundSlicePattern => default, + BoundITuplePattern => default, + BoundNegatedPattern => default, + BoundTypePattern => default, + BoundDeclarationPattern => default, + BoundConstantPattern => default, + BoundDiscardPattern => default, + BoundRelationalPattern => default, + null => default, + _ => throw ExceptionUtilities.UnexpectedValue(pattern) + }; + + static SetsOfOrCases rewriteBinary(BoundBinaryPattern binaryPattern, Conversions conversions) + { + SetsOfOrCases result = default; + + if (binaryPattern.Disjunction) + { + var patterns = ArrayBuilder.GetInstance(); + addPatternsFromOrTree(binaryPattern, patterns); + + // In `A1 or ... or An`, we produce an expansion set: `A1`, ..., `An` + OrCases resultOrSet1 = result.StartNewOrCases(); + var inputType = binaryPattern.InputType; + foreach (var pattern in patterns) + { + resultOrSet1.Add(pattern, pattern.Syntax); + } + + // If any of the nested patterns can be expanded, we carry those on. + // For example, in `... or Ai or ...`, when we found multiple `or` patterns in `Ai` to be expanded + // For each such nested expansion, we'll produce an `... or or ...` expansion + for (int i = 0; i < patterns.Count; i++) + { + BoundPattern pattern = patterns[i]; + using SetsOfOrCases setOfOrCases = RewriteToSetsOfOrCases(pattern, conversions); + if (!setOfOrCases.IsDefault) + { + foreach (OrCases orSet in setOfOrCases.Set) + { + OrCases resultOrSet = result.StartNewOrCases(); + foreach ((BoundPattern resultPattern, SyntaxNode? syntax) in orSet.Cases) + { + BoundPattern resultBinaryPattern = makeDisjunctionWithReplacement(patterns, resultPattern, i, conversions); + Debug.Assert(resultBinaryPattern is not null); + resultOrSet.Add(resultBinaryPattern, syntax); + } + } + } + } + + patterns.Free(); + } + else + { + // In `A and B`, when found multiple `or` patterns in `A` to be expanded, we drop the `and B` + result = RewriteToSetsOfOrCases(binaryPattern.Left, conversions); + + using SetsOfOrCases rightSetOfOrCases = RewriteToSetsOfOrCases(binaryPattern.Right, conversions); + if (!rightSetOfOrCases.IsDefault) + { + // In `A and B`, when found multiple `or` patterns in `B` to be expanded + // For each such nested expansion, we'll produce a `A and ...` expansion + foreach (OrCases expandedRightSet in rightSetOfOrCases.Set) + { + OrCases resultOrSet = result.StartNewOrCases(); + foreach ((BoundPattern rewrittenPattern, SyntaxNode? syntax) in expandedRightSet.Cases) + { + var rewrittenBinary = new BoundBinaryPattern(binaryPattern.Syntax, disjunction: false, binaryPattern.Left, rewrittenPattern, binaryPattern.InputType, rewrittenPattern.NarrowedType); + resultOrSet.Add(rewrittenBinary, syntax); + } + } + } + } + + return result; + } + + static BoundPattern makeDisjunctionWithReplacement(ArrayBuilder builder, BoundPattern replacementNode, int index, Conversions conversions) + { + Debug.Assert(builder.Count != 0); + + BoundPattern result = (index == builder.Count - 1) ? replacementNode : builder.Last(); + var candidateTypes = ArrayBuilder.GetInstance(builder.Count); + for (int i = builder.Count - 2; i >= 0; i--) + { + candidateTypes.Clear(); + + BoundPattern current = (i == index) ? replacementNode : builder[i]; + candidateTypes.Add(current.NarrowedType); + candidateTypes.Add(result.NarrowedType); + + var narrowedType = leastSpecificType(candidateTypes, conversions) ?? replacementNode.InputType; + result = new BoundBinaryPattern(replacementNode.Syntax, disjunction: true, current, result, replacementNode.InputType, narrowedType, replacementNode.HasErrors); + } + + candidateTypes.Free(); + return result; + } + + static TypeSymbol? leastSpecificType(ArrayBuilder candidates, Conversions conversions) + { + CompoundUseSiteInfo useSiteInfo = CompoundUseSiteInfo.Discarded; + return Binder.LeastSpecificType(conversions, candidates, ref useSiteInfo); + } + + static void addPatternsFromOrTree(BoundPattern pattern, ArrayBuilder builder) + { + if (pattern is BoundBinaryPattern { Disjunction: true } orPattern) + { + addPatternsFromOrTree(orPattern.Left, builder); + addPatternsFromOrTree(orPattern.Right, builder); + } + else + { + builder.Add(pattern); + } + } + } + + /// + /// When there are multiple `or` patterns, such as `(A or B) and (C or D)` + /// we'll expand then two sets: + /// 1. { `case A`, `case B` } + /// 2. { `case (A or B) and C`, `case (A or B) and D` } + /// Each set will be analyzed for reachability. + /// A `default` indicates that no `or` patterns were found (so there are no expansions). + /// + private struct SetsOfOrCases : IDisposable + { + public ArrayBuilder? Set; + + [MemberNotNullWhen(false, nameof(Set))] + public bool IsDefault => Set is null; + + internal OrCases StartNewOrCases() + { + return AddOrSet(new OrCases()); + } + + internal OrCases AddOrSet(OrCases orCases) + { + Set ??= ArrayBuilder.GetInstance(); + Set.Add(orCases); + return orCases; + } + + public void Dispose() + { + if (Set is not null) + { + foreach (OrCases set in Set) + { + set.Dispose(); + } + + Set.Free(); + } + } + +#if DEBUG + public override string ToString() + { + if (IsDefault) + return "DEFAULT"; + + var builder = new StringBuilder(); + foreach (var set in Set) + { + builder.AppendLine("Set:"); + foreach (var @case in set.Cases) + { + builder.Append(@case.pattern.DumpSource()); + if (@case.syntax is { } syntax) + { + builder.Append(" => "); + builder.Append(syntax.ToString()); + builder.Append(", "); + } + + builder.AppendLine(); + } + + builder.AppendLine(); + } + + return builder.ToString(); + } +#endif + } + + /// + /// When we have a single sequence of `or` patterns, such as `A or B or C` + /// we can expand it to separate cases: `A`, `B` and `C`. + /// This composes. So a nested `or` sequence can also be expanded: `X and (A or B or C)` + /// can be expanded to `X and A`, `X and B` and `X and C`. + /// + private struct OrCases : IDisposable + { + public ArrayBuilder<(BoundPattern pattern, SyntaxNode? syntax)> Cases; + + public OrCases() + { + Cases = ArrayBuilder<(BoundPattern pattern, SyntaxNode? syntax)>.GetInstance(); + } + + public void Add(BoundPattern pattern, SyntaxNode? syntax) + { + Cases.Add((pattern, pattern.WasCompilerGenerated ? null : syntax)); + } + + public void Dispose() + { + Cases.Free(); + } + } + + // The purpose of this rewriter is to push `not` patterns down the pattern tree and + // pull all the `and` and `or` patterns up the pattern tree. + // It needs to expand composite patterns in the process. + // It also erases/simplifies some patterns (variable declarations). + // Throughout the process it maintains input and narrowed types discipline, to produce a valid and consistent output. + private class PatternNormalizer : BoundTreeWalkerWithStackGuard + { + private bool _negated; + private bool? _expectingOperandOfDisjunction; + private Func? _makeEvaluationSequenceOperand; + private readonly ArrayBuilder<(BoundPattern? Operand, int? OnTheLeftOf, bool? Disjunction, SyntaxNode? OperationSyntax)> _evalSequence = ArrayBuilder<(BoundPattern?, int?, bool?, SyntaxNode?)>.GetInstance(); + + private PatternNormalizer() + { + } + + internal static BoundPattern Rewrite(BoundPattern pattern, TypeSymbol inputType) + { + var patternNormalizer = new PatternNormalizer(); + patternNormalizer.Visit(pattern); + return patternNormalizer.GetResult(inputType); + } + + private BoundPattern GetResult(TypeSymbol inputType) + { + Debug.Assert(_evalSequence is [(not null, _, _, _), ..]); + + var stack = ArrayBuilder.GetInstance(); + + int evalPosition = 0; + do + { + switch (_evalSequence[evalPosition]) + { + case (null, _, bool disjunction, SyntaxNode operationSyntax): + { + var right = stack.Pop(); + var left = stack.Pop(); + TypeSymbol narrowedTypeForBinary = NarrowedTypeForBinary(left, right, disjunction); + stack.Push(new BoundBinaryPattern(operationSyntax, disjunction, left, right, left.InputType, narrowedTypeForBinary)); + } + break; + + case ({ } operand, _, null, null): + { + stack.Push(WithInputTypeCheckIfNeeded(operand, inputType)); + } + break; + + default: + throw ExceptionUtilities.UnexpectedValue(_evalSequence[evalPosition]); + } + + if (_evalSequence[evalPosition].OnTheLeftOf is int onTheLeftOf) + { + if (_evalSequence[onTheLeftOf].Disjunction!.Value) + { + inputType = stack.Peek().InputType; + } + else + { + inputType = stack.Peek().NarrowedType; + } + } + + evalPosition++; + } + while (evalPosition < _evalSequence.Count); + + var result = stack.Single(); + stack.Free(); + _evalSequence.Free(); + return result; + } + + public override BoundNode? VisitBinaryPattern(BoundBinaryPattern node) + { + bool disjunction = node.Disjunction; + + if (_negated) + { + disjunction = !disjunction; + } + + var saveExpectingOperandOfDisjunction = _expectingOperandOfDisjunction; + _expectingOperandOfDisjunction = disjunction; + + int startOfLeft = _evalSequence.Count; + Visit(node.Left); + int endOfLeft = _evalSequence.Count - 1; + + if (endOfLeft < startOfLeft) + { + // Left is skipped + _expectingOperandOfDisjunction = saveExpectingOperandOfDisjunction; + Visit(node.Right); + return null; + } + + int startOfRight = _evalSequence.Count; + Visit(node.Right); + int endOfRight = _evalSequence.Count - 1; + + _expectingOperandOfDisjunction = saveExpectingOperandOfDisjunction; + + if (endOfRight < startOfRight) + { + // Right is skipped + return null; + } + + PushBinaryPattern(node.Syntax, endOfLeft, disjunction); + + return null; + } + + private void PushBinaryPattern(SyntaxNode syntax, int endOfLeft, bool disjunction) + { + var left = _evalSequence[endOfLeft]; + left.OnTheLeftOf = _evalSequence.Count; + _evalSequence[endOfLeft] = left; + + _evalSequence.Push((null, null, disjunction, syntax)); + } + + private void TryPush(BoundPattern pattern) + { + switch (_expectingOperandOfDisjunction) + { + case true: + if (pattern is BoundNegatedPattern { Negated: BoundDiscardPattern }) + { + return; + } + break; + + case false: + if (pattern is BoundDiscardPattern) + { + return; + } + break; + } + + _evalSequence.Push((_makeEvaluationSequenceOperand?.Invoke(pattern) ?? pattern, null, null, null)); + } + + private TypeSymbol NarrowedTypeForBinary(BoundPattern resultLeft, BoundPattern resultRight, bool resultDisjunction) + { + TypeSymbol narrowedType; + if (resultDisjunction) + { + if (resultRight.NarrowedType.Equals(resultLeft.NarrowedType, TypeCompareKind.AllIgnoreOptions)) + { + return resultLeft.NarrowedType; + } + + return resultLeft.InputType; + } + else + { + narrowedType = resultRight.NarrowedType; + } + + return narrowedType; + } + + public override BoundNode? VisitNegatedPattern(BoundNegatedPattern node) + { + var savedNegated = _negated; + _negated = !_negated; + this.Visit(node.Negated); + _negated = savedNegated; + return null; + } + + public BoundPattern NegateIfNeeded(BoundPattern node) + { + if (!_negated) + { + return node; + } + + if (node is BoundNegatedPattern { Negated: var negated }) + { + return negated; + } + + var result = new BoundNegatedPattern(node.Syntax, node, node.InputType, narrowedType: node.InputType); + if (node.WasCompilerGenerated) + { + result.MakeCompilerGenerated(); + } + + return result; + } + + public override BoundNode? VisitTypePattern(BoundTypePattern node) + { + TryPush(NegateIfNeeded(node)); + return null; + } + + public override BoundNode? VisitConstantPattern(BoundConstantPattern node) + { + TryPush(NegateIfNeeded(node)); + return null; + } + + public override BoundNode? VisitDiscardPattern(BoundDiscardPattern node) + { + TryPush(NegateIfNeeded(node)); + return null; + } + + public override BoundNode? VisitRelationalPattern(BoundRelationalPattern node) + { + TryPush(NegateIfNeeded(node)); + return null; + } + + private static BoundPattern WithInputTypeCheckIfNeeded(BoundPattern pattern, TypeSymbol inputType) + { + if (pattern.InputType.Equals(inputType, TypeCompareKind.AllIgnoreOptions)) + { + return pattern; + } + + if (pattern is BoundTypePattern typePattern1) + { + return typePattern1.Update(typePattern1.DeclaredType, typePattern1.IsExplicitNotNullTest, inputType, typePattern1.NarrowedType); + } + + if (pattern is BoundRecursivePattern recursivePattern) + { + return recursivePattern.Update( + recursivePattern.DeclaredType ?? + new BoundTypeExpression(recursivePattern.Syntax, aliasOpt: null, recursivePattern.InputType.StrippedType()), + recursivePattern.DeconstructMethod, recursivePattern.Deconstruction, + recursivePattern.Properties, recursivePattern.IsExplicitNotNullTest, + recursivePattern.Variable, recursivePattern.VariableAccess, + inputType, recursivePattern.NarrowedType); + } + + if (pattern is BoundDiscardPattern discardPattern) + { + return discardPattern.Update(inputType, inputType); + } + + if (pattern is BoundNegatedPattern negatedPattern) + { + return negatedPattern.Update( + WithInputTypeCheckIfNeeded(negatedPattern.Negated, inputType), + inputType, inputType); + } + + Debug.Assert(pattern is not BoundBinaryPattern); + + // Produce `PatternInputType and pattern` given a new input type + + BoundPattern typePattern = new BoundTypePattern(pattern.Syntax, + new BoundTypeExpression(pattern.Syntax, aliasOpt: null, pattern.InputType), + isExplicitNotNullTest: false, inputType, narrowedType: pattern.InputType).MakeCompilerGenerated(); + + var result = new BoundBinaryPattern(pattern.Syntax, disjunction: false, left: typePattern, right: pattern, inputType, pattern.NarrowedType); + + if (pattern.WasCompilerGenerated) + { + result = result.MakeCompilerGenerated(); + } + + return result; + } + + public override BoundNode? VisitDeclarationPattern(BoundDeclarationPattern node) + { + BoundPattern result; + if (node.IsVar) + { + result = MakeDiscardPattern(node).MakeCompilerGenerated(); + } + else + { + if (node.InputType.Equals(node.DeclaredType.Type, TypeCompareKind.AllIgnoreOptions)) + { + result = MakeDiscardPattern(node).MakeCompilerGenerated(); + } + else + { + result = node; + } + } + + TryPush(NegateIfNeeded(result)); + return null; + } + + public override BoundNode? VisitRecursivePattern(BoundRecursivePattern node) + { + // If we're starting with `Type (D1, D2, ...) { Prop1: P1, Prop2: P2, ... } x` + // - if we are not negating, we can expand it to + // `Type (D1, _, ...) and Type (_, D2, ...) and Type { Prop1: P1 } and Type { Prop2: P2 } ...` + // + // - if we are negating, we can expand it to + // `not Type or Type (not D1, _, ...) or Type (_, not D2, ...) or Type { Prop1: not P1 } or Type { Prop2: not P2 } or ...` + // and the `and` and `or` patterns in the sub-patterns can then be lifted out further. + // For example, if `not D1` resolves to `E1 or F1`, the `Type (not D1, _, ...)` component can be normalized to + // `Type (E1, _, ...) or `Type (F1, _, ...)`. + // If there's not Type, we substitute a null check + + var saveExpectingOperandOfDisjunction = _expectingOperandOfDisjunction; + int startOfLeft = _evalSequence.Count; + + bool isEmptyPropertyPattern = node.Deconstruction.IsDefault && node.Properties is { IsDefault: false, IsEmpty: true }; + if (_negated) + { + _expectingOperandOfDisjunction = true; + + if (node.DeclaredType is not null) + { + // `not Type` + TryPush(new BoundNegatedPattern(node.Syntax, + new BoundTypePattern(node.Syntax, node.DeclaredType, node.IsExplicitNotNullTest, node.InputType, node.NarrowedType, node.HasErrors), + node.InputType, narrowedType: node.InputType, node.HasErrors).MakeCompilerGenerated()); + } + else if (node.InputType.CanContainNull()) + { + // `null` + BoundConstantPattern nullPattern = new BoundConstantPattern(node.Syntax, + new BoundLiteral(node.Syntax, constantValueOpt: null, type: null, hasErrors: false), + ConstantValue.Null, node.InputType, node.InputType, hasErrors: false); + + if (!isEmptyPropertyPattern) + { + nullPattern = nullPattern.MakeCompilerGenerated(); + } + + TryPush(nullPattern); + } + } + else + { + _expectingOperandOfDisjunction = false; + if (isEmptyPropertyPattern) + { + TryPush(node); + } + } + + ImmutableArray deconstruction = node.Deconstruction; + if (!deconstruction.IsDefault) + { + var discards = deconstruction.SelectAsArray(d => d.WithPattern(MakeDiscardPattern(d.Syntax, d.Pattern.InputType))); + var saveMakeEvaluationSequenceOperand = _makeEvaluationSequenceOperand; + + int i = 0; + + _makeEvaluationSequenceOperand = (BoundPattern newPattern) => + { + newPattern = WithInputTypeCheckIfNeeded(newPattern, deconstruction[i].Pattern.InputType); + ImmutableArray newSubPatterns = discards.SetItem(i, deconstruction[i].WithPattern(newPattern)); + + BoundPattern newRecursive = new BoundRecursivePattern( + newPattern.Syntax, declaredType: node.DeclaredType, deconstructMethod: node.DeconstructMethod, + deconstruction: newSubPatterns, + properties: default, isExplicitNotNullTest: false, variable: null, variableAccess: null, + node.InputType, node.NarrowedType, node.HasErrors); + + return saveMakeEvaluationSequenceOperand?.Invoke(newRecursive) ?? newRecursive; + }; + + for (; i < deconstruction.Length; i++) + { + VisitPatternForSupPatternAndCombine(node.Syntax, deconstruction[i].Pattern, startOfLeft); + } + + _makeEvaluationSequenceOperand = saveMakeEvaluationSequenceOperand; + } + + if (!node.Properties.IsDefault) + { + var saveMakeEvaluationSequenceOperand = _makeEvaluationSequenceOperand; + BoundPropertySubpattern? property = null; + + _makeEvaluationSequenceOperand = (BoundPattern newPattern) => + { + newPattern = WithInputTypeCheckIfNeeded(newPattern, property!.Pattern.InputType); + ImmutableArray newSubPatterns = [property.WithPattern(newPattern)]; + + BoundPattern newRecursive = new BoundRecursivePattern( + newPattern.Syntax, declaredType: node.DeclaredType, deconstructMethod: null, deconstruction: default, + properties: newSubPatterns, + isExplicitNotNullTest: false, variable: null, variableAccess: null, + node.InputType, node.NarrowedType, node.HasErrors); + + return saveMakeEvaluationSequenceOperand?.Invoke(newRecursive) ?? newRecursive; + }; + + foreach (BoundPropertySubpattern subPattern in node.Properties) + { + property = subPattern; + VisitPatternForSupPatternAndCombine(node.Syntax, property.Pattern, startOfLeft); + } + + _makeEvaluationSequenceOperand = saveMakeEvaluationSequenceOperand; + } + + _expectingOperandOfDisjunction = saveExpectingOperandOfDisjunction; + + if (_evalSequence.Count - 1 < startOfLeft) + { + // Everything was skipped + TryPush(MakeDefaultPattern(node.Syntax, node.InputType)); + } + + return null; + } + + private void VisitPatternForSupPatternAndCombine(SyntaxNode syntax, BoundPattern pattern, int startOfLeft) + { + int endOfLeft = _evalSequence.Count - 1; + int startOfRight = _evalSequence.Count; + Visit(pattern); + int endOfRight = _evalSequence.Count - 1; + + if (endOfLeft < startOfLeft || endOfRight < startOfRight) + { + // Left or right or both are skipped + return; + } + + PushBinaryPattern(syntax, endOfLeft, disjunction: _negated); + } + + private BoundPattern MakeDefaultPattern(SyntaxNode syntax, TypeSymbol inputType) + { + BoundDiscardPattern discard = MakeDiscardPattern(syntax, inputType).MakeCompilerGenerated(); + if (_negated) + { + return new BoundNegatedPattern(syntax, discard, inputType, narrowedType: inputType).MakeCompilerGenerated(); + } + else + { + return discard; + } + } + + public override BoundNode? VisitPropertySubpattern(BoundPropertySubpattern node) + { + throw ExceptionUtilities.Unreachable(); + } + + public override BoundNode? VisitPositionalSubpattern(BoundPositionalSubpattern node) + { + throw ExceptionUtilities.Unreachable(); + } + + public override BoundNode? VisitITuplePattern(BoundITuplePattern ituplePattern) + { + var saveExpectingOperandOfDisjunction = _expectingOperandOfDisjunction; + int startOfLeft = _evalSequence.Count; + + if (_negated) + { + _expectingOperandOfDisjunction = true; + + if (ituplePattern.InputType.CanContainNull()) + { + // `null` + TryPush(new BoundConstantPattern(ituplePattern.Syntax, + new BoundLiteral(ituplePattern.Syntax, constantValueOpt: null, type: null, hasErrors: false), + ConstantValue.Null, ituplePattern.InputType, ituplePattern.InputType, hasErrors: false).MakeCompilerGenerated()); + } + } + else + { + _expectingOperandOfDisjunction = false; + } + + var subpatterns = ituplePattern.Subpatterns; + var discards = subpatterns.SelectAsArray(d => d.WithPattern(MakeDiscardPattern(d.Syntax, d.Pattern.InputType))); + var saveMakeEvaluationSequenceOperand = _makeEvaluationSequenceOperand; + int i = 0; + + _makeEvaluationSequenceOperand = (BoundPattern newPattern) => + { + newPattern = WithInputTypeCheckIfNeeded(newPattern, subpatterns[i].Pattern.InputType); + ImmutableArray newSubpatterns = discards.SetItem(i, subpatterns[i].WithPattern(newPattern)); + + BoundPattern newITuple = new BoundITuplePattern(newPattern.Syntax, ituplePattern.GetLengthMethod, + ituplePattern.GetItemMethod, newSubpatterns, ituplePattern.InputType, ituplePattern.NarrowedType); + + return saveMakeEvaluationSequenceOperand?.Invoke(newITuple) ?? newITuple; + }; + + for (; i < subpatterns.Length; i++) + { + VisitPatternForSupPatternAndCombine(ituplePattern.Syntax, subpatterns[i].Pattern, startOfLeft); + } + + _makeEvaluationSequenceOperand = saveMakeEvaluationSequenceOperand; + _expectingOperandOfDisjunction = saveExpectingOperandOfDisjunction; + + if (_evalSequence.Count - 1 < startOfLeft) + { + // Everything was skipped + TryPush(MakeDefaultPattern(ituplePattern.Syntax, ituplePattern.InputType)); + } + + return null; + } + + public override BoundNode? VisitListPattern(BoundListPattern listPattern) + { + // If we're starting with `[L1, L2, ...]` + // - if we are not negating, we can expand it to `[L1, _, ...] and [_, L2, ...] and ...` + // and the `and` and `or` patterns in the element patterns can then be lifted out further. + // + // - if we are negating, we can expand it to `null or not [_, _, ...] or [not L1, _, ...] or [_, not L2, ...] or ...` + // and the `and` and `or` patterns in the resulting element patterns can then be lifted out further. + + var saveExpectingOperandOfDisjunction = _expectingOperandOfDisjunction; + int startOfLeft = _evalSequence.Count; + + ImmutableArray discards = listPattern.Subpatterns.SelectAsArray(replaceWithDiscards); + + if (_negated) + { + _expectingOperandOfDisjunction = true; + + if (listPattern.InputType.CanContainNull()) + { + // `null` + TryPush(new BoundConstantPattern(listPattern.Syntax, + new BoundLiteral(listPattern.Syntax, constantValueOpt: null, type: null, hasErrors: false), + ConstantValue.Null, listPattern.InputType, listPattern.InputType).MakeCompilerGenerated()); + } + + // `not [_, _, ..., .._]` + BoundListPattern listOfDiscards = listPattern.WithSubpatterns(discards); + if (!ListPatternHasOnlyEmptySlice(listOfDiscards)) + { + int endOfLeft = _evalSequence.Count - 1; + + TryPush(new BoundNegatedPattern(listPattern.Syntax, + listOfDiscards, listPattern.InputType, listPattern.InputType, listPattern.HasErrors).MakeCompilerGenerated()); + + if (endOfLeft >= startOfLeft) + { + PushBinaryPattern(listPattern.Syntax, endOfLeft, disjunction: true); + } + } + } + else + { + _expectingOperandOfDisjunction = false; + + if (discards.IsEmpty) + { + TryPush(listPattern); + } + } + + var saveMakeEvaluationSequenceOperand = _makeEvaluationSequenceOperand; + + int i = 0; + + Func makeListPattern = (BoundPattern newPattern) => + { + newPattern = WithInputTypeCheckIfNeeded(newPattern, discards[i].InputType); + ImmutableArray newSubpatterns = discards.SetItem(i, newPattern); + + BoundPattern newList = new BoundListPattern( + newPattern.Syntax, newSubpatterns, hasSlice: newSubpatterns.Any(p => p is BoundSlicePattern), listPattern.LengthAccess, listPattern.IndexerAccess, + listPattern.ReceiverPlaceholder, listPattern.ArgumentPlaceholder, listPattern.Variable, listPattern.VariableAccess, + listPattern.InputType, listPattern.NarrowedType); + + return saveMakeEvaluationSequenceOperand?.Invoke(newList) ?? newList; + }; + + Func makeListPatternWithSlice = (BoundPattern newPattern) => + { + var sliceTemplate = (BoundSlicePattern)listPattern.Subpatterns[i]; + Debug.Assert(sliceTemplate.Pattern is not null); + + newPattern = WithInputTypeCheckIfNeeded(newPattern, sliceTemplate.Pattern.InputType); + newPattern = new BoundSlicePattern(newPattern.Syntax, newPattern, sliceTemplate.IndexerAccess, + sliceTemplate.ReceiverPlaceholder, sliceTemplate.ArgumentPlaceholder, sliceTemplate.InputType, sliceTemplate.NarrowedType); + + ImmutableArray newSubpatterns = discards.SetItem(i, newPattern); + + BoundPattern newList = new BoundListPattern( + newPattern.Syntax, newSubpatterns, hasSlice: true, listPattern.LengthAccess, listPattern.IndexerAccess, + listPattern.ReceiverPlaceholder, listPattern.ArgumentPlaceholder, listPattern.Variable, listPattern.VariableAccess, + listPattern.InputType, listPattern.NarrowedType); + + return saveMakeEvaluationSequenceOperand?.Invoke(newList) ?? newList; + }; + + for (; i < discards.Length; i++) + { + if (listPattern.Subpatterns[i] is BoundSlicePattern slicePattern) + { + if (slicePattern.Pattern is null) + { + continue; + } + + _makeEvaluationSequenceOperand = makeListPatternWithSlice; + VisitPatternForSupPatternAndCombine(listPattern.Syntax, slicePattern.Pattern, startOfLeft); + } + else + { + _makeEvaluationSequenceOperand = makeListPattern; + VisitPatternForSupPatternAndCombine(listPattern.Syntax, listPattern.Subpatterns[i], startOfLeft); + } + } + + _makeEvaluationSequenceOperand = saveMakeEvaluationSequenceOperand; + _expectingOperandOfDisjunction = saveExpectingOperandOfDisjunction; + + if (_evalSequence.Count - 1 < startOfLeft) + { + // Everything was skipped + TryPush(MakeDefaultPattern(listPattern.Syntax, listPattern.InputType)); + } + + return null; + + static BoundPattern replaceWithDiscards(BoundPattern pattern) + { + if (pattern is BoundSlicePattern slice) + { + return slice.WithPattern(null); + } + + return MakeDiscardPattern(pattern.Syntax, pattern.InputType); + } + } + + public override BoundNode VisitSlicePattern(BoundSlicePattern node) + { + throw ExceptionUtilities.Unreachable(); + } + + private BoundDiscardPattern MakeDiscardPattern(BoundPattern node) + { + return MakeDiscardPattern(node.Syntax, node.InputType); + } + + private static BoundDiscardPattern MakeDiscardPattern(SyntaxNode syntax, TypeSymbol inputType) + { + return new BoundDiscardPattern(syntax, inputType, inputType); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 8597cc16bdf25..96f73f126f38c 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -1467,8 +1467,8 @@ other is not (BoundDagNonNullTest or BoundDagExplicitNullTest) && // For instance [0] in nested list pattern [ 0, ..[$$], 2 ] refers to [1] in the containing list. (s1Input, BoundDagTemp s1LengthTemp, int s1Index) = GetCanonicalInput(s1); (s2Input, BoundDagTemp s2LengthTemp, int s2Index) = GetCanonicalInput(s2); - Debug.Assert(s1LengthTemp.Syntax is ListPatternSyntax); - Debug.Assert(s2LengthTemp.Syntax is ListPatternSyntax); + //Debug.Assert(s1LengthTemp.Syntax is ListPatternSyntax); // TODO2 + //Debug.Assert(s2LengthTemp.Syntax is ListPatternSyntax); // Ignore input source as it will be matched in the subsequent iterations. if (s1Input.Index == s2Input.Index && // We don't want to pair two indices within the same pattern. diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs index 1a23e72c651d4..a912a77e559f6 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; @@ -27,9 +28,7 @@ private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, BoundListPa { tests.Add(new Tests.One(new BoundDagTypeTest(list.Syntax, ErrorType(), input, hasErrors: true))); } - else if (list.HasSlice && - subpatterns.Length == 1 && - subpatterns[0] is BoundSlicePattern { Pattern: null }) + else if (ListPatternHasOnlyEmptySlice(list)) { // If `..` is the only pattern in the list, bail. This is a no-op and we don't need to match anything further. } @@ -91,5 +90,12 @@ private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, BoundListPa return Tests.AndSequence.Create(tests); } + + private static bool ListPatternHasOnlyEmptySlice(BoundListPattern list) + { + return list.HasSlice && + list.Subpatterns.Length == 1 && + list.Subpatterns[0] is BoundSlicePattern { Pattern: null }; + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs index 3dcf824ff86db..ca0a8421cb6ef 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs @@ -52,12 +52,17 @@ internal override BoundStatement BindSwitchStatementCore(SwitchStatementSyntax n diagnostics); // Report subsumption errors, but ignore the input's constant value for that. - CheckSwitchErrors(ref switchSections, decisionDag, diagnostics); + CheckSwitchErrors(ref switchSections, decisionDag, out bool wasReported, diagnostics); // When the input is constant, we use that to reshape the decision dag that is returned // so that flow analysis will see that some of the cases may be unreachable. decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(boundSwitchGoverningExpression); + if (!wasReported && diagnostics.AccumulatesDiagnostics) + { + DecisionDagBuilder.CheckRedundantPatternsForSwitchStatement(this.Compilation, syntax: node, boundSwitchGoverningExpression, switchSections, diagnostics); + } + return new BoundSwitchStatement( syntax: node, expression: boundSwitchGoverningExpression, @@ -72,8 +77,10 @@ internal override BoundStatement BindSwitchStatementCore(SwitchStatementSyntax n private void CheckSwitchErrors( ref ImmutableArray switchSections, BoundDecisionDag decisionDag, + out bool wasReported, BindingDiagnosticBag diagnostics) { + wasReported = false; var reachableLabels = decisionDag.ReachableLabels; static bool isSubsumed(BoundSwitchLabel switchLabel, ImmutableHashSet reachableLabels) { @@ -103,6 +110,7 @@ static bool isSubsumed(BoundSwitchLabel switchLabel, ImmutableHashSet switchArms = BindSwitchExpressionArms(node, originalBinder, boundInputExpression, diagnostics); TypeSymbol? naturalType = InferResultType(switchArms, diagnostics); - bool reportedNotExhaustive = CheckSwitchExpressionExhaustive(node, boundInputExpression, switchArms, out BoundDecisionDag decisionDag, out LabelSymbol? defaultLabel, diagnostics); + + bool reportedNotExhaustive = CheckSwitchExpressionExhaustive(node, boundInputExpression, switchArms, + out BoundDecisionDag decisionDag, out LabelSymbol? defaultLabel, out bool wasReported, diagnostics); // When the input is constant, we use that to reshape the decision dag that is returned // so that flow analysis will see that some of the cases may be unreachable. decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(boundInputExpression); + if (!wasReported && diagnostics.AccumulatesDiagnostics) + { + DecisionDagBuilder.CheckRedundantPatternsForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, diagnostics); + } + return new BoundUnconvertedSwitchExpression( node, boundInputExpression, switchArms, decisionDag, defaultLabel: defaultLabel, reportedNotExhaustive: reportedNotExhaustive, type: naturalType); @@ -57,8 +64,10 @@ private bool CheckSwitchExpressionExhaustive( ImmutableArray switchArms, out BoundDecisionDag decisionDag, [NotNullWhen(true)] out LabelSymbol? defaultLabel, + out bool wasReported, BindingDiagnosticBag diagnostics) { + wasReported = false; defaultLabel = new GeneratedLabelSymbol("default"); decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics); var reachableLabels = decisionDag.ReachableLabels; @@ -69,6 +78,7 @@ private bool CheckSwitchExpressionExhaustive( if (!hasErrors && !reachableLabels.Contains(arm.Label)) { diagnostics.Add(ErrorCode.ERR_SwitchArmSubsumed, arm.Pattern.Syntax.Location); + wasReported = true; } } @@ -102,6 +112,7 @@ private bool CheckSwitchExpressionExhaustive( warningCode, node.SwitchKeyword.GetLocation(), samplePattern); + wasReported = true; return true; } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundBinaryPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundBinaryPattern.cs new file mode 100644 index 0000000000000..8a54803dc2d11 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundBinaryPattern.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundBinaryPattern + { + private partial void Validate() + { + Debug.Assert(Left.InputType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); + + if (Disjunction) + { + Debug.Assert(Right.InputType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); + // Is it worth asserting that NarrowedType is either the InputType, or or the NarrowedType + // of one of the leaves in the Disjunction hierarchy? + } + else + { + Debug.Assert(Right.InputType.Equals(Left.NarrowedType, TypeCompareKind.AllIgnoreOptions)); + Debug.Assert(NarrowedType.Equals(Right.NarrowedType, TypeCompareKind.AllIgnoreOptions)); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundConstantPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundConstantPattern.cs new file mode 100644 index 0000000000000..df9ee648e1101 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundConstantPattern.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundConstantPattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions) || + NarrowedType.Equals(Value.Type, TypeCompareKind.AllIgnoreOptions)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDeclarationPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDeclarationPattern.cs new file mode 100644 index 0000000000000..6d06293512d1b --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDeclarationPattern.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundDeclarationPattern + { + private partial void Validate() + { + Debug.Assert(DeclaredType is null ? + NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions) : + NarrowedType.Equals(DeclaredType.Type, TypeCompareKind.AllIgnoreOptions)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardPattern.cs new file mode 100644 index 0000000000000..3c517185d7f48 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardPattern.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundDiscardPattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); + } + } +} + diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundITuplePattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundITuplePattern.cs new file mode 100644 index 0000000000000..5a76914306a58 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundITuplePattern.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundITuplePattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.IsCompilerServicesTopLevelType() && NarrowedType.Name == "ITuple"); + } + + internal BoundITuplePattern WithSubpatterns(ImmutableArray subpatterns) + { + return this.Update(this.GetLengthMethod, this.GetItemMethod, subpatterns, this.InputType, this.NarrowedType); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundListPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundListPattern.cs index 7522e92dcf8ff..e4adaa4ce19f1 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundListPattern.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundListPattern.cs @@ -2,17 +2,31 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; namespace Microsoft.CodeAnalysis.CSharp { internal partial class BoundListPattern { + internal BoundListPattern WithSubpatterns(ImmutableArray subpatterns) + { + return Update(subpatterns, this.HasSlice, this.LengthAccess, this.IndexerAccess, this.ReceiverPlaceholder, this.ArgumentPlaceholder, this.Variable, this.VariableAccess, this.InputType, this.NarrowedType); + } + + internal BoundPattern WithSyntax(SyntaxNode syntax) + { + return new BoundListPattern(syntax, this.Subpatterns, this.HasSlice, this.LengthAccess, this.IndexerAccess, this.ReceiverPlaceholder, + this.ArgumentPlaceholder, this.Variable, this.VariableAccess, this.InputType, this.NarrowedType, this.HasErrors); + } + private partial void Validate() { Debug.Assert(LengthAccess is null or BoundPropertyAccess or BoundBadExpression); Debug.Assert(IndexerAccess is null or BoundIndexerAccess or BoundImplicitIndexerAccess or BoundArrayAccess or BoundBadExpression or BoundDynamicIndexerAccess); Debug.Assert(Binder.GetIndexerOrImplicitIndexerSymbol(IndexerAccess) is var _); + Debug.Assert(NarrowedType.Equals(InputType.StrippedType(), TypeCompareKind.AllIgnoreOptions)); } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNegatedPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNegatedPattern.cs new file mode 100644 index 0000000000000..412e783539c5c --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNegatedPattern.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundNegatedPattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); + Debug.Assert(Negated.InputType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs index f690d13b74a01..4cae94a1418e5 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -235,21 +236,8 @@ void appendSourceCore(BoundNode node, int indent, Dictionary ">", + BinaryOperatorKind.GreaterThanOrEqual => ">=", + BinaryOperatorKind.LessThan => "<", + BinaryOperatorKind.LessThanOrEqual => "<=", + _ => relationalPattern.Relation.ToString() + }; + + append(relation); + append(" "); + appendConstantValue(relationalPattern.ConstantValue); + break; + } default: appendLine(node.Kind.ToString()); break; @@ -436,6 +548,26 @@ void appendLocal(LocalSymbol symbol) append($"({symbol.GetDebuggerDisplay()})"); } } + + void appendConstantValue(ConstantValue? constantValueOpt) + { + var value = constantValueOpt?.Value?.ToString(); + if (value is null) + { + append("null"); + return; + } + + switch (constantValueOpt?.Discriminator) + { + case ConstantValueTypeDiscriminator.String: + append($@"""{value}"""); + break; + default: + append(value); + break; + } + } } } #endif diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index b68f25b9d7efe..c9e5dd79d0626 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2386,12 +2386,14 @@ - + + - + + @@ -2408,12 +2410,14 @@ - + + - + + @@ -2421,6 +2425,7 @@ + @@ -2439,6 +2444,7 @@ + @@ -2455,7 +2461,8 @@ - + + @@ -2479,22 +2486,37 @@ - + + - + + - + + - + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundPositionalSubpattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundPositionalSubpattern.cs new file mode 100644 index 0000000000000..a59030dfad7da --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundPositionalSubpattern.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundPositionalSubpattern + { + internal BoundPositionalSubpattern WithPattern(BoundPattern pattern) + { + return this.Update(this.Symbol, pattern); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundPropertySubpattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundPropertySubpattern.cs new file mode 100644 index 0000000000000..05acf74fc0e35 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundPropertySubpattern.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundPropertySubpattern + { + internal BoundPropertySubpattern WithPattern(BoundPattern pattern) + { + return Update(this.Member, this.IsLengthOrCount, pattern); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundRecursivePattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundRecursivePattern.cs new file mode 100644 index 0000000000000..8edba44532207 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundRecursivePattern.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundRecursivePattern + { + private partial void Validate() + { + Debug.Assert(DeclaredType is null ? + NarrowedType.Equals(InputType.StrippedType(), TypeCompareKind.AllIgnoreOptions) : + NarrowedType.Equals(DeclaredType.Type, TypeCompareKind.AllIgnoreOptions)); + } + + internal BoundRecursivePattern WithDeconstruction(ImmutableArray deconstruction) + { + return this.Update(this.DeclaredType, this.DeconstructMethod, deconstruction, this.Properties, this.IsExplicitNotNullTest, + this.Variable, this.VariableAccess, this.InputType, this.NarrowedType); + } + + internal BoundRecursivePattern WithProperties(ImmutableArray properties) + { + return this.Update(this.DeclaredType, this.DeconstructMethod, this.Deconstruction, properties, this.IsExplicitNotNullTest, + this.Variable, this.VariableAccess, this.InputType, this.NarrowedType); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundRelationalPattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundRelationalPattern.cs new file mode 100644 index 0000000000000..955782d1edbe9 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundRelationalPattern.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundRelationalPattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions) || + NarrowedType.Equals(Value.Type, TypeCompareKind.AllIgnoreOptions)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundSlicePattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundSlicePattern.cs index c1434c854ca58..07ceaec240c5a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundSlicePattern.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundSlicePattern.cs @@ -8,10 +8,16 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class BoundSlicePattern { + internal BoundSlicePattern WithPattern(BoundPattern? pattern) + { + return Update(pattern, this.IndexerAccess, this.ReceiverPlaceholder, this.ArgumentPlaceholder, this.InputType, this.NarrowedType); + } + private partial void Validate() { Debug.Assert(IndexerAccess is null or BoundIndexerAccess or BoundImplicitIndexerAccess or BoundArrayAccess or BoundBadExpression or BoundDynamicIndexerAccess); Debug.Assert(Binder.GetIndexerOrImplicitIndexerSymbol(IndexerAccess) is var _); + Debug.Assert(NarrowedType.Equals(InputType, TypeCompareKind.AllIgnoreOptions)); } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundTypePattern.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundTypePattern.cs new file mode 100644 index 0000000000000..349a82f28047c --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundTypePattern.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class BoundTypePattern + { + private partial void Validate() + { + Debug.Assert(NarrowedType.Equals(DeclaredType.Type, TypeCompareKind.AllIgnoreOptions)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 502228635f40c..dc74249c95218 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -8026,4 +8026,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Element type of an iterator may not be a ref struct or a type parameter allowing ref structs + + The pattern is redundant. + + + The pattern is redundant. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 634d051f89ac3..4268bf83a5058 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2353,6 +2353,8 @@ internal enum ErrorCode WRN_ExperimentalWithMessage = 9268, + WRN_RedundantPattern = 9269, + // Note: you will need to do the following after adding errors: // 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 4a3150f3249cb..0f18c30745c10 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -566,6 +566,7 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_FieldIsAmbiguous: case ErrorCode.WRN_UninitializedNonNullableBackingField: case ErrorCode.WRN_AccessorDoesNotUseBackingField: + case ErrorCode.WRN_RedundantPattern: return 1; default: return 0; @@ -2469,6 +2470,7 @@ or ErrorCode.WRN_UninitializedNonNullableBackingField or ErrorCode.WRN_UnassignedInternalRefField or ErrorCode.WRN_AccessorDoesNotUseBackingField or ErrorCode.ERR_IteratorRefLikeElementType + or ErrorCode.WRN_RedundantPattern => false, }; #pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 595e5105ae73c..228ce05213251 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -8026,8 +8026,12 @@ public BoundConstantPattern(SyntaxNode syntax, BoundExpression value, ConstantVa this.Value = value; this.ConstantValue = constantValue; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundExpression Value { get; } public ConstantValue ConstantValue { get; } @@ -8055,8 +8059,12 @@ public BoundDiscardPattern(SyntaxNode syntax, TypeSymbol inputType, TypeSymbol n RoslynDebug.Assert(inputType is object, "Field 'inputType' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); RoslynDebug.Assert(narrowedType is object, "Field 'narrowedType' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundDiscardPattern(SyntaxNode syntax, TypeSymbol inputType, TypeSymbol narrowedType) : base(BoundKind.DiscardPattern, syntax, inputType, narrowedType) { @@ -8111,8 +8119,12 @@ public BoundDeclarationPattern(SyntaxNode syntax, BoundTypeExpression declaredTy this.DeclaredType = declaredType; this.IsVar = isVar; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundTypeExpression DeclaredType { get; } public bool IsVar { get; } @@ -8145,8 +8157,12 @@ public BoundRecursivePattern(SyntaxNode syntax, BoundTypeExpression? declaredTyp this.Deconstruction = deconstruction; this.Properties = properties; this.IsExplicitNotNullTest = isExplicitNotNullTest; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundTypeExpression? DeclaredType { get; } public MethodSymbol? DeconstructMethod { get; } public ImmutableArray Deconstruction { get; } @@ -8266,8 +8282,12 @@ public BoundITuplePattern(SyntaxNode syntax, MethodSymbol getLengthMethod, Metho this.GetLengthMethod = getLengthMethod; this.GetItemMethod = getItemMethod; this.Subpatterns = subpatterns; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public MethodSymbol GetLengthMethod { get; } public MethodSymbol GetItemMethod { get; } public ImmutableArray Subpatterns { get; } @@ -8403,8 +8423,12 @@ public BoundTypePattern(SyntaxNode syntax, BoundTypeExpression declaredType, boo this.DeclaredType = declaredType; this.IsExplicitNotNullTest = isExplicitNotNullTest; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundTypeExpression DeclaredType { get; } public bool IsExplicitNotNullTest { get; } @@ -8437,8 +8461,12 @@ public BoundBinaryPattern(SyntaxNode syntax, bool disjunction, BoundPattern left this.Disjunction = disjunction; this.Left = left; this.Right = right; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public bool Disjunction { get; } public BoundPattern Left { get; } public BoundPattern Right { get; } @@ -8469,8 +8497,12 @@ public BoundNegatedPattern(SyntaxNode syntax, BoundPattern negated, TypeSymbol i RoslynDebug.Assert(narrowedType is object, "Field 'narrowedType' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.Negated = negated; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BoundPattern Negated { get; } [DebuggerStepThrough] @@ -8502,8 +8534,12 @@ public BoundRelationalPattern(SyntaxNode syntax, BinaryOperatorKind relation, Bo this.Relation = relation; this.Value = value; this.ConstantValue = constantValue; + Validate(); } + [Conditional("DEBUG")] + private partial void Validate(); + public BinaryOperatorKind Relation { get; } public BoundExpression Value { get; } public ConstantValue ConstantValue { get; } diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 93808eab7cedd..f62ab1ff0f58f 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -344,6 +344,7 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_UnassignedInternalRefField: case ErrorCode.WRN_AccessorDoesNotUseBackingField: case ErrorCode.WRN_ExperimentalWithMessage: + case ErrorCode.WRN_RedundantPattern: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 1c4b1b4812493..79bfe192bbfd8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -3217,6 +3217,16 @@ Typy a aliasy by neměly mít název record + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Tento odkaz přiřadí {1} k {0}, ale {1} má užší řídicí obor než {0}. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 4f140d156b6f7..6ef1f29ab2777 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -3217,6 +3217,16 @@ Typen und Aliase dürfen nicht den Namen "record" aufweisen. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Diese ref-Zuweisung weist "{1}" "{0}" zu, aber "{1}" weist einen kleineren Escapebereich auf als "{0}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 00da44a7285fc..dfc95072061b3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -3217,6 +3217,16 @@ Los tipos y los alias no deben denominarse "record". + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Esta referencia asigna "{1}" a "{0}", pero "{1}" tiene un ámbito de escape más limitado que "{0}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 6ab86ad14d396..59bb1d3702f76 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -3217,6 +3217,16 @@ Les types et les alias ne doivent pas porter le nom 'record'. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Effectue une attribution par référence de '{1}' vers '{0}', mais '{1}' a une portée de sortie plus limitée que '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 19cc0ae0c1eaf..88166ac333e9b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -3217,6 +3217,16 @@ Il nome di tipi e alias non deve essere 'record'. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Questo riferimento assegna '{1}' a '{0}' ma '{1}' ha un ambito di escape più ristretto di '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 339ede626f583..7ce6a86a8d54a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -3217,6 +3217,16 @@ 型およびエイリアスに 'record' という名前を指定することはできません。 + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. これは、'{1}' を '{0}' に ref 割り当てしますが、'{1}' には '{0}' より狭いエスケープ スコープがあります。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index e68f97c8db796..3b9723c6bcc24 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -3217,6 +3217,16 @@ 형식 및 별칭의 이름은 'record'로 지정할 수 없습니다. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. 이 참조는 '{0}'에 '{1}'을(를) 할당하지만 '{1}'은(는) '{0}'보다 좁은 이스케이프 범위를 갖습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 1a3d084672a20..f6cd965b9d709 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -3217,6 +3217,16 @@ Typy i aliasy nie powinny mieć nazwy „record”. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. To odwołanie przypisuje element „{1}” do elementu „{0}”, ale „{1}” ma węższy zakres ucieczki niż „{0}”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index f1fdc5d45aac2..8ddc4d6f1674c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -3217,6 +3217,16 @@ Os tipos e os aliases não devem ser nomeados como 'registro'. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Essa referência atribui '{1}' a '{0}', mas '{1}' tem um escopo de escape mais estreito do que '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 675f2f4f1bfdf..246696e40b5b1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -3217,6 +3217,16 @@ Типы и псевдонимы не могут иметь имя "record" + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Это присваивает по ссылке "{1}" "{0}", но "{1}" имеет более узкую область выхода, чем "{0}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 4c7c14cabdf99..3b01faeaf3ecc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -3217,6 +3217,16 @@ Türler ve diğer adlar 'record' olarak adlandırılmamalıdır. + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. Bu başvuru, '{1}'i '{0}'ye atar, ancak '{1}', '{0}'ten daha dar bir kaçış kapsamına sahiptir. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index a50ad4cf7a062..e02334e89dac8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -3217,6 +3217,16 @@ 类型和别名不应命名为 "record"。 + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. 此 ref 将“{1}”分配给“{0}”,但“{1}”具有比“{0}”更窄的转义范围。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index bdd66e700bb8d..f7931e0fffe58 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -3217,6 +3217,16 @@ 類型與別名不應命名為 'record'。 + + The pattern is redundant. + The pattern is redundant. + + + + The pattern is redundant. + The pattern is redundant. + + This ref-assigns '{1}' to '{0}' but '{1}' has a narrower escape scope than '{0}'. 此參考指派 '{1}' 至 '{0}',但 '{1}' 的逸出範圍比 '{0}' 還要窄。 diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests4.cs index 4665233220ada..fb770615ebae2 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests4.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests4.cs @@ -1877,10 +1877,9 @@ static int M1(bool? b1, bool? b2) "; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( - // (13,13): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 0.cs(13,13): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. // (null, true) => 6, - Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "(null, true)").WithLocation(13, 13) - ); + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "(null, true)").WithLocation(13, 13)); } [Fact] @@ -4479,7 +4478,14 @@ public static void M(double x) } """; var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (10,22): warning CS9269: The pattern is redundant. + // >= 0 and < 10.0 => "Acceptable", + Diagnostic(ErrorCode.WRN_RedundantPattern, "< 10.0").WithLocation(10, 22), + // (11,26): warning CS9269: The pattern is redundant. + // >= -40.0 and < 0 => "Low", + Diagnostic(ErrorCode.WRN_RedundantPattern, "< 0").WithLocation(11, 26)); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); var tree = comp.SyntaxTrees.First(); @@ -4561,7 +4567,14 @@ public static void M(double x) } """; var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (11,13): warning CS9269: The pattern is redundant. + // >= -40.0 and < 0 => "Low", + Diagnostic(ErrorCode.WRN_RedundantPattern, ">= -40.0").WithLocation(11, 13), + // (12,13): warning CS9269: The pattern is redundant. + // >= 0 and < 10.0 => "Acceptable", + Diagnostic(ErrorCode.WRN_RedundantPattern, ">= 0").WithLocation(12, 13)); + var verifier = CompileAndVerify(comp, expectedOutput: "NaN"); var tree = comp.SyntaxTrees.First(); @@ -4829,5 +4842,3479 @@ class D : A { } Assert.Equal("x", x.ToString()); Assert.Equal("A? x", model.GetDeclaredSymbol(x).ToTestDisplayString()); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67923")] + public void DiscardPatternAfterDisjunctiveTypeTest() + { + var source = """ +A a = new B(); + +if (a is (B or C) and _) +{ +} + +class A { } +class B : A { } +class C : A { } +class D : A { } +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotAOrBPattern() + { + var source = """ +object o = null; +_ = o is not A or B; // 1 +_ = o is not (A and not B); // 2 +_ = o is not (A or B); +_ = o is (not A) or B; // 3 +_ = o is (not A) or (not B); // 4 +_ = o is A or B; +_ = o is A or not B; + +_ = o switch +{ + not A => 42, + B => 43, // 5 + _ => 44 +}; + +switch (o) +{ + case not A: break; + case B: break; // 6 + default: break; +}; + +_ = o switch +{ + A and not B => 42, // 7 + _ => 44 +}; + +_ = o switch +{ + not A => 42, + not B => 43, + _ => 44 // 8 +}; + +class A { } +class B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,19): warning CS9269: The pattern is redundant. + // _ = o is not A or B; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B").WithLocation(2, 19), + // (3,25): error CS8121: An expression of type 'A' cannot be handled by a pattern of type 'B'. + // _ = o is not (A and not B); // 2 + Diagnostic(ErrorCode.ERR_PatternWrongType, "B").WithArguments("A", "B").WithLocation(3, 25), + // (5,21): warning CS9269: The pattern is redundant. + // _ = o is (not A) or B; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B").WithLocation(5, 21), + // (6,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is (not A) or (not B); // 4 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is (not A) or (not B)").WithArguments("object").WithLocation(6, 5), + // (13,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B => 43, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B").WithLocation(13, 5), + // (20,10): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case B: break; // 6 + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "B").WithLocation(20, 10), + // (26,15): error CS8121: An expression of type 'A' cannot be handled by a pattern of type 'B'. + // A and not B => 42, // 7 + Diagnostic(ErrorCode.ERR_PatternWrongType, "B").WithArguments("A", "B").WithLocation(26, 15), + // (34,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 8 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(34, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotAOrBPattern_Interfaces() + { + var source = """ +object o = null; +_ = o is not A or B; +_ = o is not (A or B); +_ = o is (not A) or B; +_ = o is (not A) or (not B); +_ = o is A or B; +_ = o is A or not B; + +_ = o switch +{ + not A => 42, + B => 43, + _ => 44 +}; + +_ = o switch +{ + not A => 42, + not B => 43, + _ => 44 +}; + +interface A { } +interface B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotAOrDerivedPattern() + { + var source = """ +object o = null; +_ = o is not A or Derived; +_ = o is not Derived or A; // 1 +_ = o is not (A or Derived); // 2 +_ = o is (not A) or Derived; +_ = o is (not A) or (not Derived); +_ = o is A or Derived; // 3 +_ = o is Derived or A; +_ = o is A or not Derived; // 4 +_ = o is Derived or not A; + +_ = o switch +{ + not Derived => 42, + A => 43, + _ => 44 // 5 +}; + +_ = o switch +{ + not A => 42, + Derived => 43, + _ => 44 +}; + +_ = o switch +{ + A => 42, + Derived => 43, // 6 + _ => 44 +}; + +_ = o switch +{ + A => 42, + not Derived => 43, + _ => 44 // 7 +}; + +class A { } +class Derived : A { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is not Derived or A; // 1 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is not Derived or A").WithArguments("object").WithLocation(3, 5), + // (4,20): warning CS9269: The pattern is redundant. + // _ = o is not (A or Derived); // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "Derived").WithLocation(4, 20), + // (7,15): warning CS9269: The pattern is redundant. + // _ = o is A or Derived; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "Derived").WithLocation(7, 15), + // (9,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is A or not Derived; // 4 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is A or not Derived").WithArguments("object").WithLocation(9, 5), + // (16,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(16, 5), + // (29,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // Derived => 43, // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "Derived").WithLocation(29, 5), + // (37,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 7 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(37, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_VarOrNotAOrBPattern() + { + var source = """ +object o = null; +_ = o is var x1 or not A or B; // 1, 2 +_ = o is var x2 or not (A or B); // 3, 4 + +_ = o switch +{ + var x3 => 42, + not A or B => 43, // 5 + _ => 44 // 6 +}; + +_ = o switch +{ + var x4 => 42, + not (A or B) => 43, // 7 + _ => 44 // 8 +}; + +class A { } +class B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is var x1 or not A or B; // 1, 2 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is var x1 or not A or B").WithArguments("object").WithLocation(2, 5), + // (2,14): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is var x1 or not A or B; // 1, 2 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x1").WithLocation(2, 14), + // (3,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is var x2 or not (A or B); // 3, 4 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is var x2 or not (A or B)").WithArguments("object").WithLocation(3, 5), + // (3,14): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is var x2 or not (A or B); // 3, 4 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x2").WithLocation(3, 14), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not A or B => 43, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not A or B").WithLocation(8, 5), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(9, 5), + // (15,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not (A or B) => 43, // 7 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not (A or B)").WithLocation(15, 5), + // (16,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 8 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(16, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_AOrNotNullPattern() + { + var source = """ +#nullable enable +object? o = null; +_ = o is A or not null; + +_ = o switch +{ + A => 42, + not null => 43, + _ => 44 +}; + +class A { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotAOrBOrCPattern() + { + var source = """ +object o = null; +_ = o is not A or (B or C); // 1, 2 +_ = o is not (A or (B or C)); + +_ = o switch +{ + not A => 42, + B or C => 43, // 3 + _ => 44 +}; + +class A { } +class B { } +class C { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,20): warning CS9269: The pattern is redundant. + // _ = o is not A or (B or C); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B").WithLocation(2, 20), + // (2,25): warning CS9269: The pattern is redundant. + // _ = o is not A or (B or C); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "C").WithLocation(2, 25), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B or C => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B or C").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrBPattern() + { + var source = """ +#nullable enable +object? o = null; +_ = o is not null or string; // 1 +_ = o is (not null) or string; // 2 +_ = o is not null or (not not string); // 3 +_ = o is not (null or string); + +_ = o switch +{ + not null => 42, + string => 43, // 4 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,22): warning CS9269: The pattern is redundant. + // _ = o is not null or string; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(3, 22), + // (4,24): warning CS9269: The pattern is redundant. + // _ = o is (not null) or string; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(4, 24), + // (5,31): warning CS9269: The pattern is redundant. + // _ = o is not null or (not not string); // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(5, 31), + // (11,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 43, // 4 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(11, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrRecursivePattern() + { + // TODO2 we may need to deduplicate diagnostics + var source = """ +#nullable enable +string? s = null; +_ = s is not null or { Length: >0 }; // 1 +_ = s is null and not { Length: >0 }; // 2 +_ = s is (not null) or { Length: >0 }; // 3 +_ = s is not (null or { Length: >0 }); +_ = s is null and not { Length: >0 }; // 4 + +_ = s switch +{ + not null => 42, + "" => 43, // 5 + _ => 44 +}; + +_ = s switch +{ + not null => 42, + { Length: >0 } => 43, // 6 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,32): warning CS9269: The pattern is redundant. + // _ = s is not null or { Length: >0 }; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(3, 32), + // (3,32): warning CS9269: The pattern is redundant. + // _ = s is not null or { Length: >0 }; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(3, 32), + // (4,33): warning CS9269: The pattern is redundant. + // _ = s is null and not { Length: >0 }; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(4, 33), + // (4,33): warning CS9269: The pattern is redundant. + // _ = s is null and not { Length: >0 }; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(4, 33), + // (5,34): warning CS9269: The pattern is redundant. + // _ = s is (not null) or { Length: >0 }; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(5, 34), + // (5,34): warning CS9269: The pattern is redundant. + // _ = s is (not null) or { Length: >0 }; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(5, 34), + // (7,33): warning CS9269: The pattern is redundant. + // _ = s is null and not { Length: >0 }; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(7, 33), + // (7,33): warning CS9269: The pattern is redundant. + // _ = s is null and not { Length: >0 }; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, ">0").WithLocation(7, 33), + // (12,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "" => 43, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""""").WithLocation(12, 5), + // (19,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // { Length: >0 } => 43, // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "{ Length: >0 }").WithLocation(19, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePattern() + { + var source = """ +string s = null; +_ = s is { Length: not 42 or 43 }; // 1 + +_ = s switch +{ + { Length: not 42 or 43 } => 42, // 2 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,30): warning CS9269: The pattern is redundant. + // _ = s is { Length: not 42 or 43 }; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 30), + // (6,25): warning CS9269: The pattern is redundant. + // { Length: not 42 or 43 } => 42, // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 25)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePattern_TwoOrs() + { + var source = """ +S s = default; +_ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + +_ = s switch +{ + { Prop1: not 42 or 43, Prop2: not 44 or 45 } => 42, // 3, 4 + _ => 44 +}; + +switch (s) +{ + case { Prop1: not 42 or 43, Prop2: not 44 or 45 }: break; // 5, 6 + default: break; +}; + +struct S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,29): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 29), + // (2,50): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 50), + // (6,24): warning CS9269: The pattern is redundant. + // { Prop1: not 42 or 43, Prop2: not 44 or 45 } => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 24), + // (6,45): warning CS9269: The pattern is redundant. + // { Prop1: not 42 or 43, Prop2: not 44 or 45 } => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 45), + // (12,29): warning CS9269: The pattern is redundant. + // case { Prop1: not 42 or 43, Prop2: not 44 or 45 }: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 29), + // (12,50): warning CS9269: The pattern is redundant. + // case { Prop1: not 42 or 43, Prop2: not 44 or 45 }: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(12, 50)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePattern_Deconstruction_TwoOrs() + { + var source = """ +S s = default; +_ = s is (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }; // 1, 2, 3 + +_ = s switch +{ + (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 } => 42, // 4, 5, 6 + _ => 44 +}; + +switch (s) +{ + case (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }: break; // 7, 8, 9 + default: break; +}; + +struct S +{ + public int Prop { get; set; } + public void Deconstruct(out int i, out int j) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = s is (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }; // 1, 2, 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 21), + // (2,35): warning CS9269: The pattern is redundant. + // _ = s is (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }; // 1, 2, 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 35), + // (2,57): warning CS9269: The pattern is redundant. + // _ = s is (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }; // 1, 2, 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(2, 57), + // (6,16): warning CS9269: The pattern is redundant. + // (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 } => 42, // 4, 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 16), + // (6,30): warning CS9269: The pattern is redundant. + // (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 } => 42, // 4, 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 30), + // (6,52): warning CS9269: The pattern is redundant. + // (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 } => 42, // 4, 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(6, 52), + // (12,21): warning CS9269: The pattern is redundant. + // case (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }: break; // 7, 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 21), + // (12,35): warning CS9269: The pattern is redundant. + // case (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }: break; // 7, 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(12, 35), + // (12,57): warning CS9269: The pattern is redundant. + // case (not 42 or 43, not 44 or 45) { Prop: not 46 or 47 }: break; // 7, 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(12, 57)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_WithDiscardPattern() + { + var source = """ +int[] s = null; +_ = s is [not 42 or 43, _]; // 1 + +_ = s switch +{ + [not 42 or 43, _] => 42, // 2 + _ => 44 +}; + +switch (s) +{ + case [not 42 or 43, _]: break; // 3 + default: break; +}; +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = s is [not 42 or 43, _]; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 21), + // (6,16): warning CS9269: The pattern is redundant. + // [not 42 or 43, _] => 42, // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 16), + // (12,21): warning CS9269: The pattern is redundant. + // case [not 42 or 43, _]: break; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 21)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePattern() + { + var source = """ +S s = default; +_ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 +_ = s is [not 42 or 43, ..(not 44 or 45)]; // 3, 4 + +_ = s switch +{ + [..(not 42 or 43), not 44 or 45] => 42, // 5, 6 + _ => 44 +}; + +switch (s) +{ + case [..(not 42 or 43), not 44 or 45]: break; // 7, 8 + default: break; +}; + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,24): warning CS9269: The pattern is redundant. + // _ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 24), + // (2,39): warning CS9269: The pattern is redundant. + // _ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 39), + // (3,21): warning CS9269: The pattern is redundant. + // _ = s is [not 42 or 43, ..(not 44 or 45)]; // , + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (3,38): warning CS9269: The pattern is redundant. + // _ = s is [not 42 or 43, ..(not 44 or 45)]; // , + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 38), + // (7,19): warning CS9269: The pattern is redundant. + // [..(not 42 or 43), not 44 or 45] => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(7, 19), + // (7,34): warning CS9269: The pattern is redundant. + // [..(not 42 or 43), not 44 or 45] => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(7, 34), + // (13,24): warning CS9269: The pattern is redundant. + // case [..(not 42 or 43), not 44 or 45]: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(13, 24), + // (13,39): warning CS9269: The pattern is redundant. + // case [..(not 42 or 43), not 44 or 45]: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(13, 39)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePattern_WithObject() + { + var source = """ +S s = default; +_ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 + +_ = s switch +{ + [..(not 42 or 43), not 44 or 45] => 42, // 3, 4 + _ => 44 +}; + +switch (s) +{ + case [..(not 42 or 43), not 44 or 45]: break; // 5, 6 + default: break; +}; + +public struct S +{ + public int Length => throw null; + public object this[System.Index i] => throw null; + public object this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,24): warning CS9269: The pattern is redundant. + // _ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 24), + // (2,39): warning CS9269: The pattern is redundant. + // _ = s is [..(not 42 or 43), not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 39), + // (6,19): warning CS9269: The pattern is redundant. + // [..(not 42 or 43), not 44 or 45] => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 19), + // (6,34): warning CS9269: The pattern is redundant. + // [..(not 42 or 43), not 44 or 45] => 42, // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 34), + // (12,24): warning CS9269: The pattern is redundant. + // case [..(not 42 or 43), not 44 or 45]: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 24), + // (12,39): warning CS9269: The pattern is redundant. + // case [..(not 42 or 43), not 44 or 45]: break; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(12, 39)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePattern_WithNestedPattern() + { + var source = """ +S s = null; + +_ = s is [..({ Prop1: not 42 or 43, Prop2: not 44 or 45 }), { Prop1: not 46 or 47, Prop2: not 48 or 49 }]; // 1, 2, 3, 4 + +_ = s is [{ Prop1: not 46 or 47, Prop2: not 48 or 49 }, ..({ Prop1: not 42 or 43, Prop2: not 44 or 45 })]; // 5, 6, 7, 8 + +public class S +{ + public int Length => throw null; + public S2 this[System.Index i] => throw null; + public S2 this[System.Range r] => throw null; +} + +public class S2 +{ + public object Prop1 { get; set; } + public object Prop2 { get; set; } +} +"""; + // TODO2 broken + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,33): warning CS9269: The pattern is redundant. + // _ = s is [..({ Prop1: not 42 or 43, Prop2: not 44 or 45 }), { Prop1: not 46 or 47, Prop2: not 48 or 49 }]; // 1, 2, 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 33), + // (3,54): warning CS9269: The pattern is redundant. + // _ = s is [..({ Prop1: not 42 or 43, Prop2: not 44 or 45 }), { Prop1: not 46 or 47, Prop2: not 48 or 49 }]; // 1, 2, 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 54), + // (3,80): warning CS9269: The pattern is redundant. + // _ = s is [..({ Prop1: not 42 or 43, Prop2: not 44 or 45 }), { Prop1: not 46 or 47, Prop2: not 48 or 49 }]; // 1, 2, 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(3, 80), + // (5,30): warning CS9269: The pattern is redundant. + // _ = s is [{ Prop1: not 46 or 47, Prop2: not 48 or 49 }, ..({ Prop1: not 42 or 43, Prop2: not 44 or 45 })]; // 5, 6, 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(5, 30)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ITuplePattern() + { + var source = """ +System.Runtime.CompilerServices.ITuple s = null; +_ = s is (not 42 or 43, not 44 or 45); // 1, 2 +_ = s is not (42 and not 43, 44 and not 45); // 3, 4 +_ = s is not (42 and not 43, _ and var x); // 5, 6 +_ = s is not (_ and var x2, 42 and not 43); // 7, 8 TODO2 +_ = s is not (_, _); +_ = s is not (var x3, var x4); + +_ = s switch +{ + (not 42 or 43, not 44 or 45) => 42, // 9, 10 + _ => 44 +}; + +switch (s) +{ + case (not 42 or 43, not 44 or 45): break; // 11, 12 + default: break; +}; +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = s is (not 42 or 43, not 44 or 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 21), + // (2,35): warning CS9269: The pattern is redundant. + // _ = s is (not 42 or 43, not 44 or 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 35), + // (3,26): warning CS9269: The pattern is redundant. + // _ = s is not (42 and not 43, 44 and not 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 26), + // (3,41): warning CS9269: The pattern is redundant. + // _ = s is not (42 and not 43, 44 and not 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 41), + // (4,26): warning CS9269: The pattern is redundant. + // _ = s is not (42 and not 43, _ and var x); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(4, 26), + // (5,40): warning CS9269: The pattern is redundant. + // _ = s is not (_ and var x2, 42 and not 43); // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 40), + // (11,16): warning CS9269: The pattern is redundant. + // (not 42 or 43, not 44 or 45) => 42, // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 16), + // (11,30): warning CS9269: The pattern is redundant. + // (not 42 or 43, not 44 or 45) => 42, // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(11, 30), + // (17,21): warning CS9269: The pattern is redundant. + // case (not 42 or 43, not 44 or 45): break; // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(17, 21), + // (17,35): warning CS9269: The pattern is redundant. + // case (not 42 or 43, not 44 or 45): break; // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(17, 35)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ITuplePattern_WithInputTypeChanging() + { + var source = """ +System.Runtime.CompilerServices.ITuple i = null; +_ = i is not object or (not 42 or 43, not 44 or 45); // 1, 2 + +object o = null; +_ = o is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 3, 4 + +C c = null; +_ = c is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 5, 6 +_ = c is not object or (not 42 or 43, not 44 or 45); // 7, 8 + +S? s = null; +_ = s is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 9, 10 +_ = s is not object or (not 42 or 43, not 44 or 45); // 11, 12 + +class C : System.Runtime.CompilerServices.ITuple +{ + public object this[int index] => throw null; + public int Length => throw null; +} + +struct S : System.Runtime.CompilerServices.ITuple +{ + public object this[int index] => throw null; + public int Length => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,35): warning CS9269: The pattern is redundant. + // _ = i is not object or (not 42 or 43, not 44 or 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 35), + // (2,49): warning CS9269: The pattern is redundant. + // _ = i is not object or (not 42 or 43, not 44 or 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 49), + // (5,64): warning CS9269: The pattern is redundant. + // _ = o is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 64), + // (5,78): warning CS9269: The pattern is redundant. + // _ = o is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 78), + // (8,64): warning CS9269: The pattern is redundant. + // _ = c is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 64), + // (8,78): warning CS9269: The pattern is redundant. + // _ = c is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(8, 78), + // (9,35): warning CS9269: The pattern is redundant. + // _ = c is not object or (not 42 or 43, not 44 or 45); // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(9, 35), + // (9,49): warning CS9269: The pattern is redundant. + // _ = c is not object or (not 42 or 43, not 44 or 45); // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(9, 49), + // (12,64): warning CS9269: The pattern is redundant. + // _ = s is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 64), + // (12,78): warning CS9269: The pattern is redundant. + // _ = s is System.Runtime.CompilerServices.ITuple and (not 42 or 43, not 44 or 45); // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(12, 78), + // (13,35): warning CS9269: The pattern is redundant. + // _ = s is not object or (not 42 or 43, not 44 or 45); // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(13, 35), + // (13,49): warning CS9269: The pattern is redundant. + // _ = s is not object or (not 42 or 43, not 44 or 45); // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(13, 49)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrNumericLiteralPattern() + { + var source = """ +#nullable enable +int? i = null; +_ = i is not null or 0; // 1 +_ = i is (not null) or 0; // 2 +_ = i is not (null or 0); + +_ = i switch +{ + not null => 42, + 0 => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,22): warning CS9269: The pattern is redundant. + // _ = i is not null or 0; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "0").WithLocation(3, 22), + // (4,24): warning CS9269: The pattern is redundant. + // _ = i is (not null) or 0; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "0").WithLocation(4, 24), + // (10,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 0 => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "0").WithLocation(10, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNotNullOrNumericLiteralPattern() + { + var source = """ +#nullable enable +int? i = null; +_ = i is not not null or 0; +_ = i is not (not null) or 0; +_ = i is (not not null) or 0; + +_ = i switch +{ + not not null => 42, + 0 => 43, + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NumericLiberalOrNotNullOrNumericLiteralPattern() + { + var source = """ +#nullable enable +int? i = null; +_ = i is 0 or not null or 1; // 1 + +_ = i switch +{ + 0 => 41, + not null => 42, + 1 => 43, // 2 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,27): warning CS9269: The pattern is redundant. + // _ = i is 0 or not null or 1; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "1").WithLocation(3, 27), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 1 => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "1").WithLocation(9, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrNotNumericLiteralPattern() + { + var source = """ +#nullable enable +int? i = null; +_ = i is not null or not 0; // 1 +_ = i is null and 0; // 2 + +_ = i switch +{ + not null => 42, + not 0 => 43, + _ => 44 // 3 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,5): warning CS8794: An expression of type 'int?' always matches the provided pattern. + // _ = i is not null or not 0; // 1 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "i is not null or not 0").WithArguments("int?").WithLocation(3, 5), + // (4,5): error CS8518: An expression of type 'int?' can never match the provided pattern. + // _ = i is null and 0; // 2 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "i is null and 0").WithArguments("int?").WithLocation(4, 5), + // (10,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 44 // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(10, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrBooleanLiteralPattern() + { + var source = """ +#nullable enable +bool? b = null; +_ = b is not null or false; // 1 +_ = b is (not null) or false; // 2 +_ = b is not null or true; // 3 +_ = b is (not null) or true; // 4 +_ = b is not (null or false); +_ = b is not (null or true); + +_ = b switch +{ + not null => 42, + false => 43, // 5 + _ => 44 +}; +_ = b switch +{ + not null => 42, + true => 43, // 6 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,22): warning CS9269: The pattern is redundant. + // _ = b is not null or false; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "false").WithLocation(3, 22), + // (4,24): warning CS9269: The pattern is redundant. + // _ = b is (not null) or false; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "false").WithLocation(4, 24), + // (5,22): warning CS9269: The pattern is redundant. + // _ = b is not null or true; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "true").WithLocation(5, 22), + // (6,24): warning CS9269: The pattern is redundant. + // _ = b is (not null) or true; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "true").WithLocation(6, 24), + // (13,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // false => 43, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "false").WithLocation(13, 5), + // (19,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // true => 43, // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "true").WithLocation(19, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrBooleanLiteralPattern_WrongType() + { + var source = """ +#nullable enable +int? i = null; +_ = i is not null or false; // 1 +_ = i is (not null) or false; // 2 + +_ = i switch +{ + not null => 42, + false => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,22): error CS0029: Cannot implicitly convert type 'bool' to 'int?' + // _ = i is not null or false; // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "false").WithArguments("bool", "int?").WithLocation(3, 22), + // (4,24): error CS0029: Cannot implicitly convert type 'bool' to 'int?' + // _ = i is (not null) or false; // 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "false").WithArguments("bool", "int?").WithLocation(4, 24), + // (9,5): error CS0029: Cannot implicitly convert type 'bool' to 'int?' + // false => 43, // 3 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "false").WithArguments("bool", "int?").WithLocation(9, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrEmptyListPattern() + { + var source = """ +#nullable enable +int[]? i = null; +_ = i is not null or []; // 1 +_ = i is (not null) or []; // 2 +_ = i is not (null or []); + +_ = i switch +{ + not null => 42, + [] => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,22): warning CS9269: The pattern is redundant. + // _ = i is not null or []; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "[]").WithLocation(3, 22), + // (4,24): warning CS9269: The pattern is redundant. + // _ = i is (not null) or []; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "[]").WithLocation(4, 24), + // (10,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // [] => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "[]").WithLocation(10, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrDiscardPattern() + { + var source = """ +#nullable enable +int[]? i = null; +_ = i is not null or _; // 1 +_ = i is (not null) or _; // 2 +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,5): warning CS8794: An expression of type 'int[]' always matches the provided pattern. + // _ = i is not null or _; // 1 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "i is not null or _").WithArguments("int[]").WithLocation(3, 5), + // (4,5): warning CS8794: An expression of type 'int[]' always matches the provided pattern. + // _ = i is (not null) or _; // 2 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "i is (not null) or _").WithArguments("int[]").WithLocation(4, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrEmptyStringPattern() + { + var source = """ +#nullable enable +string? s = null; +_ = s is not null or ""; // 1 +_ = s is (not null) or ""; // 2 +_ = s is not (null or ""); + +_ = s switch +{ + not null => 42, + "" => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,22): warning CS9269: The pattern is redundant. + // _ = s is not null or ""; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""""").WithLocation(3, 22), + // (4,24): warning CS9269: The pattern is redundant. + // _ = s is (not null) or ""; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""""").WithLocation(4, 24), + // (10,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "" => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""""").WithLocation(10, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotStringLiteralOrOtherStringLiteralPattern() + { + var source = """ +object o = null; +_ = o is not "A" or "B"; // 1 +_ = o is (not "A") or "B"; // 2 +_ = o is not ("A" or "B"); + +_ = o switch +{ + not "A" => 42, + "B" => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = o is not "A" or "B"; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""B""").WithLocation(2, 21), + // (3,23): warning CS9269: The pattern is redundant. + // _ = o is (not "A") or "B"; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""B""").WithLocation(3, 23), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "B" => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""B""").WithLocation(9, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotStringLiteralOrOtherStringLiteralPatterns() + { + var source = """ +object o = null; +_ = o is not "A" or "B" or "C"; // 1, 2 +_ = o is (not "A") or "B" or "C"; // 3, 4 +_ = o is not ("A" or "B" or "C"); + +_ = o switch +{ + not "A" => 42, + "B" => 43, // 5 + "C" => 44, // 6 + _ => 45 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = o is not "A" or "B" or "C"; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""B""").WithLocation(2, 21), + // (2,28): warning CS9269: The pattern is redundant. + // _ = o is not "A" or "B" or "C"; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""C""").WithLocation(2, 28), + // (3,23): warning CS9269: The pattern is redundant. + // _ = o is (not "A") or "B" or "C"; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""B""").WithLocation(3, 23), + // (3,30): warning CS9269: The pattern is redundant. + // _ = o is (not "A") or "B" or "C"; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""C""").WithLocation(3, 30), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "B" => 43, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""B""").WithLocation(9, 5), + // (10,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "C" => 44, // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""C""").WithLocation(10, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotStringLiteralOrNullPattern() + { + var source = """ +object o = null; +_ = o is not "A" or null; // 1 +_ = o is (not "A") or null; // 2 +_ = o is not ("A" or null); + +_ = o switch +{ + not "A" => 42, + null => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = o is not "A" or null; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(2, 21), + // (3,23): warning CS9269: The pattern is redundant. + // _ = o is (not "A") or null; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(3, 23), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // null => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "null").WithLocation(9, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNumericLiteralOrOtherNumericLiteralPattern() + { + var source = """ +object o = null; +_ = o is not 42 or 43; // 1 +_ = o is (not 42) or 43; // 2 +_ = o is not (42 or 43); + +_ = o switch +{ + not 42 => 42, + 43 => 43, // 3 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,20): warning CS9269: The pattern is redundant. + // _ = o is not 42 or 43; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 20), + // (3,22): warning CS9269: The pattern is redundant. + // _ = o is (not 42) or 43; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 22), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 43 => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "43").WithLocation(9, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNumericLiteralOrTypePattern() + { + var source = """ +#nullable enable +object? o = null; +_ = o is not 42L or string; // 1 + +_ = o switch +{ + not 42L => 42, + string => 43, // 2 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is not 42L or string; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(3, 21), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotTypeAndRecursiveOrBPattern() + { + var source = """ +object o = null; +_ = o is not string { Length: > 0 } or B; // 1 +_ = o is not (string { Length: > 0 } or B); + +_ = o switch +{ + not string { Length: > 0 } => 42, + B => 43, // 2 + _ => 44 +}; + +class B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,40): warning CS9269: The pattern is redundant. + // _ = o is not string { Length: > 0 } or B; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B").WithLocation(2, 40), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotRecursiveOrStringLiteralPattern() + { + var source = """ +string o = null; +_ = o is not { Length: > 0 } or "hi"; + +_ = o switch +{ + not { Length: > 0 } => 42, + "hi" => 43, + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotRecursiveOrNullPattern() + { + var source = """ +string o = null; +_ = o is not { Length: > 0 } or null; // 1 +_ = o is not ({ Length: > 0 } or null); + +_ = o switch +{ + not { Length: > 0 } => 42, + null => 43, // 2 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,33): warning CS9269: The pattern is redundant. + // _ = o is not { Length: > 0 } or null; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(2, 33), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // null => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "null").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrVariousNumericLiteralPattern() + { + var source = """ +object o = null; +_ = o is not null or not not "1" or "2"; // 1, 2 +_ = o is not (null or not not "1" or "2"); + +_ = o switch +{ + not null => 42, + not not "1" or "2" => 43, // 3 + _ => 44 +}; + +_ = o switch +{ + not null => 42, + not not "1" => 43, // 4 + "2" => 44, // 5 + _ => 45 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,30): warning CS9269: The pattern is redundant. + // _ = o is not null or not not "1" or "2"; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""1""").WithLocation(2, 30), + // (2,37): warning CS9269: The pattern is redundant. + // _ = o is not null or not not "1" or "2"; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, @"""2""").WithLocation(2, 37), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not not "1" or "2" => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"not not ""1"" or ""2""").WithLocation(8, 5), + // (15,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not not "1" => 43, // 4 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"not not ""1""").WithLocation(15, 5), + // (16,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // "2" => 44, // 5 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, @"""2""").WithLocation(16, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrEnumPattern() + { + var source = """ +E? e = null; +_ = e is not null or E.Zero; // 1 +_ = e is not (null or E.Zero); + +_ = e switch +{ + not null => 42, + E.Zero => 43, // 2 + _ => 44 +}; +enum E { Zero = 0 } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,22): warning CS9269: The pattern is redundant. + // _ = e is not null or E.Zero; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "E.Zero").WithLocation(2, 22), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // E.Zero => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "E.Zero").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotEnumOrEnumPattern() + { + var source = """ +E e = E.Zero; +_ = e is not E.Zero or E.One; // 1 +_ = e is not (E.Zero or E.One); + +_ = e switch +{ + not E.Zero => 42, + E.One => 43, // 2 + _ => 44 +}; +enum E { Zero = 0, One = 1 } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,24): warning CS9269: The pattern is redundant. + // _ = e is not E.Zero or E.One; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "E.One").WithLocation(2, 24), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // E.One => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "E.One").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotRecursiveOrRecursivePattern() + { + var source = """ +string s = null; + +_ = s is not { Length: 0 } or { Length: 10 }; // 1 +_ = s is not ({ Length: 0 } or { Length: 10 }); + +_ = s switch +{ + not { Length: 0 } => 42, + { Length: 10 } => 43, // 2 + _ => 44 +}; + +object o = null; + +_ = s is not string { Length: 0 } or string { Length: 10 }; // 3 +_ = s is not (string { Length: 0 } or string { Length: 10 }); + +_ = o switch +{ + not string { Length: 0 } => 42, + string { Length: 10 } => 43, // 4 + _ => 44 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,41): warning CS9269: The pattern is redundant. + // _ = s is not { Length: 0 } or { Length: 10 }; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "10").WithLocation(3, 41), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // { Length: 10 } => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "{ Length: 10 }").WithLocation(9, 5), + // (15,55): warning CS9269: The pattern is redundant. + // _ = s is not string { Length: 0 } or string { Length: 10 }; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "10").WithLocation(15, 55), + // (21,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string { Length: 10 } => 43, // 4 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string { Length: 10 }").WithLocation(21, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotRedundant() + { + // A collection of legitimate usages of `not ... or ...` pattern + var source = """ +string s = null; +_ = s is not object or { Length: 0 }; + +_ = s switch +{ + not object => 42, + { Length: 0 } => 43, + _ => 44 +}; + +object o = null; + +_ = o is not Base or Derived { }; + +_ = o switch +{ + not Base => 42, + Derived => 43, + _ => 44 +}; + +_ = o is not (not string or string { Length: 0 }); +_ = o is not string or string { Length: 0 }; + +_ = o switch +{ + not string => 42, + string { Length: 0 } => 43, + _ => 44 +}; + +_ = o is not C { Prop1: true } or C { Prop2: 10 }; + +_ = o switch +{ + not C { Prop1: true } => 42, + C { Prop2: 10 } => 43, + _ => 44 +}; + +C c = null; + +_ = c is not { Prop1: true } or { Prop2: 10 }; +_ = c is not object or (0, 0); + +_ = c switch +{ + not { Prop1: true } => 42, + { Prop2: 10 } => 43, + _ => 44 +}; + +_ = o is not bool or true; + +_ = o switch +{ + not bool => 42, + true => 43, + _ => 44 +}; + +class Base { } +class Derived : Base { } + +class C +{ + public bool Prop1 { get; set; } + public int Prop2 { get; set; } + public void Deconstruct(out int i, out int j) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_TODO2_16() + { + var source = """ +object o = null; +_ = o is not string or string { }; +_ = o is not (not string or string { }); +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is not string or string { }; + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is not string or string { }").WithArguments("object").WithLocation(2, 5), + // (3,5): error CS8518: An expression of type 'object' can never match the provided pattern. + // _ = o is not (not string or string { }); + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "o is not (not string or string { })").WithArguments("object").WithLocation(3, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotNullOrNotAOrBPattern() + { + var source = """ +object o = null; +_ = o is not null or not A or B; // 1 +_ = o is {} or not A or B; // 2 + +_ = o switch +{ + not null => 41, + not A => 42, + B => 43, // 3 +}; + +_ = o switch +{ + {} => 41, + not A => 42, + B => 43, // 4 +}; + +class A { } +class B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is not null or not A or B; // 1 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is not null or not A or B").WithArguments("object").WithLocation(2, 5), + // (3,5): warning CS8794: An expression of type 'object' always matches the provided pattern. + // _ = o is {} or not A or B; // 2 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is {} or not A or B").WithArguments("object").WithLocation(3, 5), + // (9,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B => 43, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B").WithLocation(9, 5), + // (16,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B => 43, // 4 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B").WithLocation(16, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_BaseOrNotAOrBPattern() + { + var source = """ +object o = null; +_ = o is Base { Prop: 10 } or not A or B; // 1 + +_ = o switch +{ + Base { Prop: 10 } => 41, + not A => 42, + B => 43, // 2 + _ => 44 +}; + +class Base { public int Prop { get; set; } } +class A : Base { } +class B : Base { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,40): warning CS9269: The pattern is redundant. + // _ = o is Base { Prop: 10 } or not A or B; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B").WithLocation(2, 40), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B => 43, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B").WithLocation(8, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_BaseTypeOrType() + { + var source = """ +object o = null; +_ = o is object or string; // 1 + +_ = o switch +{ + object => 41, + string => 42, // 2 + _ => 43 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,20): warning CS9269: The pattern is redundant. + // _ = o is object or string; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(2, 20), + // (7,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(7, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_TypeOrBaseType() + { + var source = """ +object o = null; +_ = o is string or object; + +_ = o switch +{ + string => 41, + object => 42, + _ => 43 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotOr() + { + var source = """ +object o = null; +_ = o is (not null or 42) or string; // 1, 2 +_ = o is (not (null or 42)) or string; // 3 +_ = o is (not null and not 42) or string; // 4 +_ = o is (not (not null or 42)) or string; // 5 +_ = o is (null and not 42) or string; // 6 + +_ = o switch +{ + not null or 42 => 41, + string => 42, // 7 + _ => 43 +}; +_ = o switch +{ + not (null or 42) => 41, + string => 42, // 8 + _ => 43 +}; +_ = o switch +{ + not null and not 42 => 41, + string => 42, // 9 + _ => 43 +}; +_ = o switch +{ + not (not null or 42) => 41, // 10 + string => 42, + _ => 43 +}; +_ = o switch +{ + null and not 42 => 41, // 11 + string => 42, + _ => 43 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,23): warning CS9269: The pattern is redundant. + // _ = o is (not null or 42) or string; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "42").WithLocation(2, 23), + // (2,30): warning CS9269: The pattern is redundant. + // _ = o is (not null or 42) or string; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(2, 30), + // (3,32): warning CS9269: The pattern is redundant. + // _ = o is (not (null or 42)) or string; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(3, 32), + // (4,35): warning CS9269: The pattern is redundant. + // _ = o is (not null and not 42) or string; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(4, 35), + // (5,28): warning CS9269: The pattern is redundant. + // _ = o is (not (not null or 42)) or string; // 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "42").WithLocation(5, 28), + // (6,24): warning CS9269: The pattern is redundant. + // _ = o is (null and not 42) or string; // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "42").WithLocation(6, 24), + // (11,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 7 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(11, 5), + // (17,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 8 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(17, 5), + // (23,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 9 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(23, 5), + // (28,22): warning CS9269: The pattern is redundant. + // not (not null or 42) => 41, // 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "42").WithLocation(28, 22), + // (34,18): warning CS9269: The pattern is redundant. + // null and not 42 => 41, // 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "42").WithLocation(34, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotAnd() + { + var source = """ +object o = null; +_ = o is (not (not null and 42)) or string; // 1 +_ = o is null or not 42 or string; // 2 +_ = o is not ((not null and 42) or string); +_ = o is null or not 42 or string; // 3 + +_ = o is (not (int and int)) or string; // 4, 5 +_ = o is not ((int and int) or string); // 6 + +_ = o is (not int) or string; // 7 +_ = o is not (int or string); + +_ = o switch +{ + not (not null and 42) => 41, + string => 42, // 8 + _ => 43 +}; + +_ = o switch +{ + null => 40, + not 42 => 41, + string => 42, // 9 + _ => 43 +}; + +_ = o switch +{ + not (int and int) => 41, + string => 42, // 10 + _ => 43 +}; + +_ = o switch +{ + not int => 41, + string => 42, // 11 + _ => 43 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,37): warning CS9269: The pattern is redundant. + // _ = o is (not (not null and 42)) or string; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(2, 37), + // (3,28): warning CS9269: The pattern is redundant. + // _ = o is null or not 42 or string; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(3, 28), + // (5,28): warning CS9269: The pattern is redundant. + // _ = o is null or not 42 or string; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(5, 28), + // (7,24): warning CS9269: The pattern is redundant. + // _ = o is (not (int and int)) or string; // 4, 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(7, 24), + // (7,33): warning CS9269: The pattern is redundant. + // _ = o is (not (int and int)) or string; // 4, 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(7, 33), + // (8,24): warning CS9269: The pattern is redundant. + // _ = o is not ((int and int) or string); // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(8, 24), + // (10,23): warning CS9269: The pattern is redundant. + // _ = o is (not int) or string; // 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(10, 23), + // (16,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 8 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(16, 5), + // (24,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 9 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(24, 5), + // (31,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 10 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(31, 5), + // (38,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // string => 42, // 11 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "string").WithLocation(38, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_InSwitch() + { + var source = """ +object o = null; +_ = o switch +{ + string { Length: 0 } => 41, + not int or string => 42, // 1 + _ => 43 +}; + +_ = o switch +{ + string { Length: 0 } => 41, + not string or int => 42, // 2 + _ => 43 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (5,16): warning CS9269: The pattern is redundant. + // not int or string => 42, // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "string").WithLocation(5, 16), + // (12,19): warning CS9269: The pattern is redundant. + // not string or int => 42, // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(12, 19)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_DisallowDesignatorsUnderNotAndOr() + { + var source = """ +class C +{ + void M(object o, C c) + { + if (o is not (1 and int)) { } // 1 + if (o is (not 1) or (not int)) { } // 2 + if (o is not (1 and int x1)) { } + if (o is not (1 and not int x2)) { } // 3, 4 + if (o is not (1 and not not int x3)) { } // 5 + if (o is (string or int) and var x4) { } + if (o is (C or Derived) and var x5) { } // 6 + if (o is (Derived or C) and var x6) { } + + if (c is not (Derived and (var y1, var y2))) { } else { y1.ToString(); } + if (c is not (Derived and null)) { } else { y1.ToString(); } // 7 + if (c is not Derived or not (var z1, var z2)) { } else { z1.ToString(); } // 8, 9, 10 + + _ = c switch + { + not Derived => 41, + not (var w1, var w2) => 42, // 11, 12, 13 + _ => 43 + }; + + if (c is not (Derived and (int s1, int s2))) { } else { s1.ToString(); } + if (c is (not Derived or not (int t1, int t2))) { } else { t1.ToString(); } // 14, 15, 16 + + _ = c switch + { + not Derived => 41, + not (int u1, int u2) => 42, // 17, 18, 19 + _ => 43 + }; + + if (o is (1 or 2) or int v1) { } // 20 + } + + public void Deconstruct(out int i, out int j) => throw null; +} +class Derived : C { } +"""; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (5,29): warning CS9269: The pattern is redundant. + // if (o is not (1 and int)) { } // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(5, 29), + // (6,34): warning CS9269: The pattern is redundant. + // if (o is (not 1) or (not int)) { } // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(6, 34), + // (8,13): warning CS8794: An expression of type 'object' always matches the provided pattern. + // if (o is not (1 and not int x2)) { } // 3, 4 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is not (1 and not int x2)").WithArguments("object").WithLocation(8, 13), + // (8,37): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is not (1 and not int x2)) { } // 3, 4 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x2").WithLocation(8, 37), + // (9,41): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is not (1 and not not int x3)) { } // 5 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x3").WithLocation(9, 41), + // (11,24): warning CS9269: The pattern is redundant. + // if (o is (C or Derived) and var x5) { } // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "Derived").WithLocation(11, 24), + // (15,13): warning CS8794: An expression of type 'C' always matches the provided pattern. + // if (c is not (Derived and null)) { } else { y1.ToString(); } // 7 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "c is not (Derived and null)").WithArguments("C").WithLocation(15, 13), + // (16,42): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (c is not Derived or not (var z1, var z2)) { } else { z1.ToString(); } // 8, 9, 10 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "z1").WithLocation(16, 42), + // (16,50): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (c is not Derived or not (var z1, var z2)) { } else { z1.ToString(); } // 8, 9, 10 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "z2").WithLocation(16, 50), + // (16,66): error CS0165: Use of unassigned local variable 'z1' + // if (c is not Derived or not (var z1, var z2)) { } else { z1.ToString(); } // 8, 9, 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z1").WithArguments("z1").WithLocation(16, 66), + // (21,13): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not (var w1, var w2) => 42, // 11, 12, 13 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not (var w1, var w2)").WithLocation(21, 13), + // (21,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // not (var w1, var w2) => 42, // 11, 12, 13 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "w1").WithLocation(21, 22), + // (21,30): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // not (var w1, var w2) => 42, // 11, 12, 13 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "w2").WithLocation(21, 30), + // (26,43): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (c is (not Derived or not (int t1, int t2))) { } else { t1.ToString(); } // 14, 15, 16 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "t1").WithLocation(26, 43), + // (26,51): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (c is (not Derived or not (int t1, int t2))) { } else { t1.ToString(); } // 14, 15, 16 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "t2").WithLocation(26, 51), + // (26,68): error CS0165: Use of unassigned local variable 't1' + // if (c is (not Derived or not (int t1, int t2))) { } else { t1.ToString(); } // 14, 15, 16 + Diagnostic(ErrorCode.ERR_UseDefViolation, "t1").WithArguments("t1").WithLocation(26, 68), + // (31,13): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not (int u1, int u2) => 42, // 17, 18, 19 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not (int u1, int u2)").WithLocation(31, 13), + // (31,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // not (int u1, int u2) => 42, // 17, 18, 19 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "u1").WithLocation(31, 22), + // (31,30): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // not (int u1, int u2) => 42, // 17, 18, 19 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "u2").WithLocation(31, 30), + // (35,34): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is (1 or 2) or int v1) { } // 20 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "v1").WithLocation(35, 34)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_LeftBranches() + { + var source = """ +class C +{ + void M(object o) + { + if (o is (1 or 2) or int v1) { } // 1 + if (o is ((1 or 2) or 3) or int v2) { } // 2 + if (o is (1 or (2 or 3)) or int v3) { } // 3 + } +} +"""; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (5,34): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is (1 or 2) or int v1) { } // 1 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "v1").WithLocation(5, 34), + // (6,41): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is ((1 or 2) or 3) or int v2) { } // 2 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "v2").WithLocation(6, 41), + // (7,41): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // if (o is (1 or (2 or 3)) or int v3) { } // 3 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "v3").WithLocation(7, 41)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_DifferentOrSequences() + { + var source = """ +class C +{ + void M(S s) + { + if (s is { Prop1: 42 or 43 } or { Prop2: 44 or 45 }) { } + if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 1, 2 + if (s is { Prop1: (not 42 or 43) or 44 } or { Prop2: (not 45 or 45) or 46 }) { } // 3 + if (s is { Prop1: (42 or (not 43 or 44)) or 45 } or { Prop2: (46 or (not 44 or 45)) or 46 }) { } // 4, 5, 6, 7 + + if (s is ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) or { Prop3: not 46 or 47 }) { } // 8, 9, 10 + if (s is ({ Prop0: not 42 or 43 } or ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 })) or { Prop3: not 46 or 47 }) { } // 11, 12, 13, 14 + } +} + +public struct S +{ + public int Prop0 { get; set; } + public int Prop1 { get; set; } + public int Prop2 { get; set; } + public int Prop3 { get; set; } +} +"""; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (6,37): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 37), + // (6,64): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 64), + // (7,13): warning CS8794: An expression of type 'S' always matches the provided pattern. + // if (s is { Prop1: (not 42 or 43) or 44 } or { Prop2: (not 45 or 45) or 46 }) { } // 3 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "s is { Prop1: (not 42 or 43) or 44 } or { Prop2: (not 45 or 45) or 46 }").WithArguments("S").WithLocation(7, 13), + // (8,45): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (42 or (not 43 or 44)) or 45 } or { Prop2: (46 or (not 44 or 45)) or 46 }) { } // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "44").WithLocation(8, 45), + // (8,53): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (42 or (not 43 or 44)) or 45 } or { Prop2: (46 or (not 44 or 45)) or 46 }) { } // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(8, 53), + // (8,88): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (42 or (not 43 or 44)) or 45 } or { Prop2: (46 or (not 44 or 45)) or 46 }) { } // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(8, 88), + // (8,96): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (42 or (not 43 or 44)) or 45 } or { Prop2: (46 or (not 44 or 45)) or 46 }) { } // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "46").WithLocation(8, 96), + // (10,38): warning CS9269: The pattern is redundant. + // if (s is ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) or { Prop3: not 46 or 47 }) { } // 8, 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(10, 38), + // (10,65): warning CS9269: The pattern is redundant. + // if (s is ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) or { Prop3: not 46 or 47 }) { } // 8, 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(10, 65), + // (10,93): warning CS9269: The pattern is redundant. + // if (s is ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) or { Prop3: not 46 or 47 }) { } // 8, 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(10, 93), + // (11,38): warning CS9269: The pattern is redundant. + // if (s is ({ Prop0: not 42 or 43 } or ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 })) or { Prop3: not 46 or 47 }) { } // 11, 12, 13, 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 38), + // (11,66): warning CS9269: The pattern is redundant. + // if (s is ({ Prop0: not 42 or 43 } or ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 })) or { Prop3: not 46 or 47 }) { } // 11, 12, 13, 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 66), + // (11,93): warning CS9269: The pattern is redundant. + // if (s is ({ Prop0: not 42 or 43 } or ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 })) or { Prop3: not 46 or 47 }) { } // 11, 12, 13, 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(11, 93), + // (11,122): warning CS9269: The pattern is redundant. + // if (s is ({ Prop0: not 42 or 43 } or ({ Prop1: not 42 or 43 } or { Prop2: not 44 or 45 })) or { Prop3: not 46 or 47 }) { } // 11, 12, 13, 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(11, 122)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_DifferentOrSequences_TODO2() + { + var source = """ +class C +{ + void M(Container c) + { + if (c is { PropA: { Prop0: not 42 or 43 } or { Prop1: not 44 or 45 } } // 1, 2 + or { PropB: { Prop2: not 46 or 47 } or { Prop3: not 48 or 49 } }) { } // 3, 4 + } +} +public struct Container +{ + public S PropA { get; set; } + public S PropB { get; set; } +} +public struct S +{ + public int Prop0 { get; set; } + public int Prop1 { get; set; } + public int Prop2 { get; set; } + public int Prop3 { get; set; } +} +"""; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (5,46): warning CS9269: The pattern is redundant. + // if (c is { PropA: { Prop0: not 42 or 43 } or { Prop1: not 44 or 45 } } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 46), + // (5,73): warning CS9269: The pattern is redundant. + // if (c is { PropA: { Prop0: not 42 or 43 } or { Prop1: not 44 or 45 } } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 73), + // (6,46): warning CS9269: The pattern is redundant. + // or { PropB: { Prop2: not 46 or 47 } or { Prop3: not 48 or 49 } }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "47").WithLocation(6, 46), + // (6,73): warning CS9269: The pattern is redundant. + // or { PropB: { Prop2: not 46 or 47 } or { Prop3: not 48 or 49 } }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "49").WithLocation(6, 73)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_TODO2_2() + { + var source = """ +object o = null; +_ = o is A or B x1; // 1 +_ = o is not A or B x2; // 2, 3 + +_ = o switch +{ + not A => 1, + B y2 => 2, // 4 + _ => 3 +}; + +_ = o is not (not A and B x3); +_ = o is not (A or B x4); // 5 +_ = o is not (A and not B x5); // 6, 7 + +class A { } +class B { } +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,17): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is A or B x1; // 1 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x1").WithLocation(2, 17), + // (3,19): warning CS9269: The pattern is redundant. + // _ = o is not A or B x2; // 2, 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "B x2").WithLocation(3, 19), + // (3,21): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is not A or B x2; // 2, 3 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x2").WithLocation(3, 21), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // B y2 => 2, // 4 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "B y2").WithLocation(8, 5), + // (13,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is not (A or B x4); // 5 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x4").WithLocation(13, 22), + // (14,25): error CS8121: An expression of type 'A' cannot be handled by a pattern of type 'B'. + // _ = o is not (A and not B x5); // 6, 7 + Diagnostic(ErrorCode.ERR_PatternWrongType, "B").WithArguments("A", "B").WithLocation(14, 25), + // (14,27): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is not (A and not B x5); // 6, 7 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x5").WithLocation(14, 27)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_TypeAndDeconstruction() + { + var source = """ +object o = null; +_ = o is not S (42 and not 43, 44 and not 45); // 1, 2 +_ = o is S (not 42 or 43, not 44 or 45); // 3, 4 + +public class S +{ + public void Deconstruct(out int x, out int y) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,28): warning CS9269: The pattern is redundant. + // _ = o is not S (42 and not 43, 44 and not 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 28), + // (2,43): warning CS9269: The pattern is redundant. + // _ = o is not S (42 and not 43, 44 and not 45); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 43), + // (3,23): warning CS9269: The pattern is redundant. + // _ = o is S (not 42 or 43, not 44 or 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 23), + // (3,37): warning CS9269: The pattern is redundant. + // _ = o is S (not 42 or 43, not 44 or 45); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 37)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_TypeAndProperties() + { + var source = """ +object o = null; +_ = o is not S { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 +_ = o is S { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + +public class S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,36): warning CS9269: The pattern is redundant. + // _ = o is not S { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 36), + // (2,58): warning CS9269: The pattern is redundant. + // _ = o is not S { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 58), + // (3,31): warning CS9269: The pattern is redundant. + // _ = o is S { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 31), + // (3,52): warning CS9269: The pattern is redundant. + // _ = o is S { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 52)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_Properties() + { + var source = """ +S s = null; +_ = s is not { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 +_ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + +public class S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,34): warning CS9269: The pattern is redundant. + // _ = s is not { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 34), + // (2,56): warning CS9269: The pattern is redundant. + // _ = s is not { Prop1: 42 and not 43, Prop2: 44 and not 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 56), + // (3,29): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 29), + // (3,50): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 50)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_Properties_NullableValueTypeInput() + { + var source = """ +S? s = null; +_ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + +public struct S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,29): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 29), + // (2,50): warning CS9269: The pattern is redundant. + // _ = s is { Prop1: not 42 or 43, Prop2: not 44 or 45 }; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 50)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_EmptyVsNull() + { + var source = """ +S s = null; +_ = s is not { } or null; // 1 +_ = s is null or not { }; // 2 +_ = s is not ({ } and not null); // 3 +_ = s is { } or not null; // 4 +_ = s is not null or { }; // 5 +_ = s is not null or S { }; // 6 +_ = s is not S { } or null; // 7 +_ = s is null or not S { }; // 8 + +_ = s switch +{ + null => 0, + not { } => 1, // 9 + _ => 2 +}; + +public class S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,21): warning CS9269: The pattern is redundant. + // _ = s is not { } or null; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(2, 21), + // (3,22): warning CS9269: The pattern is redundant. + // _ = s is null or not { }; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "{ }").WithLocation(3, 22), + // (4,27): warning CS9269: The pattern is redundant. + // _ = s is not ({ } and not null); // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(4, 27), + // (5,21): warning CS9269: The pattern is redundant. + // _ = s is { } or not null; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(5, 21), + // (6,22): warning CS9269: The pattern is redundant. + // _ = s is not null or { }; // 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "{ }").WithLocation(6, 22), + // (7,22): warning CS9269: The pattern is redundant. + // _ = s is not null or S { }; // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "S { }").WithLocation(7, 22), + // (8,23): warning CS9269: The pattern is redundant. + // _ = s is not S { } or null; // 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "null").WithLocation(8, 23), + // (14,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not { } => 1, // 9 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not { }").WithLocation(14, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_AlwaysTrue() + { + var source = """ +object o = null; +_ = o is not (string and var x0); +_ = o is not S { Prop1: _, Prop2: var x1 }; +_ = o is not S (42 and not 43, int x2); // 1 +_ = o is not S { Prop1: 42 and not 43, Prop2: _ and var x3 }; // 2, 3 + +S s = default; +_ = s is not (_, int); +_ = s is not (int, int); +_ = s is not { Prop1: _ }; // 4 + +_ = s is not (_, _); // 5 +_ = s is not (var x4, var x5); // 6 + +public struct S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } + public void Deconstruct(out object x, out object y) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,28): warning CS9269: The pattern is redundant. + // _ = o is not S (42 and not 43, int x2); // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(4, 28), + // (5,36): warning CS9269: The pattern is redundant. + // _ = o is not S { Prop1: 42 and not 43, Prop2: _ and var x3 }; // 2, 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 36), + // (10,5): error CS8518: An expression of type 'S' can never match the provided pattern. + // _ = s is not { Prop1: _ }; // 4 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "s is not { Prop1: _ }").WithArguments("S").WithLocation(10, 5), + // (12,5): error CS8518: An expression of type 'S' can never match the provided pattern. + // _ = s is not (_, _); // 5 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "s is not (_, _)").WithArguments("S").WithLocation(12, 5), + // (13,5): error CS8518: An expression of type 'S' can never match the provided pattern. + // _ = s is not (var x4, var x5); // 6 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "s is not (var x4, var x5)").WithArguments("S").WithLocation(13, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_TwoPositionalPatternsWithAnd() + { + var source = """ +object o = null; +_ = o is not (S and (42 and not 43, 44 and not 45)); // 1, 2 +_ = o is not (S and (42 and not 43, _) and (_, 44 and not 45)); // 3, 4 + +public class S +{ + public void Deconstruct(out int x, out int y) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,33): warning CS9269: The pattern is redundant. + // _ = o is not (S and (42 and not 43, 44 and not 45)); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 33), + // (2,48): warning CS9269: The pattern is redundant. + // _ = o is not (S and (42 and not 43, 44 and not 45)); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 48), + // (3,33): warning CS9269: The pattern is redundant. + // _ = o is not (S and (42 and not 43, _) and (_, 44 and not 45)); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 33), + // (3,59): warning CS9269: The pattern is redundant. + // _ = o is not (S and (42 and not 43, _) and (_, 44 and not 45)); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 59)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_TwoPropertyPatternsWithAnd() + { + var source = """ +object o = null; +_ = o is not (S and { Prop1: 42 and not 43, Prop2: 44 and not 45 }); // 1, 2 +_ = o is not (S and { Prop1: 42 and not 43 } and { Prop2: 44 and not 45 }); // 3, 4 +_ = o is not (S and { } and { Prop2: 44 and not 45 }); // 5, 6 +_ = o is not (S and { Prop1: 42 and not 43 } and { }); // 7, 8 + +public class S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,41): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43, Prop2: 44 and not 45 }); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 41), + // (2,63): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43, Prop2: 44 and not 45 }); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 63), + // (3,41): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43 } and { Prop2: 44 and not 45 }); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 41), + // (3,70): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43 } and { Prop2: 44 and not 45 }); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 70), + // (4,21): warning CS9269: The pattern is redundant. + // _ = o is not (S and { } and { Prop2: 44 and not 45 }); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "{ }").WithLocation(4, 21), + // (4,49): warning CS9269: The pattern is redundant. + // _ = o is not (S and { } and { Prop2: 44 and not 45 }); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(4, 49), + // (5,41): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43 } and { }); // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 41), + // (5,50): warning CS9269: The pattern is redundant. + // _ = o is not (S and { Prop1: 42 and not 43 } and { }); // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "{ }").WithLocation(5, 50)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_RecursivePatterns_DeconstructOnDerived() + { + var source = """ +object o = null; +_ = o is C and (int, int); // 1, 2 +_ = o is C and (int x, int y); + +C c = null; +_ = c is C and (int, int); // 3, 4 +_ = c is C and (int x2, int y2); + +_ = c is (int, int); // 5, 6 +_ = c is (int x3, int y3); + +_ = c is not object or (< 42, <= 43); +_ = c is not object or (int x4, int y4); // 7, 8, 9 +_ = c is not object or (int, int); // 10 + +public class C +{ + public void Deconstruct(out int x, out int y) => throw null; +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,17): warning CS9269: The pattern is redundant. + // _ = o is C and (int, int); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(2, 17), + // (2,22): warning CS9269: The pattern is redundant. + // _ = o is C and (int, int); // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(2, 22), + // (6,17): warning CS9269: The pattern is redundant. + // _ = c is C and (int, int); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(6, 17), + // (6,22): warning CS9269: The pattern is redundant. + // _ = c is C and (int, int); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(6, 22), + // (9,11): warning CS9269: The pattern is redundant. + // _ = c is (int, int); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(9, 11), + // (9,16): warning CS9269: The pattern is redundant. + // _ = c is (int, int); // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(9, 16), + // (13,5): warning CS8794: An expression of type 'C' always matches the provided pattern. + // _ = c is not object or (int x4, int y4); // 7, 8, 9 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "c is not object or (int x4, int y4)").WithArguments("C").WithLocation(13, 5), + // (13,29): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = c is not object or (int x4, int y4); // 7, 8, 9 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x4").WithLocation(13, 29), + // (13,37): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = c is not object or (int x4, int y4); // 7, 8, 9 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y4").WithLocation(13, 37), + // (14,5): warning CS8794: An expression of type 'C' always matches the provided pattern. + // _ = c is not object or (int, int); // 10 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "c is not object or (int, int)").WithArguments("C").WithLocation(14, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPatterns() + { + var source = """ +int[] o = null; +_ = o is not [42 and not 43, 44 and not 45]; // 1, 2 +_ = o is [not 42 or 43, not 44 or 45]; // 3, 4 +_ = o is not [_, _]; +_ = o is not [.._]; // 5 +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (2,41): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 41), + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (3,35): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 35)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPatterns_WithNestedPropertyPattern() + { + var source = """ +C[] o = null; +_ = o is not [{ Prop: 42 and not 43 }, { Prop: 44 and not 45 }]; // 1, 2 +_ = o is [{ Prop: not 42 or 43 }, { Prop: not 44 or 45 }]; // 3, 4 TODO2 + +public class C +{ + public object Prop { get; set; } +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,34): warning CS9269: The pattern is redundant. + // _ = o is not [{ Prop: 42 and not 43 }, { Prop: 44 and not 45 }]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 34), + // (2,59): warning CS9269: The pattern is redundant. + // _ = o is not [{ Prop: 42 and not 43 }, { Prop: 44 and not 45 }]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 59), + // (3,29): warning CS9269: The pattern is redundant. + // _ = o is [{ Prop: not 42 or 43 }, { Prop: not 44 or 45 }]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 29)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPattern_WithInputTypeChanging() + { + var source = """ +int[] i = null; +_ = i is not object or [not 42 or 43, not 44 or 45]; // 1, 2 + +object o = null; +_ = o is int[] and [not 42 or 43, not 44 or 45]; // 3, 4 +_ = o is C and [not 42 or 43, not 44 or 45]; // 5, 6 TODO2 + +C c = null; +_ = c is not object or [not 42 or 43, not 44 or 45]; // 7, 8 TODO2 + +S? s = null; +_ = s is not object or [not 42 or 43, not 44 or 45]; // 9, 10 TODO2 + +class C +{ + public object this[int index] => throw null; + public int Length => throw null; +} + +struct S +{ + public object this[int index] => throw null; + public int Length => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,35): warning CS9269: The pattern is redundant. + // _ = i is not object or [not 42 or 43, not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 35), + // (2,49): warning CS9269: The pattern is redundant. + // _ = i is not object or [not 42 or 43, not 44 or 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 49), + // (5,31): warning CS9269: The pattern is redundant. + // _ = o is int[] and [not 42 or 43, not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 31), + // (5,45): warning CS9269: The pattern is redundant. + // _ = o is int[] and [not 42 or 43, not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 45), + // (6,27): warning CS9269: The pattern is redundant. + // _ = o is C and [not 42 or 43, not 44 or 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 27), + // (9,35): warning CS9269: The pattern is redundant. + // _ = c is not object or [not 42 or 43, not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(9, 35), + // (12,35): warning CS9269: The pattern is redundant. + // _ = s is not object or [not 42 or 43, not 44 or 45]; // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 35)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPatterns_TwoWithAnds() + { + var source = """ +int[] o = null; +_ = o is not [42 and not 43, 44 and not 45]; // 1, 2 +_ = o is not ([42 and not 43, _] and [_, 44 and not 45]); // 3, 4 +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (2,41): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 41), + // (3,27): warning CS9269: The pattern is redundant. + // _ = o is not ([42 and not 43, _] and [_, 44 and not 45]); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 27), + // (3,53): warning CS9269: The pattern is redundant. + // _ = o is not ([42 and not 43, _] and [_, 44 and not 45]); // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 53)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePatterns_Empty() + { + var source = """ +S o = default; +_ = o is not [42 and not 43, 44 and not 45, ..]; // 1, 2 +_ = o is [not 42 or 43, not 44 or 45, ..]; // 3, 4 + +_ = o is not [42 and not 43, .., 44 and not 45]; // 5, 6 +_ = o is [not 42 or 43, .., not 44 or 45]; // 7, 8 + +_ = o is not [42 and not 43, .., 44 and not 45, ..]; // 9 + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45, ..]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (2,41): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, 44 and not 45, ..]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 41), + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, not 44 or 45, ..]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (3,35): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, not 44 or 45, ..]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 35), + // (5,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, .., 44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 26), + // (5,45): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, .., 44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 45), + // (6,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, .., not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 21), + // (6,39): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, .., not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 39), + // (8,49): error CS8980: Slice patterns may only be used once and directly inside a list pattern. + // _ = o is not [42 and not 43, .., 44 and not 45, ..]; // 9 + Diagnostic(ErrorCode.ERR_MisplacedSlicePattern, "..").WithLocation(8, 49)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePatterns_AlwaysTrue() + { + var source = """ +S o = default; +_ = o is not [42 and not 43, .._]; // 1, 2 +_ = o is [not 42 or 43, .._]; // 3, 4 +_ = o is not [42 and not 43, ..(_ or _)]; // 5, 6, 7 +_ = o is not [42 and not 43, ..(_ and _)]; // 8, 9, 10 + +_ = o is not [42 and not 43, ..var x]; // 11 +_ = o is [not 42 or 43, ..var y]; // 12 + +_ = o is not [..var x2, 42 and not 43]; // 13 +_ = o is [..var y2, not 42 or 43]; // 14 + +_ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 TODO2 unexpected diagnostic on OR + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, .._]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (2,32): warning CS9269: The pattern is redundant. + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, .._]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (4,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..(_ or _)]; // 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(4, 26), + // (4,33): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..(_ or _)]; // 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "_ or _").WithLocation(4, 33), + // (4,38): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..(_ or _)]; // 5, 6, 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "_").WithLocation(4, 38), + // (5,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..(_ and _)]; // 8, 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 26), + // (7,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..var x]; // 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(7, 26), + // (8,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, ..var y]; // 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 21), + // (10,36): warning CS9269: The pattern is redundant. + // _ = o is not [..var x2, 42 and not 43]; // 13 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(10, 36), + // (11,31): warning CS9269: The pattern is redundant. + // _ = o is [..var y2, not 42 or 43]; // 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 31), + // (13,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(13, 26), + // (13,32): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 + Diagnostic(ErrorCode.WRN_RedundantPattern, "var z or var t").WithLocation(13, 32), + // (13,36): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "z").WithLocation(13, 36), + // (13,41): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 TODO2 unexpected diagnostic on OR + Diagnostic(ErrorCode.WRN_RedundantPattern, "var t").WithLocation(13, 41), + // (13,45): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = o is not [42 and not 43, ..var z or var t]; // 15, 16, 17, 18 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "t").WithLocation(13, 45)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPatterns_Nested() + { + var source = """ +S s = default; +_ = s is not [..[42]]; +_ = s is not [..[42 and not 43]]; // 1 +_ = s is [..[not 42 or 43]]; // 2 +_ = s is not [..[42 and not 43]]; // 3 +_ = s is not [..[42 and not 43]]; // 4 + +_ = s is not [..[42 and not 43, ..var x]]; // 5 +_ = s is [..[not 42 or 43]]; // 6 + +_ = s is not [..[42 and not 43]]; // 7 +_ = s is [.. [not 42 or 43]]; // 8 + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public S this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,29): warning CS9269: The pattern is redundant. + // _ = s is not [..[42 and not 43]]; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 29), + // (4,24): warning CS9269: The pattern is redundant. + // _ = s is [..[not 42 or 43]]; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(4, 24), + // (5,29): warning CS9269: The pattern is redundant. + // _ = s is not [..[42 and not 43]]; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 29), + // (6,29): warning CS9269: The pattern is redundant. + // _ = s is not [..[42 and not 43]]; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 29), + // (8,29): warning CS9269: The pattern is redundant. + // _ = s is not [..[42 and not 43, ..var x]]; // 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 29), + // (9,24): warning CS9269: The pattern is redundant. + // _ = s is [..[not 42 or 43]]; // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(9, 24), + // (11,29): warning CS9269: The pattern is redundant. + // _ = s is not [..[42 and not 43]]; // 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 29), + // (12,25): warning CS9269: The pattern is redundant. + // _ = s is [.. [not 42 or 43]]; // 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 25)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_ListPatterns_AlwaysTrue() + { + var source = """ +S o = default; +_ = o is not [42 and not 43, _]; // 1 +_ = o is [not 42 or 43, _]; // 2 + +_ = o is not [42 and not 43, var x]; // 3 +_ = o is [not 42 or 43, var y]; // 4 + +_ = o is not [var x2, 42 and not 43]; // 5 +_ = o is [var y2, not 42 or 43]; // 6 + +_ = o is not [42 and not 43, _ and var x3]; // 7, 8 +_ = o is [not 42 or 43, _ and var y3]; // 9, 10 + +_ = o is not [42 and not 43, var x4 and _]; // 11, 12 +_ = o is [not 42 or 43, var y4 and _]; // 13, 14 + +_ = o is [not 42 or 43, _ and int y5]; // 15, 16 +_ = o is [not 42 or 43, var y6]; // 17 +_ = o is [not 42 or 43, int y7]; // 18 + +_ = o is not [42 and not 43, int and var y8]; // 19, 20 + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, _]; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _]; // 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (5,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x]; // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 26), + // (6,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var y]; // 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 21), + // (8,34): warning CS9269: The pattern is redundant. + // _ = o is not [var x2, 42 and not 43]; // 5 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 34), + // (9,29): warning CS9269: The pattern is redundant. + // _ = o is [var y2, not 42 or 43]; // 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(9, 29), + // (11,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, _ and var x3]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 26), + // (12,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _ and var y3]; // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 21), + // (14,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x4 and _]; // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(14, 26), + // (15,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var y4 and _]; // 13, 14 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(15, 21), + // (17,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _ and int y5]; // 15, 16 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(17, 21), + // (18,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var y6]; // 17 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(18, 21), + // (19,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, int y7]; // 18 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(19, 21), + // (21,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, int and var y8]; // 19, 20 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(21, 26), + // (21,30): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, int and var y8]; // 19, 20 + Diagnostic(ErrorCode.WRN_RedundantPattern, "int").WithLocation(21, 30)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePatterns_WithRedundancy() + { + var source = """ +S o = default; +_ = o is not [42 and not 43, _, ..44 and not 45]; // 1, 2 +_ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + +_ = o is not [42 and not 43, var x, ..44 and not 45]; // 5, 6 +_ = o is [not 42 or 43, var y, ..not 44 or 45]; // 7, 8 + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, _, ..44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 26), + // (2,46): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, _, ..44 and not 45]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 46), + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (3,40): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 40), + // (5,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x, ..44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 26), + // (5,50): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x, ..44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 50), + // (6,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var y, ..not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 21), + // (6,44): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var y, ..not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 44)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_SlicePattern_WithInputTypeChanging() + { + var source = """ +object o = null; +_ = o is C and [not 42 or 43, ..(not 44 or 45)]; // 1, 2 +_ = o is C and [..(42 and not 43), not 44 or 45]; // 3, 4 + +C c = null; +_ = c is not object or [not 42 or 43, ..(not 44 or 45)]; // 5, 6 +_ = c is not object or [..(42 and not 43), not 44 or 45]; // 7, 8 + +S? s = null; +_ = s is not object or [not 42 or 43, ..(44 and not 45)]; // 9, 10 +_ = s is not object or [..(not 42 or 43), 44 and not 45]; // 11, 12 + +class C +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} + +struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,27): warning CS9269: The pattern is redundant. + // _ = o is C and [not 42 or 43, ..(not 44 or 45)]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(2, 27), + // (2,44): warning CS9269: The pattern is redundant. + // _ = o is C and [not 42 or 43, ..(not 44 or 45)]; // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(2, 44), + // (3,31): warning CS9269: The pattern is redundant. + // _ = o is C and [..(42 and not 43), not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 31), + // (3,46): warning CS9269: The pattern is redundant. + // _ = o is C and [..(42 and not 43), not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 46), + // (6,35): warning CS9269: The pattern is redundant. + // _ = c is not object or [not 42 or 43, ..(not 44 or 45)]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 35), + // (6,52): warning CS9269: The pattern is redundant. + // _ = c is not object or [not 42 or 43, ..(not 44 or 45)]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 52), + // (7,39): warning CS9269: The pattern is redundant. + // _ = c is not object or [..(42 and not 43), not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(7, 39), + // (7,54): warning CS9269: The pattern is redundant. + // _ = c is not object or [..(42 and not 43), not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(7, 54), + // (10,35): warning CS9269: The pattern is redundant. + // _ = s is not object or [not 42 or 43, ..(44 and not 45)]; // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(10, 35), + // (10,53): warning CS9269: The pattern is redundant. + // _ = s is not object or [not 42 or 43, ..(44 and not 45)]; // 9, 10 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(10, 53), + // (11,38): warning CS9269: The pattern is redundant. + // _ = s is not object or [..(not 42 or 43), 44 and not 45]; // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 38), + // (11,54): warning CS9269: The pattern is redundant. + // _ = s is not object or [..(not 42 or 43), 44 and not 45]; // 11, 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(11, 54)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_DeclarationPattern() + { + var source = """ +S o = default; +_ = o is not (42 and not 43 and var x1); // 1, 2 +_ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + +_ = o is not [42 and not 43, var x2, ..44 and not 45]; // 5, 6 +_ = o is [not 42 or 43, var x3, ..not 44 or 45]; // 7, 8 + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (2,15): error CS0029: Cannot implicitly convert type 'int' to 'S' + // _ = o is not (42 and not 43 and var x1); // 1, 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "42").WithArguments("int", "S").WithLocation(2, 15), + // (2,26): error CS0029: Cannot implicitly convert type 'int' to 'S' + // _ = o is not (42 and not 43 and var x1); // 1, 2 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "43").WithArguments("int", "S").WithLocation(2, 26), + // (3,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(3, 21), + // (3,40): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, _, ..not 44 or 45]; // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(3, 40), + // (5,26): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x2, ..44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 26), + // (5,51): warning CS9269: The pattern is redundant. + // _ = o is not [42 and not 43, var x2, ..44 and not 45]; // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 51), + // (6,21): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var x3, ..not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 21), + // (6,45): warning CS9269: The pattern is redundant. + // _ = o is [not 42 or 43, var x3, ..not 44 or 45]; // 7, 8 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 45)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_TODO2_15() + { + var source = """ +public class C +{ + public void M(S o) + { + _ = o switch + { + not { Length: 2 } => 1, + [not 42, _] => 2, + [_, not _] => 3, + }; + } +} + +public struct S +{ + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (5,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '[42, _]' is not covered. + // _ = o switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[42, _]").WithLocation(5, 15), + // (9,13): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // [_, not _] => 3, + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "[_, not _]").WithLocation(9, 13)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotOne() + { + var source = """ +int i = 0; +_ = i is 1 or 2 or 1; // 1 +_ = i is 1 or 2 or not 1; // 2 + +_ = i is not (1 or 2 or 1); // 3 +_ = i is not (1 or 2 or not 1); // 4 + +_ = i switch // 5 +{ + 1 => 0, + 2 => 0, + 1 => 0, // 6 +}; + +_ = i switch +{ + 1 => 0, + 2 => 0, + 1 => 0, // 7 + _ => 0, +}; + +_ = i switch +{ + 1 => 0, + 2 => 0, + not 1 => 0 +}; + +_ = i switch +{ + 1 => 0, + 2 => 0, + not 1 => 0, + _ => 0, // 8 +}; + +switch (i) +{ + case 1: break; + case 2: break; + case 1: break; // 9 +} + +switch (i) +{ + case 1: break; + case 2: break; + case 1: break; // 10 + default: break; +} + +switch (i) +{ + case 1: break; + case 2: break; + case not 1: break; +} + +switch (i) +{ + case 1: break; + case 2: break; + case not 1: break; + default: break; // 11 +} +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (2,20): warning CS9269: The pattern is redundant. + // _ = i is 1 or 2 or 1; // 1 + Diagnostic(ErrorCode.WRN_RedundantPattern, "1").WithLocation(2, 20), + // (3,5): warning CS8794: An expression of type 'int' always matches the provided pattern. + // _ = i is 1 or 2 or not 1; // 2 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "i is 1 or 2 or not 1").WithArguments("int").WithLocation(3, 5), + // (5,25): warning CS9269: The pattern is redundant. + // _ = i is not (1 or 2 or 1); // 3 + Diagnostic(ErrorCode.WRN_RedundantPattern, "1").WithLocation(5, 25), + // (6,5): error CS8518: An expression of type 'int' can never match the provided pattern. + // _ = i is not (1 or 2 or not 1); // 4 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "i is not (1 or 2 or not 1)").WithArguments("int").WithLocation(6, 5), + // (8,7): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered. + // _ = i switch // 5 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(8, 7), + // (12,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 1 => 0, // 6 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "1").WithLocation(12, 5), + // (19,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // 1 => 0, // 7 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "1").WithLocation(19, 5), + // (35,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // _ => 0, // 8 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(35, 5), + // (42,5): error CS0152: The switch statement contains multiple cases with the label value '1' + // case 1: break; // 9 + Diagnostic(ErrorCode.ERR_DuplicateCaseLabel, "case 1:").WithArguments("1").WithLocation(42, 5), + // (49,5): error CS0152: The switch statement contains multiple cases with the label value '1' + // case 1: break; // 10 + Diagnostic(ErrorCode.ERR_DuplicateCaseLabel, "case 1:").WithArguments("1").WithLocation(49, 5), + // (65,14): warning CS0162: Unreachable code detected + // default: break; // 11 + Diagnostic(ErrorCode.WRN_UnreachableCode, "break").WithLocation(65, 14)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_NotIntInt() + { + var source = """ +(object, object) o = default; + +_ = o switch +{ + _ => 0, + (_, _) => 0, // 1 +}; + +_ = o switch +{ + _ => 0, + (int, int) => 0, // 2 +}; + +_ = o switch +{ + _ => 0, + not (int, int) => 0, // 3 +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // (_, _) => 0, // 1 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "(_, _)").WithLocation(6, 5), + // (12,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // (int, int) => 0, // 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "(int, int)").WithLocation(12, 5), + // (18,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // not (int, int) => 0, // 3 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "not (int, int)").WithLocation(18, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_DeconstructionOrDeconstruction() + { + var source = """ +(object, object) o = default; +_ = o is (string, 42) or (string, 43); +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + public void RedundantPattern_BroadeningTests() + { + var source = """ +(string, string) o = default; +_ = o is (object, object); +_ = o is (object x, object y); +_ = o is (object and var z, var t and object); +"""; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + [InlineData("struct")] + [InlineData("class")] + public void RedundantPattern_PullingBinaryPatternsFromCompositePatterns(string typeKind) + { + var source = $$""" +class C +{ + void M(S s, System.Runtime.CompilerServices.ITuple t, int o) + { + if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + if (s is { Prop1: (not 42 or 43) and var x1 }) { } // 7 + + if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + if (s is (not 42 or 43, _) and var (x2, x3)) { } // 12 + if (s is (not 42 or 43, _) and (_, _)) { } // 13 + if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + + if (s is [not 42 or 43, not 44 or 45]) { } // 16, 17 + if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + if (s is [not 42 or 43, not 44 or 45] and [var x6, var x7]) { } // 20, 21 + + if (t is (not 42 or 43, not 44 or 45)) { } // 22, 23 + if (t is (42 and not 43, 44 and not 45)) { } // 24, 25 + if (t is (not 42 or 43, not 44 or 45) and (var x8, var x9)) { } // 26, 27 + + if (o is (not 42 or 43) and var x10) { } // 28 + } +} + +public {{typeKind}} S +{ + public int Prop1 { get; set; } + public int Prop2 { get; set; } + public void Deconstruct(out int x, out int y) => throw null; + + public int Length => throw null; + public int this[System.Index i] => throw null; + public int this[System.Range r] => throw null; +} +"""; + var compilation = CreateCompilation(source, targetFramework: TargetFramework.Net80); + compilation.VerifyDiagnostics( + // (5,38): warning CS9269: The pattern is redundant. + // if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 38), + // (5,66): warning CS9269: The pattern is redundant. + // if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 66), + // (6,37): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 37), + // (6,65): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 65), + // (7,37): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(7, 37), + // (7,64): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(7, 64), + // (8,38): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (not 42 or 43) and var x1 }) { } // 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 38), + // (10,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(10, 29), + // (10,43): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(10, 43), + // (11,30): warning CS9269: The pattern is redundant. + // if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 30), + // (11,45): warning CS9269: The pattern is redundant. + // if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(11, 45), + // (12,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, _) and var (x2, x3)) { } // 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 29), + // (13,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, _) and (_, _)) { } // 13 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(13, 29), + // (14,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(14, 29), + // (14,43): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(14, 43), + // (16,29): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45]) { } // 16, 17 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(16, 29), + // (16,43): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45]) { } // 16, 17 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(16, 43), + // (17,30): warning CS9269: The pattern is redundant. + // if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(17, 30), + // (17,45): warning CS9269: The pattern is redundant. + // if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(17, 45), + // (18,29): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45] and [var x6, var x7]) { } // 20, 21 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(18, 29), + // (18,43): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45] and [var x6, var x7]) { } // 20, 21 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(18, 43), + // (20,29): warning CS9269: The pattern is redundant. + // if (t is (not 42 or 43, not 44 or 45)) { } // 22, 23 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(20, 29), + // (20,43): warning CS9269: The pattern is redundant. + // if (t is (not 42 or 43, not 44 or 45)) { } // 22, 23 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(20, 43), + // (21,30): warning CS9269: The pattern is redundant. + // if (t is (42 and not 43, 44 and not 45)) { } // 24, 25 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(21, 30), + // (21,45): warning CS9269: The pattern is redundant. + // if (t is (42 and not 43, 44 and not 45)) { } // 24, 25 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(21, 45), + // (22,29): warning CS9269: The pattern is redundant. + // if (t is (not 42 or 43, not 44 or 45) and (var x8, var x9)) { } // 26, 27 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(22, 29), + // (22,43): warning CS9269: The pattern is redundant. + // if (t is (not 42 or 43, not 44 or 45) and (var x8, var x9)) { } // 26, 27 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(22, 43), + // (24,29): warning CS9269: The pattern is redundant. + // if (o is (not 42 or 43) and var x10) { } // 28 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(24, 29)); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/75506")] + [InlineData("struct")] + [InlineData("class")] + public void RedundantPattern_PullingBinaryPatternsFromCompositePatterns_WithObject(string typeKind) + { + var source = $$""" +class C +{ + void M(S s, object o) + { + if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + if (s is { Prop1: (not 42 or 43) and var x1 }) { } // 7 + + if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + if (s is (not 42 or 43, _) and var (x2, x3)) { } // 12 + if (s is (not 42 or 43, _) and (_, _)) { } // 13 + if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + + if (s is [not 42 or 43, not 44 or 45]) { } // 16, 17 TODO2 + if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + if (s is [not 42 or 43, not 44 or 45] and [var x6, var x7]) { } // 20, 21 TODO2 + + if (o is (not 42 or 43) and var x10) { } // 22 + } +} + +public {{typeKind}} S +{ + public object Prop1 { get; set; } + public object Prop2 { get; set; } + public void Deconstruct(out object x, out object y) => throw null; + + public int Length => throw null; + public object this[System.Index i] => throw null; + public object this[System.Range r] => throw null; +} +"""; + var compilation = CreateCompilation(source, targetFramework: TargetFramework.Net80); + compilation.VerifyDiagnostics( + // (5,38): warning CS9269: The pattern is redundant. + // if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(5, 38), + // (5,66): warning CS9269: The pattern is redundant. + // if (s is { Prop1: 42 and not 43 } or { Prop2: 44 and not 45 }) { } // 1, 2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(5, 66), + // (6,37): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(6, 37), + // (6,65): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } and { Prop2: not 44 or 45 }) { } // 3, 4 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(6, 65), + // (7,37): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(7, 37), + // (7,64): warning CS9269: The pattern is redundant. + // if (s is { Prop1: not 42 or 43 } or { Prop2: not 44 or 45 }) { } // 5, 6 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(7, 64), + // (8,38): warning CS9269: The pattern is redundant. + // if (s is { Prop1: (not 42 or 43) and var x1 }) { } // 7 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(8, 38), + // (10,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(10, 29), + // (10,43): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45)) { } // 8, 9 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(10, 43), + // (11,30): warning CS9269: The pattern is redundant. + // if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(11, 30), + // (11,45): warning CS9269: The pattern is redundant. + // if (s is (42 and not 43, 44 and not 45)) { } // 10, 11 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(11, 45), + // (12,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, _) and var (x2, x3)) { } // 12 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(12, 29), + // (13,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, _) and (_, _)) { } // 13 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(13, 29), + // (14,29): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(14, 29), + // (14,43): warning CS9269: The pattern is redundant. + // if (s is (not 42 or 43, not 44 or 45) and (var x4, var x5)) { } // 14, 15 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(14, 43), + // (16,29): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45]) { } // 16, 17 TODO2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(16, 29), + // (17,30): warning CS9269: The pattern is redundant. + // if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(17, 30), + // (17,45): warning CS9269: The pattern is redundant. + // if (s is [42 and not 43, 44 and not 45]) { } // 18, 19 + Diagnostic(ErrorCode.WRN_RedundantPattern, "45").WithLocation(17, 45), + // (18,29): warning CS9269: The pattern is redundant. + // if (s is [not 42 or 43, not 44 or 45] and [var x6, var x7]) { } // 20, 21 TODO2 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(18, 29), + // (20,29): warning CS9269: The pattern is redundant. + // if (o is (not 42 or 43) and var x10) { } // 22 + Diagnostic(ErrorCode.WRN_RedundantPattern, "43").WithLocation(20, 29)); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index 9f1eac05877f9..e364aefa97867 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -325,18 +325,6 @@ public void WarningLevel_2() case ErrorCode.WRN_ExperimentalWithMessage: case ErrorCode.WRN_ConvertingLock: case ErrorCode.WRN_PartialPropertySignatureDifference: - Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); - break; - case ErrorCode.WRN_MainIgnored: - case ErrorCode.WRN_UnqualifiedNestedTypeInCref: - case ErrorCode.WRN_NoRuntimeMetadataVersion: - Assert.Equal(2, ErrorFacts.GetWarningLevel(errorCode)); - break; - case ErrorCode.WRN_PdbLocalNameTooLong: - case ErrorCode.WRN_UnreferencedLocalFunction: - case ErrorCode.WRN_RecordEqualsWithoutGetHashCode: - Assert.Equal(3, ErrorFacts.GetWarningLevel(errorCode)); - break; case ErrorCode.WRN_ConvertingNullableToNonNullable: case ErrorCode.WRN_NullReferenceAssignment: case ErrorCode.WRN_NullReferenceReceiver: @@ -436,8 +424,19 @@ public void WarningLevel_2() case ErrorCode.WRN_FieldIsAmbiguous: case ErrorCode.WRN_UninitializedNonNullableBackingField: case ErrorCode.WRN_AccessorDoesNotUseBackingField: + case ErrorCode.WRN_RedundantPattern: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; + case ErrorCode.WRN_MainIgnored: + case ErrorCode.WRN_UnqualifiedNestedTypeInCref: + case ErrorCode.WRN_NoRuntimeMetadataVersion: + Assert.Equal(2, ErrorFacts.GetWarningLevel(errorCode)); + break; + case ErrorCode.WRN_PdbLocalNameTooLong: + case ErrorCode.WRN_UnreferencedLocalFunction: + case ErrorCode.WRN_RecordEqualsWithoutGetHashCode: + Assert.Equal(3, ErrorFacts.GetWarningLevel(errorCode)); + break; case ErrorCode.WRN_InvalidVersionFormat: Assert.Equal(4, ErrorFacts.GetWarningLevel(errorCode)); break; diff --git a/src/Compilers/Core/Portable/TreeDumper.cs b/src/Compilers/Core/Portable/TreeDumper.cs index fc3cf6fbabde2..b7a79aae30041 100644 --- a/src/Compilers/Core/Portable/TreeDumper.cs +++ b/src/Compilers/Core/Portable/TreeDumper.cs @@ -127,6 +127,12 @@ static bool skip(TreeDumperNode node) return true; } + if (node.Text is "aliasOpt" or "boundContainingTypeOpt" or "boundDimensionsOpt" or "deconstructMethod" + && node.Value is null) + { + return true; + } + return false; } } diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs index 521b3e2940959..9731d3609ea1d 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineTypeHintsService.cs @@ -151,6 +151,6 @@ private static TextSpan GetSpan( private static bool IsValidType([NotNullWhen(true)] ITypeSymbol? type) { - return type is not null or IErrorTypeSymbol && type.Name != "var"; + return type is not (null or IErrorTypeSymbol) && type.Name != "var"; } } diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllState.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllState.cs index 3239e0cee06e1..204692f7e80a4 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllState.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllState.cs @@ -37,7 +37,7 @@ internal FixAllState( : base(fixAllProvider, document, project, codeFixProvider, scope, codeActionEquivalenceKey) { // We need the trigger diagnostic span for span based fix all scopes, i.e. FixAllScope.ContainingMember and FixAllScope.ContainingType - Debug.Assert(diagnosticSpan.HasValue || scope is not FixAllScope.ContainingMember or FixAllScope.ContainingType); + Debug.Assert(diagnosticSpan.HasValue || scope is not (FixAllScope.ContainingMember or FixAllScope.ContainingType)); DiagnosticSpan = diagnosticSpan; DiagnosticIds = ImmutableHashSet.CreateRange(diagnosticIds);