File: Binder\Binder_Patterns.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    partial class Binder
    {
        private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node.IsKeyword);
 
            BoundExpression expression = BindRValueWithoutTargetType(node.Expression, diagnostics);
            bool hasErrors = IsOperandErrors(node, ref expression, diagnostics);
            TypeSymbol? expressionType = expression.Type;
            if (expressionType is null || expressionType.IsVoidType())
            {
                if (!hasErrors)
                {
                    // value expected
                    diagnostics.Add(ErrorCode.ERR_BadPatternExpression, node.Expression.Location, expression.Display);
                    hasErrors = true;
                }
 
                expression = BadExpression(expression.Syntax, expression);
            }
 
            Debug.Assert(expression.Type is { });
            BoundPattern pattern = BindPattern(node.Pattern, expression.Type, permitDesignations: true, hasErrors, diagnostics, underIsPattern: true);
            hasErrors |= pattern.HasErrors;
            return MakeIsPatternExpression(
                node, expression, pattern, GetSpecialType(SpecialType.System_Boolean, diagnostics, node),
                hasErrors, diagnostics);
        }
 
        private BoundExpression MakeIsPatternExpression(
            SyntaxNode node,
            BoundExpression expression,
            BoundPattern pattern,
            TypeSymbol boolType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            // Note that these labels are for the convenience of the compilation of patterns, and are not necessarily emitted into the lowered code.
            LabelSymbol whenTrueLabel = new GeneratedLabelSymbol("isPatternSuccess");
            LabelSymbol whenFalseLabel = new GeneratedLabelSymbol("isPatternFailure");
 
            bool negated = pattern.IsNegated(out var innerPattern);
            BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern(
                this.Compilation, pattern.Syntax, expression, innerPattern, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, diagnostics);
 
            if (!hasErrors && getConstantResult(decisionDag, negated, whenTrueLabel, whenFalseLabel) is { } constantResult)
            {
                if (!constantResult)
                {
                    Debug.Assert(expression.Type is object);
                    diagnostics.Add(ErrorCode.ERR_IsPatternImpossible, node.Location, expression.Type);
                    hasErrors = true;
                }
                else
                {
                    switch (pattern)
                    {
                        case BoundConstantPattern _:
                        case BoundITuplePattern _:
                            // these patterns can fail in practice
                            throw ExceptionUtilities.Unreachable();
                        case BoundRelationalPattern _:
                        case BoundTypePattern _:
                        case BoundNegatedPattern _:
                        case BoundBinaryPattern _:
                        case BoundListPattern:
                            Debug.Assert(expression.Type is object);
                            diagnostics.Add(ErrorCode.WRN_IsPatternAlways, node.Location, expression.Type);
                            break;
                        case BoundDiscardPattern _:
                            // we do not give a warning on this because it is an existing scenario, and it should
                            // have been obvious in source that it would always match.
                            break;
                        case BoundDeclarationPattern _:
                        case BoundRecursivePattern _:
                            // We do not give a warning on these because people do this to give a name to a value
                            break;
                    }
                }
            }
            else if (expression.ConstantValueOpt != null)
            {
                decisionDag = decisionDag.SimplifyDecisionDagIfConstantInput(expression);
                if (!hasErrors && getConstantResult(decisionDag, negated, whenTrueLabel, whenFalseLabel) is { } simplifiedResult)
                {
                    if (!simplifiedResult)
                    {
                        diagnostics.Add(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, node.Location);
                    }
                    else
                    {
                        switch (pattern)
                        {
                            case BoundConstantPattern _:
                                diagnostics.Add(ErrorCode.WRN_GivenExpressionAlwaysMatchesConstant, node.Location);
                                break;
                            case BoundRelationalPattern _:
                            case BoundTypePattern _:
                            case BoundNegatedPattern _:
                            case BoundBinaryPattern _:
                            case BoundDiscardPattern _:
                                diagnostics.Add(ErrorCode.WRN_GivenExpressionAlwaysMatchesPattern, node.Location);
                                break;
                        }
                    }
                }
            }
 
            // decisionDag, whenTrueLabel, and whenFalseLabel represent the decision DAG for the inner pattern,
            // after removing any outer 'not's, so consumers will need to compensate for negated patterns.
            return new BoundIsPatternExpression(
                node, expression, pattern, negated, decisionDag, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, boolType, hasErrors);
 
            static bool? getConstantResult(BoundDecisionDag decisionDag, bool negated, LabelSymbol whenTrueLabel, LabelSymbol whenFalseLabel)
            {
                if (!decisionDag.ReachableLabels.Contains(whenTrueLabel))
                {
                    return negated;
                }
                else if (!decisionDag.ReachableLabels.Contains(whenFalseLabel))
                {
                    return !negated;
                }
                return null;
            }
        }
 
        private BoundExpression BindSwitchExpression(SwitchExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            RoslynDebug.Assert(node is not null);
 
            MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node.SwitchKeyword);
 
            Binder? switchBinder = this.GetBinder(node);
            RoslynDebug.Assert(switchBinder is { });
            return switchBinder.BindSwitchExpressionCore(node, switchBinder, diagnostics);
        }
 
        internal virtual BoundExpression BindSwitchExpressionCore(
            SwitchExpressionSyntax node,
            Binder originalBinder,
            BindingDiagnosticBag diagnostics)
        {
            RoslynDebug.Assert(this.Next is { });
            return this.Next.BindSwitchExpressionCore(node, originalBinder, diagnostics);
        }
 
        internal BoundPattern BindPattern(
            PatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics,
            bool underIsPattern = false)
        {
            return node switch
            {
                DiscardPatternSyntax p => BindDiscardPattern(p, inputType, diagnostics),
                DeclarationPatternSyntax p => BindDeclarationPattern(p, inputType, permitDesignations, hasErrors, diagnostics),
                ConstantPatternSyntax p => BindConstantPatternWithFallbackToTypePattern(p, inputType, hasErrors, diagnostics),
                RecursivePatternSyntax p => BindRecursivePattern(p, inputType, permitDesignations, hasErrors, diagnostics),
                VarPatternSyntax p => BindVarPattern(p, inputType, permitDesignations, hasErrors, diagnostics),
                ParenthesizedPatternSyntax p => BindParenthesizedPattern(p, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern),
                BinaryPatternSyntax p => BindBinaryPattern(p, inputType, permitDesignations, hasErrors, diagnostics),
                UnaryPatternSyntax p => BindUnaryPattern(p, inputType, hasErrors, diagnostics, underIsPattern),
                RelationalPatternSyntax p => BindRelationalPattern(p, inputType, hasErrors, diagnostics),
                TypePatternSyntax p => BindTypePattern(p, inputType, hasErrors, diagnostics),
                ListPatternSyntax p => BindListPattern(p, inputType, permitDesignations, hasErrors, diagnostics),
                SlicePatternSyntax p => BindSlicePattern(p, inputType, permitDesignations, ref hasErrors, misplaced: true, diagnostics),
                _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()),
            };
        }
 
        private BoundPattern BindParenthesizedPattern(
            ParenthesizedPatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics,
            bool underIsPattern)
        {
            MessageID.IDS_FeatureParenthesizedPattern.CheckFeatureAvailability(diagnostics, node.OpenParenToken);
            return BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern);
        }
 
        private BoundPattern BindSlicePattern(
            SlicePatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            ref bool hasErrors,
            bool misplaced,
            BindingDiagnosticBag diagnostics)
        {
            if (misplaced && !hasErrors)
            {
                diagnostics.Add(ErrorCode.ERR_MisplacedSlicePattern, node.Location);
                hasErrors = true;
            }
 
            BoundExpression? indexerAccess = null;
            BoundPattern? pattern = null;
            BoundSlicePatternReceiverPlaceholder? receiverPlaceholder = null;
            BoundSlicePatternRangePlaceholder? argumentPlaceholder = null;
 
            // We don't require the type to be sliceable if there's no subpattern.
            if (node.Pattern is not null)
            {
                receiverPlaceholder = new BoundSlicePatternReceiverPlaceholder(node, inputType) { WasCompilerGenerated = true };
                var systemRangeType = GetWellKnownType(WellKnownType.System_Range, diagnostics, node);
                argumentPlaceholder = new BoundSlicePatternRangePlaceholder(node, systemRangeType) { WasCompilerGenerated = true };
 
                TypeSymbol sliceType;
                if (inputType.IsErrorType())
                {
                    hasErrors = true;
                    sliceType = inputType;
                }
                else
                {
                    var analyzedArguments = AnalyzedArguments.GetInstance();
                    analyzedArguments.Arguments.Add(argumentPlaceholder);
 
                    indexerAccess = BindElementAccessCore(node, receiverPlaceholder, analyzedArguments, diagnostics).MakeCompilerGenerated();
                    indexerAccess = CheckValue(indexerAccess, BindValueKind.RValue, diagnostics);
                    Debug.Assert(indexerAccess is BoundIndexerAccess or BoundImplicitIndexerAccess or BoundArrayAccess or BoundBadExpression or BoundDynamicIndexerAccess);
                    analyzedArguments.Free();
 
                    if (!systemRangeType.HasUseSiteError)
                    {
                        _ = GetWellKnownTypeMember(WellKnownMember.System_Range__ctor, diagnostics, syntax: node);
                    }
 
                    Debug.Assert(indexerAccess.Type is not null);
                    sliceType = indexerAccess.Type;
                }
 
                pattern = BindPattern(node.Pattern, sliceType, permitDesignations, hasErrors, diagnostics);
            }
 
            return new BoundSlicePattern(node, pattern, indexerAccess, receiverPlaceholder, argumentPlaceholder, inputType: inputType, narrowedType: inputType, hasErrors);
        }
 
        private ImmutableArray<BoundPattern> BindListPatternSubpatterns(
            SeparatedSyntaxList<PatternSyntax> subpatterns,
            TypeSymbol inputType,
            TypeSymbol elementType,
            bool permitDesignations,
            ref bool hasErrors,
            out bool sawSlice,
            BindingDiagnosticBag diagnostics)
        {
            sawSlice = false;
            var builder = ArrayBuilder<BoundPattern>.GetInstance(subpatterns.Count);
            foreach (PatternSyntax pattern in subpatterns)
            {
                BoundPattern boundPattern;
                if (pattern is SlicePatternSyntax slice)
                {
                    boundPattern = BindSlicePattern(slice, inputType, permitDesignations, ref hasErrors, misplaced: sawSlice, diagnostics: diagnostics);
                    sawSlice = true;
                }
                else
                {
                    boundPattern = BindPattern(pattern, elementType, permitDesignations, hasErrors, diagnostics);
                }
 
                builder.Add(boundPattern);
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private BoundListPattern BindListPattern(
            ListPatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            CheckFeatureAvailability(node, MessageID.IDS_FeatureListPattern, diagnostics);
 
            TypeSymbol elementType;
            BoundExpression? indexerAccess;
            BoundExpression? lengthAccess;
            TypeSymbol narrowedType = inputType.StrippedType();
            BoundListPatternReceiverPlaceholder? receiverPlaceholder;
            BoundListPatternIndexPlaceholder? argumentPlaceholder;
 
            if (inputType.IsDynamic())
            {
                Error(diagnostics, ErrorCode.ERR_UnsupportedTypeForListPattern, node, inputType);
            }
 
            if (inputType.IsErrorType() || inputType.IsDynamic())
            {
                hasErrors = true;
                elementType = inputType;
                indexerAccess = null;
                lengthAccess = null;
                receiverPlaceholder = null;
                argumentPlaceholder = null;
            }
            else
            {
                hasErrors |= !BindLengthAndIndexerForListPattern(node, narrowedType, diagnostics, out indexerAccess, out lengthAccess, out receiverPlaceholder, out argumentPlaceholder);
 
                Debug.Assert(indexerAccess!.Type is not null);
                elementType = indexerAccess.Type;
            }
 
            ImmutableArray<BoundPattern> subpatterns = BindListPatternSubpatterns(
                node.Patterns, inputType: narrowedType, elementType: elementType,
                permitDesignations, ref hasErrors, out bool sawSlice, diagnostics);
 
            BindPatternDesignation(
                node.Designation,
                declType: TypeWithAnnotations.Create(narrowedType, NullableAnnotation.NotAnnotated),
                permitDesignations, typeSyntax: null, diagnostics, ref hasErrors,
                out Symbol? variableSymbol, out BoundExpression? variableAccess);
 
            return new BoundListPattern(
                syntax: node, subpatterns: subpatterns, hasSlice: sawSlice, lengthAccess: lengthAccess,
                indexerAccess: indexerAccess, receiverPlaceholder, argumentPlaceholder, variable: variableSymbol,
                variableAccess: variableAccess, inputType: inputType, narrowedType: narrowedType, hasErrors);
        }
 
        /// <summary>
        /// Types which list-patterns can be used on (ie. countable and indexable ones) are assumed to have
        /// non-negative lengths.
        /// </summary>
        private bool IsCountableAndIndexable(SyntaxNode node, TypeSymbol inputType, out PropertySymbol? lengthProperty)
        {
            var success = BindLengthAndIndexerForListPattern(node, inputType, BindingDiagnosticBag.Discarded,
                indexerAccess: out _, out var lengthAccess, receiverPlaceholder: out _, argumentPlaceholder: out _);
            lengthProperty = success ? GetPropertySymbol(lengthAccess, out _, out _) : null;
            return success;
        }
 
        private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inputType, BindingDiagnosticBag diagnostics,
            out BoundExpression indexerAccess, out BoundExpression lengthAccess, out BoundListPatternReceiverPlaceholder? receiverPlaceholder, out BoundListPatternIndexPlaceholder argumentPlaceholder)
        {
            Debug.Assert(!inputType.IsDynamic());
 
            bool hasErrors = false;
            receiverPlaceholder = new BoundListPatternReceiverPlaceholder(node, inputType) { WasCompilerGenerated = true };
            if (inputType.IsSZArray())
            {
                hasErrors |= !TryGetSpecialTypeMember(Compilation, SpecialMember.System_Array__Length, node, diagnostics, out PropertySymbol lengthProperty);
                if (lengthProperty is not null)
                {
                    lengthAccess = new BoundPropertyAccess(node, receiverPlaceholder, initialBindingReceiverIsSubjectToCloning: ThreeState.False, lengthProperty, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, lengthProperty.Type) { WasCompilerGenerated = true };
                }
                else
                {
                    lengthAccess = new BoundBadExpression(node, LookupResultKind.Empty, ImmutableArray<Symbol?>.Empty, ImmutableArray<BoundExpression>.Empty, CreateErrorType(), hasErrors: true) { WasCompilerGenerated = true };
                }
            }
            else
            {
                if (!TryBindLengthOrCount(node, receiverPlaceholder, out lengthAccess, diagnostics))
                {
                    hasErrors = true;
                    Error(diagnostics, ErrorCode.ERR_ListPatternRequiresLength, node, inputType);
                }
            }
 
            var analyzedArguments = AnalyzedArguments.GetInstance();
            var systemIndexType = GetWellKnownType(WellKnownType.System_Index, diagnostics, node);
            argumentPlaceholder = new BoundListPatternIndexPlaceholder(node, systemIndexType) { WasCompilerGenerated = true };
            analyzedArguments.Arguments.Add(argumentPlaceholder);
 
            indexerAccess = BindElementAccessCore(node, receiverPlaceholder, analyzedArguments, diagnostics).MakeCompilerGenerated();
            indexerAccess = CheckValue(indexerAccess, BindValueKind.RValue, diagnostics);
            Debug.Assert(indexerAccess is BoundIndexerAccess or BoundImplicitIndexerAccess or BoundArrayAccess or BoundBadExpression or BoundDynamicIndexerAccess);
            analyzedArguments.Free();
 
            if (!systemIndexType.HasUseSiteError)
            {
                // Check required well-known member.
                _ = GetWellKnownTypeMember(WellKnownMember.System_Index__ctor, diagnostics, syntax: node);
            }
 
            return !hasErrors && !lengthAccess.HasErrors && !indexerAccess.HasErrors;
        }
 
        private static BoundPattern BindDiscardPattern(DiscardPatternSyntax node, TypeSymbol inputType, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node);
            return new BoundDiscardPattern(node, inputType: inputType, narrowedType: inputType);
        }
 
        private BoundPattern BindConstantPatternWithFallbackToTypePattern(
            ConstantPatternSyntax node,
            TypeSymbol inputType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            return BindConstantPatternWithFallbackToTypePattern(node, node.Expression, inputType, hasErrors, diagnostics);
        }
 
        internal BoundPattern BindConstantPatternWithFallbackToTypePattern(
            SyntaxNode node,
            ExpressionSyntax expression,
            TypeSymbol inputType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            ExpressionSyntax innerExpression = SkipParensAndNullSuppressions(expression, diagnostics, ref hasErrors);
            var convertedExpression = BindExpressionOrTypeForPattern(inputType, innerExpression, ref hasErrors, diagnostics, out var constantValueOpt, out bool wasExpression, out Conversion patternConversion);
            if (wasExpression)
            {
                var convertedType = convertedExpression.Type ?? inputType;
                if (convertedType.SpecialType == SpecialType.System_String && inputType.IsSpanOrReadOnlySpanChar())
                {
                    convertedType = inputType;
                }
 
                if ((constantValueOpt?.IsNumeric == true) && ShouldBlockINumberBaseConversion(patternConversion, inputType))
                {
                    // Cannot use a numeric constant or relational pattern on '{0}' because it inherits from or extends 'INumberBase&lt;T&gt;'. Consider using a type pattern to narrow to a specific numeric type.
                    diagnostics.Add(ErrorCode.ERR_CannotMatchOnINumberBase, node.Location, inputType);
                }
 
                return new BoundConstantPattern(
                    node, convertedExpression, constantValueOpt ?? ConstantValue.Bad, inputType, convertedType, hasErrors || constantValueOpt is null);
            }
            else
            {
                if (!hasErrors)
                    CheckFeatureAvailability(innerExpression, MessageID.IDS_FeatureTypePattern, diagnostics);
 
                var boundType = (BoundTypeExpression)convertedExpression;
                bool isExplicitNotNullTest = boundType.Type.SpecialType == SpecialType.System_Object;
                return new BoundTypePattern(node, boundType, isExplicitNotNullTest, inputType, boundType.Type, hasErrors);
            }
        }
 
        private bool ShouldBlockINumberBaseConversion(Conversion patternConversion, TypeSymbol inputType)
        {
            // We want to block constant and relation patterns that have an input type constrained to or inherited from INumberBase<T>, if we don't
            // know how to represent the constant being matched against in the input type. For example, `1.0 is 1` will work when written inline, but
            // will fail if the input type is `INumberBase<T>`. We block this now so that we can make make it work as expected in the future without
            // being a breaking change.
 
            if (patternConversion.IsIdentity || patternConversion.IsConstantExpression || patternConversion.IsNumeric)
            {
                return false;
            }
 
            var interfaces = inputType is TypeParameterSymbol typeParam ? typeParam.EffectiveInterfacesNoUseSiteDiagnostics : inputType.AllInterfacesNoUseSiteDiagnostics;
            return interfaces.Any(static (i, _) => i.IsWellKnownINumberBaseType(), 0) || inputType.IsWellKnownINumberBaseType();
        }
 
        private static ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e, BindingDiagnosticBag diagnostics, ref bool hasErrors)
        {
            while (true)
            {
                switch (e.Kind())
                {
                    case SyntaxKind.DefaultLiteralExpression:
                        diagnostics.Add(ErrorCode.ERR_DefaultPattern, e.Location);
                        hasErrors = true;
                        return e;
                    case SyntaxKind.ParenthesizedExpression:
                        e = ((ParenthesizedExpressionSyntax)e).Expression;
                        continue;
                    case SyntaxKind.SuppressNullableWarningExpression:
                        diagnostics.Add(ErrorCode.ERR_IllegalSuppression, e.Location);
                        hasErrors = true;
                        e = ((PostfixUnaryExpressionSyntax)e).Operand;
                        continue;
                    default:
                        return e;
                }
            }
        }
 
        /// <summary>
        /// Binds the expression for a pattern.  Sets <paramref name="wasExpression"/> if it was a type rather than an expression,
        /// and in that case it returns a <see cref="BoundTypeExpression"/>.
        /// </summary>
        private BoundExpression BindExpressionOrTypeForPattern(
            TypeSymbol inputType,
            ExpressionSyntax patternExpression,
            ref bool hasErrors,
            BindingDiagnosticBag diagnostics,
            out ConstantValue? constantValueOpt,
            out bool wasExpression,
            out Conversion patternExpressionConversion)
        {
            constantValueOpt = null;
            BoundExpression expression = BindTypeOrRValue(patternExpression, diagnostics);
            wasExpression = expression.Kind != BoundKind.TypeExpression;
            if (wasExpression)
            {
                return BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt, out patternExpressionConversion);
            }
            else
            {
                Debug.Assert(expression is { Kind: BoundKind.TypeExpression, Type: { } });
                hasErrors |= CheckValidPatternType(patternExpression, inputType, expression.Type, diagnostics: diagnostics);
                patternExpressionConversion = Conversion.NoConversion;
                return expression;
            }
        }
 
        /// <summary>
        /// Binds the expression for an is-type right-hand-side, in case it does not bind as a type.
        /// </summary>
        private BoundExpression BindExpressionForPattern(
            TypeSymbol inputType,
            ExpressionSyntax patternExpression,
            ref bool hasErrors,
            BindingDiagnosticBag diagnostics,
            out ConstantValue? constantValueOpt,
            out bool wasExpression,
            out Conversion patternExpressionConversion)
        {
            constantValueOpt = null;
            var expression = BindExpression(patternExpression, diagnostics: diagnostics, invoked: false, indexed: false);
            expression = CheckValue(expression, BindValueKind.RValue, diagnostics);
            wasExpression = expression.Kind switch { BoundKind.BadExpression => false, BoundKind.TypeExpression => false, _ => true };
            patternExpressionConversion = Conversion.NoConversion;
            return wasExpression ? BindExpressionForPatternContinued(expression, inputType, patternExpression, ref hasErrors, diagnostics, out constantValueOpt, out patternExpressionConversion) : expression;
        }
 
        private BoundExpression BindExpressionForPatternContinued(
            BoundExpression expression,
            TypeSymbol inputType,
            ExpressionSyntax patternExpression,
            ref bool hasErrors,
            BindingDiagnosticBag diagnostics,
            out ConstantValue? constantValueOpt,
            out Conversion patternExpressionConversion)
        {
            BoundExpression convertedExpression = ConvertPatternExpression(
                inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics, out patternExpressionConversion);
 
            ConstantValueUtils.CheckLangVersionForConstantValue(convertedExpression, diagnostics);
 
            if (!convertedExpression.HasErrors && !hasErrors)
            {
                if (constantValueOpt == null)
                {
                    var strippedInputType = inputType.StrippedType();
                    if (strippedInputType.Kind is not SymbolKind.ErrorType and not SymbolKind.DynamicType and not SymbolKind.TypeParameter &&
                        strippedInputType.SpecialType is not SpecialType.System_Object and not SpecialType.System_ValueType)
                    {
                        diagnostics.Add(ErrorCode.ERR_ConstantValueOfTypeExpected, patternExpression.Location, strippedInputType);
                    }
                    else
                    {
                        diagnostics.Add(ErrorCode.ERR_ConstantExpected, patternExpression.Location);
                    }
                    hasErrors = true;
                }
                else if (inputType.IsPointerType())
                {
                    CheckFeatureAvailability(patternExpression, MessageID.IDS_FeatureNullPointerConstantPattern, diagnostics);
                }
            }
 
            if (convertedExpression.Type is null && constantValueOpt != ConstantValue.Null)
            {
                Debug.Assert(hasErrors);
                convertedExpression = BindToTypeForErrorRecovery(convertedExpression);
            }
 
            return convertedExpression;
        }
 
        internal BoundExpression ConvertPatternExpression(
            TypeSymbol inputType,
            CSharpSyntaxNode node,
            BoundExpression expression,
            out ConstantValue? constantValue,
            bool hasErrors,
            BindingDiagnosticBag diagnostics,
            out Conversion patternExpressionConversion)
        {
            BoundExpression convertedExpression;
 
            // If we are pattern-matching against an open type, we do not convert the constant to the type of the input.
            // This permits us to match a value of type `IComparable<T>` with a pattern of type `int`.
            if (inputType.ContainsTypeParameter())
            {
                convertedExpression = expression;
                // If the expression does not have a constant value, an error will be reported in the caller
                if (!hasErrors && expression.ConstantValueOpt is object)
                {
                    CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                    if (expression.ConstantValueOpt == ConstantValue.Null)
                    {
                        // Pointers are value types, but they can be assigned null, so they can be matched against null.
                        if (inputType.IsNonNullableValueType() && !inputType.IsPointerOrFunctionPointer())
                        {
                            // We do not permit matching null against a struct type.
                            diagnostics.Add(ErrorCode.ERR_ValueCantBeNull, expression.Syntax.Location, inputType);
                            hasErrors = true;
                        }
                    }
                    else
                    {
                        RoslynDebug.Assert(expression.Type is { });
                        ConstantValue match = ExpressionOfTypeMatchesPatternType(Conversions, inputType, expression.Type, ref useSiteInfo, out _, operandConstantValue: null);
                        if (match == ConstantValue.False || match == ConstantValue.Bad)
                        {
                            diagnostics.Add(ErrorCode.ERR_PatternWrongType, expression.Syntax.Location, inputType, expression.Display);
                            hasErrors = true;
                        }
                    }
 
                    if (!hasErrors)
                    {
                        var requiredVersion = MessageID.IDS_FeatureRecursivePatterns.RequiredVersion();
                        patternExpressionConversion = this.Conversions.ClassifyConversionFromExpression(expression, inputType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
                        if (Compilation.LanguageVersion < requiredVersion && !patternExpressionConversion.IsImplicit)
                        {
                            diagnostics.Add(ErrorCode.ERR_ConstantPatternVsOpenType,
                                expression.Syntax.Location, inputType, expression.Display, new CSharpRequiredLanguageVersion(requiredVersion));
                        }
                    }
                    else
                    {
                        patternExpressionConversion = Conversion.NoConversion;
                    }
 
                    diagnostics.Add(node, useSiteInfo);
                }
                else
                {
                    patternExpressionConversion = Conversion.NoConversion;
                }
            }
            else
            {
                if (expression.Type?.SpecialType == SpecialType.System_String && inputType.IsSpanOrReadOnlySpanChar())
                {
                    if (MessageID.IDS_FeatureSpanCharConstantPattern.CheckFeatureAvailability(diagnostics, Compilation, node.Location))
                    {
                        // report missing member and use site diagnostics
                        bool isReadOnlySpan = inputType.IsReadOnlySpanChar();
                        _ = GetWellKnownTypeMember(
                            isReadOnlySpan ? WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T : WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T,
                            diagnostics,
                            syntax: node);
                        _ = GetWellKnownTypeMember(WellKnownMember.System_MemoryExtensions__AsSpan_String, diagnostics, syntax: node);
                        _ = GetWellKnownTypeMember(isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__get_Length : WellKnownMember.System_Span_T__get_Length,
                            diagnostics,
                            syntax: node);
                    }
 
                    convertedExpression = BindToNaturalType(expression, diagnostics);
 
                    constantValue = convertedExpression.ConstantValueOpt;
                    if (constantValue == ConstantValue.Null)
                    {
                        diagnostics.Add(ErrorCode.ERR_PatternSpanCharCannotBeStringNull, convertedExpression.Syntax.Location, inputType);
                    }
 
                    patternExpressionConversion = Conversion.NoConversion;
 
                    return convertedExpression;
                }
 
                // This will allow user-defined conversions, even though they're not permitted here.  This is acceptable
                // because the result of a user-defined conversion does not have a ConstantValue. A constant pattern
                // requires a constant value so we'll report a diagnostic to that effect later.
                convertedExpression = GenerateConversionForAssignment(inputType, expression, diagnostics, out patternExpressionConversion);
 
                if (convertedExpression.Kind == BoundKind.Conversion)
                {
                    var conversion = (BoundConversion)convertedExpression;
                    BoundExpression operand = conversion.Operand;
                    if (inputType.IsNullableType() && (convertedExpression.ConstantValueOpt == null || !convertedExpression.ConstantValueOpt.IsNull))
                    {
                        // Null is a special case here because we want to compare null to the Nullable<T> itself, not to the underlying type.
                        // We are not interested in the diagnostic that get created here
                        convertedExpression = CreateConversion(operand, inputType.GetNullableUnderlyingType(), BindingDiagnosticBag.Discarded);
                    }
                    else if ((conversion.ConversionKind == ConversionKind.Boxing || conversion.ConversionKind == ConversionKind.ImplicitReference)
                        && operand.ConstantValueOpt != null && convertedExpression.ConstantValueOpt == null)
                    {
                        // A boxed constant (or string converted to object) is a special case because we prefer
                        // to compare to the pre-converted value by casting the input value to the type of the constant
                        // (that is, unboxing or downcasting it) and then testing the resulting value using primitives.
                        // That is much more efficient than calling object.Equals(x, y), and we can share the downcasted
                        // input value among many constant tests.
                        convertedExpression = operand;
                    }
                    else if (conversion.ConversionKind == ConversionKind.ImplicitNullToPointer ||
                        (conversion.ConversionKind == ConversionKind.NoConversion && convertedExpression.Type?.IsErrorType() == true))
                    {
                        convertedExpression = operand;
                    }
                }
            }
 
            constantValue = convertedExpression.ConstantValueOpt;
            return convertedExpression;
        }
 
        /// <summary>
        /// Check that the pattern type is valid for the operand. Return true if an error was reported.
        /// </summary>
        private bool CheckValidPatternType(
            SyntaxNode typeSyntax,
            TypeSymbol inputType,
            TypeSymbol patternType,
            BindingDiagnosticBag diagnostics)
        {
            RoslynDebug.Assert((object)inputType != null);
            RoslynDebug.Assert((object)patternType != null);
 
            if (inputType.IsErrorType() || patternType.IsErrorType())
            {
                return false;
            }
            else if (inputType.IsPointerOrFunctionPointer() || patternType.IsPointerOrFunctionPointer())
            {
                // pattern-matching is not permitted for pointer types
                diagnostics.Add(ErrorCode.ERR_PointerTypeInPatternMatching, typeSyntax.Location);
                return true;
            }
            else if (patternType.IsNullableType())
            {
                // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type.
                Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType.GetNullableUnderlyingType());
                return true;
            }
            else if (typeSyntax is NullableTypeSyntax)
            {
                Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType);
                return true;
            }
            else if (patternType.IsStatic)
            {
                Error(diagnostics, ErrorCode.ERR_VarDeclIsStaticClass, typeSyntax, patternType);
                return true;
            }
            else
            {
                if (patternType.IsDynamic())
                {
                    Error(diagnostics, ErrorCode.ERR_PatternDynamicType, typeSyntax);
                    return true;
                }
 
                CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                ConstantValue matchPossible = ExpressionOfTypeMatchesPatternType(
                    Conversions, inputType, patternType, ref useSiteInfo, out Conversion conversion, operandConstantValue: null, operandCouldBeNull: true);
                diagnostics.Add(typeSyntax, useSiteInfo);
                if (matchPossible != ConstantValue.False && matchPossible != ConstantValue.Bad)
                {
                    if (!conversion.Exists && (inputType.ContainsTypeParameter() || patternType.ContainsTypeParameter()))
                    {
                        // permit pattern-matching when one of the types is an open type in C# 7.1.
                        LanguageVersion requiredVersion = MessageID.IDS_FeatureGenericPatternMatching.RequiredVersion();
                        if (requiredVersion > Compilation.LanguageVersion)
                        {
                            Error(diagnostics, ErrorCode.ERR_PatternWrongGenericTypeInVersion, typeSyntax,
                                inputType, patternType,
                                Compilation.LanguageVersion.ToDisplayString(),
                                new CSharpRequiredLanguageVersion(requiredVersion));
                            return true;
                        }
                    }
                }
                else
                {
                    Error(diagnostics, ErrorCode.ERR_PatternWrongType, typeSyntax, inputType, patternType);
                    return true;
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Does an expression of type <paramref name="expressionType"/> "match" a pattern that looks for
        /// type <paramref name="patternType"/>?
        ///  - <see cref="ConstantValue.True"/> if the matched type catches all of them
        ///  - <see cref="ConstantValue.False"/> if it catches none of them
        ///  - <see cref="ConstantValue.Bad"/> - compiler doesn't support the type check, i.e. cannot perform it, even at runtime
        ///  - 'null' if it might catch some of them.
        /// </summary>
        internal static ConstantValue ExpressionOfTypeMatchesPatternType(
            Conversions conversions,
            TypeSymbol expressionType,
            TypeSymbol patternType,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
            out Conversion conversion,
            ConstantValue? operandConstantValue = null,
            bool operandCouldBeNull = false)
        {
            RoslynDebug.Assert((object)expressionType != null);
 
            // Short-circuit a common case.  This also improves recovery for some error
            // cases, e.g. when the type is void.
            if (expressionType.Equals(patternType, TypeCompareKind.AllIgnoreOptions))
            {
                conversion = Conversion.Identity;
                return ConstantValue.True;
            }
 
            if (expressionType.IsDynamic())
            {
                // if operand is the dynamic type, we do the same thing as though it were object
                expressionType = conversions.CorLibrary.GetSpecialType(SpecialType.System_Object);
            }
 
            conversion = conversions.ClassifyBuiltInConversion(expressionType, patternType, isChecked: false, ref useSiteInfo);
            ConstantValue result = Binder.GetIsOperatorConstantResult(expressionType, patternType, conversion.Kind, operandConstantValue, operandCouldBeNull);
 
            // Don't need to worry about checked user-defined operators
            Debug.Assert(!conversion.IsUserDefined || result == ConstantValue.False || result == ConstantValue.Bad);
 
            return result;
        }
 
        private BoundPattern BindDeclarationPattern(
            DeclarationPatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            TypeSyntax typeSyntax = node.Type;
            BoundTypeExpression boundDeclType = BindTypeForPattern(typeSyntax, inputType, diagnostics, ref hasErrors);
            BindPatternDesignation(
                designation: node.Designation, declType: boundDeclType.TypeWithAnnotations, permitDesignations, typeSyntax, diagnostics,
                hasErrors: ref hasErrors, variableSymbol: out Symbol? variableSymbol, variableAccess: out BoundExpression? variableAccess);
            return new BoundDeclarationPattern(node, boundDeclType, isVar: false, variableSymbol, variableAccess, inputType: inputType, narrowedType: boundDeclType.Type, hasErrors);
        }
 
        private BoundTypeExpression BindTypeForPattern(
            TypeSyntax typeSyntax,
            TypeSymbol inputType,
            BindingDiagnosticBag diagnostics,
            ref bool hasErrors)
        {
            RoslynDebug.Assert(inputType is { });
            TypeWithAnnotations declType = BindType(typeSyntax, diagnostics, out AliasSymbol aliasOpt);
            Debug.Assert(declType.HasType);
            BoundTypeExpression boundDeclType = new BoundTypeExpression(typeSyntax, aliasOpt, typeWithAnnotations: declType);
            hasErrors |= CheckValidPatternType(typeSyntax, inputType, declType.Type, diagnostics: diagnostics);
            return boundDeclType;
        }
 
        private void BindPatternDesignation(
            VariableDesignationSyntax? designation,
            TypeWithAnnotations declType,
            bool permitDesignations,
            TypeSyntax? typeSyntax,
            BindingDiagnosticBag diagnostics,
            ref bool hasErrors,
            out Symbol? variableSymbol,
            out BoundExpression? variableAccess)
        {
            switch (designation)
            {
                case SingleVariableDesignationSyntax singleVariableDesignation:
                    SyntaxToken identifier = singleVariableDesignation.Identifier;
                    SourceLocalSymbol localSymbol = this.LookupLocal(identifier);
 
                    if (!permitDesignations && !identifier.IsMissing)
                        diagnostics.Add(ErrorCode.ERR_DesignatorBeneathPatternCombinator, identifier.GetLocation());
 
                    if (localSymbol is { })
                    {
                        RoslynDebug.Assert(ContainingMemberOrLambda is { });
                        if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType)
                            CheckFeatureAvailability(designation, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics);
 
                        localSymbol.SetTypeWithAnnotations(declType);
 
                        // Check for variable declaration errors.
                        hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);
 
                        if (!hasErrors)
                            CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax ?? (SyntaxNode)designation);
 
                        variableSymbol = localSymbol;
                        variableAccess = new BoundLocal(
                            syntax: designation, localSymbol: localSymbol, localSymbol.IsVar ? BoundLocalDeclarationKind.WithInferredType : BoundLocalDeclarationKind.WithExplicitType, constantValueOpt: null, isNullableUnknown: false, type: declType.Type);
                        return;
                    }
                    else
                    {
                        // We should have the right binder in the chain for a script or interactive, so we use the field for the pattern.
                        Debug.Assert(designation.SyntaxTree.Options.Kind != SourceCodeKind.Regular);
                        GlobalExpressionVariable expressionVariableField = LookupDeclaredField(singleVariableDesignation);
                        expressionVariableField.SetTypeWithAnnotations(declType, BindingDiagnosticBag.Discarded);
                        BoundExpression receiver = SynthesizeReceiver(designation, expressionVariableField, diagnostics);
 
                        variableSymbol = expressionVariableField;
                        variableAccess = new BoundFieldAccess(
                            syntax: designation, receiver: receiver, fieldSymbol: expressionVariableField, constantValueOpt: null, hasErrors: hasErrors);
                        return;
                    }
                case DiscardDesignationSyntax _:
                case null:
                    variableSymbol = null;
                    variableAccess = null;
                    return;
                default:
                    throw ExceptionUtilities.UnexpectedValue(designation.Kind());
            }
        }
 
        private TypeWithAnnotations BindRecursivePatternType(
            TypeSyntax? typeSyntax,
            TypeSymbol inputType,
            BindingDiagnosticBag diagnostics,
            ref bool hasErrors,
            out BoundTypeExpression? boundDeclType)
        {
            if (typeSyntax != null)
            {
                boundDeclType = BindTypeForPattern(typeSyntax, inputType, diagnostics, ref hasErrors);
                return boundDeclType.TypeWithAnnotations;
            }
            else
            {
                boundDeclType = null;
                // remove the nullable part of the input's type; e.g. a nullable int becomes an int in a recursive pattern
                return TypeWithAnnotations.Create(inputType.StrippedType(), NullableAnnotation.NotAnnotated);
            }
        }
 
        // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType`
        // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type.  We explicitly perform the tests
        // required to identify it.  When that bug is fixed we should be able to remove this code and its callers.
        internal static bool IsZeroElementTupleType(TypeSymbol type)
        {
            return type.IsStructType() && type.Name == "ValueTuple" && type.GetArity() == 0 &&
                type.ContainingSymbol is var declContainer && declContainer.Kind == SymbolKind.Namespace && declContainer.Name == "System" &&
                (declContainer.ContainingSymbol as NamespaceSymbol)?.IsGlobalNamespace == true;
        }
 
        private BoundPattern BindRecursivePattern(
            RecursivePatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node);
 
            if (inputType.IsPointerOrFunctionPointer())
            {
                diagnostics.Add(ErrorCode.ERR_PointerTypeInPatternMatching, node.Location);
                hasErrors = true;
                inputType = CreateErrorType();
            }
 
            TypeSyntax? typeSyntax = node.Type;
            TypeWithAnnotations declTypeWithAnnotations = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression? boundDeclType);
            TypeSymbol declType = declTypeWithAnnotations.Type;
 
            MethodSymbol? deconstructMethod = null;
            ImmutableArray<BoundPositionalSubpattern> deconstructionSubpatterns = default;
            if (node.PositionalPatternClause != null)
            {
                PositionalPatternClauseSyntax positionalClause = node.PositionalPatternClause;
                var patternsBuilder = ArrayBuilder<BoundPositionalSubpattern>.GetInstance(positionalClause.Subpatterns.Count);
                if (IsZeroElementTupleType(declType))
                {
                    // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType`
                    // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type.  We explicitly perform the tests
                    // required to identify it.  When that bug is fixed we should be able to remove this if statement.
                    BindValueTupleSubpatterns(
                        positionalClause, declType, ImmutableArray<TypeWithAnnotations>.Empty, permitDesignations, ref hasErrors, patternsBuilder, diagnostics);
                }
                else if (declType.IsTupleType)
                {
                    // It is a tuple type. Work according to its elements
                    BindValueTupleSubpatterns(positionalClause, declType, declType.TupleElementTypesWithAnnotations, permitDesignations, ref hasErrors, patternsBuilder, diagnostics);
                }
                else
                {
                    // It is not a tuple type. Seek an appropriate Deconstruct method.
                    var inputPlaceholder = new BoundImplicitReceiver(positionalClause, declType); // A fake receiver expression to permit us to reuse binding logic
                    var deconstructDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
                    BoundExpression deconstruct = MakeDeconstructInvocationExpression(
                        positionalClause.Subpatterns.Count, inputPlaceholder, positionalClause,
                        deconstructDiagnostics, outPlaceholders: out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders,
                        out bool anyDeconstructCandidates);
                    if (!anyDeconstructCandidates &&
                        ShouldUseITupleForRecursivePattern(node, declType, diagnostics, out var iTupleType, out var iTupleGetLength, out var iTupleGetItem))
                    {
                        // There was no Deconstruct, but the constraints for the use of ITuple are satisfied.
                        // Use that and forget any errors from trying to bind Deconstruct.
                        deconstructDiagnostics.Free();
                        BindITupleSubpatterns(positionalClause, patternsBuilder, permitDesignations, diagnostics);
                        deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree();
                        return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, deconstructionSubpatterns, inputType, iTupleType, hasErrors);
                    }
                    else
                    {
                        diagnostics.AddRangeAndFree(deconstructDiagnostics);
                    }
 
                    deconstructMethod = BindDeconstructSubpatterns(
                        positionalClause, permitDesignations, deconstruct, outPlaceholders, patternsBuilder, ref hasErrors, diagnostics);
                }
 
                deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree();
            }
 
            ImmutableArray<BoundPropertySubpattern> properties = default;
            if (node.PropertyPatternClause != null)
            {
                properties = BindPropertyPatternClause(node.PropertyPatternClause, declType, permitDesignations, diagnostics, ref hasErrors);
            }
 
            BindPatternDesignation(
                node.Designation, declTypeWithAnnotations, permitDesignations, typeSyntax, diagnostics,
                ref hasErrors, out Symbol? variableSymbol, out BoundExpression? variableAccess);
            bool isExplicitNotNullTest =
                node.Designation is null &&
                boundDeclType is null &&
                properties.IsDefaultOrEmpty &&
                deconstructMethod is null &&
                deconstructionSubpatterns.IsDefault;
            return new BoundRecursivePattern(
                syntax: node, declaredType: boundDeclType, deconstructMethod: deconstructMethod,
                deconstruction: deconstructionSubpatterns, properties: properties, isExplicitNotNullTest: isExplicitNotNullTest,
                variable: variableSymbol, variableAccess: variableAccess, inputType: inputType,
                narrowedType: boundDeclType?.Type ?? inputType.StrippedType(), hasErrors);
        }
 
        private MethodSymbol? BindDeconstructSubpatterns(
            PositionalPatternClauseSyntax node,
            bool permitDesignations,
            BoundExpression deconstruct,
            ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders,
            ArrayBuilder<BoundPositionalSubpattern> patterns,
            ref bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            var deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol;
            if (deconstructMethod is null)
                hasErrors = true;
 
            int skippedExtensionParameters = deconstructMethod?.IsExtensionMethod == true ? 1 : 0;
            for (int i = 0; i < node.Subpatterns.Count; i++)
            {
                var subPattern = node.Subpatterns[i];
 
                bool isError = hasErrors || outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length;
                TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type;
                ParameterSymbol? parameter = null;
                if (!isError)
                {
                    int parameterIndex = i + skippedExtensionParameters;
                    if (parameterIndex < deconstructMethod!.ParameterCount)
                    {
                        parameter = deconstructMethod.Parameters[parameterIndex];
                    }
                    if (subPattern.NameColon != null)
                    {
                        // Check that the given name is the same as the corresponding parameter of the method.
                        if (parameter is { })
                        {
                            string name = subPattern.NameColon.Name.Identifier.ValueText;
                            string parameterName = parameter.Name;
                            if (name != parameterName)
                            {
                                diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName);
                            }
                        }
                    }
                    else if (subPattern.ExpressionColon != null)
                    {
                        MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, subPattern.ExpressionColon.ColonToken);
 
                        diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subPattern.ExpressionColon.Expression.Location);
                    }
                }
 
                var boundSubpattern = new BoundPositionalSubpattern(
                    subPattern,
                    parameter,
                    BindPattern(subPattern.Pattern, elementType, permitDesignations, isError, diagnostics)
                    );
                patterns.Add(boundSubpattern);
            }
 
            return deconstructMethod;
        }
 
        private void BindITupleSubpatterns(
            PositionalPatternClauseSyntax node,
            ArrayBuilder<BoundPositionalSubpattern> patterns,
            bool permitDesignations,
            BindingDiagnosticBag diagnostics)
        {
            var objectType = Compilation.GetSpecialType(SpecialType.System_Object);
            foreach (var subpatternSyntax in node.Subpatterns)
            {
                if (subpatternSyntax.NameColon != null)
                {
                    // error: name not permitted in ITuple deconstruction
                    diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location);
                }
                else if (subpatternSyntax.ExpressionColon != null)
                {
                    diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subpatternSyntax.ExpressionColon.Expression.Location);
                }
 
                var boundSubpattern = new BoundPositionalSubpattern(
                    subpatternSyntax,
                    null,
                    BindPattern(subpatternSyntax.Pattern, objectType, permitDesignations, hasErrors: false, diagnostics));
                patterns.Add(boundSubpattern);
            }
        }
 
        private void BindITupleSubpatterns(
            ParenthesizedVariableDesignationSyntax node,
            ArrayBuilder<BoundPositionalSubpattern> patterns,
            bool permitDesignations,
            BindingDiagnosticBag diagnostics)
        {
            var objectType = Compilation.GetSpecialType(SpecialType.System_Object);
            foreach (var variable in node.Variables)
            {
                BoundPattern pattern = BindVarDesignation(variable, objectType, permitDesignations, hasErrors: false, diagnostics);
                var boundSubpattern = new BoundPositionalSubpattern(
                    variable,
                    null,
                    pattern);
                patterns.Add(boundSubpattern);
            }
        }
 
        private void BindValueTupleSubpatterns(
            PositionalPatternClauseSyntax node,
            TypeSymbol declType,
            ImmutableArray<TypeWithAnnotations> elementTypesWithAnnotations,
            bool permitDesignations,
            ref bool hasErrors,
            ArrayBuilder<BoundPositionalSubpattern> patterns,
            BindingDiagnosticBag diagnostics)
        {
            if (elementTypesWithAnnotations.Length != node.Subpatterns.Count && !hasErrors)
            {
                diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, node.Location, declType, elementTypesWithAnnotations.Length, node.Subpatterns.Count);
                hasErrors = true;
            }
 
            for (int i = 0; i < node.Subpatterns.Count; i++)
            {
                var subpatternSyntax = node.Subpatterns[i];
                bool isError = i >= elementTypesWithAnnotations.Length;
                TypeSymbol elementType = isError ? CreateErrorType() : elementTypesWithAnnotations[i].Type;
                FieldSymbol? foundField = null;
                if (!isError)
                {
                    if (subpatternSyntax.NameColon != null)
                    {
                        string name = subpatternSyntax.NameColon.Name.Identifier.ValueText;
                        foundField = CheckIsTupleElement(subpatternSyntax.NameColon.Name, (NamedTypeSymbol)declType, name, i, diagnostics);
                    }
                    else if (subpatternSyntax.ExpressionColon != null)
                    {
                        diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subpatternSyntax.ExpressionColon.Expression.Location);
                    }
                }
 
                BoundPositionalSubpattern boundSubpattern = new BoundPositionalSubpattern(
                    subpatternSyntax,
                    foundField,
                    BindPattern(subpatternSyntax.Pattern, elementType, permitDesignations, isError, diagnostics));
                patterns.Add(boundSubpattern);
            }
        }
 
        private bool ShouldUseITupleForRecursivePattern(
            RecursivePatternSyntax node,
            TypeSymbol declType,
            BindingDiagnosticBag diagnostics,
            [NotNullWhen(true)] out NamedTypeSymbol? iTupleType,
            [NotNullWhen(true)] out MethodSymbol? iTupleGetLength,
            [NotNullWhen(true)] out MethodSymbol? iTupleGetItem)
        {
            iTupleType = null;
            iTupleGetLength = iTupleGetItem = null;
            if (node.Type != null)
            {
                // ITuple matching only applies if no type is given explicitly.
                return false;
            }
 
            if (node.PropertyPatternClause != null)
            {
                // ITuple matching only applies if there is no property pattern part.
                return false;
            }
 
            if (node.PositionalPatternClause == null)
            {
                // ITuple matching only applies if there is a positional pattern part.
                // This can only occur as a result of syntax error recovery, if at all.
                return false;
            }
 
            if (node.Designation?.Kind() == SyntaxKind.SingleVariableDesignation)
            {
                // ITuple matching only applies if there is no variable declared (what type would the variable be?)
                return false;
            }
 
            return ShouldUseITuple(node, declType, diagnostics, out iTupleType, out iTupleGetLength, out iTupleGetItem);
        }
 
        private bool ShouldUseITuple(
            SyntaxNode node,
            TypeSymbol declType,
            BindingDiagnosticBag diagnostics,
            [NotNullWhen(true)] out NamedTypeSymbol? iTupleType,
            [NotNullWhen(true)] out MethodSymbol? iTupleGetLength,
            [NotNullWhen(true)] out MethodSymbol? iTupleGetItem)
        {
            iTupleType = null;
            iTupleGetLength = iTupleGetItem = null;
            Debug.Assert(!declType.IsTupleType);
            Debug.Assert(!IsZeroElementTupleType(declType));
 
            if (Compilation.LanguageVersion < MessageID.IDS_FeatureRecursivePatterns.RequiredVersion())
            {
                return false;
            }
 
            iTupleType = Compilation.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_ITuple);
            if (iTupleType.TypeKind != TypeKind.Interface)
            {
                // When compiling to a platform that lacks the interface ITuple (i.e. it is an error type), we simply do not match using it.
                return false;
            }
 
            // Resolution 2017-11-20 LDM: permit matching via ITuple only for `object`, `ITuple`, and types that are
            // declared to implement `ITuple`.
            if (declType != (object)Compilation.GetSpecialType(SpecialType.System_Object) &&
                declType != (object)Compilation.DynamicType &&
                declType != (object)iTupleType &&
                !hasBaseInterface(declType, iTupleType))
            {
                return false;
            }
 
            // Ensure ITuple has a Length and indexer
            iTupleGetLength = (MethodSymbol?)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Length);
            iTupleGetItem = (MethodSymbol?)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Item);
            if (iTupleGetLength is null || iTupleGetItem is null)
            {
                // This might not result in an ideal diagnostic
                return false;
            }
 
            // passed all the filters; permit using ITuple
            _ = diagnostics.ReportUseSite(iTupleType, node) ||
                diagnostics.ReportUseSite(iTupleGetLength, node) ||
                diagnostics.ReportUseSite(iTupleGetItem, node);
 
            return true;
 
            bool hasBaseInterface(TypeSymbol type, NamedTypeSymbol possibleBaseInterface)
            {
                CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
                var result = Compilation.Conversions.ClassifyBuiltInConversion(type, possibleBaseInterface, isChecked: CheckOverflowAtRuntime, ref useSiteInfo).IsImplicit;
                diagnostics.Add(node, useSiteInfo);
                return result;
            }
        }
 
        /// <summary>
        /// Check that the given name designates a tuple element at the given index, and return that element.
        /// </summary>
        private static FieldSymbol? CheckIsTupleElement(SyntaxNode node, NamedTypeSymbol tupleType, string name, int tupleIndex, BindingDiagnosticBag diagnostics)
        {
            FieldSymbol? foundElement = null;
            foreach (var symbol in tupleType.GetMembers(name))
            {
                if (symbol is FieldSymbol field && field.IsTupleElement())
                {
                    foundElement = field;
                    break;
                }
            }
 
            if (foundElement is null || foundElement.TupleElementIndex != tupleIndex)
            {
                diagnostics.Add(ErrorCode.ERR_TupleElementNameMismatch, node.Location, name, $"Item{tupleIndex + 1}");
            }
 
            return foundElement;
        }
 
        private BoundPattern BindVarPattern(
            VarPatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            if ((inputType.IsPointerOrFunctionPointer() && node.Designation.Kind() == SyntaxKind.ParenthesizedVariableDesignation)
                || (inputType.IsPointerType() && Compilation.LanguageVersion < MessageID.IDS_FeatureRecursivePatterns.RequiredVersion()))
            {
                diagnostics.Add(ErrorCode.ERR_PointerTypeInPatternMatching, node.Location);
                hasErrors = true;
                inputType = CreateErrorType();
            }
 
            Symbol foundSymbol = BindTypeOrAliasOrKeyword(node.VarKeyword, node, diagnostics, out bool isVar).Symbol;
            if (!isVar)
            {
                // Give an error if there is a bindable type "var" in scope
                diagnostics.Add(ErrorCode.ERR_VarMayNotBindToType, node.VarKeyword.GetLocation(), foundSymbol.ToDisplayString());
                hasErrors = true;
            }
 
            return BindVarDesignation(node.Designation, inputType, permitDesignations, hasErrors, diagnostics);
        }
 
        private BoundPattern BindVarDesignation(
            VariableDesignationSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            switch (node.Kind())
            {
                case SyntaxKind.DiscardDesignation:
                    {
                        return new BoundDiscardPattern(node, inputType: inputType, narrowedType: inputType);
                    }
                case SyntaxKind.SingleVariableDesignation:
                    {
                        var declType = TypeWithState.ForType(inputType).ToTypeWithAnnotations(Compilation);
                        BindPatternDesignation(
                            designation: node, declType: declType, permitDesignations: permitDesignations,
                            typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors,
                            variableSymbol: out Symbol? variableSymbol, variableAccess: out BoundExpression? variableAccess);
                        var boundOperandType = new BoundTypeExpression(syntax: node, aliasOpt: null, typeWithAnnotations: declType); // fake a type expression for the variable's type
                        // We continue to use a BoundDeclarationPattern for the var pattern, as they have more in common.
                        Debug.Assert(node.Parent is { });
                        return new BoundDeclarationPattern(
                            node.Parent.Kind() == SyntaxKind.VarPattern ? node.Parent : node, // for `var x` use whole pattern, otherwise use designation for the syntax
                            boundOperandType, isVar: true, variableSymbol, variableAccess,
                            inputType: inputType, narrowedType: inputType, hasErrors);
                    }
                case SyntaxKind.ParenthesizedVariableDesignation:
                    {
                        MessageID.IDS_FeatureRecursivePatterns.CheckFeatureAvailability(diagnostics, node);
 
                        var tupleDesignation = (ParenthesizedVariableDesignationSyntax)node;
                        var subPatterns = ArrayBuilder<BoundPositionalSubpattern>.GetInstance(tupleDesignation.Variables.Count);
                        MethodSymbol? deconstructMethod = null;
                        var strippedInputType = inputType.StrippedType();
 
                        if (IsZeroElementTupleType(strippedInputType))
                        {
                            // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType`
                            // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type.  We explicitly perform the tests
                            // required to identify it.  When that bug is fixed we should be able to remove this if statement.
                            addSubpatternsForTuple(ImmutableArray<TypeWithAnnotations>.Empty);
                        }
                        else if (strippedInputType.IsTupleType)
                        {
                            // It is a tuple type. Work according to its elements
                            addSubpatternsForTuple(strippedInputType.TupleElementTypesWithAnnotations);
                        }
                        else
                        {
                            // It is not a tuple type. Seek an appropriate Deconstruct method.
                            var inputPlaceholder = new BoundImplicitReceiver(node, strippedInputType); // A fake receiver expression to permit us to reuse binding logic
                            var deconstructDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics);
                            BoundExpression deconstruct = MakeDeconstructInvocationExpression(
                                tupleDesignation.Variables.Count, inputPlaceholder, node, deconstructDiagnostics,
                                outPlaceholders: out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders,
                                out bool anyDeconstructCandidates);
                            if (!anyDeconstructCandidates &&
                                ShouldUseITuple(node, strippedInputType, diagnostics, out var iTupleType, out var iTupleGetLength, out var iTupleGetItem))
                            {
                                // There was no applicable candidate Deconstruct, and the constraints for the use of ITuple are satisfied.
                                // Use that and forget any errors from trying to bind Deconstruct.
                                deconstructDiagnostics.Free();
                                BindITupleSubpatterns(tupleDesignation, subPatterns, permitDesignations, diagnostics);
                                return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, subPatterns.ToImmutableAndFree(), strippedInputType, iTupleType, hasErrors);
                            }
                            else
                            {
                                diagnostics.AddRangeAndFree(deconstructDiagnostics);
                            }
 
                            deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol;
                            if (!hasErrors)
                                hasErrors = outPlaceholders.IsDefault || tupleDesignation.Variables.Count != outPlaceholders.Length;
 
                            for (int i = 0; i < tupleDesignation.Variables.Count; i++)
                            {
                                var variable = tupleDesignation.Variables[i];
                                bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length;
                                TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type;
                                BoundPattern pattern = BindVarDesignation(variable, elementType, permitDesignations, isError, diagnostics);
                                subPatterns.Add(new BoundPositionalSubpattern(variable, symbol: null, pattern));
                            }
                        }
 
                        return new BoundRecursivePattern(
                            syntax: node, declaredType: null, deconstructMethod: deconstructMethod,
                            deconstruction: subPatterns.ToImmutableAndFree(), properties: default, variable: null, variableAccess: null,
                            isExplicitNotNullTest: false, inputType: inputType, narrowedType: inputType.StrippedType(), hasErrors: hasErrors);
 
                        void addSubpatternsForTuple(ImmutableArray<TypeWithAnnotations> elementTypes)
                        {
                            if (elementTypes.Length != tupleDesignation.Variables.Count && !hasErrors)
                            {
                                diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, tupleDesignation.Location,
                                    strippedInputType, elementTypes.Length, tupleDesignation.Variables.Count);
                                hasErrors = true;
                            }
                            for (int i = 0; i < tupleDesignation.Variables.Count; i++)
                            {
                                var variable = tupleDesignation.Variables[i];
                                bool isError = i >= elementTypes.Length;
                                TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].Type;
                                BoundPattern pattern = BindVarDesignation(variable, elementType, permitDesignations, isError, diagnostics);
                                subPatterns.Add(new BoundPositionalSubpattern(variable, symbol: null, pattern));
                            }
                        }
                    }
                default:
                    {
                        throw ExceptionUtilities.UnexpectedValue(node.Kind());
                    }
            }
        }
 
        private ImmutableArray<BoundPropertySubpattern> BindPropertyPatternClause(
            PropertyPatternClauseSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            BindingDiagnosticBag diagnostics,
            ref bool hasErrors)
        {
            var builder = ArrayBuilder<BoundPropertySubpattern>.GetInstance(node.Subpatterns.Count);
            foreach (SubpatternSyntax p in node.Subpatterns)
            {
                if (p.ExpressionColon is ExpressionColonSyntax)
                    MessageID.IDS_FeatureExtendedPropertyPatterns.CheckFeatureAvailability(diagnostics, p.ExpressionColon.ColonToken);
 
                ExpressionSyntax? expr = p.ExpressionColon?.Expression;
                PatternSyntax pattern = p.Pattern;
                BoundPropertySubpatternMember? member;
                TypeSymbol memberType;
                bool isLengthOrCount = false;
                if (expr == null)
                {
                    if (!hasErrors)
                        diagnostics.Add(ErrorCode.ERR_PropertyPatternNameMissing, pattern.Location, pattern);
 
                    memberType = CreateErrorType();
                    member = null;
                    hasErrors = true;
                }
                else
                {
                    member = LookupMembersForPropertyPattern(inputType, expr, diagnostics, ref hasErrors);
                    memberType = member.Type;
                    // If we're dealing with the member that makes the type countable, and the type is also indexable, then it will be assumed to always return a non-negative value
                    if (memberType.SpecialType == SpecialType.System_Int32 &&
                        member.Symbol is { Name: WellKnownMemberNames.LengthPropertyName or WellKnownMemberNames.CountPropertyName, Kind: SymbolKind.Property } memberSymbol)
                    {
                        TypeSymbol receiverType = member.Receiver?.Type ?? inputType;
                        if (!receiverType.IsErrorType())
                        {
                            isLengthOrCount = IsCountableAndIndexable(node, receiverType, out PropertySymbol? lengthProperty) &&
                                memberSymbol.Equals(lengthProperty, TypeCompareKind.ConsiderEverything); // If Length and Count are both present, only the former is assumed to be non-negative.
                        }
                    }
                }
 
                BoundPattern boundPattern = BindPattern(pattern, memberType, permitDesignations, hasErrors, diagnostics);
                builder.Add(new BoundPropertySubpattern(p, member, isLengthOrCount, boundPattern));
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private BoundPropertySubpatternMember LookupMembersForPropertyPattern(
            TypeSymbol inputType, ExpressionSyntax expr, BindingDiagnosticBag diagnostics, ref bool hasErrors)
        {
            BoundPropertySubpatternMember? receiver = null;
            Symbol? symbol = null;
            switch (expr)
            {
                case IdentifierNameSyntax name:
                    symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics);
                    break;
                case MemberAccessExpressionSyntax { Name: IdentifierNameSyntax name } memberAccess when memberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression):
                    receiver = LookupMembersForPropertyPattern(inputType, memberAccess.Expression, diagnostics, ref hasErrors);
                    symbol = BindPropertyPatternMember(receiver.Type.StrippedType(), name, ref hasErrors, diagnostics);
                    break;
                default:
                    Error(diagnostics, ErrorCode.ERR_InvalidNameInSubpattern, expr);
                    hasErrors = true;
                    break;
            }
 
            TypeSymbol memberType = symbol switch
            {
                FieldSymbol field => field.Type,
                PropertySymbol property => property.Type,
                _ => CreateErrorType()
            };
 
            return new BoundPropertySubpatternMember(expr, receiver, symbol, type: memberType, hasErrors);
        }
 
        private Symbol? BindPropertyPatternMember(
            TypeSymbol inputType,
            IdentifierNameSyntax memberName,
            ref bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            // TODO: consider refactoring out common code with BindObjectInitializerMember
            BoundImplicitReceiver implicitReceiver = new BoundImplicitReceiver(memberName, inputType);
            string name = memberName.Identifier.ValueText;
 
            BoundExpression boundMember = BindInstanceMemberAccess(
                node: memberName,
                right: memberName,
                boundLeft: implicitReceiver,
                rightName: name,
                rightArity: 0,
                typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>),
                typeArgumentsWithAnnotations: default(ImmutableArray<TypeWithAnnotations>),
                invoked: false,
                indexed: false,
                diagnostics: diagnostics);
 
            if (boundMember.Kind == BoundKind.PropertyGroup)
            {
                boundMember = BindIndexedPropertyAccess(
                    (BoundPropertyGroup)boundMember, mustHaveAllOptionalParameters: true, diagnostics: diagnostics);
            }
 
            hasErrors |= boundMember.HasAnyErrors || implicitReceiver.HasAnyErrors;
 
            switch (boundMember.Kind)
            {
                case BoundKind.FieldAccess:
                case BoundKind.PropertyAccess:
                    break;
 
                case BoundKind.IndexerAccess:
                case BoundKind.DynamicIndexerAccess:
                case BoundKind.EventAccess:
                default:
                    if (!hasErrors)
                    {
                        switch (boundMember.ResultKind)
                        {
                            case LookupResultKind.Empty:
                                Error(diagnostics, ErrorCode.ERR_NoSuchMember, memberName, implicitReceiver.Type, name);
                                break;
 
                            case LookupResultKind.Inaccessible:
                                boundMember = CheckValue(boundMember, BindValueKind.RValue, diagnostics);
                                Debug.Assert(boundMember.HasAnyErrors);
                                break;
 
                            default:
                                Error(diagnostics, ErrorCode.ERR_PropertyLacksGet, memberName, name);
                                break;
                        }
                        hasErrors = true;
                    }
                    break;
            }
 
            if (!hasErrors && !CheckValueKind(node: memberName.Parent, expr: boundMember, valueKind: BindValueKind.RValue,
                                              checkingReceiver: false, diagnostics: diagnostics))
            {
                hasErrors = true;
            }
 
            return boundMember.ExpressionSymbol;
        }
 
        private BoundPattern BindTypePattern(
            TypePatternSyntax node,
            TypeSymbol inputType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureTypePattern.CheckFeatureAvailability(diagnostics, node);
 
            var patternType = BindTypeForPattern(node.Type, inputType, diagnostics, ref hasErrors);
            bool isExplicitNotNullTest = patternType.Type.SpecialType == SpecialType.System_Object;
            return new BoundTypePattern(node, patternType, isExplicitNotNullTest, inputType, patternType.Type, hasErrors);
        }
 
        private BoundPattern BindRelationalPattern(
            RelationalPatternSyntax node,
            TypeSymbol inputType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureRelationalPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
            BoundExpression value = BindExpressionForPattern(inputType, node.Expression, ref hasErrors, diagnostics, out var constantValueOpt, out _, out Conversion patternConversion);
            ExpressionSyntax innerExpression = SkipParensAndNullSuppressions(node.Expression, diagnostics, ref hasErrors);
            var type = value.Type ?? inputType;
            Debug.Assert(type is { });
            BinaryOperatorKind operation = tokenKindToBinaryOperatorKind(node.OperatorToken.Kind());
            if (operation == BinaryOperatorKind.Equal)
            {
                diagnostics.Add(ErrorCode.ERR_InvalidExprTerm, node.OperatorToken.GetLocation(), node.OperatorToken.Text);
                hasErrors = true;
            }
 
            BinaryOperatorKind opType = RelationalOperatorType(type.EnumUnderlyingTypeOrSelf());
            switch (opType)
            {
                case BinaryOperatorKind.Float:
                case BinaryOperatorKind.Double:
                    if (!hasErrors && constantValueOpt != null && !constantValueOpt.IsBad && double.IsNaN(constantValueOpt.DoubleValue))
                    {
                        diagnostics.Add(ErrorCode.ERR_RelationalPatternWithNaN, node.Expression.Location);
                        hasErrors = true;
                    }
                    break;
                case BinaryOperatorKind.String:
                case BinaryOperatorKind.Bool:
                case BinaryOperatorKind.Error:
                    if (!hasErrors)
                    {
                        diagnostics.Add(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, node.Location, type.ToDisplayString());
                        hasErrors = true;
                    }
                    break;
            }
 
            if (constantValueOpt is null)
            {
                hasErrors = true;
                constantValueOpt = ConstantValue.Bad;
            }
 
            if (!hasErrors && ShouldBlockINumberBaseConversion(patternConversion, inputType))
            {
                // Cannot use a numeric constant or relational pattern on '{0}' because it inherits from or extends 'INumberBase&lt;T&gt;'. Consider using a type pattern to narrow to a specific numeric type.
                diagnostics.Add(ErrorCode.ERR_CannotMatchOnINumberBase, node.Location, inputType);
                hasErrors = true;
            }
 
            return new BoundRelationalPattern(node, operation | opType, value, constantValueOpt, inputType, type, hasErrors);
 
            static BinaryOperatorKind tokenKindToBinaryOperatorKind(SyntaxKind kind) => kind switch
            {
                SyntaxKind.LessThanEqualsToken => BinaryOperatorKind.LessThanOrEqual,
                SyntaxKind.LessThanToken => BinaryOperatorKind.LessThan,
                SyntaxKind.GreaterThanToken => BinaryOperatorKind.GreaterThan,
                SyntaxKind.GreaterThanEqualsToken => BinaryOperatorKind.GreaterThanOrEqual,
                // The following occurs in error recovery scenarios
                _ => BinaryOperatorKind.Equal,
            };
        }
 
        /// <summary>
        /// Compute the type code for the comparison operator to be used.  When comparing `byte`s for example,
        /// the compiler actually uses the operator on the type `int` as there is no corresponding operator for
        /// the type `byte`.
        /// </summary>
        internal static BinaryOperatorKind RelationalOperatorType(TypeSymbol type) => type.SpecialType switch
        {
            SpecialType.System_Single => BinaryOperatorKind.Float,
            SpecialType.System_Double => BinaryOperatorKind.Double,
            SpecialType.System_Char => BinaryOperatorKind.Char,
            SpecialType.System_SByte => BinaryOperatorKind.Int, // operands are converted to int
            SpecialType.System_Byte => BinaryOperatorKind.Int, // operands are converted to int
            SpecialType.System_UInt16 => BinaryOperatorKind.Int, // operands are converted to int
            SpecialType.System_Int16 => BinaryOperatorKind.Int, // operands are converted to int
            SpecialType.System_Int32 => BinaryOperatorKind.Int,
            SpecialType.System_UInt32 => BinaryOperatorKind.UInt,
            SpecialType.System_Int64 => BinaryOperatorKind.Long,
            SpecialType.System_UInt64 => BinaryOperatorKind.ULong,
            SpecialType.System_Decimal => BinaryOperatorKind.Decimal,
            SpecialType.System_String => BinaryOperatorKind.String,
            SpecialType.System_Boolean => BinaryOperatorKind.Bool,
            SpecialType.System_IntPtr when type.IsNativeIntegerType => BinaryOperatorKind.NInt,
            SpecialType.System_UIntPtr when type.IsNativeIntegerType => BinaryOperatorKind.NUInt,
            _ => BinaryOperatorKind.Error,
        };
 
        private BoundPattern BindUnaryPattern(
            UnaryPatternSyntax node,
            TypeSymbol inputType,
            bool hasErrors,
            BindingDiagnosticBag diagnostics,
            bool underIsPattern)
        {
            MessageID.IDS_FeatureNotPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
            bool permitDesignations = underIsPattern; // prevent designators under 'not' except under an is-pattern
            var subPattern = BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern);
            return new BoundNegatedPattern(node, subPattern, inputType: inputType, narrowedType: inputType, hasErrors);
        }
 
        private BoundPattern BindBinaryPattern(
            BinaryPatternSyntax node,
            TypeSymbol inputType,
            bool permitDesignations,
            bool hasErrors,
            BindingDiagnosticBag diagnostics)
        {
            // Users (such as ourselves) can have many, many nested binary patterns. To avoid crashing, do left recursion manually.
 
            var binaryPatternStack = ArrayBuilder<(BinaryPatternSyntax pat, bool permitDesignations)>.GetInstance();
            BinaryPatternSyntax? currentNode = node;
 
            do
            {
                permitDesignations = permitDesignations && currentNode.IsKind(SyntaxKind.AndPattern);
                binaryPatternStack.Push((currentNode, permitDesignations));
                currentNode = currentNode.Left as BinaryPatternSyntax;
            } while (currentNode != null);
 
            Debug.Assert(binaryPatternStack.Count > 0);
 
            var binaryPatternAndPermitDesignations = binaryPatternStack.Pop();
            BoundPattern result = BindPattern(binaryPatternAndPermitDesignations.pat.Left, inputType, binaryPatternAndPermitDesignations.permitDesignations, hasErrors, diagnostics);
            var narrowedTypeCandidates = ArrayBuilder<TypeSymbol>.GetInstance(2);
            collectCandidates(result, narrowedTypeCandidates);
 
            do
            {
                result = bindBinaryPattern(
                    result,
                    this,
                    binaryPatternAndPermitDesignations.pat,
                    binaryPatternAndPermitDesignations.permitDesignations,
                    inputType,
                    narrowedTypeCandidates,
                    hasErrors,
                    diagnostics);
            } while (binaryPatternStack.TryPop(out binaryPatternAndPermitDesignations));
 
            binaryPatternStack.Free();
            narrowedTypeCandidates.Free();
            return result;
 
            static BoundPattern bindBinaryPattern(
                BoundPattern preboundLeft,
                Binder binder,
                BinaryPatternSyntax node,
                bool permitDesignations,
                TypeSymbol inputType,
                ArrayBuilder<TypeSymbol> narrowedTypeCandidates,
                bool hasErrors,
                BindingDiagnosticBag diagnostics)
            {
                bool isDisjunction = node.Kind() == SyntaxKind.OrPattern;
                if (isDisjunction)
                {
                    Debug.Assert(!permitDesignations);
                    MessageID.IDS_FeatureOrPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
                    var right = binder.BindPattern(node.Right, inputType, permitDesignations, hasErrors, diagnostics);
 
                    // Compute the common type. This algorithm is quadratic, but disjunctive patterns are unlikely to be huge
                    collectCandidates(right, narrowedTypeCandidates);
                    var narrowedType = leastSpecificType(node, narrowedTypeCandidates, diagnostics) ?? inputType;
 
                    return new BoundBinaryPattern(node, disjunction: isDisjunction, preboundLeft, right, inputType: inputType, narrowedType: narrowedType, hasErrors);
 
                    TypeSymbol? leastSpecificType(SyntaxNode node, ArrayBuilder<TypeSymbol> candidates, BindingDiagnosticBag diagnostics)
                    {
                        Debug.Assert(candidates.Count >= 2);
                        CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
                        TypeSymbol? bestSoFar = candidates[0];
                        // first pass: select a candidate for which no other has been shown to be an improvement.
                        for (int i = 1, n = candidates.Count; i < n; i++)
                        {
                            TypeSymbol candidate = candidates[i];
                            bestSoFar = lessSpecificCandidate(bestSoFar, candidate, ref useSiteInfo) ?? bestSoFar;
                        }
                        // second pass: check that it is no more specific than any candidate.
                        for (int i = 0, n = candidates.Count; i < n; i++)
                        {
                            TypeSymbol candidate = candidates[i];
                            TypeSymbol? spoiler = lessSpecificCandidate(candidate, bestSoFar, ref useSiteInfo);
                            if (spoiler is null)
                            {
                                bestSoFar = null;
                                break;
                            }
 
                            // Our specificity criteria are transitive
                            Debug.Assert(spoiler.Equals(bestSoFar, TypeCompareKind.ConsiderEverything));
                        }
 
                        diagnostics.Add(node, useSiteInfo);
                        return bestSoFar;
                    }
 
                    // Given a candidate least specific type so far, attempt to refine it with a possibly less specific candidate.
                    TypeSymbol? lessSpecificCandidate(TypeSymbol bestSoFar, TypeSymbol possiblyLessSpecificCandidate, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
                    {
                        if (bestSoFar.Equals(possiblyLessSpecificCandidate, TypeCompareKind.AllIgnoreOptions))
                        {
                            // When the types are equivalent, merge them.
                            return bestSoFar.MergeEquivalentTypes(possiblyLessSpecificCandidate, VarianceKind.Out);
                        }
                        else if (binder.Conversions.HasImplicitReferenceConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo))
                        {
                            // When there is an implicit reference conversion from T to U, U is less specific
                            return possiblyLessSpecificCandidate;
                        }
                        else if (binder.Conversions.HasBoxingConversion(bestSoFar, possiblyLessSpecificCandidate, ref useSiteInfo))
                        {
                            // when there is a boxing conversion from T to U, U is less specific.
                            return possiblyLessSpecificCandidate;
                        }
                        else
                        {
                            // We have no improved candidate to offer.
                            return null;
                        }
                    }
                }
                else
                {
                    MessageID.IDS_FeatureAndPattern.CheckFeatureAvailability(diagnostics, node.OperatorToken);
 
                    var right = binder.BindPattern(node.Right, preboundLeft.NarrowedType, permitDesignations, hasErrors, diagnostics);
                    narrowedTypeCandidates.Clear();
                    narrowedTypeCandidates.Add(right.NarrowedType);
                    return new BoundBinaryPattern(node, disjunction: isDisjunction, preboundLeft, right, inputType: inputType, narrowedType: right.NarrowedType, hasErrors);
                }
            }
 
            static void collectCandidates(BoundPattern pat, ArrayBuilder<TypeSymbol> candidates)
            {
                if (pat is BoundBinaryPattern { Disjunction: true } p)
                {
                    collectCandidates(p.Left, candidates);
                    collectCandidates(p.Right, candidates);
                }
                else
                {
                    candidates.Add(pat.NarrowedType);
                }
            }
 
        }
    }
}