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

Deconstruction-assignment: nested scenario #11715

Merged
merged 13 commits into from
Jun 7, 2016
287 changes: 239 additions & 48 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,18 @@ public override Symbol ExpressionSymbol

internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpression
{
internal class AssignmentInfo
internal class AssignmentStep
Copy link
Contributor

Choose a reason for hiding this comment

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

I think AssignmentStep and DeconstructStep should be regular bound nodes

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

{
public BoundAssignmentOperator Assignment;
public BoundLValuePlaceholder LValuePlaceholder;
public BoundRValuePlaceholder RValuePlaceholder;
public BoundDeconstructValuePlaceholder OutputPlaceholder;
public BoundDeconstructValuePlaceholder TargetPlaceholder;
}

internal class DeconstructStep
{
public MethodSymbol DeconstructMemberOpt; // the deconstruct member, or null if tuple deconstruction
public BoundDeconstructValuePlaceholder TargetPlaceholder;
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make this BoundExpression and put Right directly as the target for the first deconstruct, removing it from the parent node.

Copy link
Member Author

Choose a reason for hiding this comment

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

put Right directly as the target for the first deconstruct, removing it from the parent node.

I didn't get that part.

Copy link
Contributor

Choose a reason for hiding this comment

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

Never mind, since we might not create the deconstruct step in case of an error, the suggestion is not an option.


In reply to: 65796074 [](ancestors = 65796074)

public ImmutableArray<BoundDeconstructValuePlaceholder> OutputPlaceholders; // placeholders for the outputs produced by this deconstruction
}
Copy link
Member

Choose a reason for hiding this comment

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

Consider making these two types immutable.

Copy link
Member

Choose a reason for hiding this comment

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

Can these types be structs?

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, I thought about struct too. I have a note on that #11299. I think I'll just make the change.

Also, from our discussion, I will move back from OneOrMany to simply use ImmutableArray.

}

Expand Down
16 changes: 11 additions & 5 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@
This node is used to represent an expression returning value of a certain type.
It is used to perform intermediate binding, and will not survive the local rewriting.
-->
<Node Name="BoundLValuePlaceholder" Base="BoundValuePlaceholderBase" HasValidate="true">
</Node>
<Node Name="BoundRValuePlaceholder" Base="BoundValuePlaceholderBase" HasValidate="true">
<Node Name="BoundDeconstructValuePlaceholder" Base="BoundValuePlaceholderBase" HasValidate="true">
</Node>

<!-- only used by codegen -->
Expand Down Expand Up @@ -394,13 +392,21 @@
<!-- Non-null type is required for this node kind -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>

<!-- Various assignable expressions, including some BoundDeconstructionVariables for nested variables -->
<Field Name="LeftVariables" Type="ImmutableArray&lt;BoundExpression&gt;"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need BoundDeconstructionVariables in this list?

Copy link
Contributor

Choose a reason for hiding this comment

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

Presence of these node can potentially confuse SemanticModel, etc.


In reply to: 65776149 [](ancestors = 65776149)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, given the current implementation, it looks like LeftVariable is always a flat list of targets, BoundDeconstructionVariables is never among them. Please adjust the comment accordingly.


In reply to: 65778993 [](ancestors = 65778993,65776149)

Copy link
Member Author

@jcouv jcouv Jun 4, 2016

Choose a reason for hiding this comment

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

@AlekseyTs, from discussion with Chuck I will remove the BoundDeconstructionVariables node and store the tree of variables some other way (without creating new kind of bound node).


<Field Name="Right" Type="BoundExpression"/>
<Field Name="DeconstructMemberOpt" Type="MethodSymbol" Null="allow"/>

<Field Name="DeconstructSteps" Type="ImmutableArray&lt;BoundDeconstructionAssignmentOperator.DeconstructStep&gt;" Null="NotApplicable" SkipInVisitor="true" />

<!-- The assignments have placeholders for the left and right part of the assignment -->
<Field Name="Assignments" Type="ImmutableArray&lt;BoundDeconstructionAssignmentOperator.AssignmentInfo&gt;" Null="NotApplicable" SkipInVisitor="true"/>
<Field Name="AssignmentSteps" Type="ImmutableArray&lt;BoundDeconstructionAssignmentOperator.AssignmentStep&gt;" Null="NotApplicable" SkipInVisitor="true"/>
</Node>

<Node Name="BoundDeconstructionVariables" Base="BoundExpression">
Copy link
Member

Choose a reason for hiding this comment

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

Why a BoundExpression rather than BoundNode?

Copy link
Member Author

Choose a reason for hiding this comment

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

So, this bound node is never output from binding at all. But during binding, I have bound expressions for the LHS, which need to be organized in a tree.
Because all simple variables are BoundExpression, it is convenient that this is also BoundExpression (rather than BoundNode). But I'm open to suggestions.
I agree that having a bound node that never escapes binding is strange.

Copy link
Member

Choose a reason for hiding this comment

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

Where do we use checkedVariables as (array of) BoundExpression rather than BoundNode? Or is it more than just checkedVariables that is the concern?

Copy link
Contributor

Choose a reason for hiding this comment

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

Given the current implementation, it looks like BoundDeconstructionVariables never escapes the binder. This is just a helper node, used in the process of binding, never gets stored in the tree produced by the Binder. Please provide a comment reflecting that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed BoundDeconstructionVariables in favor of a simple tree structure (which is not a bound node):

        private class DeconstructionVariable
        {
            public BoundExpression Single;
            public ImmutableArray<DeconstructionVariable>? Nested;
            ...
        }

<Field Name="Type" Type="TypeSymbol" Override="true" Null="always"/>

<Field Name="Variables" Type="ImmutableArray&lt;BoundExpression&gt;"/>
</Node>

<Node Name="BoundVoid" Base="BoundExpression">
Expand Down
32 changes: 16 additions & 16 deletions src/Compilers/CSharp/Portable/BoundTree/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,7 @@ Optional<object> IOperation.ConstantValue
public abstract TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument);
}

internal sealed partial class BoundLValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression
{
protected override OperationKind ExpressionKind => OperationKind.PlaceholderExpression;

public override void Accept(OperationVisitor visitor)
{
visitor.VisitPlaceholderExpression(this);
}

public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
{
return visitor.VisitPlaceholderExpression(this, argument);
}
}

internal sealed partial class BoundRValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression
internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression
{
protected override OperationKind ExpressionKind => OperationKind.PlaceholderExpression;

Expand Down Expand Up @@ -1049,6 +1034,21 @@ public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, T
}
}

internal sealed partial class BoundDeconstructionVariables : BoundExpression
{
protected override OperationKind ExpressionKind => OperationKind.None;

public override void Accept(OperationVisitor visitor)
{
visitor.VisitNoneOperation(this);
Copy link
Member

Choose a reason for hiding this comment

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

Why not visit Variables?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a dummy implementation, like BoundTupleExpression.
My understanding is that this implement IOperation, which John Hamby would take care of.

}

public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
{
return visitor.VisitNoneOperation(this, argument);
}
}

internal sealed partial class BoundVoid : BoundExpression
{
protected override OperationKind ExpressionKind => OperationKind.None;
Expand Down
9 changes: 9 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4899,4 +4899,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_OptionMustBeAbsolutePath" xml:space="preserve">
<value>Option '{0}' must be an absolute path.</value>
</data>
</root>
<data name="ERR_DeconstructWrongCardinality" xml:space="preserve">
<value>Cannot deconstruct a tuple of '{0}' elements into '{1}' variables.</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1399,5 +1399,6 @@ internal enum ErrorCode
ERR_DeconstructRequiresOutParams = 8208,
ERR_DeconstructWrongParams = 8209,
ERR_DeconstructRequiresExpression = 8210,
ERR_DeconstructWrongCardinality = 8211,
}
}
34 changes: 30 additions & 4 deletions src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ public override BoundNode VisitFixedStatement(BoundFixedStatement node)
{
foreach (LocalSymbol local in node.Locals)
{
switch(local.DeclarationKind)
switch (local.DeclarationKind)
{
case LocalDeclarationKind.RegularVariable:
case LocalDeclarationKind.PatternVariable:
Expand Down Expand Up @@ -1721,14 +1721,40 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
Assign(node.Left, node.Right, refKind: node.RefKind);
return null;
}

public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node)
{
base.VisitDeconstructionAssignmentOperator(node);

foreach(BoundExpression variable in node.LeftVariables)
foreach (BoundExpression variable in node.LeftVariables)
{
if (variable.Kind == BoundKind.DeconstructionVariables)
{
VisitDeconstructionVariables((BoundDeconstructionVariables)variable);
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't base.VisitDeconstructionAssignment do this? Same comment for VisitDeconstructionVariables below.

Copy link
Member

Choose a reason for hiding this comment

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

If the Assign call is the only difference from the base call, consider adding a virtual VisitDeconstructionAssignmentLeftVariable method to PreciseAbstractFlowPass and override that method here instead. Similarly for VisitDeconstructionVariables.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, I removed BoundDeconstructionVariables, which simplifies and removes a bunch of data flow code. I'm leaving the rest as-is with PROTOTYPE(tuples).
The Assign call is a stub, not the full implementation that I intend.

}
else
{
Assign(variable, null, refKind: RefKind.None);
}
}

return null;
}

public override BoundNode VisitDeconstructionVariables(BoundDeconstructionVariables node)
{
base.VisitDeconstructionVariables(node);

foreach (BoundExpression variable in node.Variables)
{
Assign(variable, null, refKind: RefKind.None);
if (variable.Kind == BoundKind.DeconstructionVariables)
{
VisitDeconstructionVariables((BoundDeconstructionVariables)variable);
}
else
{
Assign(variable, null, refKind: RefKind.None);
}
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1518,14 +1518,38 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
{
foreach (BoundExpression variable in node.LeftVariables)
{
VisitLvalue(variable);
if (variable.Kind == BoundKind.DeconstructionVariables)
{
VisitDeconstructionVariables((BoundDeconstructionVariables)variable);
}
else
{
VisitLvalue(variable);
}
}

VisitRvalue(node.Right);

return null;
}

public override BoundNode VisitDeconstructionVariables(BoundDeconstructionVariables node)
{
foreach (BoundExpression variable in node.Variables)
{
if (variable.Kind == BoundKind.DeconstructionVariables)
{
VisitDeconstructionVariables((BoundDeconstructionVariables)variable);
}
else
{
VisitLvalue(variable);
}
}

return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is more appropriate to throw Unreachable here.

Copy link
Contributor

Choose a reason for hiding this comment

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

And seal the method.


In reply to: 65786461 [](ancestors = 65786461)

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed BoundDeconstructionVariables.

}

public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
{
// TODO: should events be handled specially too?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen
}
}

public override BoundNode VisitLValuePlaceholder(BoundLValuePlaceholder node)
{
return PlaceholderReplacement(node);
}

public override BoundNode VisitRValuePlaceholder(BoundRValuePlaceholder node)
public override BoundNode VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node)
{
return PlaceholderReplacement(node);
}
Expand Down Expand Up @@ -287,6 +282,17 @@ private void RemovePlaceholderReplacement(BoundValuePlaceholderBase placeholder)
Debug.Assert(removed);
}

/// <summary>
/// Remove all the listed placeholders.
/// </summary>
private void RemovePlaceholderReplacements(IEnumerable<BoundValuePlaceholderBase> placeholders)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use more specific type rather than IEnumerable? ArrayBuilder/ImmutableArray

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps inline the method since it is only used in one place.

Copy link
Member Author

Choose a reason for hiding this comment

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

I suspect in the future it will be handy again to be able to remove multiple placeholders. I'm not sure what is the downside of using IEnumerable to keep options open.

Copy link
Member Author

Choose a reason for hiding this comment

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

@AlekseyTs Why not IEnumerable?

{
foreach (var placeholder in placeholders)
{
RemovePlaceholderReplacement(placeholder);
}
}

public override BoundNode VisitBadExpression(BoundBadExpression node)
{
// Cannot recurse into BadExpression children since the BadExpression
Expand Down
Loading