Skip to content

Commit

Permalink
Implement pattern-matching via ITuple
Browse files Browse the repository at this point in the history
  • Loading branch information
gafter committed Jul 26, 2018
1 parent b364cfc commit 572ed34
Show file tree
Hide file tree
Showing 34 changed files with 1,251 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ private BoundExpression MakeDeconstructInvocationExpression(
outVars.Add(variable);
}

const string methodName = "Deconstruct";
const string methodName = WellKnownMemberNames.DeconstructMethodName;
var memberAccess = BindInstanceMemberAccess(
rightSyntax, receiverSyntax, receiver, methodName, rightArity: 0,
typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>), typeArguments: default(ImmutableArray<TypeSymbol>),
Expand Down
216 changes: 179 additions & 37 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -532,6 +536,38 @@ 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);
for (int i = 0; i < deconstruction.Subpatterns.Count; i++)
{
var subpatternSyntax = deconstruction.Subpatterns[i];
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,
Expand Down Expand Up @@ -569,55 +605,161 @@ private ImmutableArray<BoundSubpattern> BindDeconstructionPatternClause(
BindPattern(subpatternSyntax.Pattern, elementType, isError, diagnostics));
patterns.Add(boundSubpattern);
}

return patterns.ToImmutableAndFree();
}
else

// 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);
deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol;
if (deconstructMethod is null)
{
// It is not a tuple type. 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);
deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol;
if (deconstructMethod is null)
{
hasErrors = true;
}
hasErrors = true;
}

int skippedExtensionParameters = deconstructMethod?.IsExtensionMethod == true ? 1 : 0;
for (int i = 0; i < node.Subpatterns.Count; i++)
int skippedExtensionParameters = deconstructMethod?.IsExtensionMethod == true ? 1 : 0;
for (int i = 0; i < node.Subpatterns.Count; i++)
{
var subPattern = node.Subpatterns[i];
bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length;
TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type;
ParameterSymbol parameter = null;
if (subPattern.NameColon != null && !isError)
{
var subPattern = node.Subpatterns[i];
bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length;
TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type;
ParameterSymbol parameter = null;
if (subPattern.NameColon != null && !isError)
// Check that the given name is the same as the corresponding parameter of the method.
string name = subPattern.NameColon.Name.Identifier.ValueText;
int parameterIndex = i + skippedExtensionParameters;
if (parameterIndex < deconstructMethod.ParameterCount)
{
// Check that the given name is the same as the corresponding parameter of the method.
string name = subPattern.NameColon.Name.Identifier.ValueText;
int parameterIndex = i + skippedExtensionParameters;
if (parameterIndex < deconstructMethod.ParameterCount)
parameter = deconstructMethod.Parameters[parameterIndex];
string parameterName = parameter.Name;
if (name != parameterName)
{
parameter = deconstructMethod.Parameters[parameterIndex];
string parameterName = parameter.Name;
if (name != parameterName)
{
diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName);
}
diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName);
}
else
}
else
{
Debug.Assert(deconstructMethod is ErrorMethodSymbol);
}
}
var boundSubpattern = new BoundSubpattern(
subPattern,
parameter,
BindPattern(subPattern.Pattern, elementType, isError, diagnostics)
);
patterns.Add(boundSubpattern);
}

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 (((CSharpParseOptions)node.SyntaxTree.Options).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 &&
!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)
{
Debug.Assert(deconstructMethod is ErrorMethodSymbol);
return false;
}
}
BoundSubpattern boundSubpattern = new BoundSubpattern(
subPattern,
parameter,
BindPattern(subPattern.Pattern, elementType, isError, diagnostics)
);
patterns.Add(boundSubpattern);
}
}
finally
{
lookupResult.Free();
}

return patterns.ToImmutableAndFree();
// 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>
Expand Down
37 changes: 37 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,48 @@ private void MakeTestsAndBindings(
case BoundRecursivePattern recursive:
MakeTestsAndBindings(input, recursive, tests, bindings);
break;
case BoundITuplePattern iTuple:
MakeTestsAndBindings(input, iTuple, tests, bindings);
break;
default:
throw new NotImplementedException(pattern.Kind.ToString());
}
}

private void MakeTestsAndBindings(
BoundDagTemp input,
BoundITuplePattern pattern,
ArrayBuilder<BoundDagTest> tests,
ArrayBuilder<BoundPatternBinding> bindings)
{
var syntax = pattern.Syntax;
var patternLength = pattern.Deconstruction.Length;
var objectType = this._compilation.GetSpecialType(SpecialType.System_Object);
var getLengthProperty = (PropertySymbol)pattern.getLengthMethod.AssociatedSymbol;
Debug.Assert(getLengthProperty.Type.SpecialType == SpecialType.System_Int32);
var getItemProperty = (PropertySymbol)pattern.getItemMethod.AssociatedSymbol;
var iTupleType = getLengthProperty.ContainingType;
Debug.Assert(iTupleType.Name == "ITuple");

tests.Add(new BoundDagTypeTest(syntax, iTupleType, input));
var valueAsITupleEvaluation = new BoundDagTypeEvaluation(syntax, iTupleType, input);
tests.Add(valueAsITupleEvaluation);
var valueAsITuple = new BoundDagTemp(syntax, iTupleType, valueAsITupleEvaluation, 0);

var lengthEvaluation = new BoundDagPropertyEvaluation(syntax, getLengthProperty, valueAsITuple);
tests.Add(lengthEvaluation);
var lengthTemp = new BoundDagTemp(syntax, this._compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation, 0);
tests.Add(new BoundDagValueTest(syntax, ConstantValue.Create(patternLength), lengthTemp));

for (int i = 0; i < patternLength; i++)
{
var indexEvaluation = new BoundDagIndexEvaluation(syntax, getItemProperty, i, valueAsITuple);
tests.Add(indexEvaluation);
var indexTemp = new BoundDagTemp(syntax, objectType, indexEvaluation, 0);
MakeTestsAndBindings(indexTemp, pattern.Deconstruction[i].Pattern, tests, bindings);
}
}

private void MakeTestsAndBindings(
BoundDagTemp input,
BoundDeclarationPattern declaration,
Expand Down
16 changes: 14 additions & 2 deletions src/Compilers/CSharp/Portable/BoundTree/BoundDagEvaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.CSharp
partial class BoundDagEvaluation
{
public override bool Equals(object obj) => obj is BoundDagEvaluation other && this.Equals(other);
public bool Equals(BoundDagEvaluation other)
public virtual bool Equals(BoundDagEvaluation other)
{
return other != (object)null && this.Kind == other.Kind && this.Input.Equals(other.Input) && this.Symbol == other.Symbol;
}
Expand All @@ -21,6 +21,7 @@ private Symbol Symbol
case BoundDagPropertyEvaluation e: return e.Property;
case BoundDagTypeEvaluation e: return e.Type;
case BoundDagDeconstructEvaluation e: return e.DeconstructMethod;
case BoundDagIndexEvaluation e: return e.Property;
default: throw ExceptionUtilities.UnexpectedValue(this.Kind);
}
}
Expand All @@ -31,11 +32,22 @@ public override int GetHashCode()
}
public static bool operator ==(BoundDagEvaluation left, BoundDagEvaluation right)
{
return (left == (object)null) ? right == (object)null : left.Equals(right);
return (left is null) ? right is null : left.Equals(right);
}
public static bool operator !=(BoundDagEvaluation left, BoundDagEvaluation right)
{
return !(left == right);
}
}

partial class BoundDagIndexEvaluation
{
public override bool Equals(object obj) => obj is BoundDagIndexEvaluation other && this.Equals(other);
public override bool Equals(BoundDagEvaluation obj) => obj is BoundDagIndexEvaluation other && this.Equals(other);
public override int GetHashCode() => base.GetHashCode() ^ this.Index;
public bool Equals(BoundDagIndexEvaluation other)
{
return base.Equals(other) && this.Index == other.Index;
}
}
}
Loading

0 comments on commit 572ed34

Please sign in to comment.