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 for tuples #11457

Merged
merged 10 commits into from
May 24, 2016

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented May 20, 2016

This PR mainly adds deconstruction-assignment with the right-hand-side being a tuple type or a tuple literal (with or without type).

This also adds tests for follow-ups from previous feedback:

  • ref returning methods on the left. Check sideeffects.
  • Foo()[Bar()] on the left, where all Foo, Bar and the indexer have sideeffects - print something
  • check IL in simple/common cases
  • Optimize the case where LHS variable is simple ref value

@dotnet/roslyn-compiler for review.

@@ -1737,6 +1744,52 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax

var checkedVariables = checkedVariablesBuilder.ToImmutableAndFree();

// tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())`
if (boundRHS.Kind == BoundKind.TupleLiteral || ((object)boundRHS.Type != null && boundRHS.Type.IsTupleType))
Copy link
Member

Choose a reason for hiding this comment

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

Is the first condition necessary or would a literal also satisfy the second condition?

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason for the first condition is tuple literals that don't have a natural type, such as (null, null).

if (boundRHS.Type == null)
{
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node);
return BadExpression(node, new[] { boundRHS });
Copy link
Member

Choose a reason for hiding this comment

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

Consider using overload BadExpression(CSharpSyntaxNode, 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.

Thanks. Done

@jcouv
Copy link
Member Author

jcouv commented May 20, 2016

I just pushed a change to address Chuck's feedback so far.

}

[Fact]
public void DeconstructIL()
Copy link
Member

Choose a reason for hiding this comment

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

Is this a subset of the Deconstruct test above?

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, it is now that I added VerifyIL to the test above. I'll remove the redundant one.

@cston
Copy link
Member

cston commented May 20, 2016

LGTM


private ImmutableArray<BoundExpression> AccessTupleFields(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder<LocalSymbol> temps, ArrayBuilder<BoundExpression> stores)
{
var tupleType = loweredRight.Type.IsTupleType ? loweredRight.Type : TupleTypeSymbol.Create((NamedTypeSymbol)loweredRight.Type);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this function is never called when IsTupleType is false. Consider simply asserting this instead.

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. This is un-necessary now that the conversion is applied to all tuple cases. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

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

Turns out I was wrong. Tests won't pass without this logic. The key is that the node on the right may have a tuple type before lowering, but after lowering it becomes a tuple constructor call (which returns underlying type).

// (x, y) = (1, 2);
Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("(int, int)", "(byte, string)").WithLocation(9, 18)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding deconstruction tests for tuples on the right that have names.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added test with names.

Copy link
Member

Choose a reason for hiding this comment

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

Add tests where RHS is used on the left:

(object i, object ii) x = (1,2);
object y;

(x.ii, y) = x;

should result in x: {1, 1} , y: 2

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'll add the one you suggested. Note that I already have the one Mads suggested (x, y) = (y, x);.

Copy link
Member

@VSadov VSadov May 23, 2016

Choose a reason for hiding this comment

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

Also try a variant of above:

(object i, object ii) x = (1,2);
object y;

ref var a = ref x;

(a.ii, y) = x;

should result in x: {1, 1} , y: 2

Copy link
Member

Choose a reason for hiding this comment

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

these are some cases where you need a temp for the RHS even when it is a local/parameter.

@gafter
Copy link
Member

gafter commented May 23, 2016

tempZ = &evaluate expressionZ

I believe the LDM said that we want to evaluate all side-effects on the left before side-effects on this right.


Refers to: docs/features/deconstruction.md:69 in 7170bd2. [](commit_id = 7170bd2, deletion_comment = False)

@@ -397,6 +397,19 @@ public virtual FieldSymbol TupleUnderlyingField
}
}

/// <summary>
/// If this is a field of a tuple type,
/// id is an index of the element (zero-based).
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably shouldn't mention Id

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, should probably say "If this is a field representing a tuple element", rather than "If this is a field of a tuple type", because some fields of a tuple type return negative numbers.


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

@jcouv
Copy link
Member Author

jcouv commented May 23, 2016

@AlekseyTs Pushed the latest change with doc tweaks and improving the logic to collect fields from tuple symbol.
Calling ArrayBuilder.SetItem on last position seems the best way to force it to size.

private ImmutableArray<FieldSymbol> CollectTupleElementFields()
{
var builder = ArrayBuilder<FieldSymbol>.GetInstance(_elementTypes.Length);
builder.SetItem(_elementTypes.Length - 1, null);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is subtle, I think you can simply pass null as the second parameter for ArrayBuilder.GetInstance to have the builder allocate the items.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah! That's much nicer. Thanks

@AlekseyTs
Copy link
Contributor

LGTM, but do consider my last two comments.

@gafter
Copy link
Member

gafter commented May 24, 2016

This implementation fails to compile this test:

namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;

        public override string ToString()
        {
            T1 k;
            T2 v;
            // error: no Destruct instance or extension method was found for ValueTuple<T1, T2>
            (k, v) = this;
            return $"({k}, {v})";
        }
    }
}

@VSadov
Copy link
Member

VSadov commented May 24, 2016

@gafter the example that you gave is essentially the same as

ValueTuple<T1, T2> t = this;
(k, v) = t;

I wonder if that has similar problems.

Overall, it would seem acceptable for tuple types to behave differently inside their own definition types, since that scenario is diminishingly small, but I see no reasons why it would be the case here.

@gafter
Copy link
Member

gafter commented May 24, 2016

@VSadov It isn't the same, in the current implementation, because inside the body of ValueTuple'2, the type ValueTuple<T1, T2> is a tuple type and the type of this isn't a tuple type. They are not the same type!

@AlekseyTs
Copy link
Contributor

There is a hole in the way we assign the type for this parameter symbol. I think we should tackle it separately because its not exclusively related to deconstruction. It does affect deconstruction though.

@VSadov
Copy link
Member

VSadov commented May 24, 2016

@gafter interesting.
It seems not a deconstruction issue though, but general appearance of "this" inside the VT definition. We should proceed with this PR and enter a bug to figure what to do with "this".

@jcouv
Copy link
Member Author

jcouv commented May 24, 2016

please test all

@VSadov
Copy link
Member

VSadov commented May 24, 2016

We can make "this" to be a tuple inside VT definition, or we can make an exception for that case - whatever.
After all definition of Int32 contains itself and not that many people care :-)

@jcouv
Copy link
Member Author

jcouv commented May 24, 2016

@gafter @VSadov Interesting case. It's possible this behavior (not treating "this" inside ValueTuple as a tuple type) actually helps us.
Without this behavior, we could have a circularity problem in trying to build the ValueTuple library with new compiler..

@AlekseyTs
Copy link
Contributor

I don't think we would face a circularity problem the check for tuple-compatible type is "local".

@gafter
Copy link
Member

gafter commented May 24, 2016

See also #11530 for another symptom of the same issue.

@jaredpar
Copy link
Member

test open please

@jcouv jcouv merged commit 6cb6df1 into dotnet:features/tuples May 24, 2016
@jcouv jcouv deleted the tuple-deconstruction branch May 24, 2016 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants