Skip to content

Commit

Permalink
Overhaul syntax model for deconstruction decl
Browse files Browse the repository at this point in the history
Also update the out var syntax node to use the newly introduced
VariableComponent so it can be extended in the future to allow `out *`
or `out var (x, y)` etc without requiring any further changes to the
syntax model.
Fixes dotnet#12747, dotnet#12664, dotnet#12588, dotnet#1503
  • Loading branch information
gafter committed Aug 3, 2016
1 parent 6647aa5 commit df48ec6
Show file tree
Hide file tree
Showing 59 changed files with 1,324 additions and 1,124 deletions.
118 changes: 66 additions & 52 deletions src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,23 +537,66 @@ private BoundBadExpression MissingDeconstruct(BoundExpression receiver, CSharpSy
return BadExpression(syntax, childNode);
}

internal BoundLocalDeconstructionDeclaration BindDeconstructionDeclaration(CSharpSyntaxNode node, VariableDeclarationSyntax declaration, DiagnosticBag diagnostics)
internal BoundLocalDeconstructionDeclaration BindDeconstructionDeclarationStatement(DeconstructionDeclarationStatementSyntax node, DiagnosticBag diagnostics)
{
Debug.Assert(node.Kind() == SyntaxKind.LocalDeclarationStatement || node.Kind() == SyntaxKind.VariableDeclaration);
Debug.Assert(declaration.IsDeconstructionDeclaration);

var value = declaration.Deconstruction.Value;
return new BoundLocalDeconstructionDeclaration(node, BindDeconstructionDeclaration(node, declaration, value, diagnostics));
return new BoundLocalDeconstructionDeclaration(node, BindDeconstructionDeclaration(node, node.Assignment.Declaration, node.Assignment.Value, diagnostics));
}

internal BoundDeconstructionAssignmentOperator BindDeconstructionDeclaration(CSharpSyntaxNode node, VariableDeclarationSyntax declaration, ExpressionSyntax right, DiagnosticBag diagnostics, BoundDeconstructValuePlaceholder rightPlaceholder = null)
internal BoundDeconstructionAssignmentOperator BindDeconstructionDeclaration(CSharpSyntaxNode node, VariableComponentSyntax declaration, ExpressionSyntax right, DiagnosticBag diagnostics, BoundDeconstructValuePlaceholder rightPlaceholder = null)
{
ArrayBuilder<DeconstructionVariable> locals = BindDeconstructionDeclarationLocals(declaration, declaration.Type, diagnostics);
DeconstructionVariable locals = BindDeconstructionDeclarationLocals(declaration, diagnostics);
Debug.Assert(locals.HasNestedVariables);
var result = BindDeconstructionAssignment(node, right, locals.NestedVariables, diagnostics, isDeclaration: true, rhsPlaceholder: rightPlaceholder);
FreeDeconstructionVariables(locals.NestedVariables);
return result;
}

var result = BindDeconstructionAssignment(node, right, locals, diagnostics, isDeclaration: true, rhsPlaceholder: rightPlaceholder);
FreeDeconstructionVariables(locals);
private DeconstructionVariable BindDeconstructionDeclarationLocals(VariableComponentSyntax node, DiagnosticBag diagnostics)
{
switch (node.Kind())
{
case SyntaxKind.TypedVariableComponent:
{
var component = (TypedVariableComponentSyntax)node;
return BindDeconstructionDeclarationLocals(component.Type, component.Designation, diagnostics);
}
case SyntaxKind.TupleDeconstructionVariableComponent:
{
var component = (TupleDeconstructionVariableComponentSyntax)node;
var builder = ArrayBuilder<DeconstructionVariable>.GetInstance();
foreach (var n in component.Variables)
{
builder.Add(BindDeconstructionDeclarationLocals(n, diagnostics));
}
return new DeconstructionVariable(builder, node);
}
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}

return result;
private DeconstructionVariable BindDeconstructionDeclarationLocals(TypeSyntax type, VariableDesignationSyntax node, DiagnosticBag diagnostics)
{
switch (node.Kind())
{
case SyntaxKind.SingleVariableDesignation:
{
var single = (SingleVariableDesignationSyntax)node;
return new DeconstructionVariable(BindDeconstructionDeclarationLocal(type, single, diagnostics), node);
}
case SyntaxKind.TupleDeconstructionVariableDesignation:
{
var tuple = (TupleDeconstructionVariableDesignationSyntax)node;
var builder = ArrayBuilder<DeconstructionVariable>.GetInstance();
foreach (var n in tuple.Variables)
{
builder.Add(BindDeconstructionDeclarationLocals(type, n, diagnostics));
}
return new DeconstructionVariable(builder, node);
}
default:
throw ExceptionUtilities.UnexpectedValue(node.Kind());
}
}

/// <summary>
Expand All @@ -562,49 +605,19 @@ internal BoundDeconstructionAssignmentOperator BindDeconstructionDeclaration(CSh
/// Each local is either a simple local (when its type is known) or a deconstruction local pending inference.
/// The caller is responsible for releasing the nested ArrayBuilders.
/// </summary>
private ArrayBuilder<DeconstructionVariable> BindDeconstructionDeclarationLocals(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics)
private ArrayBuilder<DeconstructionVariable> BindDeconstructionDeclarationLocals(DeconstructionDeclarationAssignmentSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics)
{
Debug.Assert(node.IsDeconstructionDeclaration);
SeparatedSyntaxList<VariableDeclarationSyntax> variables = node.Deconstruction.Variables;

// There are four cases for VariableDeclaration:
// - type and declarators are set, but deconstruction is null. This could represent `int x`, which is a single variable.
// - type is null, declarators are set, but deconstruction is null. This could represent `x`, which is a single variable.
// - type is set to 'var', declarators are null, and deconstruction is set. This could represent `var (...)`
// - type and declarators are null, but deconstruction is set. This could represent `(int x, ...)`

var localsBuilder = ArrayBuilder<DeconstructionVariable>.GetInstance(variables.Count);
foreach (var variable in variables)
{
TypeSyntax typeSyntax = variable.Type ?? closestTypeSyntax;

DeconstructionVariable local;
if (variable.IsDeconstructionDeclaration)
{
local = new DeconstructionVariable(BindDeconstructionDeclarationLocals(variable, typeSyntax, diagnostics), variable.Deconstruction);
}
else
{
local = new DeconstructionVariable(BindDeconstructionDeclarationLocal(variable, typeSyntax, diagnostics), variable);
}

localsBuilder.Add(local);
}

return localsBuilder;
var deconstructionVariable = BindDeconstructionDeclarationLocals(node.Declaration, diagnostics);
Debug.Assert(deconstructionVariable.HasNestedVariables);
return deconstructionVariable.NestedVariables;
}

/// <summary>
/// Returns a BoundLocal when the type was explicit, otherwise returns a DeconstructionLocalPendingInference.
/// </summary>
private BoundExpression BindDeconstructionDeclarationLocal(VariableDeclarationSyntax node, TypeSyntax closestTypeSyntax, DiagnosticBag diagnostics)
private BoundExpression BindDeconstructionDeclarationLocal(TypeSyntax typeSyntax, SingleVariableDesignationSyntax designation, DiagnosticBag diagnostics)
{
Debug.Assert(!node.IsDeconstructionDeclaration);
Debug.Assert(node.Variables.Count == 1);

var declarator = node.Variables[0];

var localSymbol = LocateDeclaredVariableSymbol(declarator, closestTypeSyntax);
var localSymbol = LocateDeclaredVariableSymbol(designation, typeSyntax);

// Check for variable declaration errors.
// Use the binder that owns the scope for the local because this (the current) binder
Expand All @@ -614,20 +627,21 @@ private BoundExpression BindDeconstructionDeclarationLocal(VariableDeclarationSy
bool isVar;
bool isConst = false;
AliasSymbol alias;
TypeSymbol declType = BindVariableType(node, diagnostics, closestTypeSyntax, ref isConst, out isVar, out alias);
TypeSymbol declType = BindVariableType(designation, diagnostics, typeSyntax, ref isConst, out isVar, out alias);

if (!isVar)
{
if (node.Type == null)
if (designation.Parent.Kind() == SyntaxKind.TupleDeconstructionVariableDesignation)
{
// An explicit type can only be provided next to the variable
Error(diagnostics, ErrorCode.ERR_DeconstructionVarFormDisallowsSpecificType, node);
Error(diagnostics, ErrorCode.ERR_DeconstructionVarFormDisallowsSpecificType, designation);
hasErrors = true;
}

return new BoundLocal(declarator, localSymbol, constantValueOpt: null, type: declType, hasErrors: node.Type == null);
return new BoundLocal(designation, localSymbol, constantValueOpt: null, type: declType, hasErrors: hasErrors);
}

return new DeconstructionLocalPendingInference(declarator, localSymbol);
return new DeconstructionLocalPendingInference(designation, localSymbol);
}
}
}
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2105,7 +2105,7 @@ private BoundExpression BindArgumentValue(DiagnosticBag diagnostics, ArgumentSyn
}

var declarationExpression = (DeclarationExpressionSyntax)argumentSyntax.Expression;
var declaration = declarationExpression.Declaration;
var declaration = (TypedVariableComponentSyntax)declarationExpression.Declaration;
var typeSyntax = declaration.Type;

bool isConst = false;
Expand Down Expand Up @@ -2299,7 +2299,7 @@ private void CoerceArguments<TMember>(
&& ((MethodSymbol)this.ContainingMemberOrLambda).IsAsync
&& parameterType.IsRestrictedType())
{
var declaration = ((DeclarationExpressionSyntax)argument.Syntax).Declaration;
var declaration = (TypedVariableComponentSyntax)((DeclarationExpressionSyntax)argument.Syntax).Declaration;
Error(diagnostics, ErrorCode.ERR_BadSpecialByRefLocal, declaration.Type, parameterType);
hasErrors = true;
}
Expand Down
48 changes: 29 additions & 19 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public virtual BoundStatement BindStatement(StatementSyntax node, DiagnosticBag
case SyntaxKind.LocalDeclarationStatement:
result = BindLocalDeclarationStatement((LocalDeclarationStatementSyntax)node, diagnostics);
break;
case SyntaxKind.DeconstructionDeclarationStatement:
result = BindDeconstructionDeclarationStatement((DeconstructionDeclarationStatementSyntax)node, diagnostics);
break;
case SyntaxKind.LocalFunctionStatement:
result = BindLocalFunctionStatement((LocalFunctionStatementSyntax)node, diagnostics);
break;
Expand All @@ -65,7 +68,8 @@ public virtual BoundStatement BindStatement(StatementSyntax node, DiagnosticBag
result = BindFor((ForStatementSyntax)node, diagnostics);
break;
case SyntaxKind.ForEachStatement:
result = BindForEach((ForEachStatementSyntax)node, diagnostics);
case SyntaxKind.ForEachComponentStatement:
result = BindForEach((CommonForEachStatementSyntax)node, diagnostics);
break;
case SyntaxKind.BreakStatement:
result = BindBreak((BreakStatementSyntax)node, diagnostics);
Expand Down Expand Up @@ -549,15 +553,7 @@ internal BoundStatement BindLocalDeclarationStatement(LocalDeclarationStatementS
Debug.Assert(binder != null);

BoundStatement bound;
if (node.Declaration.IsDeconstructionDeclaration)
{
bound = binder.BindDeconstructionDeclaration(node, node.Declaration, diagnostics);
}
else
{
bound = binder.BindDeclarationStatementParts(node, diagnostics);
}

bound = binder.BindDeclarationStatementParts(node, diagnostics);
return binder.WrapWithVariablesIfAny(node, bound);
}

Expand All @@ -568,7 +564,7 @@ private BoundStatement BindDeclarationStatementParts(LocalDeclarationStatementSy

bool isVar;
AliasSymbol alias;
TypeSymbol declType = BindVariableType(node, diagnostics, typeSyntax, ref isConst, isVar: out isVar, alias: out alias);
TypeSymbol declType = BindVariableType(node.Declaration, diagnostics, typeSyntax, ref isConst, isVar: out isVar, alias: out alias);

// UNDONE: "possible expression" feature for IDE

Expand Down Expand Up @@ -601,7 +597,10 @@ private BoundStatement BindDeclarationStatementParts(LocalDeclarationStatementSy

private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, DiagnosticBag diagnostics, TypeSyntax typeSyntax, ref bool isConst, out bool isVar, out AliasSymbol alias)
{
Debug.Assert(declarationNode.Kind() == SyntaxKind.LocalDeclarationStatement || declarationNode.Kind() == SyntaxKind.DeclarationExpression || declarationNode.Kind() == SyntaxKind.VariableDeclaration);
Debug.Assert(
declarationNode.Kind() == SyntaxKind.SingleVariableDesignation ||
declarationNode.Kind() == SyntaxKind.VariableDeclaration ||
declarationNode.Kind() == SyntaxKind.DeclarationExpression);

// If the type is "var" then suppress errors when binding it. "var" might be a legal type
// or it might not; if it is not then we do not want to report an error. If it is, then
Expand Down Expand Up @@ -638,7 +637,8 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic
// more sense to treat that as "var x = 10; var y = 123.4;" and do each inference
// separately.

if (declarationNode.Kind() == SyntaxKind.LocalDeclarationStatement && ((LocalDeclarationStatementSyntax)declarationNode).Declaration.Variables.Count > 1 && !declarationNode.HasErrors)
if (declarationNode.Parent.Kind() == SyntaxKind.LocalDeclarationStatement &&
((VariableDeclarationSyntax)declarationNode).Variables.Count > 1 && !declarationNode.HasErrors)
{
Error(diagnostics, ErrorCode.ERR_ImplicitlyTypedVariableMultipleDeclarator, declarationNode);
}
Expand Down Expand Up @@ -970,7 +970,17 @@ private ImmutableArray<BoundExpression> BindDeclaratorArguments(VariableDeclarat

private SourceLocalSymbol LocateDeclaredVariableSymbol(VariableDeclaratorSyntax declarator, TypeSyntax typeSyntax)
{
SourceLocalSymbol localSymbol = this.LookupLocal(declarator.Identifier);
return LocateDeclaredVariableSymbol(declarator.Identifier, typeSyntax, declarator.Initializer);
}

private SourceLocalSymbol LocateDeclaredVariableSymbol(SingleVariableDesignationSyntax designation, TypeSyntax typeSyntax)
{
return LocateDeclaredVariableSymbol(designation.Identifier, typeSyntax, null);
}

private SourceLocalSymbol LocateDeclaredVariableSymbol(SyntaxToken identifier, TypeSyntax typeSyntax, EqualsValueClauseSyntax equalsValue)
{
SourceLocalSymbol localSymbol = this.LookupLocal(identifier);

// In error scenarios with misplaced code, it is possible we can't bind the local declaration.
// This occurs through the semantic model. In that case concoct a plausible result.
Expand All @@ -981,9 +991,9 @@ private SourceLocalSymbol LocateDeclaredVariableSymbol(VariableDeclaratorSyntax
this,
RefKind.None,
typeSyntax,
declarator.Identifier,
identifier,
LocalDeclarationKind.RegularVariable,
declarator.Initializer);
equalsValue);
}

return localSymbol;
Expand Down Expand Up @@ -3072,7 +3082,7 @@ internal BoundStatement BindStatementExpressionList(SeparatedSyntaxList<Expressi
}
}

private BoundStatement BindForEach(ForEachStatementSyntax node, DiagnosticBag diagnostics)
private BoundStatement BindForEach(CommonForEachStatementSyntax node, DiagnosticBag diagnostics)
{
Binder loopBinder = this.GetBinder(node);
return this.GetBinder(node.Expression).WrapWithVariablesIfAny(node.Expression, loopBinder.BindForEachParts(diagnostics, loopBinder));
Expand All @@ -3086,9 +3096,9 @@ internal virtual BoundStatement BindForEachParts(DiagnosticBag diagnostics, Bind
/// <summary>
/// Like BindForEachParts, but only bind the deconstruction part of the foreach, for purpose of inferring the types of the declared locals.
/// </summary>
internal virtual void BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder)
internal virtual BoundStatement BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder)
{
this.Next.BindForEachDeconstruction(diagnostics, originalBinder);
return this.Next.BindForEachDeconstruction(diagnostics, originalBinder);
}

private BoundStatement BindBreak(BreakStatementSyntax node, DiagnosticBag diagnostics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ internal override BoundStatement BindForEachParts(DiagnosticBag diagnostics, Bin
throw ExceptionUtilities.Unreachable;
}

internal override void BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder)
internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnostics, Binder originalBinder)
{
// There's supposed to be a ForEachLoopBinder (or other overrider of this method) in the chain.
throw ExceptionUtilities.Unreachable;
Expand Down
Loading

0 comments on commit df48ec6

Please sign in to comment.