Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explainer for list-patterns #57210

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 133 additions & 22 deletions src/Compilers/CSharp/Portable/Binder/PatternExplainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ private static string SamplePatternForTemp(
tryHandleTuplePattern(ref unnamedEnumValue) ??
tryHandleNumericLimits(ref unnamedEnumValue) ??
tryHandleRecursivePattern(ref unnamedEnumValue) ??
tryHandleListPattern(ref unnamedEnumValue) ??
Comment on lines 179 to +180
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this after recursive patterns will make it unnecessary to worry about [] or [..] but we may choose to generate those instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that something I've been debating. Should we say { Length: 0 } or []?

produceFallbackPattern();

static ImmutableArray<T> getArray<T>(Dictionary<BoundDagTemp, ArrayBuilder<T>> map, BoundDagTemp temp)
Expand Down Expand Up @@ -283,40 +284,150 @@ 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)
IValueSet remainingValues = computeRemainingValues(fac, constraints);
if (remainingValues.Complement().IsEmpty)
return "_";

return SampleValueString(remainingValues, input.Type, requireExactType: requireExactType, unnamedEnumValue: ref unnamedEnumValue);
}

return null;
}

static 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)
{
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;
}

// Handle the special case of a list pattern
string tryHandleListPattern(ref bool unnamedEnumValue)
{
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;
}

if (evaluations[0] is not BoundDagPropertyEvaluation { IsLengthOrCount: true } lengthOrCount)
{
return null;
}
int length = computeLength(new BoundDagTemp(lengthOrCount.Syntax, lengthOrCount.Property.Type, lengthOrCount));

BoundDagSliceEvaluation slice = null;
var subpatterns = new ArrayBuilder<string>(length);
subpatterns.AddMany("_", length);

for (int i = 1; i < evaluations.Length; i++)
{
switch (evaluations[i])
{
case BoundDagIndexerEvaluation e:
var indexerTemp = new BoundDagTemp(e.Syntax, e.IndexerType, e);
int integerIndex = e.Index;
var newPattern = SamplePatternForTemp(indexerTemp, constraintMap, evaluationMap, requireExactType: false, ref unnamedEnumValue);
var index = integerIndex >= 0 ? integerIndex : length + integerIndex;

Debug.Assert(subpatterns[index] == "_");
subpatterns[index] = newPattern;
break;
case BoundDagSliceEvaluation e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may produce multiple slice patterns in the list but since we won't construct a dag for invalid patterns that probably won't happen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we don't get here when multiple slices. Added test

Debug.Assert(slice is null);
slice = e;
break;
default:
return null;
}
}

var pooledBuilder = PooledStringBuilder.GetInstance();
var builder = pooledBuilder.Builder;

builder.Append("[");
for (int i = 0; i < length; i++)
{
if (slice?.StartIndex == i)
{
var subInput = new BoundDagTemp(slice.Syntax, slice.SliceType, slice);
var sample2 = SamplePatternForTemp(subInput, constraintMap, evaluationMap, false, ref unnamedEnumValue);
if (sample2 != "_")
{
case BoundDagValueTest v:
addRelation(BinaryOperatorKind.Equal, v.Value);
break;
case BoundDagRelationalTest r:
addRelation(r.Relation, r.Value);
break;
appendWithSeparatorIfNeeded(builder, ".. " + sample2);
}
void addRelation(BinaryOperatorKind relation, ConstantValue value)

if (sample2.StartsWith("["))
{
if (value.IsBad)
return;
var filtered = fac.Related(relation, value);
if (!sense)
filtered = filtered.Complement();
remainingValues = remainingValues.Intersect(filtered);
// The nested slice is handling a section of the list
int endIndex = slice.EndIndex >= 0 ? slice.EndIndex : length + slice.EndIndex - 1;
i += endIndex - slice.StartIndex;
continue;
}
}

if (remainingValues.Complement().IsEmpty)
return "_";
appendWithSeparatorIfNeeded(builder, subpatterns[i]);
}
builder.Append("]");

return SampleValueString(remainingValues, input.Type, requireExactType: requireExactType, unnamedEnumValue: ref unnamedEnumValue);
return pooledBuilder.ToStringAndFree();
}

static void appendWithSeparatorIfNeeded(StringBuilder builder, string sample)
{
if (builder.Length > 1)
{
builder.Append(", ");
}

return null;
builder.Append(sample);
}

int computeLength(BoundDagTemp lengthTemp)
{
if (lengthTemp is { Source: BoundDagPropertyEvaluation { Input: { Source: BoundDagSliceEvaluation slice } } })
{
var outerLength = computeLength(slice.LengthTemp);
return outerLength - slice.StartIndex + slice.EndIndex;
}

var lengthValues = (IValueSet<int>)computeRemainingValues(ValueSetFactory.ForLength, getArray(constraintMap, lengthTemp));
var length = lengthValues.Sample.Int32Value;
return length;
}

// Handle the special case of a recursive pattern
Expand Down
Loading