/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs
680 строк
33 KB
Cyrus Najmabadi
Fix parsing of parenthesized type in switch arm (#81863)
13 янв 2026, 20:33
Не верифицирован
13 янв 2026, 20:33
fc2b820
Код
Авторство
О чём код?
// 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 if (this.CurrentToken.Kind == SyntaxKind.EqualsEqualsToken) { return AddLeadingSkippedSyntax( skippedSyntax: AddError(this.EatToken(), ErrorCode.ERR_EqualityOperatorInPatternNotSupported), node: ParseNegatedPattern(precedence, afterIs, inSwitchArmPattern)); } else if (this.CurrentToken.Kind == SyntaxKind.ExclamationEqualsToken) { // Handle != in patterns - suggest using 'not' instead return _syntaxFactory.UnaryPattern( this.AddTrailingSkippedSyntax( SyntaxFactory.MissingToken(SyntaxKind.NotKeyword), this.AddError(this.EatToken(), ErrorCode.ERR_InequalityOperatorInPatternNotSupported)), 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) { // Check for the `id {` and report that the designator has to come after the property pattern. var misplacedIdentifier = this.IsTrueIdentifier() && this.IsValidPatternDesignation(inSwitchArmPattern) && this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken ? AddError(this.EatToken(), ErrorCode.ERR_DesignatorBeforePropertyPattern) : null; if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken) { propertyPatternClauseResult = AddLeadingSkippedSyntax(ParsePropertyPatternClause(), misplacedIdentifier); return true; } propertyPatternClauseResult = null; return false; } bool looksLikeCast() { using var _ = this.GetDisposableResetPoint(resetOnDispose: true); return this.ScanCast(forPattern: true, inSwitchArmPattern); } } private VariableDesignationSyntax? TryParseSimpleDesignation(bool whenIsKeyword) { return this.IsTrueIdentifier() && this.IsValidPatternDesignation(whenIsKeyword) ? ParseSimpleDesignation() : null; } private bool IsValidPatternDesignation(bool inSwitchArmPattern) { if (CurrentToken.Kind == SyntaxKind.IdentifierToken) { switch (CurrentToken.ContextualKind) { case SyntaxKind.WhenKeyword: // When directly in a switch arm, we *always* treat 'when' as a keyword starting the 'when // clause'. In other patterns, we allow 'when' to be a normal designator. return !inSwitchArmPattern; 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)); } } }