Skip to content

Commit

Permalink
Implement type merging in deconstruction with tuple literal (#12526)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored Jul 20, 2016
1 parent ced621e commit 5c8da03
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 27 deletions.
94 changes: 74 additions & 20 deletions src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, Compilation, diagnostics);
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(
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -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);
}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -282,21 +290,67 @@ 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.
/// 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, CSharpCompilation compilation, DiagnosticBag diagnostics)
{
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);
for (int i = 0; i < rightLength; i++)
{
if (variable.HasNestedVariables)
BoundExpression element = rhsLiteral.Arguments[i];
TypeSymbol mergedType = element.Type;

if (i < leftLength)
{
typesBuilder.Add(MakeTupleTypeFromDeconstructionLHS(variable.NestedVariables, diagnostics, compilation));
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, compilation, diagnostics);
}
else if ((object)mergedType == null)
{
// (variables) on the left and null on the right
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax);
}
}
else
{
if ((object)variable.Single.Type != null)
{
// typed-variable on the left
mergedType = variable.Single.Type;
}
else if ((object)mergedType == null)
{
// typeless-variable on the left and typeless-element on the right
Error(diagnostics, ErrorCode.ERR_DeconstructCouldNotInferMergedType, syntax, variable.Syntax, element.Syntax);
}
}
}
else
{
typesBuilder.Add(variable.Single.Type);
if ((object)mergedType == null)
{
// a typeless element on the right, matching no variable on the left
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, 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);
Expand Down Expand Up @@ -327,7 +381,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));
}
}

Expand Down Expand Up @@ -527,11 +581,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);
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.

3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4917,4 +4917,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_ExplicitTupleElementNames" xml:space="preserve">
<value>Cannot reference 'System.Runtime.CompilerServices.TupleElementNamesAttribute' explicitly. Use the tuple syntax to define tuple names.</value>
</data>
<data name="ERR_DeconstructCouldNotInferMergedType" xml:space="preserve">
<value>The type information on the left-hand-side '{0}' and right-hand-side '{1}' of the deconstruction was insufficient to infer a merged type.</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 @@ -1365,6 +1365,7 @@ internal enum ErrorCode

ERR_PredefinedTypeMemberNotFoundInAssembly = 8205,
ERR_MissingDeconstruct = 8206,
ERR_DeconstructCouldNotInferMergedType = 8209,
ERR_DeconstructRequiresExpression = 8210,
ERR_DeconstructWrongCardinality = 8211,
ERR_CannotDeconstructDynamic = 8212,
Expand Down
Loading

0 comments on commit 5c8da03

Please sign in to comment.