File: Binder\Binder_Deconstruct.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// This portion of the binder converts deconstruction-assignment syntax (AssignmentExpressionSyntax nodes with the left
    /// being a tuple expression or declaration expression) into a BoundDeconstructionAssignmentOperator (or bad node).
    /// The BoundDeconstructionAssignmentOperator will have:
    /// - a BoundTupleLiteral as its Left,
    /// - a BoundConversion as its Right, holding:
    ///     - a tree of Conversion objects with Kind=Deconstruction, information about a Deconstruct method (optional) and
    ///         an array of nested Conversions (like a tuple conversion),
    ///     - a BoundExpression as its Operand.
    /// </summary>
    internal partial class Binder
    {
        internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, BindingDiagnosticBag diagnostics, bool resultIsUsedOverride = false)
        {
            var left = node.Left;
            var right = node.Right;
            DeclarationExpressionSyntax? declaration = null;
            ExpressionSyntax? expression = null;
            var result = BindDeconstruction(node, left, right, diagnostics, ref declaration, ref expression, resultIsUsedOverride);
            if (declaration != null)
            {
                // only allowed at the top level, or in a for loop
                switch (node.Parent?.Kind())
                {
                    case null:
                    case SyntaxKind.ExpressionStatement:
                        if (expression != null)
                        {
                            MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction
                                .CheckFeatureAvailability(diagnostics, Compilation, node.Location);
                        }
                        break;
                    case SyntaxKind.ForStatement:
                        if (((ForStatementSyntax)node.Parent).Initializers.Contains(node))
                        {
                            if (expression != null)
                            {
                                MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction
                                    .CheckFeatureAvailability(diagnostics, Compilation, node.Location);
                            }
                        }
                        else
                        {
                            Error(diagnostics, ErrorCode.ERR_DeclarationExpressionNotPermitted, declaration);
                        }
                        break;
                    default:
                        Error(diagnostics, ErrorCode.ERR_DeclarationExpressionNotPermitted, declaration);
                        break;
                }
            }
 
            return result;
        }
 
        /// <summary>
        /// Bind a deconstruction assignment.
        /// </summary>
        /// <param name="deconstruction">The deconstruction operation</param>
        /// <param name="left">The left (tuple) operand</param>
        /// <param name="right">The right (deconstructable) operand</param>
        /// <param name="diagnostics">Where to report diagnostics</param>
        /// <param name="declaration">A variable set to the first variable declaration found in the left</param>
        /// <param name="expression">A variable set to the first expression in the left that isn't a declaration or discard</param>
        /// <param name="resultIsUsedOverride">The expression evaluator needs to bind deconstructions (both assignments and declarations) as expression-statements
        ///     and still access the returned value</param>
        /// <param name="rightPlaceholder"></param>
        /// <returns></returns>
        internal BoundDeconstructionAssignmentOperator BindDeconstruction(
            CSharpSyntaxNode deconstruction,
            ExpressionSyntax left,
            ExpressionSyntax right,
            BindingDiagnosticBag diagnostics,
            ref DeclarationExpressionSyntax? declaration,
            ref ExpressionSyntax? expression,
            bool resultIsUsedOverride = false,
            BoundDeconstructValuePlaceholder? rightPlaceholder = null)
        {
            DeconstructionVariable locals = BindDeconstructionVariables(left, diagnostics, ref declaration, ref expression);
            Debug.Assert(locals.NestedVariables is object);
 
            var deconstructionDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: diagnostics.AccumulatesDependencies);
            BoundExpression boundRight = rightPlaceholder ?? BindValue(right, deconstructionDiagnostics, BindValueKind.RValue);
 
            boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics);
            boundRight = BindToNaturalType(boundRight, diagnostics);
 
            bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left);
            var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, deconstructionDiagnostics);
            DeconstructionVariable.FreeDeconstructionVariables(locals.NestedVariables);
 
            diagnostics.AddRangeAndFree(deconstructionDiagnostics);
            return assignment;
        }
 
        private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment(
                                                        CSharpSyntaxNode node,
                                                        ExpressionSyntax left,
                                                        BoundExpression boundRHS,
                                                        ArrayBuilder<DeconstructionVariable> checkedVariables,
                                                        bool resultIsUsed,
                                                        BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(diagnostics.DiagnosticBag is object);
 
            if ((object?)boundRHS.Type == null || boundRHS.Type.IsErrorType())
            {
                // we could still not infer a type for the RHS
                FailRemainingInferences(checkedVariables, diagnostics);
                var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node);
 
                var type = boundRHS.Type ?? voidType;
                return new BoundDeconstructionAssignmentOperator(
                            node,
                            DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true),
                            new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null,
                                constantValueOpt: null, type: type, hasErrors: true),
                            resultIsUsed,
                            voidType,
                            hasErrors: true);
            }
 
            Conversion conversion;
            // Among other things, MakeDeconstructionConversion() will handle
            // value escape analysis for the Deconstruct() method group.
            bool hasErrors = !MakeDeconstructionConversion(
                                    boundRHS.Type,
                                    node,
                                    boundRHS.Syntax,
                                    diagnostics,
                                    checkedVariables,
                                    out conversion);
 
            if (conversion.Method != null)
            {
                CheckImplicitThisCopyInReadOnlyMember(boundRHS, conversion.Method, diagnostics);
            }
 
            FailRemainingInferences(checkedVariables, diagnostics);
 
            var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed);
            Debug.Assert(hasErrors || lhsTuple.Type is object);
            TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type!;
 
            var boundConversion = new BoundConversion(
                boundRHS.Syntax,
                boundRHS,
                conversion,
                @checked: false,
                explicitCastInCode: false,
                conversionGroupOpt: null,
                constantValueOpt: null,
                type: returnType,
                hasErrors: hasErrors)
            { WasCompilerGenerated = true };
 
            return new BoundDeconstructionAssignmentOperator(node, lhsTuple, boundConversion, resultIsUsed, returnType);
        }
 
        private static bool IsDeconstructionResultUsed(ExpressionSyntax left)
        {
            var parent = left.Parent;
            if (parent is null || parent.Kind() == SyntaxKind.ForEachVariableStatement)
            {
                return false;
            }
 
            Debug.Assert(parent.Kind() == SyntaxKind.SimpleAssignmentExpression);
 
            var grandParent = parent.Parent;
            if (grandParent is null)
            {
                return false;
            }
 
            switch (grandParent.Kind())
            {
                case SyntaxKind.ExpressionStatement:
                    return ((ExpressionStatementSyntax)grandParent).Expression != parent;
 
                case SyntaxKind.ForStatement:
                    // Incrementors and Initializers don't have to produce a value
                    var loop = (ForStatementSyntax)grandParent;
                    return !loop.Incrementors.Contains(parent) && !loop.Initializers.Contains(parent);
 
                default:
                    return true;
            }
        }
 
        /// <summary>When boundRHS is a tuple literal, fix it up by inferring its types.</summary>
        private BoundExpression FixTupleLiteral(ArrayBuilder<DeconstructionVariable> checkedVariables, BoundExpression boundRHS, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(diagnostics.DiagnosticBag is object);
 
            if (boundRHS.Kind == BoundKind.TupleLiteral)
            {
                // 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
 
                // If we already have diagnostics at this point, it is not worth collecting likely duplicate diagnostics from making the merged type
                bool hadErrors = diagnostics.HasAnyErrors();
                TypeSymbol? mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, hadErrors ? null : diagnostics);
                if ((object?)mergedTupleType != null)
                {
                    boundRHS = GenerateConversionForAssignment(mergedTupleType, boundRHS, diagnostics);
                }
            }
            else if ((object?)boundRHS.Type == null)
            {
                Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, boundRHS.Syntax);
            }
 
            return boundRHS;
        }
 
        /// <summary>
        /// Recursively builds a Conversion object with Kind=Deconstruction including information about any necessary
        /// Deconstruct method and any element-wise conversion.
        ///
        /// Note that the variables may either be plain or nested variables.
        /// The variables may be updated with inferred types if they didn't have types initially.
        /// Returns false if there was an error.
        /// </summary>
        private bool MakeDeconstructionConversion(
                        TypeSymbol type,
                        SyntaxNode syntax,
                        SyntaxNode rightSyntax,
                        BindingDiagnosticBag diagnostics,
                        ArrayBuilder<DeconstructionVariable> variables,
                        out Conversion conversion)
        {
            Debug.Assert((object)type != null);
            ImmutableArray<TypeSymbol> tupleOrDeconstructedTypes;
            conversion = Conversion.Deconstruction;
 
            // Figure out the deconstruct method (if one is required) and determine the types we get from the RHS at this level
            var deconstructMethod = default(DeconstructMethodInfo);
            if (type.IsTupleType)
            {
                // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())`
                tupleOrDeconstructedTypes = type.TupleElementTypesWithAnnotations.SelectAsArray(TypeMap.AsTypeSymbol);
                SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics);
 
                if (variables.Count != tupleOrDeconstructedTypes.Length)
                {
                    Error(diagnostics, ErrorCode.ERR_DeconstructWrongCardinality, syntax, tupleOrDeconstructedTypes.Length, variables.Count);
                    return false;
                }
            }
            else
            {
                if (variables.Count < 2)
                {
                    Error(diagnostics, ErrorCode.ERR_DeconstructTooFewElements, syntax);
                    return false;
                }
 
                var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, variableSymbol: null, isDiscardExpression: false, type);
                BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count,
                    inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, out _, variables);
 
                if (deconstructInvocation.HasAnyErrors)
                {
                    return false;
                }
 
                deconstructMethod = new DeconstructMethodInfo(deconstructInvocation, inputPlaceholder, outPlaceholders);
 
                tupleOrDeconstructedTypes = outPlaceholders.SelectAsArray(p => p.Type);
                SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics);
            }
 
            // Figure out whether those types will need conversions, including further deconstructions
            bool hasErrors = false;
 
            int count = variables.Count;
            var nestedConversions = ArrayBuilder<(BoundValuePlaceholder?, BoundExpression?)>.GetInstance(count);
            for (int i = 0; i < count; i++)
            {
                var variable = variables[i];
 
                Conversion nestedConversion;
                if (variable.NestedVariables is object)
                {
                    var elementSyntax = syntax.Kind() == SyntaxKind.TupleExpression ? ((TupleExpressionSyntax)syntax).Arguments[i] : syntax;
 
                    hasErrors |= !MakeDeconstructionConversion(tupleOrDeconstructedTypes[i], elementSyntax, rightSyntax, diagnostics,
                        variable.NestedVariables, out nestedConversion);
 
                    Debug.Assert(nestedConversion.Kind == ConversionKind.Deconstruction);
                    var operandPlaceholder = new BoundValuePlaceholder(syntax, ErrorTypeSymbol.UnknownResultType).MakeCompilerGenerated();
                    nestedConversions.Add((operandPlaceholder, new BoundConversion(syntax, operandPlaceholder, nestedConversion,
                                                                                   @checked: false, explicitCastInCode: false,
                                                                                   conversionGroupOpt: null, constantValueOpt: null,
#pragma warning disable format
                                                                                   type: ErrorTypeSymbol.UnknownResultType) { WasCompilerGenerated = true }));
#pragma warning restore format
                }
                else
                {
                    var single = variable.Single;
                    Debug.Assert(single is object);
                    Debug.Assert(single.Type is not null);
                    CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                    nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                    diagnostics.Add(single.Syntax, useSiteInfo);
 
                    if (!nestedConversion.IsImplicit)
                    {
                        hasErrors = true;
                        GenerateImplicitConversionError(diagnostics, Compilation, single.Syntax, nestedConversion, tupleOrDeconstructedTypes[i], single.Type);
                        nestedConversions.Add((null, null));
                    }
                    else
                    {
                        var operandPlaceholder = new BoundValuePlaceholder(syntax, tupleOrDeconstructedTypes[i]).MakeCompilerGenerated();
                        nestedConversions.Add((operandPlaceholder, CreateConversion(syntax, operandPlaceholder,
                                                                                    nestedConversion, isCast: false, conversionGroupOpt: null, single.Type, diagnostics)));
                    }
                }
            }
 
            conversion = new Conversion(ConversionKind.Deconstruction, deconstructMethod, nestedConversions.ToImmutableAndFree());
 
            return !hasErrors;
        }
 
        /// <summary>
        /// Inform the variables about found types.
        /// </summary>
        private void SetInferredTypes(ArrayBuilder<DeconstructionVariable> variables, ImmutableArray<TypeSymbol> foundTypes, BindingDiagnosticBag diagnostics)
        {
            var matchCount = Math.Min(variables.Count, foundTypes.Length);
            for (int i = 0; i < matchCount; i++)
            {
                var variable = variables[i];
                if (variable.Single is { } pending)
                {
                    if ((object?)pending.Type != null)
                    {
                        continue;
                    }
 
                    variables[i] = new DeconstructionVariable(SetInferredType(pending, foundTypes[i], diagnostics), variable.Syntax);
                }
            }
        }
 
        private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol type, BindingDiagnosticBag diagnostics)
        {
            switch (expression.Kind)
            {
                case BoundKind.DeconstructionVariablePendingInference:
                    {
                        var pending = (DeconstructionVariablePendingInference)expression;
                        return pending.SetInferredTypeWithAnnotations(TypeWithAnnotations.Create(type), this, diagnostics);
                    }
                case BoundKind.DiscardExpression:
                    {
                        var pending = (BoundDiscardExpression)expression;
                        Debug.Assert((object?)pending.Type == null);
                        return pending.SetInferredTypeWithAnnotations(TypeWithAnnotations.Create(type));
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(expression.Kind);
            }
        }
 
        /// <summary>
        /// Find any deconstruction locals that are still pending inference and fail their inference.
        /// Set the safe-to-escape scope for all deconstruction locals.
        /// </summary>
        private void FailRemainingInferences(ArrayBuilder<DeconstructionVariable> variables, BindingDiagnosticBag diagnostics)
        {
            int count = variables.Count;
            for (int i = 0; i < count; i++)
            {
                var variable = variables[i];
                if (variable.NestedVariables is object)
                {
                    FailRemainingInferences(variable.NestedVariables, diagnostics);
                }
                else
                {
                    Debug.Assert(variable.Single is object);
                    switch (variable.Single.Kind)
                    {
                        case BoundKind.DeconstructionVariablePendingInference:
                            BoundExpression errorLocal = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics);
                            variables[i] = new DeconstructionVariable(errorLocal, errorLocal.Syntax);
                            break;
                        case BoundKind.DiscardExpression:
                            var pending = (BoundDiscardExpression)variable.Single;
                            if ((object?)pending.Type == null)
                            {
                                Error(diagnostics, ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, pending.Syntax, "_");
                                variables[i] = new DeconstructionVariable(pending.FailInference(this, diagnostics), pending.Syntax);
                            }
                            break;
                    }
 
                    // at this point we expect to have a type for every lvalue
                    Debug.Assert((object?)variables[i].Single!.Type != null);
                }
            }
        }
 
        /// <summary>
        /// Holds the variables on the LHS of a deconstruction as a tree of bound expressions.
        /// </summary>
        [DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
        internal sealed class DeconstructionVariable
        {
            internal readonly BoundExpression? Single;
            internal readonly ArrayBuilder<DeconstructionVariable>? NestedVariables;
            internal readonly CSharpSyntaxNode Syntax;
 
            internal DeconstructionVariable(BoundExpression variable, SyntaxNode syntax)
            {
                Single = variable;
                NestedVariables = null;
                Syntax = (CSharpSyntaxNode)syntax;
            }
 
            internal DeconstructionVariable(ArrayBuilder<DeconstructionVariable> variables, SyntaxNode syntax)
            {
                Single = null;
                NestedVariables = variables;
                Syntax = (CSharpSyntaxNode)syntax;
            }
 
            internal static void FreeDeconstructionVariables(ArrayBuilder<DeconstructionVariable> variables)
            {
                variables.FreeAll(v => v.NestedVariables);
            }
 
            private string GetDebuggerDisplay()
            {
                if (Single != null)
                {
                    return Single.GetDebuggerDisplay();
                }
                Debug.Assert(NestedVariables is object);
                return $"Nested variables ({NestedVariables.Count})";
            }
        }
 
        /// <summary>
        /// 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 TypeSymbol? MakeMergedTupleType(ArrayBuilder<DeconstructionVariable> lhsVariables, BoundTupleLiteral rhsLiteral, CSharpSyntaxNode syntax, BindingDiagnosticBag? diagnostics)
        {
            int leftLength = lhsVariables.Count;
            int rightLength = rhsLiteral.Arguments.Length;
 
            var typesWithAnnotationsBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(leftLength);
            var locationsBuilder = ArrayBuilder<Location?>.GetInstance(leftLength);
            for (int i = 0; i < rightLength; i++)
            {
                BoundExpression element = rhsLiteral.Arguments[i];
                TypeSymbol? mergedType = element.Type;
 
                if (i < leftLength)
                {
                    var variable = lhsVariables[i];
                    if (variable.NestedVariables is object)
                    {
                        if (element.Kind == BoundKind.TupleLiteral)
                        {
                            // (variables) on the left and (elements) on the right
                            mergedType = MakeMergedTupleType(variable.NestedVariables, (BoundTupleLiteral)element, syntax, diagnostics);
                        }
                        else if ((object?)mergedType == null && diagnostics is object)
                        {
                            // (variables) on the left and null on the right
                            Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax);
                        }
                    }
                    else
                    {
                        Debug.Assert(variable.Single is object);
                        if ((object?)variable.Single.Type != null)
                        {
                            // typed-variable on the left
                            mergedType = variable.Single.Type;
                        }
                    }
                }
                else
                {
                    if ((object?)mergedType == null && diagnostics is object)
                    {
                        // a typeless element on the right, matching no variable on the left
                        Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax);
                    }
                }
 
                typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(mergedType));
                locationsBuilder.Add(element.Syntax.Location);
            }
 
            if (typesWithAnnotationsBuilder.Any(t => !t.HasType))
            {
                typesWithAnnotationsBuilder.Free();
                locationsBuilder.Free();
                return null;
            }
 
            // The tuple created here is not identical to the one created by
            // DeconstructionVariablesAsTuple. It represents a smaller
            // tree of types used for figuring out natural types in tuple literal.
            return NamedTypeSymbol.CreateTuple(
                locationOpt: null,
                elementTypesWithAnnotations: typesWithAnnotationsBuilder.ToImmutableAndFree(),
                elementLocations: locationsBuilder.ToImmutableAndFree(),
                elementNames: default(ImmutableArray<string?>),
                compilation: Compilation,
                diagnostics: diagnostics,
                shouldCheckConstraints: true,
                includeNullability: false,
                errorPositions: default(ImmutableArray<bool>),
                syntax: syntax);
        }
 
        private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder<DeconstructionVariable> variables,
            BindingDiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple)
        {
            int count = variables.Count;
            var valuesBuilder = ArrayBuilder<BoundExpression>.GetInstance(count);
            var typesWithAnnotationsBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(count);
            var locationsBuilder = ArrayBuilder<Location?>.GetInstance(count);
            var namesBuilder = ArrayBuilder<string?>.GetInstance(count);
 
            foreach (var variable in variables)
            {
                BoundExpression value;
                if (variable.NestedVariables is object)
                {
                    value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple);
                    namesBuilder.Add(null);
                }
                else
                {
                    Debug.Assert(variable.Single is object);
                    value = variable.Single;
                    namesBuilder.Add(ExtractDeconstructResultElementName(value));
                }
                valuesBuilder.Add(value);
                typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(value.Type));
                locationsBuilder.Add(variable.Syntax.Location);
            }
            ImmutableArray<BoundExpression> arguments = valuesBuilder.ToImmutableAndFree();
 
            var uniqueFieldNames = PooledHashSet<string>.GetInstance();
            RemoveDuplicateInferredTupleNamesAndFreeIfEmptied(ref namesBuilder, uniqueFieldNames);
            uniqueFieldNames.Free();
 
            ImmutableArray<string?> tupleNames = namesBuilder is null ? default : namesBuilder.ToImmutableAndFree();
            ImmutableArray<bool> inferredPositions = tupleNames.IsDefault ? default : tupleNames.SelectAsArray(n => n != null);
            bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames();
 
            var type = NamedTypeSymbol.CreateTuple(
                syntax.Location,
                typesWithAnnotationsBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(),
                tupleNames, this.Compilation,
                shouldCheckConstraints: !ignoreDiagnosticsFromTuple,
                includeNullability: false,
                errorPositions: disallowInferredNames ? inferredPositions : default,
                syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics);
 
            return (BoundTupleExpression)BindToNaturalType(new BoundTupleLiteral(syntax, arguments, tupleNames, inferredPositions, type), diagnostics);
        }
 
        /// <summary>Extract inferred name from a single deconstruction variable.</summary>
        private static string? ExtractDeconstructResultElementName(BoundExpression expression)
        {
            if (expression.Kind == BoundKind.DiscardExpression)
            {
                return null;
            }
 
            return InferTupleElementName(expression.Syntax);
        }
 
        /// <summary>
        /// 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 <c>receiver.Deconstruct(out var x1, out var x2, ...)</c>.
        /// </summary>
        private BoundExpression MakeDeconstructInvocationExpression(
            int numCheckedVariables,
            BoundExpression receiver,
            SyntaxNode rightSyntax,
            BindingDiagnosticBag diagnostics,
            out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders,
            out bool anyApplicableCandidates,
            ArrayBuilder<DeconstructionVariable>? variablesOpt = null)
        {
            anyApplicableCandidates = false;
            var receiverSyntax = (CSharpSyntaxNode)receiver.Syntax;
            if (receiver.Type?.IsDynamic() ?? false)
            {
                Error(diagnostics, ErrorCode.ERR_CannotDeconstructDynamic, rightSyntax);
                outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);
 
                return BadExpression(receiverSyntax, receiver);
            }
 
            receiver = BindToNaturalType(receiver, diagnostics);
            var analyzedArguments = AnalyzedArguments.GetInstance();
            var outVars = ArrayBuilder<OutDeconstructVarPendingInference>.GetInstance(numCheckedVariables);
 
            try
            {
                for (int i = 0; i < numCheckedVariables; i++)
                {
                    var variableOpt = variablesOpt?[i].Single;
                    var variableSymbol = variableOpt switch
                    {
                        DeconstructionVariablePendingInference { VariableSymbol: var symbol } => symbol,
                        BoundLocal { DeclarationKind: BoundLocalDeclarationKind.WithExplicitType or BoundLocalDeclarationKind.WithInferredType, LocalSymbol: var symbol } => symbol,
                        _ => null,
                    };
                    var variable = new OutDeconstructVarPendingInference(receiverSyntax, variableSymbol: variableSymbol, isDiscardExpression: variableOpt is BoundDiscardExpression);
                    analyzedArguments.Arguments.Add(variable);
                    analyzedArguments.RefKinds.Add(RefKind.Out);
                    outVars.Add(variable);
                }
 
                const string methodName = WellKnownMemberNames.DeconstructMethodName;
                var memberAccess = BindInstanceMemberAccess(
                                        rightSyntax, receiverSyntax, receiver, methodName, rightArity: 0,
                                        typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>),
                                        typeArgumentsWithAnnotations: default(ImmutableArray<TypeWithAnnotations>),
                                        invoked: true, indexed: false, diagnostics: diagnostics);
 
                memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics);
                memberAccess.WasCompilerGenerated = true;
 
                if (memberAccess.Kind != BoundKind.MethodGroup)
                {
                    return MissingDeconstruct(receiver, rightSyntax, 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.
                BoundExpression result = BindMethodGroupInvocation(
                    rightSyntax, rightSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null,
                    ignoreNormalFormIfHasValidParamsParameter: false, anyApplicableCandidates: out anyApplicableCandidates);
 
                result.WasCompilerGenerated = true;
 
                if (!anyApplicableCandidates)
                {
                    return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
                }
 
                // Verify all the parameters (except "this" for extension methods) are out parameters.
                // This prevents, for example, an unused params parameter after the out parameters.
                var deconstructMethod = ((BoundCall)result).Method;
                var parameters = deconstructMethod.Parameters;
                for (int i = (deconstructMethod.IsExtensionMethod ? 1 : 0); i < parameters.Length; i++)
                {
                    if (parameters[i].RefKind != RefKind.Out)
                    {
                        return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
                    }
                }
 
                if (deconstructMethod.ReturnType.GetSpecialTypeSafe() != SpecialType.System_Void)
                {
                    return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
                }
 
                if (outVars.Any(v => v.Placeholder is null))
                {
                    return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result);
                }
 
                outPlaceholders = outVars.SelectAsArray(v => v.Placeholder!);
 
                return result;
            }
            finally
            {
                analyzedArguments.Free();
                outVars.Free();
            }
        }
 
        private BoundBadExpression MissingDeconstruct(BoundExpression receiver, SyntaxNode rightSyntax, int numParameters, BindingDiagnosticBag diagnostics,
                                    out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, BoundExpression childNode)
        {
            if (receiver.Type?.IsErrorType() == false)
            {
                Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type!, numParameters);
            }
 
            outPlaceholders = default;
            return BadExpression(rightSyntax, childNode);
        }
 
        /// <summary>
        /// Prepares locals (or fields in global statement) and lvalue expressions corresponding to the variables of the declaration.
        /// The locals/fields/lvalues are kept in a tree which captures the nesting of variables.
        /// Each local or field is either a simple local or field access (when its type is known) or a deconstruction variable pending inference.
        /// The caller is responsible for releasing the nested ArrayBuilders.
        /// </summary>
        private DeconstructionVariable BindDeconstructionVariables(
            ExpressionSyntax node,
            BindingDiagnosticBag diagnostics,
            ref DeclarationExpressionSyntax? declaration,
            ref ExpressionSyntax? expression)
        {
            switch (node.Kind())
            {
                case SyntaxKind.DeclarationExpression:
                    {
                        var component = (DeclarationExpressionSyntax)node;
                        if (declaration == null)
                        {
                            declaration = component;
                        }
 
                        bool isVar;
                        bool isConst = false;
                        AliasSymbol alias;
                        var declType = BindVariableTypeWithAnnotations(component.Designation, diagnostics, component.Type.SkipScoped(out _).SkipRef(), ref isConst, out isVar, out alias);
                        Debug.Assert(isVar == !declType.HasType);
                        if (component.Designation.Kind() == SyntaxKind.ParenthesizedVariableDesignation)
                        {
                            if (!isVar)
                            {
                                // An explicit type is not allowed with a parenthesized designation
                                Error(diagnostics, ErrorCode.ERR_DeconstructionVarFormDisallowsSpecificType, component.Designation);
                            }
                            else if (node.Parent is not ArgumentSyntax)
                            {
                                // check for use of `var (x, y, z)`.  Only need to report this in a non-argument
                                // position. If it's an argument, then we have `(int x, var (y, z))` and we will have
                                // already reported the parent tuple, so no need to report on the inner designation.
                                MessageID.IDS_FeatureTuples.CheckFeatureAvailability(diagnostics, component.Designation);
                            }
                        }
 
                        return BindDeconstructionVariables(declType, component.Designation, component, diagnostics);
                    }
                case SyntaxKind.TupleExpression:
                    {
                        MessageID.IDS_FeatureTuples.CheckFeatureAvailability(diagnostics, node);
 
                        var component = (TupleExpressionSyntax)node;
                        var builder = ArrayBuilder<DeconstructionVariable>.GetInstance(component.Arguments.Count);
                        foreach (var arg in component.Arguments)
                        {
                            if (arg.NameColon != null)
                            {
                                Error(diagnostics, ErrorCode.ERR_TupleElementNamesInDeconstruction, arg.NameColon);
                            }
 
                            builder.Add(BindDeconstructionVariables(arg.Expression, diagnostics, ref declaration, ref expression));
                        }
 
                        return new DeconstructionVariable(builder, node);
                    }
                default:
                    var boundVariable = BindExpression(node, diagnostics, invoked: false, indexed: false);
                    var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics);
                    if (expression == null && checkedVariable.Kind != BoundKind.DiscardExpression)
                    {
                        expression = node;
                    }
 
                    return new DeconstructionVariable(checkedVariable, node);
            }
        }
 
        private DeconstructionVariable BindDeconstructionVariables(
            TypeWithAnnotations declTypeWithAnnotations,
            VariableDesignationSyntax node,
            CSharpSyntaxNode syntax,
            BindingDiagnosticBag diagnostics)
        {
            switch (node.Kind())
            {
                case SyntaxKind.SingleVariableDesignation:
                    {
                        var single = (SingleVariableDesignationSyntax)node;
                        return new DeconstructionVariable(BindDeconstructionVariable(declTypeWithAnnotations, single, syntax, diagnostics), syntax);
                    }
                case SyntaxKind.DiscardDesignation:
                    {
                        var discarded = (DiscardDesignationSyntax)node;
 
                        if (discarded.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == discarded)
                        {
                            TypeSyntax typeSyntax = declExpr.Type;
 
                            if (typeSyntax is ScopedTypeSyntax scopedType)
                            {
                                diagnostics.Add(ErrorCode.ERR_ScopedDiscard, scopedType.ScopedKeyword.GetLocation());
                                typeSyntax = scopedType.Type;
                            }
 
                            if (typeSyntax is RefTypeSyntax refType)
                            {
                                diagnostics.Add(ErrorCode.ERR_DeconstructVariableCannotBeByRef, refType.RefKeyword.GetLocation());
                            }
                        }
 
                        return new DeconstructionVariable(BindDiscardExpression(syntax, declTypeWithAnnotations), syntax);
                    }
                case SyntaxKind.ParenthesizedVariableDesignation:
                    {
                        var tuple = (ParenthesizedVariableDesignationSyntax)node;
                        var builder = ArrayBuilder<DeconstructionVariable>.GetInstance();
                        foreach (var n in tuple.Variables)
                        {
                            builder.Add(BindDeconstructionVariables(declTypeWithAnnotations, n, n, diagnostics));
                        }
                        return new DeconstructionVariable(builder, syntax);
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(node.Kind());
            }
        }
 
        private BoundDiscardExpression BindDiscardExpression(
            SyntaxNode syntax,
            TypeWithAnnotations declTypeWithAnnotations)
        {
            var type = declTypeWithAnnotations.Type;
            return new BoundDiscardExpression(syntax, declTypeWithAnnotations.NullableAnnotation, isInferred: type is null, type);
        }
 
        /// <summary>
        /// In embedded statements, returns a BoundLocal when the type was explicit.
        /// In global statements, returns a BoundFieldAccess when the type was explicit.
        /// Otherwise returns a DeconstructionVariablePendingInference when the type is implicit.
        /// </summary>
        private BoundExpression BindDeconstructionVariable(
            TypeWithAnnotations declTypeWithAnnotations,
            SingleVariableDesignationSyntax designation,
            CSharpSyntaxNode syntax,
            BindingDiagnosticBag diagnostics)
        {
            SourceLocalSymbol localSymbol = LookupLocal(designation.Identifier);
 
            // is this a local?
            if ((object)localSymbol != null)
            {
                if (designation.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == designation)
                {
                    TypeSyntax typeSyntax = declExpr.Type;
 
                    if (typeSyntax is ScopedTypeSyntax scopedType)
                    {
                        // Check for support for 'scoped'.
                        ModifierUtils.CheckScopedModifierAvailability(typeSyntax, scopedType.ScopedKeyword, diagnostics);
                        typeSyntax = scopedType.Type;
                    }
 
                    if (typeSyntax is RefTypeSyntax refType)
                    {
                        diagnostics.Add(ErrorCode.ERR_DeconstructVariableCannotBeByRef, refType.RefKeyword.GetLocation());
                    }
 
                    if (declTypeWithAnnotations.HasType)
                    {
                        CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declTypeWithAnnotations.Type, diagnostics, typeSyntax);
                    }
 
                    if (declTypeWithAnnotations.HasType &&
                        localSymbol.Scope == ScopedKind.ScopedValue && !declTypeWithAnnotations.Type.IsErrorOrRefLikeOrAllowsRefLikeType())
                    {
                        diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location);
                    }
                }
 
                // Check for variable declaration errors.
                // Use the binder that owns the scope for the local because this (the current) binder
                // might own nested scope.
                var hasErrors = localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);
 
                if (declTypeWithAnnotations.HasType)
                {
                    return new BoundLocal(syntax, localSymbol, BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declTypeWithAnnotations.Type, hasErrors: hasErrors);
                }
 
                return new DeconstructionVariablePendingInference(syntax, localSymbol, receiverOpt: null);
            }
            else
            {
                // Is this a field?
                GlobalExpressionVariable field = LookupDeclaredField(designation);
 
                if ((object)field == null)
                {
                    // We should have the right binder in the chain, cannot continue otherwise.
                    throw ExceptionUtilities.Unreachable();
                }
 
                if (designation.Parent is DeclarationExpressionSyntax declExpr && declExpr.Designation == designation)
                {
                    TypeSyntax typeSyntax = declExpr.Type;
 
                    if (typeSyntax is ScopedTypeSyntax scopedType)
                    {
                        diagnostics.Add(ErrorCode.ERR_UnexpectedToken, scopedType.ScopedKeyword.GetLocation(), scopedType.ScopedKeyword.ValueText);
                        typeSyntax = scopedType.Type;
                    }
 
                    if (typeSyntax is RefTypeSyntax refType)
                    {
                        diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refType.RefKeyword.GetLocation(), refType.RefKeyword.ValueText);
                    }
                }
 
                BoundThisReference receiver = ThisReference(designation, this.ContainingType, hasErrors: false,
                                                wasCompilerGenerated: true);
 
                if (declTypeWithAnnotations.HasType)
                {
                    var fieldType = field.GetFieldType(this.FieldsBeingBound);
                    Debug.Assert(TypeSymbol.Equals(declTypeWithAnnotations.Type, fieldType.Type, TypeCompareKind.ConsiderEverything2));
                    return new BoundFieldAccess(syntax,
                                                receiver,
                                                field,
                                                constantValueOpt: null,
                                                resultKind: LookupResultKind.Viable,
                                                isDeclaration: true,
                                                type: fieldType.Type);
                }
 
                return new DeconstructionVariablePendingInference(syntax, field, receiver);
            }
        }
    }
}