-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Explainer for list-patterns #57210
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -177,6 +177,7 @@ private static string SamplePatternForTemp( | |
tryHandleTuplePattern(ref unnamedEnumValue) ?? | ||
tryHandleNumericLimits(ref unnamedEnumValue) ?? | ||
tryHandleRecursivePattern(ref unnamedEnumValue) ?? | ||
tryHandleListPattern(ref unnamedEnumValue) ?? | ||
produceFallbackPattern(); | ||
|
||
static ImmutableArray<T> getArray<T>(Dictionary<BoundDagTemp, ArrayBuilder<T>> map, BoundDagTemp temp) | ||
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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[]
?