-
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
Implement pattern-matching via ITuple #28859
Changes from 3 commits
572ed34
f8f9900
1b4d070
c84817f
ef7f052
691acf9
7372e86
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 |
---|---|---|
|
@@ -479,7 +479,6 @@ private void BindPatternDesignation( | |
default: | ||
throw ExceptionUtilities.UnexpectedValue(designation.Kind()); | ||
} | ||
|
||
} | ||
|
||
TypeSymbol BindRecursivePatternType(TypeSyntax typeSyntax, TypeSymbol inputType, DiagnosticBag diagnostics, ref bool hasErrors, out BoundTypeExpression boundDeclType) | ||
|
@@ -513,6 +512,11 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo | |
TypeSyntax typeSyntax = node.Type; | ||
TypeSymbol declType = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression boundDeclType); | ||
|
||
if (ShouldUseITuple(node, declType, diagnostics, out NamedTypeSymbol iTupleType, out MethodSymbol iTupleGetLength, out MethodSymbol iTupleGetItem)) | ||
{ | ||
return BindITuplePattern(node, inputType, iTupleType, iTupleGetLength, iTupleGetItem, hasErrors, diagnostics); | ||
} | ||
|
||
MethodSymbol deconstructMethod = null; | ||
ImmutableArray<BoundSubpattern> deconstructionSubpatterns = default; | ||
if (node.DeconstructionPatternClause != null) | ||
|
@@ -532,6 +536,37 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo | |
deconstruction: deconstructionSubpatterns, properties: properties, variable: variableSymbol, variableAccess: variableAccess, hasErrors: hasErrors); | ||
} | ||
|
||
private BoundPattern BindITuplePattern( | ||
RecursivePatternSyntax node, | ||
TypeSymbol inputType, | ||
NamedTypeSymbol iTupleType, | ||
MethodSymbol iTupleGetLength, | ||
MethodSymbol iTupleGetItem, | ||
bool hasErrors, | ||
DiagnosticBag diagnostics) | ||
{ | ||
var objectType = Compilation.GetSpecialType(SpecialType.System_Object); | ||
var deconstruction = node.DeconstructionPatternClause; | ||
var patterns = ArrayBuilder<BoundSubpattern>.GetInstance(deconstruction.Subpatterns.Count); | ||
foreach (var subpatternSyntax in deconstruction.Subpatterns) | ||
{ | ||
TypeSymbol elementType = objectType; | ||
if (subpatternSyntax.NameColon != null) | ||
{ | ||
// error: name not permitted in ITuple deconstruction | ||
diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location); | ||
} | ||
|
||
var boundSubpattern = new BoundSubpattern( | ||
subpatternSyntax, | ||
null, | ||
BindPattern(subpatternSyntax.Pattern, elementType, false, diagnostics)); | ||
patterns.Add(boundSubpattern); | ||
} | ||
|
||
return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, patterns.ToImmutableAndFree(), inputType, hasErrors); | ||
} | ||
|
||
private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause( | ||
DeconstructionPatternClauseSyntax node, | ||
TypeSymbol declType, | ||
|
@@ -551,6 +586,7 @@ private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause( | |
diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, location, declType, elementTypes.Length, node.Subpatterns.Count); | ||
hasErrors = true; | ||
} | ||
|
||
for (int i = 0; i < node.Subpatterns.Count; i++) | ||
{ | ||
var subpatternSyntax = node.Subpatterns[i]; | ||
|
@@ -572,7 +608,7 @@ private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause( | |
} | ||
else | ||
{ | ||
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. Consider keeping |
||
// It is not a tuple type. Seek an appropriate Deconstruct method. | ||
// It is not a tuple type or ITuple. Seek an appropriate Deconstruct method. | ||
var inputPlaceholder = new BoundImplicitReceiver(node, declType); // A fake receiver expression to permit us to reuse binding logic | ||
BoundExpression deconstruct = MakeDeconstructInvocationExpression( | ||
node.Subpatterns.Count, inputPlaceholder, node, diagnostics, outPlaceholders: out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders); | ||
|
@@ -608,7 +644,8 @@ private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause( | |
Debug.Assert(deconstructMethod is ErrorMethodSymbol); | ||
} | ||
} | ||
BoundSubpattern boundSubpattern = new BoundSubpattern( | ||
|
||
var boundSubpattern = new BoundSubpattern( | ||
subPattern, | ||
parameter, | ||
BindPattern(subPattern.Pattern, elementType, isError, diagnostics) | ||
|
@@ -620,6 +657,112 @@ private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause( | |
return patterns.ToImmutableAndFree(); | ||
} | ||
|
||
private bool ShouldUseITuple( | ||
RecursivePatternSyntax node, | ||
TypeSymbol declType, | ||
DiagnosticBag diagnostics, | ||
out NamedTypeSymbol iTupleType, | ||
out MethodSymbol iTupleGetLength, | ||
out MethodSymbol iTupleGetItem) | ||
{ | ||
iTupleType = null; | ||
iTupleGetLength = iTupleGetItem = null; | ||
if (declType.IsTupleType) | ||
{ | ||
return false; | ||
} | ||
|
||
if (node.Type != null) | ||
{ | ||
// ITuple matching only applies if no type is given explicitly. | ||
return false; | ||
} | ||
|
||
if (node.PropertyPatternClause != null) | ||
{ | ||
// ITuple matching only applies if there is no property pattern part. | ||
return false; | ||
} | ||
|
||
if (node.DeconstructionPatternClause == null) | ||
{ | ||
// ITuple matching only applies if there is a deconstruction pattern part. | ||
return false; | ||
} | ||
|
||
if (node.Designation != null) | ||
{ | ||
// ITuple matching only applies if there is no designation (what type would the designation be?) | ||
return false; | ||
} | ||
|
||
if (Compilation.LanguageVersion < MessageID.IDS_FeatureRecursivePatterns.RequiredVersion()) | ||
{ | ||
return false; | ||
} | ||
|
||
iTupleType = Compilation.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_ITuple); | ||
if (iTupleType.TypeKind != TypeKind.Interface) | ||
{ | ||
// When compiling to a platform that lacks the interface ITuple (i.e. it is an error type), we simply do not match using it. | ||
return false; | ||
} | ||
|
||
// Resolution 2017-11-20 LDM: permit matching via ITuple only for `object`, `ITuple`, and types that are | ||
// declared to implement `ITuple` but contain no `Deconstruct` methods. | ||
if (declType != (object)Compilation.GetSpecialType(SpecialType.System_Object) && | ||
declType != (object)Compilation.DynamicType && | ||
declType != (object)iTupleType && | ||
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.
I'm surprised we're using reference equality. Shouldn't we be using symbol equality ( 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. They are the same for "original" type symbols, which all of these are. In reply to: 206291161 [](ancestors = 206291161) |
||
!hasBaseInterface(declType, iTupleType)) | ||
{ | ||
return false; | ||
} | ||
|
||
// If there are any accessible Deconstruct method members, then we do not permit ITuple | ||
var lookupResult = LookupResult.GetInstance(); | ||
try | ||
{ | ||
const LookupOptions options = LookupOptions.MustBeInvocableIfMember | LookupOptions.AllMethodsOnArityZero; | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
this.LookupMembersWithFallback(lookupResult, declType, WellKnownMemberNames.DeconstructMethodName, arity: 0, ref useSiteDiagnostics, basesBeingResolved: null, options: options); | ||
diagnostics.Add(node, useSiteDiagnostics); | ||
if (lookupResult.IsMultiViable) | ||
{ | ||
foreach (var symbol in lookupResult.Symbols) | ||
{ | ||
if (symbol.Kind == SymbolKind.Method) | ||
{ | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
finally | ||
{ | ||
lookupResult.Free(); | ||
} | ||
|
||
// Ensure ITuple has a Length and indexer | ||
iTupleGetLength = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Length); | ||
iTupleGetItem = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Item); | ||
if (iTupleGetLength is null || iTupleGetItem is null) | ||
{ | ||
// This might not result in an ideal diagnostic | ||
return false; | ||
} | ||
|
||
// passed all the filters; permit using ITuple | ||
return true; | ||
|
||
bool hasBaseInterface(TypeSymbol type, NamedTypeSymbol possibleBaseInterface) | ||
{ | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
var result = Compilation.Conversions.ClassifyBuiltInConversion(type, possibleBaseInterface, ref useSiteDiagnostics).IsImplicit; | ||
diagnostics.Add(node, useSiteDiagnostics); | ||
return result; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Check that the given name designates a tuple element at the given index, and return that element. | ||
/// </summary> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1177,6 +1177,10 @@ | |
<Node Name="BoundDagPropertyEvaluation" Base="BoundDagEvaluation"> | ||
<Field Name="Property" Type="PropertySymbol" Null="disallow"/> | ||
</Node> | ||
<Node Name="BoundDagIndexEvaluation" Base="BoundDagEvaluation"> | ||
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.
Do we need to customize the 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. No, that is for nodes that extend |
||
<Field Name="Property" Type="PropertySymbol" Null="disallow"/> | ||
<Field Name="Index" Type="int"/> | ||
</Node> | ||
|
||
<Node Name="BoundSwitchSection" Base="BoundStatementList"> | ||
<Field Name="Locals" Type="ImmutableArray<LocalSymbol>"/> | ||
|
@@ -1778,6 +1782,12 @@ | |
<Field Name="VariableAccess" Type="BoundExpression" Null="allow"/> | ||
</Node> | ||
|
||
<Node Name="BoundITuplePattern" Base="BoundPattern"> | ||
<Field Name="GetLengthMethod" Type="MethodSymbol" Null="disallow"/> | ||
<Field Name="GetItemMethod" Type="MethodSymbol" Null="disallow"/> | ||
<Field Name="Subpatterns" Type="ImmutableArray<BoundSubpattern>" Null="disallow"/> | ||
</Node> | ||
|
||
<!-- A subpattern, either in a positional or property part of a recursive pattern. --> | ||
<Node Name="BoundSubpattern" Base="BoundNode"> | ||
<!-- The tuple element or parameter in a positional pattern, or the property or field in a property pattern. --> | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Please add a test for this case. #Resolved