Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
alrz committed Aug 1, 2021
1 parent 91dbb3a commit bcdc483
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 32 deletions.
107 changes: 75 additions & 32 deletions src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ private static string SamplePatternForTemp(
tryHandleSingleTest() ??
tryHandleTypeTestAndTypeEvaluation(ref unnamedEnumValue) ??
tryHandleUnboxNullableValueType(ref unnamedEnumValue) ??
tryHandleListPattern(ref unnamedEnumValue) ??
tryHandleTuplePattern(ref unnamedEnumValue) ??
tryHandleNumericLimits(ref unnamedEnumValue) ??
tryHandleRecursivePattern(ref unnamedEnumValue) ??
Expand Down Expand Up @@ -236,6 +237,48 @@ constraints[0] is (BoundDagNonNullTest _, true) &&
return null;
}

// Handle the special case of a list pattern
string tryHandleListPattern(ref bool unnamedEnumValue)
{
if (constraints.IsEmpty &&
evaluations.Length >= 1 &&
evaluations[0] is BoundDagPropertyEvaluation { IsLengthOrCount: true } lengthOrCount &&
evaluations.Skip(1) is var elements && elements.All(e => e is BoundDagIndexerEvaluation))
{
Debug.Assert(lengthOrCount.Property.Type.SpecialType == SpecialType.System_Int32);
var lengthTemp = new BoundDagTemp(lengthOrCount.Syntax, lengthOrCount.Property.Type, lengthOrCount);
var lengthValues = (IValueSet<int>)computeRemainingValues(ValueSetFactory.ForLength, getArray(constraintMap, lengthTemp));
// We would not have been asked to produce an example of a missing pattern if no values are missing
Debug.Assert(!lengthValues.IsEmpty);
if (lengthValues.Any(BinaryOperatorKind.Equal, 0))
return "[]";
int lengthValue = lengthValues.Sample.Int32Value;
var subpatterns = new ArrayBuilder<string>(lengthValue);
subpatterns.AddMany("_", lengthValue);
foreach (BoundDagIndexerEvaluation e in elements)
{
var elementTemp = new BoundDagTemp(e.Syntax, e.IndexerType, e);
var index = e.Index < 0 ? e.Index + lengthValue : e.Index - lengthValue;
if (index < 0 || index >= lengthValue)
break; // TODO
var oldPattern = subpatterns[index];
var newPattern = SamplePatternForTemp(elementTemp, constraintMap, evaluationMap, requireExactType: false, ref unnamedEnumValue);
subpatterns[index] = makeConjunct(oldPattern, newPattern);
}

return "[" + string.Join(", ", subpatterns) + "]";
}

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 a tuple pattern
string tryHandleTuplePattern(ref bool unnamedEnumValue)
{
Expand All @@ -262,13 +305,6 @@ string tryHandleTuplePattern(ref bool unnamedEnumValue)
}

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
Expand All @@ -283,33 +319,10 @@ string tryHandleNumericLimits(ref bool unnamedEnumValue)
(BoundDagNonNullTest _, true) => true,
_ => false
}) &&
ValueSetFactory.ForType(input.Type) is { } fac)
ValueSetFactory.ForInput(input) 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)
{
if (value.IsBad)
return;
var filtered = fac.Related(relation, value);
if (!sense)
filtered = filtered.Complement();
remainingValues = remainingValues.Intersect(filtered);
}
}

IValueSet remainingValues = computeRemainingValues(fac, constraints);
if (remainingValues.Complement().IsEmpty)
return "_";

Expand Down Expand Up @@ -399,6 +412,36 @@ string produceFallbackPattern()
{
return requireExactType ? input.Type.ToDisplayString() : "_";
}

IValueSet computeRemainingValues(IValueSetFactory fac, ImmutableArray<(BoundDagTest test, bool sense)> constraints)
{
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)
{
if (value.IsBad)
return;
var filtered = fac.Related(relation, value);
if (!sense)
filtered = filtered.Complement();
remainingValues = remainingValues.Intersect(filtered);
}
}

return remainingValues;
}
}

private static string SampleValueString(IValueSet remainingValues, TypeSymbol type, bool requireExactType, ref bool unnamedEnumValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,58 @@ public static void Test(Span<int> a, Span<C> b)
comp.VerifyEmitDiagnostics();
}

[Fact]
public void Explainer_01()
{
var src = @"
using System;
class C
{
public int X = 0, Y = 0;
public static void Test(Span<int> a, Span<C> b)
{
/*01*/ _ = a switch { [.., >=0] or [<0] or { Length: 0 } => 0 };
/*02*/ _ = a switch { [.., >=0] or [<0] or { Length: > 1 } => 0 };
/*03*/ _ = a switch { [.., >=0] or { Length: 0 or > 1 } => 0 };
/*04*/ _ = a switch { [<0] or { Length: > 1 } => 0 };
/*05*/ _ = a switch { [.., >=0] or [..[.., <0] ] => 0 };
/*06*/ _ = b switch { [.., { X: >=0, Y: <0}] or [ { Y: >=0, X: <0} ] or
[.., { Y: >=0, X: <0}] or [ { X: >=0, Y: <0} ] or
[.., { X: <=0 }] or { Length: 0 or > 1 } => 0 };
/*07*/ _ = b switch { [.., { X: >=0, Y: <0}] or [ { Y: >=0, X: <0} ] or
[.., { Y: >=0, X: <0}] or [ { X: >=0, Y: <0} ] or
[{ X: >0 }] or { Length: 0 or > 1 } => 0 };
/*08*/ _ = b switch { [.., { X: >=0, Y: <0}] or
[.., { Y: >=0, X: <0}] or [ { X: >=0, Y: <0} ] or
[.., { X: <=0 }] or [{ X: >0 }] or { Length: 0 or > 1 } => 0 };
}
}";
var comp = CreateCompilationWithIndexAndRangeAndSpan(src, parseOptions: TestOptions.RegularWithListPatterns);
comp.VerifyEmitDiagnostics(
// (8,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.
// /*01*/ _ = a switch { [.., >=0] or [<0] or { Length: 0 } => 0 };
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[_, -1]").WithLocation(8, 22),
// (9,22): 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.
// /*02*/ _ = a switch { [.., >=0] or [<0] or { Length: > 1 } => 0 };
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[]").WithLocation(9, 22),
// (10,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.
// /*03*/ _ = a switch { [.., >=0] or { Length: 0 or > 1 } => 0 };
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[-1]").WithLocation(10, 22),
// (11,22): 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.
// /*04*/ _ = a switch { [<0] or { Length: > 1 } => 0 };
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[_]").WithLocation(11, 22),
// (12,22): 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.
// /*05*/ _ = a switch { [.., >=0] or [..[.., <0] ] => 0 };
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[]").WithLocation(12, 22),
// (13,22): 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: 0 }]' is not covered.
// /*06*/ _ = b switch { [.., { X: >=0, Y: <0}] or [ { Y: >=0, X: <0} ] or
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[{ X: 1, Y: 0 }]").WithLocation(13, 22),
// (16,22): 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 }]' is not covered.
// /*07*/ _ = b switch { [.., { X: >=0, Y: <0}] or [ { Y: >=0, X: <0} ] or
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("[{ X: -1 }]").WithLocation(16, 22)
);
}

[Fact]
public void Exhaustiveness_02()
{
Expand Down

0 comments on commit bcdc483

Please sign in to comment.