From 212fc8f225108b5f45b5c60c5a2037733427d1b9 Mon Sep 17 00:00:00 2001 From: Neal Gafter Date: Mon, 22 Jun 2020 19:49:36 -0700 Subject: [PATCH] Give an example in the non-exhaustive diagnostic (#44702) * When reporting a switch is not exhaustive, give an example why. Fixes #43943 * Skip a flaky test Related to #45234 --- .../Portable/Binder/PatternExplainer.cs | 505 +++++++++++ .../Portable/Binder/SwitchExpressionBinder.cs | 8 +- .../CSharp/Portable/CSharpResources.resx | 4 +- .../FlowAnalysis/NullableWalker_Patterns.cs | 9 +- .../CSharp/Portable/Utilities/IValueSet.cs | 8 + .../Portable/Utilities/IValueSetFactory.cs | 10 + .../Utilities/ValueSetFactory.BoolValueSet.cs | 3 + .../ValueSetFactory.BoolValueSetFactory.cs | 4 + .../Utilities/ValueSetFactory.ByteTC.cs | 2 + .../Utilities/ValueSetFactory.CharTC.cs | 8 +- .../Utilities/ValueSetFactory.DecimalTC.cs | 2 + .../ValueSetFactory.DecimalValueSetFactory.cs | 4 + .../Utilities/ValueSetFactory.DoubleTC.cs | 2 + .../ValueSetFactory.EnumeratedValueSet.cs | 41 +- ...lueSetFactory.EnumeratedValueSetFactory.cs | 10 +- .../ValueSetFactory.FloatingValueSet.cs | 23 +- ...ValueSetFactory.FloatingValueSetFactory.cs | 4 + .../Utilities/ValueSetFactory.INumericTC.cs | 5 + .../Utilities/ValueSetFactory.IntTC.cs | 2 + .../Utilities/ValueSetFactory.LongTC.cs | 2 + .../Utilities/ValueSetFactory.NintValueSet.cs | 22 +- .../ValueSetFactory.NintValueSetFactory.cs | 4 + .../ValueSetFactory.NuintValueSet.cs | 22 +- .../ValueSetFactory.NuintValueSetFactory.cs | 4 + .../ValueSetFactory.NumericValueSet.cs | 21 +- .../ValueSetFactory.NumericValueSetFactory.cs | 4 + .../Utilities/ValueSetFactory.SByteTC.cs | 2 + .../Utilities/ValueSetFactory.ShortTC.cs | 2 + .../Utilities/ValueSetFactory.SingleTC.cs | 2 + .../Utilities/ValueSetFactory.StringTC.cs | 2 +- .../Utilities/ValueSetFactory.UIntTC.cs | 2 + .../Utilities/ValueSetFactory.ULongTC.cs | 2 + .../Utilities/ValueSetFactory.UShortTC.cs | 2 + .../Portable/xlf/CSharpResources.cs.xlf | 8 +- .../Portable/xlf/CSharpResources.de.xlf | 8 +- .../Portable/xlf/CSharpResources.es.xlf | 8 +- .../Portable/xlf/CSharpResources.fr.xlf | 8 +- .../Portable/xlf/CSharpResources.it.xlf | 8 +- .../Portable/xlf/CSharpResources.ja.xlf | 8 +- .../Portable/xlf/CSharpResources.ko.xlf | 8 +- .../Portable/xlf/CSharpResources.pl.xlf | 8 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 8 +- .../Portable/xlf/CSharpResources.ru.xlf | 8 +- .../Portable/xlf/CSharpResources.tr.xlf | 8 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 8 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 8 +- .../CSharp/Test/Emit/CodeGen/PatternTests.cs | 8 +- .../IOperationTests_ISwitchExpression.cs | 8 +- .../IOperationTests_ISwitchOperation.cs | 4 +- .../Semantics/NullableReferenceTypesTests.cs | 8 +- .../NullableReferenceTypesVsPatterns.cs | 44 +- .../Semantics/PatternMatchingTests2.cs | 12 +- .../Semantics/PatternMatchingTests3.cs | 833 +++++++++++++++++- .../Semantics/PatternMatchingTests4.cs | 44 +- .../Parsing/StatementAttributeParsingTests.cs | 4 +- .../VisualBasic/BasicIntelliSense.cs | 2 +- 56 files changed, 1652 insertions(+), 166 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - switch 式が一部の null 入力を処理しません (すべてを網羅していません)。 + 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 (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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - switch 식은 일부 null 입력을 처리하지 않습니다(전체 아님). + 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 (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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - Выражение 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. + 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 some null inputs (it is not exhaustive). - Выражение switch не обрабатывает некоторые входные данные NULL (оно не полное). + 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 (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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - 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. + 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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - Switch 表达式不会处理某些为 null 的输入(它并非详尽无遗)。 + 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 (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 @@ - The switch expression does not handle all possible values of its input type (it is not exhaustive). - 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. + 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 some null inputs (it is not exhaustive). - Switch 運算式未處理某些 null 輸入 (並不徹底)。 + 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 (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(@"