-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Implement type merging in deconstruction with tuple literal #12526
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,16 +56,25 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(CShar | |
|
||
if ((object)boundRHS.Type == null) | ||
{ | ||
if (boundRHS.Kind == BoundKind.TupleLiteral && !isDeclaration) | ||
if (boundRHS.Kind == BoundKind.TupleLiteral) | ||
{ | ||
// tuple literal without type such as `(null, null)`, let's fix it up by peeking at the LHS | ||
TypeSymbol lhsAsTuple = MakeTupleTypeFromDeconstructionLHS(checkedVariables, diagnostics, Compilation); | ||
boundRHS = GenerateConversionForAssignment(lhsAsTuple, boundRHS, diagnostics); | ||
// Let's fix the literal up by figuring out its type | ||
// For declarations, that means merging type information from the LHS and RHS | ||
// For assignments, only the LHS side matters since it is necessarily typed | ||
TypeSymbol lhsAsTuple = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, node, diagnostics, Compilation); | ||
if (lhsAsTuple != null) | ||
{ | ||
boundRHS = GenerateConversionForAssignment(lhsAsTuple, boundRHS, diagnostics); | ||
} | ||
} | ||
else | ||
{ | ||
// expression without type such as `null` | ||
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, right); | ||
} | ||
|
||
if ((object)boundRHS.Type == null) | ||
{ | ||
// we could still not infer a type for the RHS | ||
FailRemainingInferences(checkedVariables, diagnostics); | ||
|
||
return new BoundDeconstructionAssignmentOperator( | ||
|
@@ -74,7 +83,6 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(CShar | |
voidType, hasErrors: true); | ||
} | ||
} | ||
|
||
var deconstructionSteps = ArrayBuilder<BoundDeconstructionDeconstructStep>.GetInstance(1); | ||
var assignmentSteps = ArrayBuilder<BoundDeconstructionAssignmentStep>.GetInstance(1); | ||
bool hasErrors = !DeconstructIntoSteps(new BoundDeconstructValuePlaceholder(boundRHS.Syntax, boundRHS.Type), node, diagnostics, checkedVariables, deconstructionSteps, assignmentSteps); | ||
|
@@ -188,7 +196,7 @@ private static void SetInferredTypes(ArrayBuilder<DeconstructionVariable> variab | |
if (!variable.HasNestedVariables && variable.Single.Kind == BoundKind.DeconstructionLocalPendingInference) | ||
{ | ||
BoundLocal local = ((DeconstructionLocalPendingInference)variable.Single).SetInferredType(foundTypes[i], success: true); | ||
variables[i] = new DeconstructionVariable(local); | ||
variables[i] = new DeconstructionVariable(local, local.Syntax); | ||
} | ||
} | ||
} | ||
|
@@ -211,7 +219,7 @@ private void FailRemainingInferences(ArrayBuilder<DeconstructionVariable> variab | |
if (variable.Single.Kind == BoundKind.DeconstructionLocalPendingInference) | ||
{ | ||
var local = ((DeconstructionLocalPendingInference)variable.Single).FailInference(this); | ||
variables[i] = new DeconstructionVariable(local); | ||
variables[i] = new DeconstructionVariable(local, local.Syntax); | ||
} | ||
} | ||
} | ||
|
@@ -226,11 +234,11 @@ private class DeconstructionVariable | |
public readonly ArrayBuilder<DeconstructionVariable> NestedVariables; | ||
public readonly CSharpSyntaxNode Syntax; | ||
|
||
public DeconstructionVariable(BoundExpression variable) | ||
public DeconstructionVariable(BoundExpression variable, CSharpSyntaxNode syntax) | ||
{ | ||
Single = variable; | ||
NestedVariables = null; | ||
Syntax = variable.Syntax; | ||
Syntax = syntax; | ||
} | ||
|
||
public DeconstructionVariable(ArrayBuilder<DeconstructionVariable> variables, CSharpSyntaxNode syntax) | ||
|
@@ -282,21 +290,59 @@ private bool DeconstructOrAssignOutputs( | |
} | ||
|
||
/// <summary> | ||
/// For cases where the RHS of a deconstruction-assignment has no type (TupleLiteral), we squint and look at the LHS as a tuple type to give the RHS a type. | ||
/// For cases where the RHS of a deconstruction-declaration is a tuple literal, we merge type information from both the LHS and RHS. | ||
/// For cases where the RHS of a deconstruction-assignment is a tuple literal, the type information from the LHS determines the merged type, since all variables have a type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where in the method do we check for declaration vs. assignment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We never have to check explicitly. The LHS in an assignment is always completely typed (no vars). |
||
/// Returns null if a merged tuple type could not be fabricated. | ||
/// </summary> | ||
private static TypeSymbol MakeTupleTypeFromDeconstructionLHS(ArrayBuilder<DeconstructionVariable> topLevelCheckedVariables, DiagnosticBag diagnostics, CSharpCompilation compilation) | ||
private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariable> lhsVariables, BoundTupleLiteral rhsLiteral, CSharpSyntaxNode syntax, DiagnosticBag diagnostics, CSharpCompilation compilation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor point: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
{ | ||
var typesBuilder = ArrayBuilder<TypeSymbol>.GetInstance(topLevelCheckedVariables.Count); | ||
foreach (var variable in topLevelCheckedVariables) | ||
int leftLength = lhsVariables.Count; | ||
int rightLength = rhsLiteral.Arguments.Length; | ||
|
||
var typesBuilder = ArrayBuilder<TypeSymbol>.GetInstance(lhsVariables.Count); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next PR (#12527) |
||
for (int i = 0; i < rightLength; i++) | ||
{ | ||
if (variable.HasNestedVariables) | ||
{ | ||
typesBuilder.Add(MakeTupleTypeFromDeconstructionLHS(variable.NestedVariables, diagnostics, compilation)); | ||
} | ||
else | ||
BoundExpression element = rhsLiteral.Arguments[i]; | ||
TypeSymbol mergedType = element.Type; | ||
|
||
if (i < leftLength) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the scenario where the right length is greater than left length? And are we reporting an error if there are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Below is the test for that scenario. The merged type is [Fact]
public void TypeMergingWithTooManyRightElements()
{
string source = @"
class C
{
static void Main()
{
(string x1, var x2) = (null, ""hello"", 3);
}
}
";
var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef });
comp.VerifyDiagnostics(
// (6,9): error CS8211: Cannot deconstruct a tuple of '3' elements into '2' variables.
// (string x1, var x2) = (null, "hello", 3);
Diagnostic(ErrorCode.ERR_DeconstructWrongCardinality, @"(string x1, var x2) = (null, ""hello"", 3);").WithArguments("3", "2").WithLocation(6, 9)
);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I misspoke. Actually, with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
{ | ||
typesBuilder.Add(variable.Single.Type); | ||
var variable = lhsVariables[i]; | ||
if (variable.HasNestedVariables) | ||
{ | ||
if (element.Kind == BoundKind.TupleLiteral) | ||
{ | ||
// (variables) on the left and (elements) on the right | ||
mergedType = MakeMergedTupleType(variable.NestedVariables, (BoundTupleLiteral)element, syntax, diagnostics, compilation); | ||
} | ||
else if (element.Type == null) | ||
{ | ||
// (variables) on the left and null on the right | ||
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax); | ||
} | ||
} | ||
else | ||
{ | ||
if (variable.Single.Type != null) | ||
{ | ||
// typed-variable on the left | ||
mergedType = variable.Single.Type; | ||
} | ||
else if (element.Type == null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I had a rationale earlier, but forgot what it was... Fixed :-) |
||
{ | ||
// typeless-variable on the left and typeless-element on the right | ||
Error(diagnostics, ErrorCode.ERR_DeconstructCouldNotInferMergedType, syntax, variable.Syntax, element.Syntax); | ||
} | ||
} | ||
} | ||
|
||
typesBuilder.Add(mergedType); | ||
} | ||
|
||
if (typesBuilder.Any(t => t == null)) | ||
{ | ||
typesBuilder.Free(); | ||
return null; | ||
} | ||
|
||
return TupleTypeSymbol.Create(locationOpt: null, elementTypes: typesBuilder.ToImmutableAndFree(), elementLocations: default(ImmutableArray<Location>), elementNames: default(ImmutableArray<string>), compilation: compilation, diagnostics: diagnostics); | ||
|
@@ -327,7 +373,7 @@ private ArrayBuilder<DeconstructionVariable> BindDeconstructionAssignmentVariabl | |
var boundVariable = BindExpression(argument.Expression, diagnostics, invoked: false, indexed: false); | ||
var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignment, diagnostics); | ||
|
||
checkedVariablesBuilder.Add(new DeconstructionVariable(checkedVariable)); | ||
checkedVariablesBuilder.Add(new DeconstructionVariable(checkedVariable, argument)); | ||
} | ||
} | ||
|
||
|
@@ -527,11 +573,11 @@ private ArrayBuilder<DeconstructionVariable> BindDeconstructionDeclarationLocals | |
DeconstructionVariable local; | ||
if (variable.IsDeconstructionDeclaration) | ||
{ | ||
local = new DeconstructionVariable(BindDeconstructionDeclarationLocals(variable, typeSyntax, diagnostics), node.Deconstruction); | ||
local = new DeconstructionVariable(BindDeconstructionDeclarationLocals(variable, typeSyntax, diagnostics), variable.Deconstruction); | ||
} | ||
else | ||
{ | ||
local = new DeconstructionVariable(BindDeconstructionDeclarationLocal(variable, typeSyntax, diagnostics)); | ||
local = new DeconstructionVariable(BindDeconstructionDeclarationLocal(variable, typeSyntax, diagnostics), variable); | ||
} | ||
|
||
localsBuilder.Add(local); | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(object)lhsAsTuple != null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'm still not quite sure when that is useful. Is it for certain types only (such as Symbols)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll queue that and your other suggestion in next PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only for types that define
operator==
.