File: Parser\LanguageParser_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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
    using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
 
    internal partial class LanguageParser : SyntaxParser
    {
        /// <summary>
        /// Parses the type, or pattern, right-hand operand of an is expression.
        /// Priority is the TypeSyntax. It may return a TypeSyntax which turns out in binding to
        /// be a constant pattern such as enum 'Days.Sunday'. We handle such cases in the binder of the is operator.
        /// Note that the syntax `_` will be parsed as a type.
        /// </summary>
        private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
        {
            var pattern = ParsePattern(GetPrecedence(SyntaxKind.IsPatternExpression), afterIs: true);
            return pattern switch
            {
                ConstantPatternSyntax cp when ConvertExpressionToType(cp.Expression, out NameSyntax? type) => type,
                TypePatternSyntax tp => tp.Type,
                DiscardPatternSyntax dp => _syntaxFactory.IdentifierName(ConvertToIdentifier(dp.UnderscoreToken)),
                var p => p,
            };
        }
 
        private bool ConvertExpressionToType(ExpressionSyntax expression, [NotNullWhen(true)] out NameSyntax? type)
        {
            switch (expression)
            {
                case SimpleNameSyntax s:
                    type = s;
                    return true;
                case MemberAccessExpressionSyntax { Expression: var expr, OperatorToken: { Kind: SyntaxKind.DotToken } dotToken, Name: var simpleName }
                        when ConvertExpressionToType(expr, out var leftType):
                    type = _syntaxFactory.QualifiedName(leftType, dotToken, simpleName);
                    return true;
                case AliasQualifiedNameSyntax a:
                    type = a;
                    return true;
                default:
                    type = null;
                    return false;
            };
        }
 
        private PatternSyntax ParsePattern(Precedence precedence, bool afterIs = false, bool inSwitchArmPattern = false)
        {
            return ParseDisjunctivePattern(precedence, afterIs, inSwitchArmPattern);
        }
 
        private PatternSyntax ParseDisjunctivePattern(Precedence precedence, bool afterIs, bool inSwitchArmPattern)
        {
            PatternSyntax result = ParseConjunctivePattern(precedence, afterIs, inSwitchArmPattern);
            while (this.CurrentToken.ContextualKind == SyntaxKind.OrKeyword)
            {
                result = _syntaxFactory.BinaryPattern(
                    SyntaxKind.OrPattern,
                    result,
                    ConvertToKeyword(this.EatToken()),
                    ParseConjunctivePattern(precedence, afterIs, inSwitchArmPattern));
            }
 
            return result;
        }
 
        /// <summary>
        /// Given tk, the type of the current token, does this look like the type of a pattern?
        /// </summary>
        private bool LooksLikeTypeOfPattern()
        {
            var tk = CurrentToken.Kind;
            if (SyntaxFacts.IsPredefinedType(tk))
            {
                return true;
            }
 
            if (tk == SyntaxKind.IdentifierToken && this.CurrentToken.ContextualKind != SyntaxKind.UnderscoreToken &&
                (this.CurrentToken.ContextualKind != SyntaxKind.NameOfKeyword || this.PeekToken(1).Kind != SyntaxKind.OpenParenToken))
            {
                return true;
            }
 
            if (LooksLikeTupleArrayType())
            {
                return true;
            }
 
            // We'll parse the function pointer, but issue an error in semantic analysis
            if (IsFunctionPointerStart())
            {
                return true;
            }
 
            return false;
        }
 
        private PatternSyntax ParseConjunctivePattern(Precedence precedence, bool afterIs, bool inSwitchArmPattern)
        {
            PatternSyntax result = ParseNegatedPattern(precedence, afterIs, inSwitchArmPattern);
            while (this.CurrentToken.ContextualKind == SyntaxKind.AndKeyword)
            {
                result = _syntaxFactory.BinaryPattern(
                    SyntaxKind.AndPattern,
                    result,
                    ConvertToKeyword(this.EatToken()),
                    ParseNegatedPattern(precedence, afterIs, inSwitchArmPattern));
            }
 
            return result;
        }
 
        private bool ScanDesignation(bool permitTuple)
        {
            switch (this.CurrentToken.Kind)
            {
                default:
                    return false;
                case SyntaxKind.IdentifierToken:
                    bool result = this.IsTrueIdentifier();
                    this.EatToken();
                    return result;
                case SyntaxKind.OpenParenToken:
                    if (!permitTuple)
                    {
                        return false;
                    }
 
                    bool sawComma = false;
                    while (true)
                    {
                        this.EatToken(); // consume the `(` or `,`
                        if (!ScanDesignation(permitTuple: true))
                        {
                            return false;
                        }
                        switch (this.CurrentToken.Kind)
                        {
                            case SyntaxKind.CloseParenToken:
                                this.EatToken();
                                return sawComma;
                            case SyntaxKind.CommaToken:
                                sawComma = true;
                                continue;
                            default:
                                return false;
                        }
                    }
            }
        }
 
        private PatternSyntax ParseNegatedPattern(Precedence precedence, bool afterIs, bool inSwitchArmPattern)
        {
            if (this.CurrentToken.ContextualKind == SyntaxKind.NotKeyword)
            {
                return _syntaxFactory.UnaryPattern(
                    ConvertToKeyword(this.EatToken()),
                    ParseNegatedPattern(precedence, afterIs, inSwitchArmPattern));
            }
            else
            {
                return ParsePrimaryPattern(precedence, afterIs, inSwitchArmPattern);
            }
        }
 
        private PatternSyntax ParsePrimaryPattern(Precedence precedence, bool afterIs, bool inSwitchArmPattern)
        {
            // handle common error recovery situations during typing
            var tk = this.CurrentToken.Kind;
            switch (tk)
            {
                case SyntaxKind.CommaToken:
                case SyntaxKind.SemicolonToken:
                case SyntaxKind.CloseBraceToken:
                case SyntaxKind.CloseParenToken:
                case SyntaxKind.CloseBracketToken:
                case SyntaxKind.EqualsGreaterThanToken:
                    return _syntaxFactory.ConstantPattern(this.ParseIdentifierName(ErrorCode.ERR_MissingPattern));
            }
 
            if (CurrentToken.ContextualKind == SyntaxKind.UnderscoreToken)
            {
                return _syntaxFactory.DiscardPattern(this.EatContextualToken(SyntaxKind.UnderscoreToken));
            }
 
            switch (CurrentToken.Kind)
            {
                case SyntaxKind.OpenBracketToken:
                    return this.ParseListPattern(inSwitchArmPattern);
                case SyntaxKind.DotToken when IsAtDotDotToken():
                    return _syntaxFactory.SlicePattern(
                        EatDotDotToken(),
                        IsPossibleSubpatternElement()
                            ? ParsePattern(precedence, afterIs: false, inSwitchArmPattern)
                            : null);
                case SyntaxKind.LessThanToken:
                case SyntaxKind.LessThanEqualsToken:
                case SyntaxKind.GreaterThanToken:
                case SyntaxKind.GreaterThanEqualsToken:
                case SyntaxKind.EqualsEqualsToken:
                case SyntaxKind.ExclamationEqualsToken:
                    // this is a relational pattern.
                    Debug.Assert(precedence < Precedence.Shift);
                    return _syntaxFactory.RelationalPattern(
                        this.EatToken(),
                        this.ParseSubExpression(Precedence.Relational));
            }
 
            using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
 
            TypeSyntax? type = null;
            if (LooksLikeTypeOfPattern())
            {
                type = this.ParseType(
                    afterIs ? ParseTypeMode.AfterIs : ParseTypeMode.DefinitePattern);
                if (type.IsMissing || !CanTokenFollowTypeInPattern(precedence))
                {
                    // either it is not shaped like a type, or it is a constant expression.
                    resetPoint.Reset();
                    type = null;
                }
            }
 
            var pattern = ParsePatternContinued(type, precedence, inSwitchArmPattern);
            if (pattern != null)
                return pattern;
 
            resetPoint.Reset();
            var value = this.ParseSubExpression(precedence);
            return _syntaxFactory.ConstantPattern(value);
        }
 
        /// <summary>
        /// Is the current token something that could follow a type in a pattern?
        /// </summary>
        bool CanTokenFollowTypeInPattern(Precedence precedence)
        {
            switch (this.CurrentToken.Kind)
            {
                case SyntaxKind.OpenParenToken:
                case SyntaxKind.OpenBraceToken:
                case SyntaxKind.IdentifierToken:
                case SyntaxKind.CloseBraceToken:   // for efficiency, test some tokens that can follow a type pattern
                case SyntaxKind.CloseBracketToken:
                case SyntaxKind.CloseParenToken:
                case SyntaxKind.CommaToken:
                case SyntaxKind.SemicolonToken:
                    return true;
                case SyntaxKind.DotToken:
                    // int.MaxValue is an expression, not a type.
                    return false;
                case SyntaxKind.MinusGreaterThanToken:
                case SyntaxKind.ExclamationToken:
                    // parse as an expression for error recovery
                    return false;
                case var kind:
                    // If we find what looks like a continuation of an expression, it is not a type.
                    return !SyntaxFacts.IsBinaryExpressionOperatorToken(kind) ||
                           GetPrecedence(SyntaxFacts.GetBinaryExpression(kind)) <= precedence;
            }
        }
 
        private PatternSyntax? ParsePatternContinued(TypeSyntax? type, Precedence precedence, bool inSwitchArmPattern)
        {
            if (type?.Kind == SyntaxKind.IdentifierName)
            {
                var typeIdentifier = (IdentifierNameSyntax)type;
                var typeIdentifierToken = typeIdentifier.Identifier;
                if (typeIdentifierToken.ContextualKind == SyntaxKind.VarKeyword &&
                    (this.CurrentToken.Kind == SyntaxKind.OpenParenToken || this.IsValidPatternDesignation(inSwitchArmPattern)))
                {
                    // we have a "var" pattern; "var" is not permitted to be a stand-in for a type (or a constant) in a pattern.
                    var varToken = ConvertToKeyword(typeIdentifierToken);
                    var varDesignation = ParseDesignation(forPattern: true);
                    return _syntaxFactory.VarPattern(varToken, varDesignation);
                }
            }
 
            if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken && (type != null || !looksLikeCast()))
            {
                // It is possible this is a parenthesized (constant) expression.
                // We normalize later.
                var openParenToken = this.EatToken(SyntaxKind.OpenParenToken);
                var subPatterns = this.ParseCommaSeparatedSyntaxList(
                    ref openParenToken,
                    SyntaxKind.CloseParenToken,
                    static @this => @this.IsPossibleSubpatternElement(),
                    static @this => @this.ParseSubpatternElement(),
                    SkipBadPatternListTokens,
                    allowTrailingSeparator: false,
                    requireOneElement: false,
                    allowSemicolonAsSeparator: false);
                var closeParenToken = this.EatToken(SyntaxKind.CloseParenToken);
 
                parsePropertyPatternClause(out PropertyPatternClauseSyntax? propertyPatternClause0);
                var designation0 = TryParseSimpleDesignation(inSwitchArmPattern);
 
                if (type == null &&
                    propertyPatternClause0 == null &&
                    designation0 == null &&
                    subPatterns.Count == 1 &&
                    subPatterns.SeparatorCount == 0)
                {
                    var firstSubPattern = subPatterns[0];
                    RoslynDebug.AssertNotNull(firstSubPattern);
 
                    if (firstSubPattern.ExpressionColon == null)
                    {
                        var subpattern = firstSubPattern.Pattern;
                        switch (subpattern)
                        {
                            case ConstantPatternSyntax cp:
                                // There is an ambiguity between a positional pattern `(` pattern `)`
                                // and a constant expression pattern that happens to be parenthesized.
                                // Per 2017-11-20 LDM we treat such syntax as a parenthesized expression always.
                                ExpressionSyntax expression = _syntaxFactory.ParenthesizedExpression(openParenToken, cp.Expression, closeParenToken);
                                expression = ParseExpressionContinued(expression, precedence);
                                return _syntaxFactory.ConstantPattern(expression);
                            default:
                                return _syntaxFactory.ParenthesizedPattern(openParenToken, subpattern, closeParenToken);
                        }
                    }
                }
 
                var positionalPatternClause = _syntaxFactory.PositionalPatternClause(openParenToken, subPatterns, closeParenToken);
                var result = _syntaxFactory.RecursivePattern(type, positionalPatternClause, propertyPatternClause0, designation0);
                return result;
            }
 
            if (parsePropertyPatternClause(out PropertyPatternClauseSyntax? propertyPatternClause))
            {
                return _syntaxFactory.RecursivePattern(
                    type, positionalPatternClause: null, propertyPatternClause,
                    TryParseSimpleDesignation(inSwitchArmPattern));
            }
 
            if (type != null)
            {
                var designation = TryParseSimpleDesignation(inSwitchArmPattern);
                if (designation != null)
                    return _syntaxFactory.DeclarationPattern(type, designation);
 
                // We normally prefer an expression rather than a type in a pattern.
                return ConvertTypeToExpression(type, out var expression)
                    ? _syntaxFactory.ConstantPattern(ParseExpressionContinued(expression, precedence))
                    : _syntaxFactory.TypePattern(type);
            }
 
            // let the caller fall back to parsing an expression
            return null;
 
            bool parsePropertyPatternClause([NotNullWhen(true)] out PropertyPatternClauseSyntax? propertyPatternClauseResult)
            {
                if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
                {
                    propertyPatternClauseResult = ParsePropertyPatternClause();
                    return true;
                }
 
                propertyPatternClauseResult = null;
                return false;
            }
 
            bool looksLikeCast()
            {
                using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
                return this.ScanCast(forPattern: true);
            }
        }
 
        private VariableDesignationSyntax? TryParseSimpleDesignation(bool whenIsKeyword)
        {
            return this.IsTrueIdentifier() && this.IsValidPatternDesignation(whenIsKeyword)
                ? ParseSimpleDesignation()
                : null;
        }
 
        private bool IsValidPatternDesignation(bool whenIsKeyword)
        {
            if (CurrentToken.Kind == SyntaxKind.IdentifierToken)
            {
                switch (CurrentToken.ContextualKind)
                {
                    case SyntaxKind.WhenKeyword:
                        return !whenIsKeyword;
                    case SyntaxKind.AndKeyword:
                    case SyntaxKind.OrKeyword:
                        var tk = PeekToken(1).Kind;
                        switch (tk)
                        {
                            case SyntaxKind.CloseBraceToken:
                            case SyntaxKind.CloseBracketToken:
                            case SyntaxKind.CloseParenToken:
                            case SyntaxKind.CommaToken:
                            case SyntaxKind.SemicolonToken:
                            case SyntaxKind.QuestionToken:
                            case SyntaxKind.ColonToken:
                                return true;
                            case SyntaxKind.LessThanEqualsToken:
                            case SyntaxKind.LessThanToken:
                            case SyntaxKind.GreaterThanEqualsToken:
                            case SyntaxKind.GreaterThanToken:
                            case SyntaxKind.IdentifierToken:
                            case SyntaxKind.OpenBraceToken:
                            case SyntaxKind.OpenParenToken:
                            case SyntaxKind.OpenBracketToken:
                                // these all can start a pattern
                                return false;
                            default:
                                {
                                    if (SyntaxFacts.IsBinaryExpression(tk)) return true; // `e is int and && true` is valid C# 7.0 code with `and` being a designator
 
                                    // If the following token could start an expression, it may be a constant pattern after a combinator.
                                    using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
                                    this.EatToken();
                                    return !CanStartExpression();
                                }
                        }
                    case SyntaxKind.UnderscoreToken: // discard is a valid pattern designation
                    default:
                        return true;
                }
            }
 
            return false;
        }
 
        private CSharpSyntaxNode ParseExpressionOrPatternForSwitchStatement()
        {
            var savedState = _termState;
            _termState |= TerminatorState.IsExpressionOrPatternInCaseLabelOfSwitchStatement;
            var pattern = ParsePattern(Precedence.Conditional, inSwitchArmPattern: true);
            _termState = savedState;
            return ConvertPatternToExpressionIfPossible(pattern);
        }
 
        private CSharpSyntaxNode ConvertPatternToExpressionIfPossible(PatternSyntax pattern, bool permitTypeArguments = false)
        {
            return pattern switch
            {
                ConstantPatternSyntax cp => cp.Expression,
                TypePatternSyntax tp when ConvertTypeToExpression(tp.Type, out ExpressionSyntax? expr, permitTypeArguments) => expr,
                DiscardPatternSyntax dp => _syntaxFactory.IdentifierName(ConvertToIdentifier(dp.UnderscoreToken)),
                var p => p,
            };
        }
 
        private bool ConvertTypeToExpression(TypeSyntax type, [NotNullWhen(true)] out ExpressionSyntax? expr, bool permitTypeArguments = false)
        {
            switch (type)
            {
                case GenericNameSyntax g:
                    expr = g;
                    return permitTypeArguments;
                case SimpleNameSyntax s:
                    expr = s;
                    return true;
                case QualifiedNameSyntax { Left: var left, dotToken: var dotToken, Right: var right }
                            when (permitTypeArguments || right is not GenericNameSyntax):
                    var newLeft = ConvertTypeToExpression(left, out var leftExpr, permitTypeArguments: true) ? leftExpr : left;
                    expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, newLeft, dotToken, right);
                    return true;
                default:
                    expr = null;
                    return false;
            }
        }
 
        private bool LooksLikeTupleArrayType()
        {
            if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
                return false;
 
            using var _ = GetDisposableResetPoint(resetOnDispose: true);
            return ScanType(forPattern: true) != ScanTypeFlags.NotType;
        }
 
        private PropertyPatternClauseSyntax ParsePropertyPatternClause()
        {
            var openBraceToken = this.EatToken(SyntaxKind.OpenBraceToken);
            var subPatterns = this.ParseCommaSeparatedSyntaxList(
                ref openBraceToken,
                SyntaxKind.CloseBraceToken,
                static @this => @this.IsPossibleSubpatternElement(),
                static @this => @this.ParseSubpatternElement(),
                SkipBadPatternListTokens,
                allowTrailingSeparator: true,
                requireOneElement: false,
                allowSemicolonAsSeparator: false);
 
            return _syntaxFactory.PropertyPatternClause(
                openBraceToken,
                subPatterns,
                this.EatToken(SyntaxKind.CloseBraceToken));
        }
 
        private SubpatternSyntax ParseSubpatternElement()
        {
            BaseExpressionColonSyntax? exprColon = null;
 
            PatternSyntax pattern = ParsePattern(Precedence.Conditional);
            // If there is a colon but it's not preceded by a valid expression, leave it out to parse it as a missing comma, preserving C# 9.0 behavior.
            if (this.CurrentToken.Kind == SyntaxKind.ColonToken && ConvertPatternToExpressionIfPossible(pattern, permitTypeArguments: true) is ExpressionSyntax expr)
            {
                var colon = EatToken();
                exprColon = expr is IdentifierNameSyntax identifierName
                    ? _syntaxFactory.NameColon(identifierName, colon)
                    : _syntaxFactory.ExpressionColon(expr, colon);
 
                pattern = ParsePattern(Precedence.Conditional);
            }
 
            return _syntaxFactory.Subpattern(exprColon, pattern);
        }
 
        /// <summary>
        /// Check the next token to see if it is valid as the first token of a subpattern element.
        /// Used to assist in error recovery for subpattern lists (e.g. determining which tokens to skip)
        /// to ensure we make forward progress during recovery.
        /// </summary>
        private bool IsPossibleSubpatternElement()
        {
            return this.CanStartExpression() ||
                this.CurrentToken.Kind is
                    SyntaxKind.OpenBraceToken or
                    SyntaxKind.OpenBracketToken or
                    SyntaxKind.LessThanToken or
                    SyntaxKind.LessThanEqualsToken or
                    SyntaxKind.GreaterThanToken or
                    SyntaxKind.GreaterThanEqualsToken;
        }
 
        private static PostSkipAction SkipBadPatternListTokens<T>(
            LanguageParser @this, ref SyntaxToken open, SeparatedSyntaxListBuilder<T> list, SyntaxKind expectedKind, SyntaxKind closeKind)
            where T : CSharpSyntaxNode
        {
            if (@this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBraceToken or SyntaxKind.CloseBracketToken or SyntaxKind.SemicolonToken)
                return PostSkipAction.Abort;
 
            // `:` is usually treated as incorrect separation token. This helps for error recovery in basic typing scenarios like `{ Prop:$$ Prop1: { ... } }`.
            // However, such behavior isn't much desirable when parsing pattern of a case label in a switch statement. For instance, consider the following example: `case { Prop: { }: case ...`.
            // Normally we would skip second `:` and `case` keyword after it as bad tokens and continue parsing pattern, which produces a lot of noise errors.
            // In order to avoid that and produce single error of missing `}` we exit on unexpected `:` in such cases.
            if (@this._termState.HasFlag(TerminatorState.IsExpressionOrPatternInCaseLabelOfSwitchStatement) && @this.CurrentToken.Kind is SyntaxKind.ColonToken)
                return PostSkipAction.Abort;
 
            // This is pretty much the same as above, but for switch expressions and `=>` and `:` tokens.
            // The reason why we cannot use single flag for both cases is because we want `=>` to be the "exit" token only for switch expressions.
            // Consider the following example: `case (() => 0):`. Normally `=>` is treated as bad separator, so we parse this basically the same as `case ((), 1):`, which is syntactically valid.
            // However, if we treated `=>` as "exit" token, parsing wouldn't consume full case label properly and would produce a lot of noise errors.
            // We can afford `:` to be the exit token for switch expressions because error recovery is already good enough and treats `:` as bad `=>`,
            // meaning that switch expression arm `{ : 1` can be recovered to `{ } => 1` where the closing `}` is missing and instead of `=>` we have `:`.
            if (@this._termState.HasFlag(TerminatorState.IsPatternInSwitchExpressionArm) && @this.CurrentToken.Kind is SyntaxKind.EqualsGreaterThanToken or SyntaxKind.ColonToken)
                return PostSkipAction.Abort;
 
            return @this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list,
                static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleSubpatternElement(),
                static (p, closeKind) => p.CurrentToken.Kind == closeKind || p.CurrentToken.Kind == SyntaxKind.SemicolonToken,
                expectedKind, closeKind);
        }
 
        private SwitchExpressionSyntax ParseSwitchExpression(ExpressionSyntax governingExpression, SyntaxToken switchKeyword)
        {
            // For better error recovery when an expression is typed on a line before a switch statement,
            // the caller checks if the switch keyword is followed by an open curly brace. Only if it is
            // would we attempt to parse it as a switch expression here.
            return _syntaxFactory.SwitchExpression(
                governingExpression,
                switchKeyword,
                this.EatToken(SyntaxKind.OpenBraceToken),
                parseSwitchExpressionArms(),
                this.EatToken(SyntaxKind.CloseBraceToken));
 
            SeparatedSyntaxList<SwitchExpressionArmSyntax> parseSwitchExpressionArms()
            {
                var arms = _pool.AllocateSeparated<SwitchExpressionArmSyntax>();
 
                while (this.CurrentToken.Kind != SyntaxKind.CloseBraceToken)
                {
                    // Help out in the case where a user is converting a switch statement to a switch expression. Note:
                    // `default(...)` and `default` will also be consumed as a legal syntactic patterns (though the
                    // latter will fail during binding).  So if the user has `default:` we will recover fine as we
                    // handle the errant colon below.
                    var errantCase = this.CurrentToken.Kind == SyntaxKind.CaseKeyword
                        ? AddError(this.EatToken(), ErrorCode.ERR_BadCaseInSwitchArm)
                        : null;
 
                    var savedState = _termState;
                    _termState |= TerminatorState.IsPatternInSwitchExpressionArm;
                    var pattern = ParsePattern(Precedence.Coalescing, inSwitchArmPattern: true);
                    _termState = savedState;
 
                    // We use a precedence that excludes lambdas, assignments, and a conditional which could have a
                    // lambda on the right, because we need the parser to leave the EqualsGreaterThanToken to be
                    // consumed by the switch arm. The strange side-effect of that is that the conditional expression is
                    // not permitted as a constant expression here; it would have to be parenthesized.
 
                    var switchExpressionCase = _syntaxFactory.SwitchExpressionArm(
                        pattern,
                        ParseWhenClause(Precedence.Coalescing),
                        // Help out in the case where a user is converting a switch statement to a switch expression.
                        // Consume the `:` as a `=>` and report an error.
                        this.CurrentToken.Kind == SyntaxKind.ColonToken
                            ? this.EatTokenAsKind(SyntaxKind.EqualsGreaterThanToken)
                            : this.EatToken(SyntaxKind.EqualsGreaterThanToken),
                        ParseExpressionCore());
 
                    // If we're not making progress, abort
                    if (errantCase is null && switchExpressionCase.FullWidth == 0 && this.CurrentToken.Kind != SyntaxKind.CommaToken)
                        break;
 
                    if (errantCase != null)
                        switchExpressionCase = AddLeadingSkippedSyntax(switchExpressionCase, errantCase);
 
                    arms.Add(switchExpressionCase);
                    if (this.CurrentToken.Kind != SyntaxKind.CloseBraceToken)
                    {
                        var commaToken = this.CurrentToken.Kind == SyntaxKind.SemicolonToken
                            ? this.EatTokenAsKind(SyntaxKind.CommaToken)
                            : this.EatToken(SyntaxKind.CommaToken);
                        arms.AddSeparator(commaToken);
                    }
                }
 
                return _pool.ToListAndFree(arms);
            }
        }
 
        private ListPatternSyntax ParseListPattern(bool inSwitchArmPattern)
        {
            var openBracket = this.EatToken(SyntaxKind.OpenBracketToken);
            var list = this.ParseCommaSeparatedSyntaxList(
                ref openBracket,
                SyntaxKind.CloseBracketToken,
                static @this => @this.IsPossibleSubpatternElement(),
                static @this => @this.ParsePattern(Precedence.Conditional),
                SkipBadPatternListTokens,
                allowTrailingSeparator: true,
                requireOneElement: false,
                allowSemicolonAsSeparator: false);
 
            return _syntaxFactory.ListPattern(
                openBracket,
                list,
                this.EatToken(SyntaxKind.CloseBracketToken),
                TryParseSimpleDesignation(inSwitchArmPattern));
        }
    }
}