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 2 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
11 changes: 10 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 @@ -1100,7 +1101,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 +1122,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
113 changes: 69 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,69 @@ 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);

// 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;
}
string methodName = "Deconstruct";
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.

const? #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.

Sure. Added.

var boundExpression = 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;
}
boundExpression = CheckValue(boundExpression, BindValueKind.RValueOrMethodGroup, diagnostics);
boundExpression.WasCompilerGenerated = true;

BoundExpression result = BindInvocationExpression(
receiverSyntax, receiverSyntax, methodName, boundExpression, analyzedArguments, diagnostics, 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).


if (deconstructMethod.Parameters.Any(p => p.RefKind != RefKind.Out))
result.WasCompilerGenerated = true;

if (result.HasAnyErrors)
{
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);
return BadExpression(assignmentSyntax, 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();
}

return deconstructMethod;
}

private BoundAssignmentOperator BindAssignment(AssignmentExpressionSyntax node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -2196,6 +2217,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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;
using System;

namespace Microsoft.CodeAnalysis.CSharp
{
Expand Down Expand Up @@ -1230,7 +1229,18 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2);

bool okToDowngradeToNeither;
var r = BetterConversionFromExpression(arguments[i],
BetterResult r;

if (argumentKind == BoundKind.OutDeconstructVarPendingInference)
{
// If argument is an out variable that needs type inference,
// neither candidate is better in this argument.
r = BetterResult.Neither;
okToDowngradeToNeither = false;
}
else
{
r = BetterConversionFromExpression(arguments[i],
type1,
m1.Result.ConversionForArg(i),
refKind1,
Expand All @@ -1240,6 +1250,7 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
considerRefKinds,
ref useSiteDiagnostics,
out okToDowngradeToNeither);
}

if (r == BetterResult.Neither)
{
Expand Down Expand Up @@ -2906,6 +2917,14 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind
return Conversion.ImplicitDynamic;
}

if (argument.Kind == BoundKind.OutDeconstructVarPendingInference)
{
Debug.Assert(argRefKind != RefKind.None);

// Any parameter type is good, we'll use it for the var local.
return Conversion.Identity;
}

if (argRefKind == RefKind.None)
{
var conversion = Conversions.ClassifyImplicitConversionFromExpression(argument, parameterType, ref useSiteDiagnostics);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed
// If the expression is untyped because it is a lambda, anonymous method, method group or null
// then we never want to report the error "you need a ref on that thing". Rather, we want to
// say that you can't convert "null" to "ref int".
if (!argument.HasExpressionType())
if (!argument.HasExpressionType() && argument.Kind != BoundKind.OutDeconstructVarPendingInference)
Copy link
Member

Choose a reason for hiding this comment

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

Is there a test for this case (where the out var has no type)?

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, for instance "DeconstructWithInParam" reaches this code and the second argument has no type.

    static void Main()
    {
        int x;
        int y;
        (x, y) = new C();
    }
    public void Deconstruct(out int x, int y) { x = 1; }

The overload resolution code generates this error, a few lines below:
// (8,9): error CS1615: Argument 2 may not be passed with the 'out' keyword

{
// If the problem is that a lambda isn't convertible to the given type, also report why.
// The argument and parameter type might match, but may not have same in/out modifiers
Expand Down Expand Up @@ -971,6 +971,8 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed
}
else
{
Debug.Assert(argument.Kind != BoundKind.OutDeconstructVarPendingInference);

TypeSymbol argType = argument.Display as TypeSymbol;
Debug.Assert((object)argType != null);

Expand Down
Loading