diff --git a/src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs b/src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs
new file mode 100644
index 0000000000000..76fc06cf53568
--- /dev/null
+++ b/src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs
@@ -0,0 +1,505 @@
+// 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.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis.CSharp.Symbols;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.CSharp
+{
+ internal static class PatternExplainer
+ {
+ ///
+ /// Find the shortest path from the root node to the node of interest.
+ ///
+ /// The set of nodes in topological order.
+ /// The node of interest.
+ /// Whether to permit following paths that test for null.
+ /// The shortest path, excluding the node of interest.
+ private static ImmutableArray ShortestPathToNode(
+ ImmutableArray nodes,
+ BoundDecisionDagNode node,
+ bool nullPaths)
+ {
+ // compute the distance from each node to the endpoint.
+ var dist = PooledDictionary.GetInstance();
+ int nodeCount = nodes.Length;
+ int distance(BoundDecisionDagNode x)
+ {
+ if (x == null)
+ return nodeCount + 2;
+ if (dist.TryGetValue(x, out var v))
+ return v.distance;
+ Debug.Assert(!nodes.Contains(x));
+ return nodeCount + 2;
+ }
+
+ for (int i = nodeCount - 1; i >= 0; i--)
+ {
+ var n = nodes[i];
+ dist.Add(n, n switch
+ {
+ BoundEvaluationDecisionDagNode e => (distance(e.Next), e.Next),
+ BoundTestDecisionDagNode { Test: BoundDagNonNullTest _ } t when !nullPaths => (1 + distance(t.WhenTrue), t.WhenTrue),
+ BoundTestDecisionDagNode { Test: BoundDagExplicitNullTest _ } t when !nullPaths => (1 + distance(t.WhenFalse), t.WhenFalse),
+ BoundTestDecisionDagNode t when distance(t.WhenTrue) is var trueDist && distance(t.WhenFalse) is var falseDist =>
+ (trueDist <= falseDist) ? (1 + trueDist, t.WhenTrue) : (1 + falseDist, t.WhenFalse),
+ BoundWhenDecisionDagNode w when distance(w.WhenTrue) is var trueDist && distance(w.WhenFalse) is var falseDist =>
+ (trueDist <= falseDist) ? (1 + trueDist, w.WhenTrue) : (1 + falseDist, w.WhenFalse),
+ // treat the endpoint as distance 1.
+ // treat other nodes as not on the path to the endpoint
+ _ => ((n == node) ? 1 : nodeCount + 2, null),
+ });
+ }
+
+ // trace a path from the root node to the node of interest
+ var result = ArrayBuilder.GetInstance(capacity: dist[nodes[0]].distance);
+ for (BoundDecisionDagNode n = nodes[0]; n != node;)
+ {
+ result.Add(n);
+ switch (n)
+ {
+ case BoundEvaluationDecisionDagNode e:
+ n = e.Next;
+ break;
+ case BoundTestDecisionDagNode t:
+ (int d, BoundDecisionDagNode next) = dist[t];
+ Debug.Assert(next != null);
+ Debug.Assert(distance(next) == (d - 1));
+ n = next;
+ break;
+ case BoundWhenDecisionDagNode w:
+ result.RemoveLast();
+ n = w.WhenFalse;
+ break;
+ default:
+ throw ExceptionUtilities.Unreachable;
+ }
+ }
+
+ dist.Free();
+ return result.ToImmutableAndFree();
+ }
+
+ ///
+ /// Return a sample pattern that would lead to the given decision dag node.
+ ///
+ /// A topologically sorted list of nodes in the decision dag.
+ /// A node of interest (typically, the default node for a non-exhaustive switch).
+ /// Permit the use of "null" paths on tests which check for null.
+ ///
+ internal static string SamplePatternForPathToDagNode(
+ BoundDagTemp rootIdentifier,
+ ImmutableArray nodes,
+ BoundDecisionDagNode targetNode,
+ bool nullPaths)
+ {
+ // Compute the path to the node, excluding the node itself.
+ var pathToNode = ShortestPathToNode(nodes, targetNode, nullPaths);
+
+ var constraints = new Dictionary>();
+ var evaluations = new Dictionary>();
+ for (int i = 0, n = pathToNode.Length; i < n; i++)
+ {
+ BoundDecisionDagNode node = pathToNode[i];
+ switch (node)
+ {
+ case BoundTestDecisionDagNode t:
+ {
+ BoundDecisionDagNode nextNode = (i < n - 1) ? pathToNode[i + 1] : targetNode;
+ bool sense = t.WhenTrue == nextNode || (t.WhenFalse != nextNode && t.WhenTrue is BoundWhenDecisionDagNode);
+ BoundDagTest test = t.Test;
+ BoundDagTemp temp = test.Input;
+ if (test is BoundDagTypeTest && sense == false)
+ {
+ // A failed type test is not very useful in constructing a counterexample,
+ // at least not without discriminated unions, so we just drop them.
+ }
+ else
+ {
+ if (!constraints.TryGetValue(temp, out var constraintBuilder))
+ {
+ constraints.Add(temp, constraintBuilder = new ArrayBuilder<(BoundDagTest, bool)>());
+ }
+ constraintBuilder.Add((test, sense));
+ }
+ }
+ break;
+ case BoundEvaluationDecisionDagNode e:
+ {
+ BoundDagTemp temp = e.Evaluation.Input;
+ if (!evaluations.TryGetValue(temp, out var evaluationBuilder))
+ {
+ evaluations.Add(temp, evaluationBuilder = new ArrayBuilder());
+ }
+ evaluationBuilder.Add(e.Evaluation);
+ }
+ break;
+ }
+ }
+
+ return SamplePatternForTemp(rootIdentifier, constraints, evaluations, requireExactType: false);
+ }
+
+ private static string SamplePatternForTemp(
+ BoundDagTemp input,
+ Dictionary> constraintMap,
+ Dictionary> evaluationMap,
+ bool requireExactType)
+ {
+ var constraints = getArray(constraintMap, input);
+ var evaluations = getArray(evaluationMap, input);
+
+ return
+ tryHandleSingleTest() ??
+ tryHandleTypeTestAndTypeEvaluation() ??
+ tryHandleUnboxNullableValueType() ??
+ tryHandleTuplePattern() ??
+ tryHandleNumericLimits() ??
+ tryHandleRecursivePattern() ??
+ produceFallbackPattern();
+
+ static ImmutableArray getArray(Dictionary> map, BoundDagTemp temp)
+ {
+ return map.TryGetValue(temp, out var builder) ? builder.ToImmutable() : ImmutableArray.Empty;
+ }
+
+ // Handle the special case of a single test that is not handled.
+ string tryHandleSingleTest()
+ {
+ if (evaluations.IsEmpty && constraints.Length == 1)
+ {
+ switch (constraints[0])
+ {
+ case (test: BoundDagNonNullTest _, sense: var sense):
+ return !sense ? "null" : requireExactType ? input.Type.ToDisplayString() : "not null";
+ case (test: BoundDagExplicitNullTest _, sense: var sense):
+ return sense ? "null" : requireExactType ? input.Type.ToDisplayString() : "not null";
+ case (test: BoundDagTypeTest { Type: var testedType }, sense: var sense):
+ Debug.Assert(sense); // we have dropped failing type tests
+ return testedType.ToDisplayString();
+ }
+ }
+
+ return null;
+ }
+
+ // Handle the special case of a type test and a type evaluation.
+ string tryHandleTypeTestAndTypeEvaluation()
+ {
+ if (evaluations.Length == 1 && constraints.Length == 1 &&
+ constraints[0] is (BoundDagTypeTest { Type: var constraintType }, true) &&
+ evaluations[0] is BoundDagTypeEvaluation { Type: var evaluationType } te &&
+ constraintType.Equals(evaluationType, TypeCompareKind.AllIgnoreOptions))
+ {
+ var typedTemp = new BoundDagTemp(te.Syntax, te.Type, te);
+ return SamplePatternForTemp(typedTemp, constraintMap, evaluationMap, requireExactType: true);
+ }
+
+ return null;
+ }
+
+ // Handle the special case of a null test and a type evaluation to unbox a nullable value type
+ string tryHandleUnboxNullableValueType()
+ {
+ if (evaluations.Length == 1 && constraints.Length == 1 &&
+ constraints[0] is (BoundDagNonNullTest _, true) &&
+ evaluations[0] is BoundDagTypeEvaluation { Type: var evaluationType } te &&
+ input.Type.IsNullableType() && input.Type.GetNullableUnderlyingType().Equals(evaluationType, TypeCompareKind.AllIgnoreOptions))
+ {
+ var typedTemp = new BoundDagTemp(te.Syntax, te.Type, te);
+ var result = SamplePatternForTemp(typedTemp, constraintMap, evaluationMap, requireExactType: false);
+ // We need a null check. If not included in the result, add it.
+ return (result == "_") ? "not null" : result;
+ }
+
+ return null;
+ }
+
+ // Handle the special case of a tuple pattern
+ string tryHandleTuplePattern()
+ {
+ if (input.Type.IsTupleType &&
+ constraints.IsEmpty &&
+ evaluations.All(e => e is BoundDagFieldEvaluation { Field: var field } && field.IsTupleElement()))
+ {
+ var elements = input.Type.TupleElements;
+ int cardinality = elements.Length;
+ var subpatterns = new ArrayBuilder(cardinality);
+ subpatterns.AddMany("_", cardinality);
+ foreach (BoundDagFieldEvaluation e in evaluations)
+ {
+ var elementTemp = new BoundDagTemp(e.Syntax, e.Field.Type, e);
+ var index = e.Field.TupleElementIndex;
+ if (index < 0 || index >= cardinality)
+ return null;
+ var oldPattern = subpatterns[index];
+ var newPattern = SamplePatternForTemp(elementTemp, constraintMap, evaluationMap, requireExactType: false);
+ subpatterns[index] = makeConjunct(oldPattern, newPattern);
+ }
+
+ return "(" + string.Join(", ", subpatterns) + ")" + (subpatterns.Count == 1 ? " { }" : null);
+ }
+
+ return null;
+
+ static string makeConjunct(string oldPattern, string newPattern) => (oldPattern, newPattern) switch
+ {
+ ("_", var x) => x,
+ (var x, "_") => x,
+ (var x, var y) => x + " and " + y
+ };
+ }
+
+ // Handle the special case of numeric limits
+ string tryHandleNumericLimits()
+ {
+ if (evaluations.IsEmpty &&
+ constraints.All(t => t switch
+ {
+ (BoundDagValueTest _, _) => true,
+ (BoundDagRelationalTest _, _) => true,
+ (BoundDagExplicitNullTest _, false) => true,
+ (BoundDagNonNullTest _, true) => true,
+ _ => false
+ }) &&
+ ValueSetFactory.ForType(input.Type) is { } fac)
+ {
+ // All we have are numeric constraints. Process them to compute a value not covered.
+ var remainingValues = fac.AllValues;
+ foreach (var constraint in constraints)
+ {
+ var (test, sense) = constraint;
+ switch (test)
+ {
+ case BoundDagValueTest v:
+ addRelation(BinaryOperatorKind.Equal, v.Value);
+ break;
+ case BoundDagRelationalTest r:
+ addRelation(r.Relation, r.Value);
+ break;
+ }
+ void addRelation(BinaryOperatorKind relation, ConstantValue value)
+ {
+ var filtered = fac.Related(relation, value);
+ if (!sense)
+ filtered = filtered.Complement();
+ remainingValues = remainingValues.Intersect(filtered);
+ }
+ }
+
+ if (remainingValues.Complement().IsEmpty)
+ return "_";
+
+ return SampleValueString(remainingValues, input.Type, requireExactType: requireExactType);
+ }
+
+ return null;
+ }
+
+ // Handle the special case of a recursive pattern
+ string tryHandleRecursivePattern()
+ {
+ if (constraints.IsEmpty && evaluations.IsEmpty)
+ return null;
+
+ if (!constraints.All(c => c switch
+ {
+ // not-null tests are implicitly incorporated into a recursive pattern
+ (test: BoundDagNonNullTest _, sense: true) => true,
+ (test: BoundDagExplicitNullTest _, sense: false) => true,
+ _ => false,
+ }))
+ {
+ return null;
+ }
+
+ string deconstruction = null;
+ var properties = new Dictionary();
+ bool needsPropertyString = false;
+
+ foreach (var eval in evaluations)
+ {
+ switch (eval)
+ {
+ case BoundDagDeconstructEvaluation e:
+ var method = e.DeconstructMethod;
+ int extensionExtra = method.RequiresInstanceReceiver ? 0 : 1;
+ int count = method.Parameters.Length - extensionExtra;
+ var subpatternBuilder = new StringBuilder("(");
+ for (int j = 0; j < count; j++)
+ {
+ var elementTemp = new BoundDagTemp(e.Syntax, method.Parameters[j + extensionExtra].Type, e, j);
+ var newPattern = SamplePatternForTemp(elementTemp, constraintMap, evaluationMap, requireExactType: false);
+ if (j != 0)
+ subpatternBuilder.Append(", ");
+ subpatternBuilder.Append(newPattern);
+ }
+ subpatternBuilder.Append(")");
+ var result = subpatternBuilder.ToString();
+ if (deconstruction != null && needsPropertyString)
+ {
+ deconstruction = deconstruction + " { }";
+ needsPropertyString = properties.Count != 0;
+ }
+
+ deconstruction = (deconstruction is null) ? result : deconstruction + " and " + result;
+ needsPropertyString |= count == 1;
+ break;
+ case BoundDagFieldEvaluation e:
+ {
+ var subInput = new BoundDagTemp(e.Syntax, e.Field.Type, e);
+ var subPattern = SamplePatternForTemp(subInput, constraintMap, evaluationMap, false);
+ properties.Add(e.Field, subPattern);
+ }
+ break;
+ case BoundDagPropertyEvaluation e:
+ {
+ var subInput = new BoundDagTemp(e.Syntax, e.Property.Type, e);
+ var subPattern = SamplePatternForTemp(subInput, constraintMap, evaluationMap, false);
+ properties.Add(e.Property, subPattern);
+ }
+ break;
+ default:
+ return null;
+ }
+ }
+
+ string typeName = requireExactType ? input.Type.ToDisplayString() : null;
+ needsPropertyString |= deconstruction == null && typeName == null || properties.Count != 0;
+ var propertyString = needsPropertyString ? (deconstruction != null ? " {" : "{") + string.Join(", ", properties.Select(kvp => $" {kvp.Key.Name}: {kvp.Value}")) + " }" : null;
+ Debug.Assert(typeName != null || deconstruction != null || propertyString != null);
+ return typeName + deconstruction + propertyString;
+ }
+
+ // Produce a fallback pattern when we were not able to produce a more specific pattern.
+ string produceFallbackPattern()
+ {
+ return requireExactType ? input.Type.ToDisplayString() : "_";
+ }
+ }
+
+ private static string SampleValueString(IValueSet remainingValues, TypeSymbol type, bool requireExactType)
+ {
+ // We would not have been asked to produce an example of a missing pattern if no values are missing
+ Debug.Assert(!remainingValues.IsEmpty);
+
+ // If the input is an enumeration type, see if any declared enumeration constant values are in the set.
+ // If so, that is what to report.
+ if (type is NamedTypeSymbol { TypeKind: TypeKind.Enum } e)
+ {
+ foreach (var declaredMember in e.GetMembers())
+ {
+ if (declaredMember is FieldSymbol { IsConst: true, IsStatic: true, DeclaredAccessibility: Accessibility.Public } field &&
+ field.GetConstantValue(ConstantFieldsInProgress.Empty, false) is ConstantValue constantValue &&
+ remainingValues.Any(BinaryOperatorKind.Equal, constantValue))
+ {
+ return field.ToDisplayString();
+ }
+ }
+ }
+
+ var sample = remainingValues.Sample;
+ if (sample != null)
+ return ValueString(sample, type, requireExactType);
+
+ // IValueSet.Sample cannot produce a sample of type `nint` or `nuint` outside the range
+ // of values of `int` and `uint`. So if we get here we need to produce a pattern indicating
+ // such an out-of-range value.
+
+ var underlyingType = type.EnumUnderlyingTypeOrSelf();
+ Debug.Assert(underlyingType.IsNativeIntegerType);
+ if (underlyingType.SpecialType == SpecialType.System_IntPtr)
+ {
+ if (remainingValues.Any(BinaryOperatorKind.GreaterThan, ConstantValue.Create(int.MaxValue)))
+ return $"> ({type.ToDisplayString()})int.MaxValue";
+
+ if (remainingValues.Any(BinaryOperatorKind.LessThan, ConstantValue.Create(int.MinValue)))
+ return $"< ({type.ToDisplayString()})int.MinValue";
+ }
+ else if (underlyingType.SpecialType == SpecialType.System_UIntPtr)
+ {
+ if (remainingValues.Any(BinaryOperatorKind.GreaterThan, ConstantValue.Create(uint.MaxValue)))
+ return $"> ({type.ToDisplayString()})uint.MaxValue";
+ }
+
+ throw ExceptionUtilities.Unreachable;
+ }
+
+ private static string ValueString(ConstantValue value, TypeSymbol type, bool requireExactType)
+ {
+ bool requiresCast = (type.IsEnumType() || requireExactType || type.IsNativeIntegerType) &&
+ !(typeHasExactTypeLiteral(type) && !value.IsNull);
+ string valueString = PrimitiveValueString(value, type.EnumUnderlyingTypeOrSelf());
+ return requiresCast ? $"({type.ToDisplayString()}){valueString}" : valueString;
+
+ static bool typeHasExactTypeLiteral(TypeSymbol type) => type.SpecialType switch
+ {
+ SpecialType.System_Int32 => true,
+ SpecialType.System_Int64 => true,
+ SpecialType.System_UInt32 => true,
+ SpecialType.System_UInt64 => true,
+ SpecialType.System_String => true,
+ SpecialType.System_Decimal => true,
+ SpecialType.System_Single => true,
+ SpecialType.System_Double => true,
+ SpecialType.System_Boolean => true,
+ SpecialType.System_Char => true,
+ _ => false,
+ };
+ }
+
+ private static string PrimitiveValueString(ConstantValue value, TypeSymbol type)
+ {
+ if (value.IsNull)
+ return "null";
+
+ switch (type.SpecialType)
+ {
+ case SpecialType.System_Boolean:
+ case SpecialType.System_Byte:
+ case SpecialType.System_SByte:
+ case SpecialType.System_UInt16:
+ case SpecialType.System_Int16:
+ case SpecialType.System_Int32:
+ case SpecialType.System_UInt32:
+ case SpecialType.System_UInt64:
+ case SpecialType.System_Int64:
+ case SpecialType.System_IntPtr when type.IsNativeIntegerType:
+ case SpecialType.System_UIntPtr when type.IsNativeIntegerType:
+ case SpecialType.System_Decimal:
+ case SpecialType.System_Char:
+ case SpecialType.System_String:
+ return ObjectDisplay.FormatPrimitive(value.Value, ObjectDisplayOptions.EscapeNonPrintableCharacters | ObjectDisplayOptions.IncludeTypeSuffix | ObjectDisplayOptions.UseQuotes);
+
+ case SpecialType.System_Single:
+ return value.SingleValue switch
+ {
+ float.NaN => "float.NaN",
+ float.NegativeInfinity => "float.NegativeInfinity",
+ float.PositiveInfinity => "float.PositiveInfinity",
+ var x => ObjectDisplay.FormatPrimitive(x, ObjectDisplayOptions.IncludeTypeSuffix)
+ };
+
+ case SpecialType.System_Double:
+ return value.DoubleValue switch
+ {
+ double.NaN => "double.NaN",
+ double.NegativeInfinity => "double.NegativeInfinity",
+ double.PositiveInfinity => "double.PositiveInfinity",
+ var x => ObjectDisplay.FormatPrimitive(x, ObjectDisplayOptions.IncludeTypeSuffix)
+ };
+
+ default:
+ return "_";
+ }
+ }
+ }
+}
diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs
index 56f3ee9dd7c96..82e5c228e853a 100644
--- a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs
+++ b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs
@@ -93,11 +93,15 @@ private bool CheckSwitchExpressionExhaustive(
// nulls is the job of the nullable walker.
if (!hasErrors)
{
- foreach (var n in TopologicalSort.IterativeSort(new[] { decisionDag.RootNode }, nonNullSuccessors))
+ var nodes = TopologicalSort.IterativeSort(new[] { decisionDag.RootNode }, nonNullSuccessors);
+ foreach (var n in nodes)
{
if (n is BoundLeafDecisionDagNode leaf && leaf.Label == defaultLabel)
{
- diagnostics.Add(ErrorCode.WRN_SwitchExpressionNotExhaustive, node.SwitchKeyword.GetLocation());
+ diagnostics.Add(
+ ErrorCode.WRN_SwitchExpressionNotExhaustive,
+ node.SwitchKeyword.GetLocation(),
+ PatternExplainer.SamplePatternForPathToDagNode(BoundDagTemp.ForOriginalInput(boundInputExpression), nodes, n, nullPaths: false));
return true;
}
}
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index bb0de1a1e7493..6bf06fd5cde2f 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -5750,7 +5750,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
The syntax 'var' for a pattern is not permitted to refer to a type, but '{0}' is in scope here.
- The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ 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.The switch expression does not handle all possible values of its input type (it is not exhaustive).
@@ -5981,7 +5981,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Type '{0}' cannot be embedded because it has a non-abstract member. Consider setting the 'Embed Interop Types' property to false.
- The switch expression does not handle some null inputs (it is not exhaustive).
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.The switch expression does not handle some null inputs.
diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs
index 875ec5ac04452..ac75cd825cde0 100644
--- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs
+++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
@@ -590,7 +591,13 @@ private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferTyp
labelStateMap.TryGetValue(node.DefaultLabel, out var defaultLabelState) && defaultLabelState.believedReachable)
{
SetState(defaultLabelState.state);
- ReportDiagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, ((SwitchExpressionSyntax)node.Syntax).SwitchKeyword.GetLocation());
+ var nodes = node.DecisionDag.TopologicallySortedNodes;
+ var leaf = nodes.Where(n => n is BoundLeafDecisionDagNode leaf && leaf.Label == node.DefaultLabel).First();
+ ReportDiagnostic(
+ ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull,
+ ((SwitchExpressionSyntax)node.Syntax).SwitchKeyword.GetLocation(),
+ PatternExplainer.SamplePatternForPathToDagNode(BoundDagTemp.ForOriginalInput(node.Expression), nodes, leaf, nullPaths: true)
+ );
}
// collect expressions, conversions and result types
diff --git a/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs
index 74c17c283450f..47efd70f8bc20 100644
--- a/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs
@@ -4,6 +4,8 @@
#nullable enable
+using System;
+
namespace Microsoft.CodeAnalysis.CSharp
{
///
@@ -51,6 +53,12 @@ internal interface IValueSet
/// Does this value set contain no values?
///
bool IsEmpty { get; }
+
+ ///
+ /// Produce a sample value contained in the set. Throws if the set is empty. If the set
+ /// contains values but we cannot produce a particular value (e.g. for the set `nint > int.MaxValue`), returns null.
+ ///
+ ConstantValue? Sample { get; }
}
///
diff --git a/src/Compilers/CSharp/Portable/Utilities/IValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/IValueSetFactory.cs
index 42e4928a8a897..22883c0ed413e 100644
--- a/src/Compilers/CSharp/Portable/Utilities/IValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/IValueSetFactory.cs
@@ -33,6 +33,16 @@ internal interface IValueSetFactory
/// Produce a random value for testing.
///
ConstantValue RandomValue(Random random);
+
+ ///
+ /// The set containing all values of the type.
+ ///
+ IValueSet AllValues { get; }
+
+ ///
+ /// The empty set of values.
+ ///
+ IValueSet NoValues { get; }
}
///
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSet.cs
index 170ec193d01a0..2811dd5157c06 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSet.cs
@@ -40,6 +40,9 @@ public static BoolValueSet Create(bool hasFalse, bool hasTrue)
bool IValueSet.IsEmpty => !_hasFalse && !_hasTrue;
+ ConstantValue IValueSet.Sample => ConstantValue.Create(_hasTrue ? true : _hasFalse ? false : throw new ArgumentException());
+
+
public bool Any(BinaryOperatorKind relation, bool value)
{
switch (relation, value)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSetFactory.cs
index 66b20d0a28268..225a56964f890 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.BoolValueSetFactory.cs
@@ -23,6 +23,10 @@ private sealed class BoolValueSetFactory : IValueSetFactory
private BoolValueSetFactory() { }
+ IValueSet IValueSetFactory.AllValues => BoolValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => BoolValueSet.None;
+
public IValueSet Related(BinaryOperatorKind relation, bool value)
{
switch (relation, value)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs
index 355cf93d14761..7e11209dfb8e8 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs
@@ -19,6 +19,8 @@ private struct ByteTC : INumericTC
byte INumericTC.MaxValue => byte.MaxValue;
+ byte INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, byte left, byte right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs
index 71e8833e3955f..f192c8be6bd00 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs
@@ -20,6 +20,8 @@ private struct CharTC : INumericTC
char INumericTC.MaxValue => char.MaxValue;
+ char INumericTC.Zero => (char)0;
+
bool INumericTC.Related(BinaryOperatorKind relation, char left, char right)
{
switch (relation)
@@ -49,11 +51,7 @@ char INumericTC.Next(char value)
string INumericTC.ToString(char c)
{
- var isPrintable = char.IsWhiteSpace(c) ||
- // exclude the Unicode character categories containing non-rendering,
- // unknown, or incomplete characters.
- char.GetUnicodeCategory(c) switch { UnicodeCategory.Control => false, UnicodeCategory.OtherNotAssigned => false, UnicodeCategory.Surrogate => false, _ => true };
- return isPrintable ? $"'{c}'" : $"\\u{(int)c:X4}";
+ return ObjectDisplay.FormatPrimitive(c, ObjectDisplayOptions.EscapeNonPrintableCharacters | ObjectDisplayOptions.UseQuotes);
}
char INumericTC.Prev(char value)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs
index e755e3d5a2ecc..78e235f599f42 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs
@@ -30,6 +30,8 @@ private struct DecimalTC : INumericTC
decimal INumericTC.MaxValue => decimal.MaxValue;
+ decimal INumericTC.Zero => 0M;
+
public decimal FromConstantValue(ConstantValue constantValue) => constantValue.IsBad ? 0m : constantValue.DecimalValue;
public ConstantValue ToConstantValue(decimal value) => ConstantValue.Create(value);
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs
index 2e8abee831943..1853b554c5a96 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs
@@ -16,6 +16,10 @@ private sealed class DecimalValueSetFactory : IValueSetFactory, IValueS
private readonly IValueSetFactory _underlying = NumericValueSetFactory.Instance;
+ IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues;
+
public IValueSet Related(BinaryOperatorKind relation, decimal value) => _underlying.Related(relation, DecimalTC.Normalize(value));
IValueSet IValueSetFactory.Random(int expectedSize, Random random) => _underlying.Random(expectedSize, random);
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs
index 6ff9c00be961b..ab05ea50b5cf7 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs
@@ -21,6 +21,8 @@ private struct DoubleTC : FloatingTC, INumericTC
double FloatingTC.NaN => double.NaN;
+ double INumericTC.Zero => 0.0;
+
///
/// The implementation of Next depends critically on the internal representation of an IEEE floating-point
/// number. Every bit sequence between the representation of 0 and MaxValue represents a distinct
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs
index cf0d85a433498..1a6405efeb8ad 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs
@@ -4,6 +4,7 @@
#nullable enable
+using System;
using System.Collections.Immutable;
using System.Linq;
using Roslyn.Utilities;
@@ -24,18 +25,50 @@ private sealed class EnumeratedValueSet : IValueSet where TTC : struc
/// In , then members are listed by inclusion. Otherwise all members
/// are assumed to be contained in the set unless excluded.
///
- private bool _included;
+ private readonly bool _included;
- private ImmutableHashSet _membersIncludedOrExcluded;
+ private readonly ImmutableHashSet _membersIncludedOrExcluded;
private EnumeratedValueSet(bool included, ImmutableHashSet membersIncludedOrExcluded) =>
(this._included, this._membersIncludedOrExcluded) = (included, membersIncludedOrExcluded);
- public static EnumeratedValueSet AllValues = new EnumeratedValueSet(included: false, ImmutableHashSet.Empty);
+ public static readonly EnumeratedValueSet AllValues = new EnumeratedValueSet(included: false, ImmutableHashSet.Empty);
+
+ public static readonly EnumeratedValueSet NoValues = new EnumeratedValueSet(included: true, ImmutableHashSet.Empty);
internal static EnumeratedValueSet Including(T value) => new EnumeratedValueSet(included: true, ImmutableHashSet.Empty.Add(value));
- bool IValueSet.IsEmpty => _included && _membersIncludedOrExcluded.IsEmpty;
+ public bool IsEmpty => _included && _membersIncludedOrExcluded.IsEmpty;
+
+ ConstantValue IValueSet.Sample
+ {
+ get
+ {
+ if (IsEmpty) throw new ArgumentException();
+ var tc = default(TTC);
+ if (_included)
+ return tc.ToConstantValue(_membersIncludedOrExcluded.OrderBy(k => k).First());
+ if (typeof(T) == typeof(string))
+ {
+ // try some simple strings.
+ if (this.Any(BinaryOperatorKind.Equal, (T)(object)""))
+ return tc.ToConstantValue((T)(object)"");
+ for (char c = 'A'; c <= 'z'; c++)
+ if (this.Any(BinaryOperatorKind.Equal, (T)(object)c.ToString()))
+ return tc.ToConstantValue((T)(object)c.ToString());
+ }
+ // If that doesn't work, choose from a sufficiently large random selection of values.
+ // Since this is an excluded set, they cannot all be excluded
+ var candidates = tc.RandomValues(_membersIncludedOrExcluded.Count + 1, new Random(0), _membersIncludedOrExcluded.Count + 1);
+ foreach (var value in candidates)
+ {
+ if (this.Any(BinaryOperatorKind.Equal, value))
+ return tc.ToConstantValue(value);
+ }
+
+ throw ExceptionUtilities.Unreachable;
+ }
+ }
public bool Any(BinaryOperatorKind relation, T value)
{
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs
index 4d3ee6c5b0d91..931380d28bd6a 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs
@@ -19,7 +19,11 @@ internal static partial class ValueSetFactory
///
private sealed class EnumeratedValueSetFactory : IValueSetFactory where TTC : struct, IEquatableValueTC where T : notnull
{
- public static EnumeratedValueSetFactory Instance = new EnumeratedValueSetFactory();
+ public static readonly EnumeratedValueSetFactory Instance = new EnumeratedValueSetFactory();
+
+ IValueSet IValueSetFactory.AllValues => EnumeratedValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => EnumeratedValueSet.NoValues;
private EnumeratedValueSetFactory() { }
@@ -44,11 +48,11 @@ bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, C
return tc.FromConstantValue(left).Equals(tc.FromConstantValue(right));
}
- IValueSet IValueSetFactory.Random(int expectedSize, Random random)
+ public IValueSet Random(int expectedSize, Random random)
{
TTC tc = default;
T[] values = tc.RandomValues(expectedSize, random, expectedSize * 2);
- IValueSet result = EnumeratedValueSet.AllValues.Complement();
+ IValueSet result = EnumeratedValueSet.NoValues;
Debug.Assert(result.IsEmpty);
foreach (T value in values)
result = result.Union(Related(Equal, value));
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs
index 268d39f0b70be..bb545dab8caca 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs
@@ -5,6 +5,7 @@
#nullable enable
using System;
+using System.Diagnostics;
using System.Text;
using Roslyn.Utilities;
@@ -47,7 +48,27 @@ internal static IValueSet Random(int expectedSize, Random random)
numbers: (IValueSet)NumericValueSetFactory.Instance.Random(expectedSize, random), hasNaN: hasNan);
}
- bool IValueSet.IsEmpty => !_hasNaN && _numbers.IsEmpty;
+ public bool IsEmpty => !_hasNaN && _numbers.IsEmpty;
+
+ ConstantValue IValueSet.Sample
+ {
+ get
+ {
+ if (IsEmpty)
+ throw new ArgumentException();
+
+ if (!_numbers.IsEmpty)
+ {
+ var sample = _numbers.Sample;
+ Debug.Assert(sample is { });
+ return sample;
+ }
+
+ Debug.Assert(_hasNaN);
+ var tc = default(TFloatingTC);
+ return tc.ToConstantValue(tc.NaN);
+ }
+ }
public static IValueSet Related(BinaryOperatorKind relation, TFloating value)
{
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs
index f01ffd022afc0..67bc0f9c22e40 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs
@@ -16,6 +16,10 @@ private sealed class FloatingValueSetFactory : IValueSet
private FloatingValueSetFactory() { }
+ IValueSet IValueSetFactory.AllValues => FloatingValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => FloatingValueSet.NoValues;
+
public IValueSet Related(BinaryOperatorKind relation, TFloating value) =>
FloatingValueSet.Related(relation, value);
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.INumericTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.INumericTC.cs
index 7c9a250d489c7..df0281adc8e27 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.INumericTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.INumericTC.cs
@@ -59,6 +59,11 @@ private interface INumericTC
///
T Random(Random random);
+ ///
+ /// Produce the zero value for the type.
+ ///
+ T Zero { get; }
+
///
/// A formatter for values of type . This is needed for testing because
/// the default ToString output for float and double changed between desktop and .net core,
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs
index 96652fab749fc..47e721a0d3662 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs
@@ -19,6 +19,8 @@ private struct IntTC : INumericTC
int INumericTC.MaxValue => int.MaxValue;
+ int INumericTC.Zero => 0;
+
public bool Related(BinaryOperatorKind relation, int left, int right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs
index 5a35e264b25f1..b0d4c183eec35 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs
@@ -19,6 +19,8 @@ private struct LongTC : INumericTC
long INumericTC.MaxValue => long.MaxValue;
+ long INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, long left, long right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs
index 7cc48dfd0a3f8..1f1d65c1b44d7 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs
@@ -4,6 +4,7 @@
#nullable enable
+using System;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
@@ -17,6 +18,8 @@ private sealed class NintValueSet : IValueSet, IValueSet
{
public readonly static NintValueSet AllValues = new NintValueSet(hasSmall: true, values: NumericValueSet.AllValues, hasLarge: true);
+ public readonly static NintValueSet NoValues = new NintValueSet(hasSmall: false, values: NumericValueSet.NoValues, hasLarge: false);
+
private readonly IValueSet _values;
///
@@ -42,7 +45,24 @@ internal NintValueSet(bool hasSmall, IValueSet values, bool hasLarge)
_hasLarge = hasLarge;
}
- bool IValueSet.IsEmpty => !_hasSmall && !_hasLarge && _values.IsEmpty;
+ public bool IsEmpty => !_hasSmall && !_hasLarge && _values.IsEmpty;
+
+ ConstantValue? IValueSet.Sample
+ {
+ get
+ {
+ if (IsEmpty)
+ throw new ArgumentException();
+
+ if (!_values.IsEmpty)
+ return _values.Sample;
+
+ // We do not support sampling from a nint value set without a specific value. The caller
+ // must arrange another way to get a sample, since we can return no specific value. This
+ // occurs when the value set was constructed from a pattern like `> (nint)int.MaxValue`.
+ return null;
+ }
+ }
public bool All(BinaryOperatorKind relation, int value)
{
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs
index 22bb7c5a6b4df..4c2774fe00f44 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs
@@ -18,6 +18,10 @@ private sealed class NintValueSetFactory : IValueSetFactory, IValueSetFacto
private NintValueSetFactory() { }
+ IValueSet IValueSetFactory.AllValues => NintValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => NintValueSet.NoValues;
+
public IValueSet Related(BinaryOperatorKind relation, int value)
{
return new NintValueSet(
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs
index 74c8381f2b166..2b7ace45bdbcd 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs
@@ -4,6 +4,7 @@
#nullable enable
+using System;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
@@ -17,6 +18,8 @@ private sealed class NuintValueSet : IValueSet, IValueSet
{
public readonly static NuintValueSet AllValues = new NuintValueSet(values: NumericValueSet.AllValues, hasLarge: true);
+ public readonly static NuintValueSet NoValues = new NuintValueSet(values: NumericValueSet.NoValues, hasLarge: false);
+
private readonly IValueSet _values;
///
@@ -33,7 +36,24 @@ internal NuintValueSet(IValueSet values, bool hasLarge)
_hasLarge = hasLarge;
}
- bool IValueSet.IsEmpty => !_hasLarge && _values.IsEmpty;
+ public bool IsEmpty => !_hasLarge && _values.IsEmpty;
+
+ ConstantValue? IValueSet.Sample
+ {
+ get
+ {
+ if (IsEmpty)
+ throw new ArgumentException();
+
+ if (!_values.IsEmpty)
+ return _values.Sample;
+
+ // We do not support sampling from a nuint value set without a specific value. The caller
+ // must arrange another way to get a sample, since we can return no specific value. This
+ // occurs when the value set was constructed from a pattern like `> (nuint)uint.MaxValue`.
+ return null;
+ }
+ }
public bool All(BinaryOperatorKind relation, uint value)
{
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs
index b4606ce7a2d53..06f5aa7c7d53c 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs
@@ -18,6 +18,10 @@ private sealed class NuintValueSetFactory : IValueSetFactory, IValueSetFac
private NuintValueSetFactory() { }
+ IValueSet IValueSetFactory.AllValues => NuintValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => NuintValueSet.NoValues;
+
public IValueSet Related(BinaryOperatorKind relation, uint value)
{
return new NuintValueSet(
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs
index 9b4da121c21e0..421fd9fb49d2f 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs
@@ -8,6 +8,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
@@ -25,6 +26,7 @@ private sealed class NumericValueSet : IValueSet where TTC : struct,
private readonly ImmutableArray<(T first, T last)> _intervals;
public readonly static NumericValueSet AllValues = new NumericValueSet(default(TTC).MinValue, default(TTC).MaxValue);
+
public readonly static NumericValueSet NoValues = new NumericValueSet(ImmutableArray<(T first, T last)>.Empty);
internal NumericValueSet(T first, T last) : this(ImmutableArray.Create((first, last)))
@@ -49,7 +51,24 @@ internal NumericValueSet(ImmutableArray<(T first, T last)> intervals)
_intervals = intervals;
}
- bool IValueSet.IsEmpty => _intervals.Length == 0;
+ public bool IsEmpty => _intervals.Length == 0;
+
+ ConstantValue IValueSet.Sample
+ {
+ get
+ {
+ if (IsEmpty)
+ throw new ArgumentException();
+
+ // Prefer a value near zero.
+ var tc = default(TTC);
+ var gz = NumericValueSetFactory.Instance.Related(BinaryOperatorKind.GreaterThanOrEqual, tc.Zero);
+ var t = (NumericValueSet)this.Intersect(gz);
+ if (!t.IsEmpty)
+ return tc.ToConstantValue(t._intervals[0].first);
+ return tc.ToConstantValue(this._intervals[this._intervals.Length - 1].last);
+ }
+ }
public bool Any(BinaryOperatorKind relation, T value)
{
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs
index 58f9e09a8cf31..12d48f24625c4 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs
@@ -22,6 +22,10 @@ private sealed class NumericValueSetFactory : IValueSetFactory where
{
public static readonly NumericValueSetFactory Instance = new NumericValueSetFactory();
+ IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues;
+
+ IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues;
+
private NumericValueSetFactory() { }
public IValueSet Related(BinaryOperatorKind relation, T value)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs
index c3486ca3023c9..7e745dd05b157 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs
@@ -19,6 +19,8 @@ private struct SByteTC : INumericTC
sbyte INumericTC.MaxValue => sbyte.MaxValue;
+ sbyte INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, sbyte left, sbyte right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs
index 1c07ce50fadcb..6e8fc986f9412 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs
@@ -19,6 +19,8 @@ private struct ShortTC : INumericTC
short INumericTC.MaxValue => short.MaxValue;
+ short INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, short left, short right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs
index 52e21a673f7bd..371a534dafc58 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs
@@ -21,6 +21,8 @@ private struct SingleTC : FloatingTC, INumericTC
float FloatingTC.NaN => float.NaN;
+ float INumericTC.Zero => 0;
+
///
/// The implementation of Next depends critically on the internal representation of an IEEE floating-point
/// number. Every bit sequence between the representation of 0 and MaxValue represents a distinct
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs
index 44f2fe5f0280c..f6d5c2a2ae6a3 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs
@@ -23,7 +23,7 @@ string IEquatableValueTC.FromConstantValue(ConstantValue constantValue)
string[] IEquatableValueTC.RandomValues(int count, Random random, int scope)
{
Debug.Assert(count > 0);
- Debug.Assert(scope > count);
+ Debug.Assert(scope >= count);
string[] result = new string[count];
int next = 0;
for (int i = 0; i < scope; i++)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs
index 069f69e05154e..24fceb6700e84 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs
@@ -19,6 +19,8 @@ private struct UIntTC : INumericTC
uint INumericTC.MaxValue => uint.MaxValue;
+ uint INumericTC.Zero => 0;
+
public bool Related(BinaryOperatorKind relation, uint left, uint right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs
index ce7558c5967e0..02d3c7b77e868 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs
@@ -19,6 +19,8 @@ private struct ULongTC : INumericTC
ulong INumericTC.MaxValue => ulong.MaxValue;
+ ulong INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, ulong left, ulong right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs
index 19440d0288c66..af44f257e758a 100644
--- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs
+++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs
@@ -19,6 +19,8 @@ private struct UShortTC : INumericTC
ushort INumericTC.MaxValue => ushort.MaxValue;
+ ushort INumericTC.Zero => 0;
+
bool INumericTC.Related(BinaryOperatorKind relation, ushort left, ushort right)
{
switch (relation)
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index b29c49b51361d..11321d0f1f607 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -2280,13 +2280,13 @@
-
- Výraz switch nezpracovává všechny možné hodnoty svého vstupního typu (není úplný).
+
+ 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.
-
- Výraz switch nezpracovává některé vstupy s hodnotou null (není vyčerpávající).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index 935515d0a8674..06512493acfa0 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -2280,13 +2280,13 @@
-
- Der switch-Ausdruck verarbeitet nicht alle möglichen Werte des zugehörigen Eingabetyps (nicht umfassend).
+
+ 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.
-
- Der switch-Ausdruck verarbeitet einige NULL-Eingaben nicht (nicht umfassend).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index 87f343ed6efdf..d951fffe51264 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -2280,13 +2280,13 @@
-
- La expresión switch no controla todos los valores posibles de su tipo de entrada (no es exhaustiva).
+
+ 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.
-
- La expresión switch no controla algunas entradas de tipo NULL (no es exhaustiva).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index 1186574e2cbd1..be5d17fe01552 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -2280,13 +2280,13 @@
-
- L'expression switch ne prend pas en charge toutes les valeurs possibles de son type d'entrée (elle n'est pas exhaustive).
+
+ 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.
-
- L'expression switch ne prend pas en charge certaines entrées ayant une valeur null (elle n'est pas exhaustive).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index b30f6939144b8..0c39aea3033d4 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -2280,13 +2280,13 @@
-
- L'espressione switch non gestisce tutti i possibili valori del relativo tipo di input (non è esaustiva).
+
+ 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.
-
- L'espressione switch non gestisce alcuni input Null (non è esaustiva).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index da5f28027d5d9..83ca6b556423b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -2280,13 +2280,13 @@
-
- switch 式が入力の種類で可能なすべての値を処理していません (すべてを網羅していません)。
+
+ 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.
-
- switch 式が一部の null 入力を処理しません (すべてを網羅していません)。
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index 9ff84d130fe38..7c64d29d60080 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -2280,13 +2280,13 @@
-
- switch 식에서 입력 형식의 가능한 값을 모두 처리하지는 않습니다(전체 아님).
+
+ 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.
-
- switch 식은 일부 null 입력을 처리하지 않습니다(전체 아님).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index a90cfb2a9545f..025b4b94771d1 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -2280,13 +2280,13 @@
-
- Wyrażenie switch nie obsługuje wszystkich możliwych wartości jego typu danych wejściowych (nie jest kompletne).
+
+ 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.
-
- Wyrażenie switch nie obsługuje niektórych danych wejściowych o wartości null (nie jest kompletne).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index 9ce099fe06cb7..a66efba099ab8 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -2278,13 +2278,13 @@
-
- A expressão switch não manipula todos os valores possíveis de seu tipo de entrada (não é exaustiva).
+
+ 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.
-
- A expressão switch não manipula algumas entradas nulas (não é exaustiva).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index 2487721e91dc2..f77f0fa45eb4e 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -2280,13 +2280,13 @@
-
- Выражение switch обрабатывает не все возможные значения своего типа входных данных (оно не полное).
+
+ 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.
-
- Выражение switch не обрабатывает некоторые входные данные NULL (оно не полное).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index e7fae4b9f9620..98644e909656f 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -2280,13 +2280,13 @@
-
- Switch ifadesi giriş türünün tüm olası değerlerini işlemiyor. (İfade tam kapsamlı değil.)
+
+ 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.
-
- Switch ifadesi bazı null girişleri işlemiyor (tam kapsamlı değil).
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index 14469ea631e81..033f7b228d986 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -2280,13 +2280,13 @@
-
- switch 表达式不会处理属于其输入类型的所有可能值(它并非详尽无遗)。
+
+ 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.
-
- Switch 表达式不会处理某些为 null 的输入(它并非详尽无遗)。
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index ef97a010d4afb..04fc47746bd1a 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -2280,13 +2280,13 @@
-
- switch 運算式未處理其輸入類型可能的值 (並非全部)。
+
+ 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.
-
- Switch 運算式未處理某些 null 輸入 (並不徹底)。
+
+ The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '{0}' is not covered.
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs
index 6cec7b87bf41f..9af3c03850154 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs
@@ -3199,9 +3199,9 @@ static class C {
";
var compilation = CreateEmptyCompilation(source, options: TestOptions.ReleaseDll);
compilation.GetDiagnostics().Verify(
- // (9,38): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,38): 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.
// public static bool M(int i) => i switch { 1 => true };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 38)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(9, 38)
);
compilation.GetEmitDiagnostics().Verify(
// warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options.
@@ -3209,9 +3209,9 @@ static class C {
// (9,36): error CS0656: Missing compiler required member 'System.InvalidOperationException..ctor'
// public static bool M(int i) => i switch { 1 => true };
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "i switch { 1 => true }").WithArguments("System.InvalidOperationException", ".ctor").WithLocation(9, 36),
- // (9,38): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,38): 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.
// public static bool M(int i) => i switch { 1 => true };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 38)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(9, 38)
);
}
diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs
index b90ed82441dfc..98790da7d6fe9 100644
--- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs
+++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchExpression.cs
@@ -77,9 +77,9 @@ void M(int? x, object y)
Arms(0)
";
var expectedDiagnostics = new[] {
- // file.cs(7,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // file.cs(7,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
// y = /**/x switch { }/**/;
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 25)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(7, 25)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
@@ -509,9 +509,9 @@ void M(int? x, object y)
ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5')
";
var expectedDiagnostics = new[] {
- // file.cs(7,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // file.cs(7,25): 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.
// y = /**/x switch { 1 => 2, _ when false => 5 }/**/;
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 25)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(7, 25)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchOperation.cs
index 5ba16efabc223..9ccab4d2b78fa 100644
--- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchOperation.cs
+++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_ISwitchOperation.cs
@@ -3326,9 +3326,9 @@ public static void Main()
}
";
var expectedDiagnostics = new[] {
- // file.cs(6,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // file.cs(6,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
// var r = 1 switch { };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 19),
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(6, 19),
// file.cs(6,19): error CS8506: No best type was found for the switch expression.
// var r = 1 switch { };
Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(6, 19)
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
index 8455d11226e6b..7b329cc0ca67a 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
@@ -129399,12 +129399,12 @@ public void M7(C? a, bool b)
";
var comp = CreateCompilation(source);
comp.VerifyDiagnostics(
- // (4,30): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ // (4,30): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered.
// public int M1(C? a) => a switch { C _ => 0 }; // warns
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(4, 30),
- // (8,35): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(4, 30),
+ // (8,35): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, null)' is not covered.
// public int M4(C? a) => (1, a) switch { (_, C _) => 0 }; // warns
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(8, 35),
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, null)").WithLocation(8, 35),
// (36,9): warning CS8602: Dereference of a possibly null reference.
// a.ToString(); // warns
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(36, 9)
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs
index 2f6a11192c05d..ad8e2f2118223 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs
@@ -1319,27 +1319,27 @@ void M8(object o, bool b)
";
var comp = CreateNullableCompilation(source);
comp.VerifyDiagnostics(
- // (18,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ // (18,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// _ = t switch // 1 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(18, 15),
- // (27,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(18, 15),
+ // (27,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// _ = t switch // 2 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(27, 15),
- // (36,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(27, 15),
+ // (36,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(not null, null)' is not covered.
// _ = t switch // 3 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(36, 15),
- // (46,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(not null, null)").WithLocation(36, 15),
+ // (46,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// _ = t switch // 4 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(46, 15),
- // (55,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(46, 15),
+ // (55,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// _ = t switch // 5 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(55, 15),
- // (64,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(55, 15),
+ // (64,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(not null, null)' is not covered.
// _ = t switch // 6 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(64, 15),
- // (73,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(not null, null)").WithLocation(64, 15),
+ // (73,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered.
// _ = o switch // 7 not exhaustive
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(73, 15));
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(73, 15));
}
[Fact]
@@ -1384,18 +1384,18 @@ int M5(string s1, string s2)
}";
var comp = CreateNullableCompilation(source);
comp.VerifyDiagnostics(
- // (12,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ // (12,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// return (s1, s2) switch { // 1
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(12, 25),
- // (18,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(12, 25),
+ // (18,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// return (s1, s2) switch { // 2
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(18, 25),
- // (24,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(18, 25),
+ // (24,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, _)' is not covered.
// return (s1, s2) switch { // 3
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(24, 25),
- // (30,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, _)").WithLocation(24, 25),
+ // (30,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, "")' is not covered.
// return (s1, s2) switch { // 4
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(30, 25));
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, \"\")").WithLocation(30, 25));
}
[Fact, WorkItem(31881, "https://github.com/dotnet/roslyn/issues/31881")]
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs
index bcd136036de16..187d4b65adea0 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs
@@ -494,9 +494,9 @@ public static void Main()
}
}";
CreatePatternCompilation(source).VerifyDiagnostics(
- // (5,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (5,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
// var r = 1 switch { };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(5, 19),
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(5, 19),
// (5,19): error CS8506: No best type was found for the switch expression.
// var r = 1 switch { };
Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(5, 19));
@@ -518,9 +518,9 @@ public static void M() {}
public delegate void D();
}";
CreatePatternCompilation(source).VerifyDiagnostics(
- // (5,19): warning CS8409: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (5,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.
// var x = 1 switch { 0 => M, 1 => new D(M), 2 => M };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(5, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("3").WithLocation(5, 19)
);
}
@@ -609,9 +609,9 @@ public static void Main()
}";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (7,19): warning CS8409: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (7,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
// var c = a switch { var x2 when x2 is var x3 => x3 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(7, 19)
);
var names = new[] { "x1", "x2", "x3", "x4", "x5" };
var tree = compilation.SyntaxTrees[0];
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
index 37c64e5ad9d73..cfde836b41983 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
@@ -1612,12 +1612,12 @@ static void Main() {
}
}";
CreateCompilation(source).VerifyDiagnostics(
- // (5,41): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
- // var x = ((Func)(0 switch { 0 => _ => {}}))(0);
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(5, 41),
- // (5,57): error CS1643: Not all code paths return a value in lambda expression of type 'Func'
- // var x = ((Func)(0 switch { 0 => _ => {}}))(0);
- Diagnostic(ErrorCode.ERR_AnonymousReturnExpected, "=>").WithArguments("lambda expression", "System.Func").WithLocation(5, 57)
+ // (5,41): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '1' is not covered.
+ // var x = ((Func)(0 switch { 0 => _ => {}}))(0);
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("1").WithLocation(5, 41),
+ // (5,57): error CS1643: Not all code paths return a value in lambda expression of type 'Func'
+ // var x = ((Func)(0 switch { 0 => _ => {}}))(0);
+ Diagnostic(ErrorCode.ERR_AnonymousReturnExpected, "=>").WithArguments("lambda expression", "System.Func").WithLocation(5, 57)
);
}
@@ -1633,12 +1633,12 @@ static void Main() {
static void M(int x) {}
}";
CreateCompilation(source).VerifyDiagnostics(
- // (5,41): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
- // var x = ((Func)(0 switch { 0 => M }))(0);
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(5, 41),
- // (5,55): error CS0407: 'void C.M(int)' has the wrong return type
- // var x = ((Func)(0 switch { 0 => M }))(0);
- Diagnostic(ErrorCode.ERR_BadRetType, "M").WithArguments("C.M(int)", "void").WithLocation(5, 55)
+ // (5,41): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '1' is not covered.
+ // var x = ((Func)(0 switch { 0 => M }))(0);
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("1").WithLocation(5, 41),
+ // (5,55): error CS0407: 'void C.M(int)' has the wrong return type
+ // var x = ((Func)(0 switch { 0 => M }))(0);
+ Diagnostic(ErrorCode.ERR_BadRetType, "M").WithArguments("C.M(int)", "void").WithLocation(5, 55)
);
}
@@ -2460,9 +2460,9 @@ static void M2(object o)
// (9,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match.
// case 1L or 2L: Console.Write(2); break;
Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "1L or 2L").WithLocation(9, 18),
- // (14,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (14,15): 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.
// _ = o switch
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(14, 15),
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(14, 15),
// (17,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.
// 1L or 2L => 2,
Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "1L or 2L").WithLocation(17, 13)
@@ -2908,9 +2908,9 @@ class C
else
{
compilation.VerifyDiagnostics(
- // (15,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (15,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'E.Five' is not covered.
// static int M(E c) => c switch
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(15, 28)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("E.Five").WithLocation(15, 28)
);
}
}
@@ -2964,9 +2964,9 @@ class C
else
{
compilation.VerifyEmitDiagnostics(
- // (15,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (15,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'E.Five' is not covered.
// static int M(E c) => c switch
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(15, 28)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("E.Five").WithLocation(15, 28)
);
}
}
@@ -4857,7 +4857,7 @@ public void RelationalFuzz_01(int seed, int numCases, char kind, string point)
'x' => "nint",
'y' => "nuint",
'z' => "int",
- _ => throw new ArgumentException(nameof(kind)),
+ _ => throw new ArgumentException("unexpected", nameof(kind)),
};
if (kind is 'x' || kind is 'y' || kind is 'z')
{
@@ -5488,16 +5488,254 @@ static void Main()
string expectedOutput = "bb";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
- // (7,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
- // var str = x switch // does not handle zero
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 21),
- // (15,17): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
- // str = x switch // does not handle zero
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(15, 17)
+ // (7,21): 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.
+ // var str = x switch // does not handle zero
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(7, 21),
+ // (15,17): 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.
+ // str = x switch // does not handle zero
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("0").WithLocation(15, 17)
);
var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_01()
+ {
+ var source =
+@"
+class C
+{
+ int M(Color color) => color switch
+ {
+ Color.Red => 0,
+ Color.Blue => 2,
+ };
+}
+enum Color { Red, Greed, Blue }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,33): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Color.Greed' is not covered.
+ // int M(Color color) => color switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Color.Greed").WithLocation(4, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_02()
+ {
+ var source =
+@"
+class C
+{
+ int M(Color color) => color switch
+ {
+ <= Color.Greed => 0,
+ };
+}
+enum Color { Red, Greed, Blue }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,33): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Color.Blue' is not covered.
+ // int M(Color color) => color switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Color.Blue").WithLocation(4, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_03()
+ {
+ var source =
+@"
+class C
+{
+ int M(Color color) => color switch
+ {
+ <= Color.Greed => 0,
+ > Color.Greed => 1,
+ };
+}
+enum Color { Red, Greed, Blue }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_04()
+ {
+ var source =
+@"
+class C
+{
+ int M(Color color) => color switch
+ {
+ Color.Red => 0,
+ Color.Greed => 1,
+ Color.Blue => 2,
+ };
+}
+enum Color { Red, Greed, Blue }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,33): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Color)3' is not covered.
+ // int M(Color color) => color switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Color)3").WithLocation(4, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_05()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M(bool a, bool b) => (a, b) switch
+ {
+ (true, _) => 1,
+ (_, true) => 2,
+ };
+}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,37): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(false, false)' is not covered.
+ // int M(bool a, bool b) => (a, b) switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(false, false)").WithLocation(4, 37)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_06()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M(string? a, string? b) => (a, b) switch
+ {
+ (string, _) => 1,
+ (_, string) => 2,
+ };
+ void M2(object? a, object? b)
+ {
+ _ = (a, b) switch { (null, _) => 1 };
+ _ = (a, b) switch { (null, _) => 1, (_, null) => 2 };
+ _ = (a, b) switch { (_, not null) => 1 };
+ _ = (a, b) switch { (_, not null) => 1, (not null, _) => 2 };
+ }
+}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,43): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, null)' is not covered.
+ // int M(string? a, string? b) => (a, b) switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, null)").WithLocation(4, 43),
+ // (11,20): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(not null, _)' is not covered.
+ // _ = (a, b) switch { (null, _) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(not null, _)").WithLocation(11, 20),
+ // (12,20): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(not null, not null)' is not covered.
+ // _ = (a, b) switch { (null, _) => 1, (_, null) => 2 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(not null, not null)").WithLocation(12, 20),
+ // (13,20): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, null)' is not covered.
+ // _ = (a, b) switch { (_, not null) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, null)").WithLocation(13, 20),
+ // (14,20): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(null, null)' is not covered.
+ // _ = (a, b) switch { (_, not null) => 1, (not null, _) => 2 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(null, null)").WithLocation(14, 20)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_07()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(nint a) => a switch { <= (nint)int.MaxValue => 1 };
+ int M2(nint a) => a switch { >= (nint)int.MinValue => 1 };
+ int M3(nuint a) => a switch { <= (nuint)uint.MaxValue => 1 };
+ // Cannot test these cases due to https://github.com/dotnet/roslyn/issues/44651
+ //int M4(Enint a) => a switch { <= (Enint)int.MaxValue => 1 };
+ //int M5(Enint a) => a switch { >= (Enint)int.MinValue => 1 };
+ //int M6(Enuint a) => a switch { <= (Enuint)uint.MaxValue => 1 };
+}
+//enum Enint : nint { }
+//enum Enuint : nuint { }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '> (nint)int.MaxValue' is not covered.
+ // int M1(nint a) => a switch { <= (nint)int.MaxValue => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("> (nint)int.MaxValue").WithLocation(4, 25),
+ // (5,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '< (nint)int.MinValue' is not covered.
+ // int M2(nint a) => a switch { >= (nint)int.MinValue => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("< (nint)int.MinValue").WithLocation(5, 25),
+ // (6,26): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '> (nuint)uint.MaxValue' is not covered.
+ // int M3(nuint a) => a switch { <= (nuint)uint.MaxValue => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("> (nuint)uint.MaxValue").WithLocation(6, 26)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_08()
+ {
+ var source =
+@"
+class C
+{
+ int M1(float f) => f switch { < 0 => 1, >= 0 => 2 }; // float.NaN
+ int M2(float f) => f switch { > float.NegativeInfinity => 1, float.NaN => 2 }; // float.NegativeInfinity
+ int M3(float f) => f switch { < float.PositiveInfinity => 1, float.NaN => 2 }; // float.PositiveInfinity
+ int M1(double f) => f switch { < 0 => 1, >= 0 => 2 }; // double.NaN
+ int M2(double f) => f switch { > double.NegativeInfinity => 1, double.NaN => 2 }; // double.NegativeInfinity
+ int M3(double f) => f switch { < double.PositiveInfinity => 1, double.NaN => 2 }; // double.PositiveInfinity
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,26): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'float.NaN' is not covered.
+ // int M1(float f) => f switch { < 0 => 1, >= 0 => 2 }; // float.NaN
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("float.NaN").WithLocation(4, 26),
+ // (5,26): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'float.NegativeInfinity' is not covered.
+ // int M2(float f) => f switch { > float.NegativeInfinity => 1, float.NaN => 2 }; // float.NegativeInfinity
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("float.NegativeInfinity").WithLocation(5, 26),
+ // (6,26): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'float.PositiveInfinity' is not covered.
+ // int M3(float f) => f switch { < float.PositiveInfinity => 1, float.NaN => 2 }; // float.PositiveInfinity
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("float.PositiveInfinity").WithLocation(6, 26),
+ // (7,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'double.NaN' is not covered.
+ // int M1(double f) => f switch { < 0 => 1, >= 0 => 2 }; // double.NaN
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("double.NaN").WithLocation(7, 27),
+ // (8,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'double.NegativeInfinity' is not covered.
+ // int M2(double f) => f switch { > double.NegativeInfinity => 1, double.NaN => 2 }; // double.NegativeInfinity
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("double.NegativeInfinity").WithLocation(8, 27),
+ // (9,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'double.PositiveInfinity' is not covered.
+ // int M3(double f) => f switch { < double.PositiveInfinity => 1, double.NaN => 2 }; // double.PositiveInfinity
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("double.PositiveInfinity").WithLocation(9, 27)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_09()
+ {
+ var source =
+@"
+class C
+{
+ int M1(string s) => s switch { """" => 1, ""1"" => 2, ""A"" => 3, };
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '"B"' is not covered.
+ // int M1(string s) => s switch { "" => 1, "1" => 2, "A" => 3, };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("\"B\"").WithLocation(4, 27)
+ );
+ }
+
[Fact, WorkItem(44398, "https://github.com/dotnet/roslyn/issues/44398")]
public void MismatchedExpressionPattern()
{
@@ -5833,5 +6071,550 @@ static void Main()
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(26, 21)
);
}
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_10()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(string? s) => s switch { string => 1 };
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,28): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered.
+ // int M1(string? s) => s switch { string => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(4, 28)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_11()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(object o) => o switch { not Q(1, 2.5) => 1 };
+ int M2(object o) => o switch { not Q(1L, 2.5F) => 1 };
+ int M3(object o) => o switch { not Q((byte)1, (short)2) => 1 };
+ int M4(object o) => o switch { not Q((uint)1, (ulong)2) => 1 };
+ int M5(object o) => o switch { not Q((long)1, (sbyte)2) => 1 };
+}
+class Q
+{
+ public void Deconstruct(out object o1, out object o2) => throw null!;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q(1, 2.5D)' is not covered.
+ // int M1(object o) => o switch { not Q(1, 2.5) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q(1, 2.5D)").WithLocation(4, 27),
+ // (5,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q(1L, 2.5F)' is not covered.
+ // int M2(object o) => o switch { not Q(1L, 2.5F) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q(1L, 2.5F)").WithLocation(5, 27),
+ // (6,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q((byte)1, (short)2)' is not covered.
+ // int M3(object o) => o switch { not Q((byte)1, (short)2) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q((byte)1, (short)2)").WithLocation(6, 27),
+ // (7,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q(1U, 2UL)' is not covered.
+ // int M4(object o) => o switch { not Q((uint)1, (ulong)2) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q(1U, 2UL)").WithLocation(7, 27),
+ // (8,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q(1L, (sbyte)2)' is not covered.
+ // int M5(object o) => o switch { not Q((long)1, (sbyte)2) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q(1L, (sbyte)2)").WithLocation(8, 27)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_12()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(object o) => o switch { not (Q(1, 2.5) or I) => 1 };
+ int M2(object o) => o switch { not (Q(1, 2.5) { P1: 1 } and Q(3, 4, 5) { P2: 2 }) => 1 };
+}
+class Q
+{
+ public void Deconstruct(out object o1, out object o2) => throw null!;
+ public void Deconstruct(out object o1, out object o2, out object o3) => throw null!;
+ public int P1 = 5;
+ public int P2 = 6;
+}
+interface I
+{
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'I' is not covered.
+ // int M1(object o) => o switch { not (Q(1, 2.5) or I) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("I").WithLocation(4, 27),
+ // (5,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q(1, 2.5D) and (3, 4, 5) { P1: 1, P2: 2 }' is not covered.
+ // int M2(object o) => o switch { not (Q(1, 2.5) { P1: 1 } and Q(3, 4, 5) { P2: 2 }) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q(1, 2.5D) and (3, 4, 5) { P1: 1, P2: 2 }").WithLocation(5, 27)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_13()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(Q? q) => q switch { not null => 1 };
+ int M2(Q? q) => q switch { null => 1 };
+}
+class Q
+{
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,23): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered.
+ // int M1(Q? q) => q switch { not null => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(4, 23),
+ // (5,23): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'not null' is not covered.
+ // int M2(Q? q) => q switch { null => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("not null").WithLocation(5, 23)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_14()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1((int x, int y) t) => t switch { not ((1, _) { y: 2 }) => 1 };
+ int M2((int x, int y) t) => t switch { not ((1, _) { Item2: 2 }) => 1 };
+ int M3((int x, int y)? t) => t switch { (_, _) => 1 };
+ int M4((int x, int y)? t) => t switch { not (_, _) => 1 };
+}
+class Q
+{
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,35): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(1, 2)' is not covered.
+ // int M1((int x, int y) t) => t switch { not ((1, _) { y: 2 }) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(1, 2)").WithLocation(4, 35),
+ // (5,35): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(1, 2)' is not covered.
+ // int M2((int x, int y) t) => t switch { not ((1, _) { Item2: 2 }) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(1, 2)").WithLocation(5, 35),
+ // (6,36): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered.
+ // int M3((int x, int y)? t) => t switch { (_, _) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(6, 36),
+ // (7,36): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'not null' is not covered.
+ // int M4((int x, int y)? t) => t switch { not (_, _) => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("not null").WithLocation(7, 36)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_15()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(Q q) => q switch { { P1: < 10 } => 1 };
+ int M2(Q q) => q switch { { P1: > -100, P2: < int.MaxValue } => 1 };
+}
+class Q
+{
+ public int P1 = 5;
+ public int P2 = 6;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ P1: 10 }' is not covered.
+ // int M1(Q q) => q switch { { P1: < 10 } => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ P1: 10 }").WithLocation(4, 22),
+ // (5,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ P1: -100 }' is not covered.
+ // int M2(Q q) => q switch { { P1: > -100, P2: < int.MaxValue } => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ P1: -100 }").WithLocation(5, 22)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_16()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(Q q) => q switch { { P1: < 10 } => 1, null => 2 };
+ int M2(Q q) => q switch { { P1: > -100, P2: < int.MaxValue } => 1, null => 2 };
+}
+class Q
+{
+ public int P1 = 5;
+ public int P2 = 6;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ P1: 10 }' is not covered.
+ // int M1(Q q) => q switch { { P1: < 10 } => 1, null => 2 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ P1: 10 }").WithLocation(4, 22),
+ // (5,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ P1: -100 }' is not covered.
+ // int M2(Q q) => q switch { { P1: > -100, P2: < int.MaxValue } => 1, null => 2 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ P1: -100 }").WithLocation(5, 22)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_17()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(object o) => o switch { not Q => 1 };
+ int M2(object o) => o switch { not Q and not W => 1 };
+ int M3(object o) => o switch { not W and not Q => 1 };
+}
+class Q { }
+class W { }
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q' is not covered.
+ // int M1(object o) => o switch { not Q => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q").WithLocation(4, 27),
+ // (5,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Q' is not covered.
+ // int M2(object o) => o switch { not Q and not W => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("Q").WithLocation(5, 27),
+ // (6,27): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'W' is not covered.
+ // int M3(object o) => o switch { not W and not Q => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("W").WithLocation(6, 27)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_18()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(Q q) => q switch { not (1) { } => 1 };
+ int M2(System.ValueTuple q) => q switch { not (1) { } => 1 };
+}
+class Q
+{
+ public void Deconstruct(out int X) => throw null!;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(1) { }' is not covered.
+ // int M1(Q q) => q switch { not (1) { } => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(1) { }").WithLocation(4, 22),
+ // (5,43): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(1) { }' is not covered.
+ // int M2(System.ValueTuple q) => q switch { not (1) { } => 1 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(1) { }").WithLocation(5, 43)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_19()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M((bool, bool, bool, bool) t) => t switch
+ {
+ (false, true, true, true) => 3,
+ // (false, true, false, true) => 4,
+ (false, false, false, true) => 5,
+ (false, false, true, true) => 6,
+ (true, _, false, _) => 1,
+ (true, _, true, _) => 2,
+ (false, _, _, false) => 7,
+ };
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,44): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(false, true, false, true)' is not covered.
+ // int M((bool, bool, bool, bool) t) => t switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(false, true, false, true)").WithLocation(4, 44)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_20()
+ {
+ // The is an example of a situation which is too complicated for our implementation to produce a precise
+ // example pattern that is not covered. We fall back to suggesting the pattern `_` in that case.
+ // For situations such as this, that is probably better than producing a precise pattern.
+ var source =
+@"#nullable enable
+class C
+{
+ int M(string s) => s switch
+ {
+ """" => 0,
+ ""A"" => 1,
+ ""BB"" => 2,
+ { Length: 1 } => 3,
+ };
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,26): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
+ // int M(string s) => s switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(4, 26)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_21()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M(Q t) => t switch
+ {
+ { A: false, B: true, C: true, D: true } => 3,
+ // { A: false, B: true, C: false, D: true } => 4,
+ { A: false, B: false, C: false, D: true } => 5,
+ { A: false, B: false, C: true, D: true } => 6,
+ { A: true, C: false } => 1,
+ { A: true, C: true } => 2,
+ { A: false, D: false } => 7,
+ };
+}
+class Q
+{
+ public bool A = true;
+ public bool B = true;
+ public bool C = true;
+ public bool D = true;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ A: false, B: true, C: false, D: true }' is not covered.
+ // int M(Q t) => t switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ A: false, B: true, C: false, D: true }").WithLocation(4, 21)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_22()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M(Q t) => t switch
+ {
+ { A: true, C: true } => 2,
+ { A: false, D: false } => 7,
+ { A: false, B: true, C: true, D: true } => 3,
+ // { A: false, B: true, C: false, D: true } => 4,
+ { A: false, B: false, C: false, D: true } => 5,
+ { A: false, B: false, C: true, D: true } => 6,
+ { A: true, C: false } => 1,
+ };
+}
+class Q
+{
+ public bool A = true;
+ public bool B = true;
+ public bool C = true;
+ public bool D = true;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ A: false, D: true, B: true, C: false }' is not covered.
+ // int M(Q t) => t switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ A: false, D: true, B: true, C: false }").WithLocation(4, 21)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_23()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M(Q t) => t switch
+ {
+ (0, """") => 0,
+ (1, ""A"") => 1,
+ };
+}
+class Q
+{
+}
+static class Extensions
+{
+ public static void Deconstruct(this Q q, out int X, out string Y)
+ {
+ X = 2;
+ Y = ""Y"";
+ }
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(0, "A")' is not covered.
+ // int M(Q t) => t switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments(@"(0, ""A"")").WithLocation(4, 21)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_24()
+ {
+ var source =
+@"#nullable enable
+class C
+{
+ int M1(Q o) => o switch { not ((1) { } and (2, 3)) => 0 };
+ int M2(Q o) => o switch { not ((2, 3) and (1) { }) => 0 };
+}
+class Q
+{
+ public void Deconstruct(out object o1) => throw null!;
+ public void Deconstruct(out object o1, out object o2) => throw null!;
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (4,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(1) { } and (2, 3)' is not covered.
+ // int M1(Q o) => o switch { not ((1) { } and (2, 3)) => 0 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(1) { } and (2, 3)").WithLocation(4, 22),
+ // (5,22): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(2, 3) and (1) { }' is not covered.
+ // int M2(Q o) => o switch { not ((2, 3) and (1) { }) => 0 };
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(2, 3) and (1) { }").WithLocation(5, 22)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_25()
+ {
+ var source =
+@$"class C
+{{
+ static int M(string s) => s switch
+ {{
+ """" => 0,
+ {Enumerable.Range((int)'A', (int)('z' - 'A') + 1).Select(x => (char)x)
+ .Aggregate("", (s, c) => s + $"{ObjectDisplay.FormatPrimitive(c.ToString(), ObjectDisplayOptions.EscapeNonPrintableCharacters | ObjectDisplayOptions.UseQuotes)} => 0, ")}
+ }};
+}}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (3,33): 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.
+ // static int M(string s) => s switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments(@"""0""").WithLocation(3, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_26()
+ {
+ var source =
+@$"class C
+{{
+ static int M(string s) => s switch
+ {{
+ """" => 0,
+ {Enumerable.Range((int)'A', (int)('z' - 'A') + 1).Select(x => (char)x)
+ .Aggregate("", (s, c) => s + $"{ObjectDisplay.FormatPrimitive(c.ToString(), ObjectDisplayOptions.EscapeNonPrintableCharacters | ObjectDisplayOptions.UseQuotes)} => 0, ")}
+ {Enumerable.Range(0, 20)
+ .Aggregate("", (s, i) => s + $"{ObjectDisplay.FormatPrimitive(i.ToString(), ObjectDisplayOptions.EscapeNonPrintableCharacters | ObjectDisplayOptions.UseQuotes)} => 0, ")}
+ }};
+}}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (3,33): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '"20"' is not covered.
+ // static int M(string s) => s switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments(@"""20""").WithLocation(3, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_27()
+ {
+ var source =
+@"class C
+{
+ static int M(string s) => s switch
+ {
+ not { Length: 1, Length: 1 } => 0
+ };
+}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (3,33): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Length: 1 }' is not covered.
+ // static int M(string s) => s switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Length: 1 }").WithLocation(3, 33)
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_28()
+ {
+ // Note that "not" of an impossible pattern handles everything.
+ var source =
+@"class C
+{
+ static int M(string s) => s switch
+ {
+ not { Length: 1, Length: 2 } => 0
+ };
+}";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ );
+ }
+
+ [Fact]
+ public void NonexhaustiveEnumDiagnostic_29()
+ {
+ var source =
+@"class C
+{
+ static int M((int x, int y) s) => s switch
+ {
+ not (1, 2) { Extra: 3 } => 0
+ };
+}
+namespace System
+{
+ public struct ValueTuple
+ {
+ public T1 Item1;
+ public T2 Item2;
+ public int Extra;
+ }
+}
+";
+ var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithPatternCombinators);
+ compilation.VerifyDiagnostics(
+ // (3,41): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ x: 1, y: 2, Extra: 3 }' is not covered.
+ // static int M((int x, int y) s) => s switch
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ x: 1, y: 2, Extra: 3 }").WithLocation(3, 41)
+ );
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs
index a081c9a3aa235..12e5e3e0b8984 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs
@@ -1778,9 +1778,9 @@ static int M1(bool? b1, bool? b2)
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (6,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (6,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(true, false)' is not covered.
// return (b1, b2) switch {
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 25)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(true, false)").WithLocation(6, 25)
);
}
@@ -2050,9 +2050,9 @@ static void Main()
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (9,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,19): 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.
// _ = t switch { (3, 4) => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(9, 19)
);
CompileAndVerify(compilation, expectedOutput: "InvalidOperationException");
}
@@ -2093,9 +2093,9 @@ public SwitchExpressionException() {}
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (9,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,19): 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.
// _ = t switch { (3, 4) => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(9, 19)
);
CompileAndVerify(compilation, expectedOutput: "SwitchExpressionException()");
}
@@ -2136,9 +2136,9 @@ public class SwitchExpressionException : InvalidOperationException
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (9,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,19): 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.
// _ = t switch { (3, 4) => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(9, 19)
);
CompileAndVerify(compilation, expectedOutput: "SwitchExpressionException((1, 2))");
}
@@ -2178,9 +2178,9 @@ public class SwitchExpressionException : InvalidOperationException
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (8,24): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (8,24): 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.
// _ = (1, 2) switch { (3, 4) => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(8, 24)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(8, 24)
);
CompileAndVerify(compilation, expectedOutput: "SwitchExpressionException((1, 2))");
}
@@ -2225,9 +2225,9 @@ public SwitchExpressionException() {}
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (9,19): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (9,19): 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.
// _ = r switch { (3, 4) => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(9, 19)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(9, 19)
);
CompileAndVerify(compilation, expectedOutput: "SwitchExpressionException()");
}
@@ -2817,9 +2817,9 @@ public static void Main()
";
var compilation = CreatePatternCompilation(source, options: TestOptions.ReleaseExe);
compilation.VerifyDiagnostics(
- // (7,32): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (7,32): 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.
// Console.Write((x, 300) switch { ((1, int x2), int y) => x2+y });
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(7, 32)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("((0, _), _)").WithLocation(7, 32)
);
CompileAndVerify(compilation, expectedOutput: "320");
}
@@ -2893,9 +2893,9 @@ public static void Main()
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (6,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (6,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'not null' is not covered.
// _ = o switch { null => 1 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 15)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("not null").WithLocation(6, 15)
);
}
@@ -2932,9 +2932,9 @@ public int M(bool b)
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (22,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (22,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'false' is not covered.
// return b switch
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(22, 18)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("false").WithLocation(22, 18)
);
CompileAndVerify(compilation, expectedOutput: "1 throw");
}
@@ -3084,9 +3084,9 @@ public class SwitchExpressionException : InvalidOperationException
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (17,23): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (17,23): 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.
// return (x, y) switch { (1, 2) => 3 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(17, 23)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _)").WithLocation(17, 23)
);
CompileAndVerify(compilation, expectedOutput: @"3
SwitchExpressionException((1, 3))");
@@ -3220,9 +3220,9 @@ public class SwitchExpressionException : InvalidOperationException
";
var compilation = CreatePatternCompilation(source);
compilation.VerifyDiagnostics(
- // (17,44): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (17,44): 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.
// return (x, y, a, b, c, d, e, f, g) switch { (1, 2, _, _, _, _, _, _, _) => 3 };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(17, 44)
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(0, _, _, _, _, _, _, _, _)").WithLocation(17, 44)
);
CompileAndVerify(compilation, expectedOutput: @"3
SwitchExpressionException((1, 3, 3, 4, 5, 6, 7, 8, 9))");
diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/StatementAttributeParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/StatementAttributeParsingTests.cs
index 25f5c3137b85e..4cda7e70f87a7 100644
--- a/src/Compilers/CSharp/Test/Syntax/Parsing/StatementAttributeParsingTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Parsing/StatementAttributeParsingTests.cs
@@ -6230,9 +6230,9 @@ void Goo(int a)
// (6,12): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
// [A]a switch { };
Diagnostic(ErrorCode.ERR_IllegalStatement, "a switch { }").WithLocation(6, 12),
- // (6,14): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
+ // (6,14): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.
// [A]a switch { };
- Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 14),
+ Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(6, 14),
// (6,14): error CS8506: No best type was found for the switch expression.
// [A]a switch { };
Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(6, 14));
diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicIntelliSense.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicIntelliSense.cs
index aa4228142eeff..b39b95567d02c 100644
--- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicIntelliSense.cs
+++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicIntelliSense.cs
@@ -140,7 +140,7 @@ End Sub
assertCaretPosition: true);
}
- [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
+ [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/45234"), Trait(Traits.Feature, Traits.Features.Completion)]
public void TypeAVariableDeclaration()
{
SetUpEditor(@"