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

Lookup for Deconstruct method #11873

Merged
merged 11 commits into from
Jun 13, 2016
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/features/deconstruction.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ It will be updated to support deconstruction-assignment, ie. when the left-hand-
- Each item on the left needs to be assignable and needs to be compatible with corresponding position on the right (resulting from previous step).
- Needs to handle nesting case such as `(x, (y, z)) = M();`, but note that the second item in the top-level group has no discernable type.

#### Evaluation order

The evaluation order can be summarized as: (1) all the side-effects on the left-hand-side, (2) all the Deconstruct invocations (if not tuple), (3) conversions (if needed), and (4) assignments.

In the general case, the lowering for deconstruction-assignment would translate: `(expressionX, expressionY, expressionZ) = expressionRight` into:
Expand Down Expand Up @@ -100,7 +102,17 @@ evaluate the right-hand-side and do a tuple conversion (using a fake tuple repre
assign element-wise from the right to the left
```

Note that tuples (`System.ValueTuple` and `System.Tuple`) don't need to invoke Deconstruct.
#### Resolution of the Deconstruct method

The resolution is equivalent to typing `rhs.Deconstruct(out var x1, out var x2, ...);` with the appropriate number of parameters to deconstruct into.
It is based on normal overload resolution.
This implies that `rhs` cannot be dynamic.
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

I am not sure if the Out Var limitation should necessarily translate into limitation for Deconstruction. I think it would be reasonable to treat all the output values as dynamic if the rhs is dynamic. #Resolved

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 take a note for LDM on the dynamic question.

Also, the `Deconstruct` method must be an instance method or an extension (but not a static method).

#### Tuple Deconstruction

Note that tuples (`System.ValueTuple`) don't need to invoke Deconstruct.
`System.Tuple` are not recognized as tuples, and so will rely on Deconstruct (which will be provided for up to 3 nestings deep, that is 21 elements)


###Deconstruction-declaration (deconstruction into new variables):
Expand Down
13 changes: 9 additions & 4 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ internal BoundExpression BindValueAllowArgList(ExpressionSyntax node, Diagnostic
if (isMemberInitializer)
{
initializer = initializerBinder.WrapWithVariablesIfAny(initializerOpt, initializer);
}
}

return initializer;
}
Expand Down Expand Up @@ -1389,8 +1389,8 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Diag
var parameter = (ParameterSymbol)symbol;
if (IsBadLocalOrParameterCapture(parameter, parameter.RefKind))
{
Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name);
}
Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name);
}
return new BoundParameter(node, parameter, hasErrors: isError);
}

Expand Down Expand Up @@ -1829,7 +1829,7 @@ private BoundExpression BindOriginal(OriginalExpressionSyntax syntax, Diagnostic
{
var replacedProperty = (PropertySymbol)replaced;
Debug.Assert((object)replacedProperty != null);
if (replacedProperty.IsIndexer || replacedProperty.IsIndexedProperty)
if (replacedProperty.IsIndexer || replacedProperty.IsIndexedProperty)
{
return new BoundPropertyGroup(
syntax,
Expand Down Expand Up @@ -2230,6 +2230,11 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn

arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics);
}
else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference)
{
TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg);
arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, success: true);
}
}
}

Expand Down
13 changes: 12 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private static ImmutableArray<MethodSymbol> GetOriginalMethods(OverloadResolutio

var analyzedArguments = AnalyzedArguments.GetInstance();
analyzedArguments.Arguments.AddRange(args);
Debug.Assert(!args.Any(e => e.Kind == BoundKind.OutDeconstructVarPendingInference));
BoundExpression result = BindInvocationExpression(
node, node, methodName, boundExpression, analyzedArguments, diagnostics, queryClause,
allowUnexpandedForm: allowUnexpandedForm);
Expand Down Expand Up @@ -329,6 +330,8 @@ private BoundExpression BindArgListOperator(InvocationExpressionSyntax node, Dia

private ImmutableArray<BoundExpression> BuildArgumentsForDynamicInvocation(AnalyzedArguments arguments, DiagnosticBag diagnostics)
{
Debug.Assert(!arguments.Arguments.Any(a => a.Kind == BoundKind.OutDeconstructVarPendingInference));

return arguments.Arguments.ToImmutable();
}

Expand Down Expand Up @@ -1100,7 +1103,8 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
{
BoundKind argumentKind = oldArguments[i].Kind;

if (argumentKind == BoundKind.UnboundLambda && i < parameterCount)
if (argumentKind == BoundKind.OutDeconstructVarPendingInference ||
(argumentKind == BoundKind.UnboundLambda && i < parameterCount))
{
ArrayBuilder<BoundExpression> newArguments = ArrayBuilder<BoundExpression>.GetInstance(argumentCount);
newArguments.AddRange(oldArguments);
Expand All @@ -1120,8 +1124,15 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
newArguments[i] = ((UnboundLambda)oldArgument).Bind(parameterType);
}
break;
case BoundKind.OutDeconstructVarPendingInference:
newArguments[i] = ((OutDeconstructVarPendingInference)oldArgument).SetInferredType(parameters[i].Type, success: true);
break;
}
}
else if (oldArgument.Kind == BoundKind.OutDeconstructVarPendingInference)
{
newArguments[i] = ((OutDeconstructVarPendingInference)oldArgument).FailInference(this);
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

We probably need to have similar code in BuildArgumentsForDynamicInvocation, or at least an assert that we cannot run into OutDeconstructVarPendingInference there. #Resolved

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 assertion.

}

i++;
}
Expand Down
158 changes: 114 additions & 44 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -1831,7 +1830,7 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax
/// This will generate and stack appropriate deconstruction and assignment steps for a non-tuple type.
/// Returns null if there was an error (if a suitable Deconstruct method was not found).
/// </summary>
static private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep(
private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep(
BoundDeconstructValuePlaceholder targetPlaceholder,
AssignmentExpressionSyntax syntax,
DiagnosticBag diagnostics,
Expand All @@ -1840,16 +1839,22 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax
ArrayBuilder<BoundDeconstructionAssignmentStep> assignmentSteps)
{
// symbol and parameters for Deconstruct
MethodSymbol deconstructMethod = FindDeconstruct(variables.Length, targetPlaceholder, syntax, diagnostics);
ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders;
var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Length, targetPlaceholder, syntax, diagnostics, out outPlaceholders);

if ((object)deconstructMethod == null)
if (deconstructInvocation.HasAnyErrors)
{
return null;
}

return new BoundDeconstructionDeconstructStep(syntax, deconstructMethod, targetPlaceholder, deconstructMethod.Parameters.SelectAsArray((p, s) => new BoundDeconstructValuePlaceholder(s, p.Type), syntax));
else
{
return new BoundDeconstructionDeconstructStep(syntax, deconstructInvocation, targetPlaceholder, outPlaceholders);
}
}

/// <summary>
/// Holds the variables on the LHS of a deconstruction as a tree of bound expressions.
/// </summary>
private class DeconstructionVariable
{
public readonly BoundExpression Single;
Expand Down Expand Up @@ -1898,7 +1903,7 @@ public DeconstructionVariable(ImmutableArray<DeconstructionVariable> variables)
}
else
{
var assignment = MakeAssignmentInfo(variable.Single, valuePlaceholder.Type, valuePlaceholder, syntax, diagnostics);
var assignment = MakeDeconstructionAssignmentStep(variable.Single, valuePlaceholder.Type, valuePlaceholder, syntax, diagnostics);
assignmentSteps.Add(assignment);
}
}
Expand Down Expand Up @@ -1962,7 +1967,9 @@ private ImmutableArray<DeconstructionVariable> BindDeconstructionVariables(Separ
/// <summary>
/// Figures out how to assign from sourceType into receivingVariable and bundles the information (leaving holes for the actual source and receiver) into an AssignmentInfo.
/// </summary>
private BoundDeconstructionAssignmentStep MakeAssignmentInfo(BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder, AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
private BoundDeconstructionAssignmentStep MakeDeconstructionAssignmentStep(
BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder,
AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
{
var outputPlaceholder = new BoundDeconstructValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true };

Expand Down Expand Up @@ -1996,55 +2003,114 @@ static private void FlattenDeconstructVariables(ImmutableArray<DeconstructionVar
}

/// <summary>
/// Find the Deconstruct method for the expression on the right, that will fit the assignable bound expressions on the left.
/// Returns true if the Deconstruct method is found.
/// If so, it outputs the method.
/// Find the Deconstruct method for the expression on the right, that will fit the number of assignable variables on the left.
/// Returns an invocation expression if the Deconstruct method is found.
/// If so, it outputs placeholders that were coerced to the output types of the resolved Deconstruct method.
/// The overload resolution is similar to writing `receiver.Deconstruct(out var x1, out var x2, ...)`.
/// </summary>
private static MethodSymbol FindDeconstruct(int numCheckedVariables, BoundExpression boundRHS, SyntaxNode node, DiagnosticBag diagnostics)
private BoundExpression MakeDeconstructInvocationExpression(
int numCheckedVariables, BoundExpression receiver, AssignmentExpressionSyntax assignmentSyntax,
DiagnosticBag diagnostics, out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders)
{
// find symbol for Deconstruct member
ImmutableArray<Symbol> candidates = boundRHS.Type.GetMembers("Deconstruct");
switch (candidates.Length)
var receiverSyntax = receiver.Syntax;

if (receiver.Type.IsDynamic())
{
case 0:
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type);
return null;
case 1:
break;
default:
Error(diagnostics, ErrorCode.ERR_AmbiguousDeconstruct, boundRHS.Syntax, boundRHS.Type);
return null;
Error(diagnostics, ErrorCode.ERR_CannotDeconstructDynamic, receiverSyntax);
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);

return BadExpression(receiverSyntax, receiver);
}

Symbol deconstructMember = candidates[0];
var analyzedArguments = AnalyzedArguments.GetInstance();
var outVars = ArrayBuilder<OutDeconstructVarPendingInference>.GetInstance(numCheckedVariables);
DiagnosticBag bag = null;

// check that the deconstruct fits
if (deconstructMember.Kind != SymbolKind.Method)
try
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type);
return null;
}
for (int i = 0; i < numCheckedVariables; i++)
{
var variable = new OutDeconstructVarPendingInference(assignmentSyntax);
analyzedArguments.Arguments.Add(variable);
analyzedArguments.RefKinds.Add(RefKind.Out);
outVars.Add(variable);
}

MethodSymbol deconstructMethod = (MethodSymbol)deconstructMember;
if (deconstructMethod.MethodKind != MethodKind.Ordinary)
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type);
return null;
}
const string methodName = "Deconstruct";
var memberAccess = BindInstanceMemberAccess(
receiverSyntax, receiverSyntax, receiver, methodName, rightArity: 0,
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

Do we have a test when Deconstruct method is a generic extension method? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Not yet. Thanks for the suggestion. I'll add.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, the tests for deconstructing System.Tuple did involve generic extensions. I added an explicit test though.

typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>), typeArguments: default(ImmutableArray<TypeSymbol>),
invoked: true, diagnostics: diagnostics);

if (deconstructMethod.ParameterCount != numCheckedVariables)
{
Error(diagnostics, ErrorCode.ERR_DeconstructWrongParams, boundRHS.Syntax, deconstructMethod, numCheckedVariables);
return null;
}
memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics);
memberAccess.WasCompilerGenerated = true;

if (memberAccess.Kind != BoundKind.MethodGroup)
{
return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, receiver);
}

// After the overload resolution completes, the last step is to coerce the arguments with inferred types.
// That step returns placeholder (of correct type) instead of the outVar nodes that were passed in as arguments.
// So the generated invocation expression will contain placeholders instead of those outVar nodes.
// Those placeholders are also recorded in the outVar for easy access below, by the `SetInferredType` call on the outVar nodes.
bag = DiagnosticBag.GetInstance();
BoundExpression result = BindMethodGroupInvocation(
receiverSyntax, receiverSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, bag, queryClause: null,
allowUnexpandedForm: true);
Copy link
Member

Choose a reason for hiding this comment

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

Will this bind successfully to a field: dynamic Deconstruct;? Is that allowed?

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 added two tests: one with an object Deconstruct field (which properly reports that no invocable Deconstruct method was found), and one with dynamic Deconstruct field (as you suggested).
This latter one crashes. I'll investigate and fix.
Thanks for the suggestion!

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 added a check that the member we're trying to use is a MethodGroup before we proceed with its invocation.

Copy link
Member

Choose a reason for hiding this comment

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

If we're checking for MethodGroup consider calling BindMethodGroupInvocation directly instead of BindInvocationExpression.

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 (calling BindMethodGroupInvocation directly).


result.WasCompilerGenerated = true;
diagnostics.AddRange(bag);

if (deconstructMethod.Parameters.Any(p => p.RefKind != RefKind.Out))
if (bag.HasAnyErrors())
{
return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

I suspect that at this point the bag might contain use-site errors that are the actual source why binding failed. Not reporting them might actually make it harder to figure out why compilation fails. Consider to still include what we have in the bag to diagnostics. Other scenarios could benefit from this too, neither overload is better, etc. #Resolved

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 stop by to discuss with Vlad and you today.

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 latest commit I pushed shows what the errors look like if we put both (the regular errors from overload resolution plus the Deconstruct-specific one).

}

// Verify all the parameters (except "this" for extension methods) are out parameters
if (result.Kind != BoundKind.Call)
{
return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
}

var deconstructMethod = ((BoundCall)result).Method;
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

The conversion to BoundCall doesn't feel safe. If BindMethodGroupInvocation always returns BoundCall, we should change its return type to BoundCall and avoid conversion here. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

It could return BoundCall or BoundDynamicInvocation, but I'm blocking the dynamic case so far. I'll switch this to do a kind check, rather than a blind cast.

var parameters = deconstructMethod.Parameters;
for (int i = (deconstructMethod.IsExtensionMethod ? 1 : 0); i < parameters.Length; i++)
{
if (parameters[i].RefKind != RefKind.Out)
{
return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

Is this code reachable? #Resolved

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, in the tests that involve optional or params parameters. For example, DeconstructMethodHasOptionalParam.

}
}

if (outVars.Any(v => (object)v.Placeholder == null))
{
return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
}

outPlaceholders = outVars.SelectAsArray(v => v.Placeholder);
Copy link
Member

Choose a reason for hiding this comment

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

Is outVars modified in BindInvocationExpression? Were we previously mutating AnalyzedArguments?

Copy link
Member Author

@jcouv jcouv Jun 9, 2016

Choose a reason for hiding this comment

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

Yes, AnalyzedArguments were previously updated after overload resolution. Look at the CoerceArguments method, which is modified above to call SetInferredType. A few lines above, pre-existing code was also updating the arguments:

                if (!kind.IsIdentity)
                {
                    TypeSymbol type = GetCorrespondingParameterType(ref result, parameters, arg);
                    // [removed some code]
                    arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics);
                }
                else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference)
                {
                    TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg);
                    arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, success: true);
                }

As for the outVars local, it does not get passed into any method and it is not modified. The reason I need it is because it is a more reliable way of collecting the placeholders than trying to get them from modified AnalyzedArguments (which is tricky in extension method scenarios).

Copy link
Member

@VSadov VSadov Jun 9, 2016

Choose a reason for hiding this comment

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

It seems fragile. You are building a mutable bound tree, then calling some binding methods and as sideeffect of binding the nodes are supposed to be mutated to the right values. It is hard to track when that happens, why it is guaranteed to happen. Why it would happen only once.

This is the only place where you are relying on implicit mutations during binding.
Perhaps there is a way to flow the placeholders out of binding in a more explicit way? As optional out parameter or something like that?

Copy link
Member

Choose a reason for hiding this comment

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

Given that analyzedArguments.Arguments is being mutated, can we use those values rather than making OutDeconstructVarPendingInference mutable as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

@cston, that is what I tried to do initially, but in the case of extension methods, the arguments object that is present at time of coercion is not the one that is passed in. The arguments seen during coercion have one extra parameter (for this).

In stacktrace format:

  • CoerceArguments receives 3 arguments
  • BindInvocationExpressionContinued receives 3 arguments
  • BindMethodGroupInvocation receives 2 arguments

The arguments are substituted with a different object when BindMethodGroupInvocation calls BindInvocationExpressionContinued:

   result = BindInvocationExpressionContinued(
                            syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments,
                            resolution.MethodGroup, null, diagnostics, resolution.ExtensionMethodsOfSameViabilityAreAvailable, queryClause);

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for explaining. Are there tests for Deconstruct extension methods?

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, They're using the Deconstruct extension methods that I just added to the ValueTuple library.

Copy link
Contributor

@AlekseyTs AlekseyTs Jun 12, 2016

Choose a reason for hiding this comment

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

We should at least assert that there are no null placeholders. I would call MissingDeconstruct if that happened. #Resolved

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, but I don't think I can his that case presently.


return result;
}
finally
{
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresOutParams, boundRHS.Syntax, deconstructMethod);
return null;
analyzedArguments.Free();
outVars.Free();

if (bag != null)
{
bag.Free();
}
}

return deconstructMethod;
}

private BoundExpression MissingDeconstruct(BoundExpression receiver, AssignmentExpressionSyntax syntax, int numParameters, DiagnosticBag diagnostics, out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, BoundNode childNode)
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, receiver.Syntax, receiver.Type, numParameters);
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);

return BadExpression(syntax, childNode);
}

private BoundAssignmentOperator BindAssignment(AssignmentExpressionSyntax node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -2196,6 +2262,10 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind
case BoundKind.PropertyGroup:
expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics);
break;

case BoundKind.OutDeconstructVarPendingInference:
Debug.Assert(valueKind == BindValueKind.RefOrOut);
return expr;
}

bool hasResolutionErrors = false;
Expand Down
Loading