Deconstruction-assignment for tuples#11457
Conversation
| 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)) |
There was a problem hiding this comment.
Is the first condition necessary or would a literal also satisfy the second condition?
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
Consider using overload BadExpression(CSharpSyntaxNode, BoundNode).
|
I just pushed a change to address Chuck's feedback so far. |
| } | ||
|
|
||
| [Fact] | ||
| public void DeconstructIL() |
There was a problem hiding this comment.
Is this a subset of the Deconstruct test above?
There was a problem hiding this comment.
Yes, it is now that I added VerifyIL to the test above. I'll remove the redundant one.
|
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); |
There was a problem hiding this comment.
It looks like this function is never called when IsTupleType is false. Consider simply asserting this instead.
There was a problem hiding this comment.
Yes. This is un-necessary now that the conversion is applied to all tuple cases. Thanks
There was a problem hiding this comment.
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) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Consider adding deconstruction tests for tuples on the right that have names.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I'll add the one you suggested. Note that I already have the one Mads suggested (x, y) = (y, x);.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
these are some cases where you need a temp for the RHS even when it is a local/parameter.
|
|
||
| /// <summary> | ||
| /// If this is a field of a tuple type, | ||
| /// id is an index of the element (zero-based). |
There was a problem hiding this comment.
Probably shouldn't mention Id
There was a problem hiding this comment.
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)
f49cbfe to
a5e4fc6
Compare
|
@AlekseyTs Pushed the latest change with doc tweaks and improving the logic to collect fields from tuple symbol. |
a5e4fc6 to
66662a3
Compare
|
|
||
| private ImmutableArray<FieldSymbol> CollectTupleElementFields() | ||
| { | ||
| var builder = ArrayBuilder<FieldSymbol>.GetInstance(_elementTypes.Length); |
There was a problem hiding this comment.
This is subtle, I think you can simply pass null as the second parameter for ArrayBuilder.GetInstance to have the builder allocate the items.
There was a problem hiding this comment.
Ah! That's much nicer. Thanks
|
LGTM, but do consider my last two comments. |
|
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})";
}
}
} |
|
@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. |
|
@VSadov It isn't the same, in the current implementation, because inside the body of |
|
There is a hole in the way we assign the type for |
|
@gafter interesting. |
|
please test all |
|
We can make "this" to be a tuple inside VT definition, or we can make an exception for that case - whatever. |
|
I don't think we would face a circularity problem the check for tuple-compatible type is "local". |
|
See also #11530 for another symptom of the same issue. |
|
test open please |
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:
@dotnet/roslyn-compiler for review.