|
// 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.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
internal sealed partial class LanguageParser : SyntaxParser
{
// list pools - allocators for lists that are used to build sequences of nodes. The lists
// can be reused (hence pooled) since the syntax factory methods don't keep references to
// them
private readonly SyntaxListPool _pool = new SyntaxListPool(); // Don't need to reset this.
private readonly SyntaxFactoryContext _syntaxFactoryContext; // Fields are resettable.
private readonly ContextAwareSyntax _syntaxFactory; // Has context, the fields of which are resettable.
private int _recursionDepth;
private TerminatorState _termState; // Resettable
// NOTE: If you add new state, you should probably add it to ResetPoint as well.
internal LanguageParser(
Lexer lexer,
CSharp.CSharpSyntaxNode? oldTree,
IEnumerable<TextChangeRange>? changes,
LexerMode lexerMode = LexerMode.Syntax,
CancellationToken cancellationToken = default(CancellationToken))
: base(lexer, lexerMode, oldTree, changes, allowModeReset: false,
preLexIfNotIncremental: true, cancellationToken: cancellationToken)
{
_syntaxFactoryContext = new SyntaxFactoryContext();
_syntaxFactory = new ContextAwareSyntax(_syntaxFactoryContext);
}
private static bool IsSomeWord(SyntaxKind kind)
{
return kind == SyntaxKind.IdentifierToken || SyntaxFacts.IsKeywordKind(kind);
}
// Parsing rule terminating conditions. This is how we know if it is
// okay to abort the current parsing rule when unexpected tokens occur.
[Flags]
internal enum TerminatorState
{
EndOfFile = 0,
IsNamespaceMemberStartOrStop = 1 << 0,
IsAttributeDeclarationTerminator = 1 << 1,
IsPossibleAggregateClauseStartOrStop = 1 << 2,
IsPossibleMemberStartOrStop = 1 << 3,
IsEndOfReturnType = 1 << 4,
IsEndOfParameterList = 1 << 5,
IsEndOfFieldDeclaration = 1 << 6,
IsPossibleEndOfVariableDeclaration = 1 << 7,
IsEndOfTypeArgumentList = 1 << 8,
IsPossibleStatementStartOrStop = 1 << 9,
IsEndOfFixedStatement = 1 << 10,
IsEndOfTryBlock = 1 << 11,
IsEndOfCatchClause = 1 << 12,
IsEndOfFilterClause = 1 << 13,
IsEndOfCatchBlock = 1 << 14,
IsEndOfDoWhileExpression = 1 << 15,
IsEndOfForStatementArgument = 1 << 16,
IsEndOfDeclarationClause = 1 << 17,
IsEndOfArgumentList = 1 << 18,
IsSwitchSectionStart = 1 << 19,
IsEndOfTypeParameterList = 1 << 20,
IsEndOfMethodSignature = 1 << 21,
IsEndOfNameInExplicitInterface = 1 << 22,
IsEndOfFunctionPointerParameterList = 1 << 23,
IsEndOfFunctionPointerParameterListErrored = 1 << 24,
IsEndOfFunctionPointerCallingConvention = 1 << 25,
IsEndOfRecordOrClassOrStructOrInterfaceSignature = 1 << 26,
IsExpressionOrPatternInCaseLabelOfSwitchStatement = 1 << 27,
IsPatternInSwitchExpressionArm = 1 << 28,
}
private const int LastTerminatorState = (int)TerminatorState.IsPatternInSwitchExpressionArm;
private bool IsTerminator()
{
if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken)
{
return true;
}
for (int i = 1; i <= LastTerminatorState; i <<= 1)
{
switch (_termState & (TerminatorState)i)
{
case TerminatorState.IsNamespaceMemberStartOrStop when this.IsNamespaceMemberStartOrStop():
case TerminatorState.IsAttributeDeclarationTerminator when this.IsAttributeDeclarationTerminator():
case TerminatorState.IsPossibleAggregateClauseStartOrStop when this.IsPossibleAggregateClauseStartOrStop():
case TerminatorState.IsPossibleMemberStartOrStop when this.IsPossibleMemberStartOrStop():
case TerminatorState.IsEndOfReturnType when this.IsEndOfReturnType():
case TerminatorState.IsEndOfParameterList when this.IsEndOfParameterList():
case TerminatorState.IsEndOfFieldDeclaration when this.IsEndOfFieldDeclaration():
case TerminatorState.IsPossibleEndOfVariableDeclaration when this.IsPossibleEndOfVariableDeclaration():
case TerminatorState.IsEndOfTypeArgumentList when this.IsEndOfTypeArgumentList():
case TerminatorState.IsPossibleStatementStartOrStop when this.IsPossibleStatementStartOrStop():
case TerminatorState.IsEndOfFixedStatement when this.IsEndOfFixedStatement():
case TerminatorState.IsEndOfTryBlock when this.IsEndOfTryBlock():
case TerminatorState.IsEndOfCatchClause when this.IsEndOfCatchClause():
case TerminatorState.IsEndOfFilterClause when this.IsEndOfFilterClause():
case TerminatorState.IsEndOfCatchBlock when this.IsEndOfCatchBlock():
case TerminatorState.IsEndOfDoWhileExpression when this.IsEndOfDoWhileExpression():
case TerminatorState.IsEndOfForStatementArgument when this.IsEndOfForStatementArgument():
case TerminatorState.IsEndOfDeclarationClause when this.IsEndOfDeclarationClause():
case TerminatorState.IsEndOfArgumentList when this.IsEndOfArgumentList():
case TerminatorState.IsSwitchSectionStart when this.IsPossibleSwitchSection():
case TerminatorState.IsEndOfTypeParameterList when this.IsEndOfTypeParameterList():
case TerminatorState.IsEndOfMethodSignature when this.IsEndOfMethodSignature():
case TerminatorState.IsEndOfNameInExplicitInterface when this.IsEndOfNameInExplicitInterface():
case TerminatorState.IsEndOfFunctionPointerParameterList when this.IsEndOfFunctionPointerParameterList(errored: false):
case TerminatorState.IsEndOfFunctionPointerParameterListErrored when this.IsEndOfFunctionPointerParameterList(errored: true):
case TerminatorState.IsEndOfFunctionPointerCallingConvention when this.IsEndOfFunctionPointerCallingConvention():
case TerminatorState.IsEndOfRecordOrClassOrStructOrInterfaceSignature when this.IsEndOfRecordOrClassOrStructOrInterfaceSignature():
return true;
}
}
return false;
}
private static CSharp.CSharpSyntaxNode? GetOldParent(CSharp.CSharpSyntaxNode node)
{
return node != null ? node.Parent : null;
}
private struct NamespaceBodyBuilder
{
public SyntaxListBuilder<ExternAliasDirectiveSyntax> Externs;
public SyntaxListBuilder<UsingDirectiveSyntax> Usings;
public SyntaxListBuilder<AttributeListSyntax> Attributes;
public SyntaxListBuilder<MemberDeclarationSyntax> Members;
public NamespaceBodyBuilder(SyntaxListPool pool)
{
Externs = pool.Allocate<ExternAliasDirectiveSyntax>();
Usings = pool.Allocate<UsingDirectiveSyntax>();
Attributes = pool.Allocate<AttributeListSyntax>();
Members = pool.Allocate<MemberDeclarationSyntax>();
}
internal void Free(SyntaxListPool pool)
{
pool.Free(Members);
pool.Free(Attributes);
pool.Free(Usings);
pool.Free(Externs);
}
}
internal CompilationUnitSyntax ParseCompilationUnit()
{
return ParseWithStackGuard(
static @this => @this.ParseCompilationUnitCore(),
static @this => SyntaxFactory.CompilationUnit(
new SyntaxList<ExternAliasDirectiveSyntax>(),
new SyntaxList<UsingDirectiveSyntax>(),
new SyntaxList<AttributeListSyntax>(),
new SyntaxList<MemberDeclarationSyntax>(),
SyntaxFactory.Token(SyntaxKind.EndOfFileToken)));
}
internal CompilationUnitSyntax ParseCompilationUnitCore()
{
SyntaxToken? tmp = null;
SyntaxListBuilder? initialBadNodes = null;
var body = new NamespaceBodyBuilder(_pool);
try
{
this.ParseNamespaceBody(ref tmp, ref body, ref initialBadNodes, SyntaxKind.CompilationUnit);
var eof = this.EatToken(SyntaxKind.EndOfFileToken);
var result = _syntaxFactory.CompilationUnit(body.Externs, body.Usings, body.Attributes, body.Members, eof);
if (initialBadNodes != null)
{
// attach initial bad nodes as leading trivia on first token
result = AddLeadingSkippedSyntax(result, initialBadNodes.ToListNode());
_pool.Free(initialBadNodes);
}
return result;
}
finally
{
body.Free(_pool);
}
}
internal TNode ParseWithStackGuard<TNode>(Func<LanguageParser, TNode> parseFunc, Func<LanguageParser, TNode> createEmptyNodeFunc) where TNode : CSharpSyntaxNode
{
// If this value is non-zero then we are nesting calls to ParseWithStackGuard which should not be
// happening. It's not a bug but it's inefficient and should be changed.
Debug.Assert(_recursionDepth == 0);
try
{
return parseFunc(this);
}
catch (InsufficientExecutionStackException)
{
return CreateForGlobalFailure(lexer.TextWindow.Position, createEmptyNodeFunc(this));
}
}
private TNode CreateForGlobalFailure<TNode>(int position, TNode node) where TNode : CSharpSyntaxNode
{
// Turn the complete input into a single skipped token. This avoids running the lexer, and therefore
// the preprocessor directive parser, which may itself run into the same problem that caused the
// original failure.
var builder = new SyntaxListBuilder(1);
builder.Add(SyntaxFactory.BadToken(null, lexer.TextWindow.Text.ToString(), null));
var fileAsTrivia = _syntaxFactory.SkippedTokensTrivia(builder.ToList<SyntaxToken>());
node = AddLeadingSkippedSyntax(node, fileAsTrivia);
ForceEndOfFile(); // force the scanner to report that it is at the end of the input.
return AddError(node, position, 0, ErrorCode.ERR_InsufficientStack);
}
private BaseNamespaceDeclarationSyntax ParseNamespaceDeclaration(
SyntaxList<AttributeListSyntax> attributeLists,
SyntaxListBuilder modifiers)
{
_recursionDepth++;
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
var result = ParseNamespaceDeclarationCore(attributeLists, modifiers);
_recursionDepth--;
return result;
}
private BaseNamespaceDeclarationSyntax ParseNamespaceDeclarationCore(
SyntaxList<AttributeListSyntax> attributeLists,
SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.NamespaceKeyword);
var namespaceToken = this.EatToken(SyntaxKind.NamespaceKeyword);
if (IsScript)
{
namespaceToken = this.AddError(namespaceToken, ErrorCode.ERR_NamespaceNotAllowedInScript);
}
var name = this.ParseQualifiedName();
SyntaxToken? openBrace = null;
SyntaxToken? semicolon = null;
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
else if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken || IsPossibleNamespaceMemberDeclaration())
{
//either we see the brace we expect here or we see something that could come after a brace
//so we insert a missing one
openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
}
else
{
//the next character is neither the brace we expect, nor a token that could follow the expected
//brace so we assume it's a mistake and replace it with a missing brace
openBrace = this.EatTokenWithPrejudice(SyntaxKind.OpenBraceToken);
openBrace = this.ConvertToMissingWithTrailingTrivia(openBrace, SyntaxKind.OpenBraceToken);
}
Debug.Assert(semicolon != null || openBrace != null);
var body = new NamespaceBodyBuilder(_pool);
try
{
if (openBrace == null)
{
Debug.Assert(semicolon != null);
SyntaxListBuilder? initialBadNodes = null;
this.ParseNamespaceBody(ref semicolon, ref body, ref initialBadNodes, SyntaxKind.FileScopedNamespaceDeclaration);
Debug.Assert(initialBadNodes == null); // init bad nodes should have been attached to semicolon...
return _syntaxFactory.FileScopedNamespaceDeclaration(
attributeLists,
modifiers.ToList(),
namespaceToken,
name,
semicolon,
body.Externs,
body.Usings,
body.Members);
}
else
{
SyntaxListBuilder? initialBadNodes = null;
this.ParseNamespaceBody(ref openBrace, ref body, ref initialBadNodes, SyntaxKind.NamespaceDeclaration);
Debug.Assert(initialBadNodes == null); // init bad nodes should have been attached to open brace...
return _syntaxFactory.NamespaceDeclaration(
attributeLists,
modifiers.ToList(),
namespaceToken,
name,
openBrace,
body.Externs,
body.Usings,
body.Members,
this.EatToken(SyntaxKind.CloseBraceToken),
this.TryEatToken(SyntaxKind.SemicolonToken));
}
}
finally
{
body.Free(_pool);
}
}
/// <summary>Are we possibly at the start of an attribute list, or at a modifier which is valid on a type, or on a keyword of a type declaration?</summary>
private static bool IsPossibleStartOfTypeDeclaration(SyntaxKind kind)
{
return IsTypeModifierOrTypeKeyword(kind) || kind == SyntaxKind.OpenBracketToken;
}
/// <summary>Are we at a modifier which is valid on a type declaration or at a type keyword?</summary>
private static bool IsTypeModifierOrTypeKeyword(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.EnumKeyword:
case SyntaxKind.DelegateKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.StructKeyword:
case SyntaxKind.AbstractKeyword:
case SyntaxKind.InternalKeyword:
case SyntaxKind.NewKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.SealedKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.UnsafeKeyword:
return true;
default:
return false;
}
}
private void AddSkippedNamespaceText(
ref SyntaxToken? openBraceOrSemicolon,
ref NamespaceBodyBuilder body,
ref SyntaxListBuilder? initialBadNodes,
CSharpSyntaxNode skippedSyntax)
{
if (body.Members.Count > 0)
{
AddTrailingSkippedSyntax(body.Members, skippedSyntax);
}
else if (body.Attributes.Count > 0)
{
AddTrailingSkippedSyntax(body.Attributes, skippedSyntax);
}
else if (body.Usings.Count > 0)
{
AddTrailingSkippedSyntax(body.Usings, skippedSyntax);
}
else if (body.Externs.Count > 0)
{
AddTrailingSkippedSyntax(body.Externs, skippedSyntax);
}
else if (openBraceOrSemicolon != null)
{
openBraceOrSemicolon = AddTrailingSkippedSyntax(openBraceOrSemicolon, skippedSyntax);
}
else
{
if (initialBadNodes == null)
{
initialBadNodes = _pool.Allocate();
}
initialBadNodes.AddRange(skippedSyntax);
}
}
// Parts of a namespace declaration in the order they can be defined.
private enum NamespaceParts
{
None = 0,
ExternAliases = 1,
Usings = 2,
GlobalAttributes = 3,
MembersAndStatements = 4,
TypesAndNamespaces = 5,
TopLevelStatementsAfterTypesAndNamespaces = 6,
}
private void ParseNamespaceBody(
[NotNullIfNotNull(nameof(openBraceOrSemicolon))] ref SyntaxToken? openBraceOrSemicolon,
ref NamespaceBodyBuilder body,
ref SyntaxListBuilder? initialBadNodes,
SyntaxKind parentKind)
{
ParseNamespaceBodyWorker(
ref openBraceOrSemicolon, ref body, ref initialBadNodes, parentKind, out var sawMemberDeclarationOnlyValidWithinTypeDeclaration);
// In the common case, we will not see errant type-only member declarations in the namespace itself. In
// that case, we have no extra work to do and can return immediately.
//
// If we do see errant type-only members (like a method/property/constructor/etc.), then see if they follow
// some normal type declaration. If so, it's likely there was a misplaced close curly that preemptively
// ended the type declaration, and the member declaration was supposed to go in it instead.
if (!sawMemberDeclarationOnlyValidWithinTypeDeclaration)
return;
// In a script file, it can be ok to have these members at the top level. For example, a field is actually
// ok to parse out at the top level as it will become a field on the script global object.
if (IsScript && parentKind == SyntaxKind.CompilationUnit)
return;
var finalMembers = _pool.Allocate<MemberDeclarationSyntax>();
// Do a single linear sweep, examining each type declaration we run into within the namespace.
for (var currentBodyMemberIndex = 0; currentBodyMemberIndex < body.Members.Count;)
{
var currentMember = body.Members[currentBodyMemberIndex];
// If we have a suitable type declaration that ended without problem (has a real close curly and no
// trailing semicolon), then see if there are any type-only members following it that should be moved
// into it.
if (currentMember is TypeDeclarationSyntax
{
SemicolonToken: null,
CloseBraceToken: { IsMissing: false, ContainsDiagnostics: false }
} currentTypeDeclaration)
{
var siblingsToMoveIntoType = determineSiblingsToMoveIntoType(typeDeclarationIndex: currentBodyMemberIndex, body);
if (siblingsToMoveIntoType is (var firstSiblingToMoveInclusive, var lastSiblingToMoveExclusive))
{
// We found sibling type-only members. Move them into the preceding type declaration.
var finalTypeDeclaration = moveSiblingMembersIntoPrecedingType(
currentTypeDeclaration, body, firstSiblingToMoveInclusive, lastSiblingToMoveExclusive);
finalMembers.Add(finalTypeDeclaration);
// We moved a sequence of type-only-members into the preceding type declaration. We need to
// continue processing from the end of that sequence.
currentBodyMemberIndex = lastSiblingToMoveExclusive;
continue;
}
}
// Simple case. A normal namespace member we don't need to do anything with.
finalMembers.Add(currentMember);
currentBodyMemberIndex++;
}
_pool.Free(body.Members);
body.Members = finalMembers;
return;
(int firstSiblingToMoveInclusive, int lastSiblingToMoveExclusive)? determineSiblingsToMoveIntoType(
int typeDeclarationIndex,
in NamespaceBodyBuilder body)
{
var startInclusive = typeDeclarationIndex + 1;
if (startInclusive < body.Members.Count &&
IsMemberDeclarationOnlyValidWithinTypeDeclaration(body.Members[startInclusive]))
{
var endExclusive = startInclusive + 1;
while (endExclusive < body.Members.Count &&
IsMemberDeclarationOnlyValidWithinTypeDeclaration(body.Members[endExclusive]))
{
endExclusive++;
}
return (startInclusive, endExclusive);
}
return null;
}
TypeDeclarationSyntax moveSiblingMembersIntoPrecedingType(
TypeDeclarationSyntax typeDeclaration,
in NamespaceBodyBuilder body,
int firstSiblingToMoveInclusive,
int lastSiblingToMoveExclusive)
{
var finalTypeDeclarationMembers = _pool.Allocate<MemberDeclarationSyntax>();
finalTypeDeclarationMembers.AddRange(typeDeclaration.Members);
for (var memberToMoveIndex = firstSiblingToMoveInclusive; memberToMoveIndex < lastSiblingToMoveExclusive; memberToMoveIndex++)
{
var currentSibling = body.Members[memberToMoveIndex];
if (memberToMoveIndex == firstSiblingToMoveInclusive)
{
// Move the existing close brace token to the first member as a skipped token, with a
// diagnostic saying that it was unexpected.
currentSibling = AddLeadingSkippedSyntax(
currentSibling,
AddError(typeDeclaration.CloseBraceToken, ErrorCode.ERR_InvalidMemberDecl, "}"));
}
finalTypeDeclarationMembers.Add(currentSibling);
}
var isLast = lastSiblingToMoveExclusive == body.Members.Count;
// The existing close brace token is moved to the first member as a skipped token, with a diagnostic saying
// it was unexpected. The type decl will then get a missing close brace token if there are still members
// following. If not, we'll try to eat an actual close brace token.
var finalCloseBraceToken = isLast
? EatToken(SyntaxKind.CloseBraceToken)
: AddError(
SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken), ErrorCode.ERR_RbraceExpected);
var newMembers = _pool.ToListAndFree(finalTypeDeclarationMembers);
return typeDeclaration.UpdateCore(
typeDeclaration.AttributeLists,
typeDeclaration.Modifiers,
typeDeclaration.Keyword,
typeDeclaration.Identifier,
typeDeclaration.TypeParameterList,
typeDeclaration.ParameterList,
typeDeclaration.BaseList,
typeDeclaration.ConstraintClauses,
typeDeclaration.OpenBraceToken,
newMembers,
finalCloseBraceToken,
typeDeclaration.SemicolonToken);
}
}
private static bool IsMemberDeclarationOnlyValidWithinTypeDeclaration(MemberDeclarationSyntax? memberDeclaration)
{
return memberDeclaration?.Kind
is SyntaxKind.ConstructorDeclaration
or SyntaxKind.ConversionOperatorDeclaration
or SyntaxKind.DestructorDeclaration
or SyntaxKind.EventDeclaration
or SyntaxKind.EventFieldDeclaration
or SyntaxKind.FieldDeclaration
or SyntaxKind.IndexerDeclaration
or SyntaxKind.MethodDeclaration
or SyntaxKind.OperatorDeclaration
or SyntaxKind.PropertyDeclaration;
}
private void ParseNamespaceBodyWorker(
[NotNullIfNotNull(nameof(openBraceOrSemicolon))] ref SyntaxToken? openBraceOrSemicolon,
ref NamespaceBodyBuilder body,
ref SyntaxListBuilder? initialBadNodes,
SyntaxKind parentKind,
out bool sawMemberDeclarationOnlyValidWithinTypeDeclaration)
{
// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);
bool isGlobal = openBraceOrSemicolon == null;
var saveTerm = _termState;
_termState |= TerminatorState.IsNamespaceMemberStartOrStop;
NamespaceParts seen = NamespaceParts.None;
var pendingIncompleteMembers = _pool.Allocate<MemberDeclarationSyntax>();
bool reportUnexpectedToken = true;
sawMemberDeclarationOnlyValidWithinTypeDeclaration = false;
try
{
while (true)
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.NamespaceKeyword:
// incomplete members must be processed before we add any nodes to the body:
AddIncompleteMembers(ref pendingIncompleteMembers, ref body);
var attributeLists = _pool.Allocate<AttributeListSyntax>();
var modifiers = _pool.Allocate();
body.Members.Add(adjustStateAndReportStatementOutOfOrder(ref seen, this.ParseNamespaceDeclaration(attributeLists, modifiers)));
_pool.Free(attributeLists);
_pool.Free(modifiers);
reportUnexpectedToken = true;
break;
case SyntaxKind.CloseBraceToken:
// A very common user error is to type an additional }
// somewhere in the file. This will cause us to stop parsing
// the root (global) namespace too early and will make the
// rest of the file unparseable and unusable by intellisense.
// We detect that case here and we skip the close curly and
// continue parsing as if we did not see the }
if (isGlobal)
{
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBraceOrSemicolon, ref body, ref initialBadNodes);
var token = this.EatToken();
token = this.AddError(token,
IsScript ? ErrorCode.ERR_GlobalDefinitionOrStatementExpected : ErrorCode.ERR_EOFExpected);
this.AddSkippedNamespaceText(ref openBraceOrSemicolon, ref body, ref initialBadNodes, token);
reportUnexpectedToken = true;
break;
}
else
{
// This token marks the end of a namespace body
return;
}
case SyntaxKind.EndOfFileToken:
// This token marks the end of a namespace body
return;
case SyntaxKind.ExternKeyword:
if (isGlobal && !ScanExternAliasDirective())
{
// extern member or a local function
goto default;
}
else
{
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBraceOrSemicolon, ref body, ref initialBadNodes);
var @extern = ParseExternAliasDirective();
if (seen > NamespaceParts.ExternAliases)
{
@extern = this.AddErrorToFirstToken(@extern, ErrorCode.ERR_ExternAfterElements);
this.AddSkippedNamespaceText(ref openBraceOrSemicolon, ref body, ref initialBadNodes, @extern);
}
else
{
body.Externs.Add(@extern);
seen = NamespaceParts.ExternAliases;
}
reportUnexpectedToken = true;
break;
}
case SyntaxKind.UsingKeyword:
if (isGlobal && (this.PeekToken(1).Kind == SyntaxKind.OpenParenToken || (!IsScript && IsPossibleTopLevelUsingLocalDeclarationStatement())))
{
// Top-level using statement or using local declaration
goto default;
}
else
{
parseUsingDirective(ref openBraceOrSemicolon, ref body, ref initialBadNodes, ref seen, ref pendingIncompleteMembers);
}
reportUnexpectedToken = true;
break;
case SyntaxKind.IdentifierToken:
if (this.CurrentToken.ContextualKind != SyntaxKind.GlobalKeyword || this.PeekToken(1).Kind != SyntaxKind.UsingKeyword)
{
goto default;
}
else
{
parseUsingDirective(ref openBraceOrSemicolon, ref body, ref initialBadNodes, ref seen, ref pendingIncompleteMembers);
}
reportUnexpectedToken = true;
break;
case SyntaxKind.OpenBracketToken:
if (this.IsPossibleGlobalAttributeDeclaration())
{
// Could be an attribute, or it could be a collection expression at the top level. e.g.
// `[assembly: 1].XYZ();`. While this is definitely odd code, it is totally legal (as
// `assembly` is just an identifier).
var attribute = this.TryParseAttributeDeclaration(inExpressionContext: parentKind == SyntaxKind.CompilationUnit);
if (attribute != null)
{
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBraceOrSemicolon, ref body, ref initialBadNodes);
if (!isGlobal || seen > NamespaceParts.GlobalAttributes)
{
RoslynDebug.Assert(attribute.Target != null, "Must have a target as IsPossibleGlobalAttributeDeclaration checks for that");
attribute = this.AddError(attribute, attribute.Target.Identifier, ErrorCode.ERR_GlobalAttributesNotFirst);
this.AddSkippedNamespaceText(ref openBraceOrSemicolon, ref body, ref initialBadNodes, attribute);
}
else
{
body.Attributes.Add(attribute);
seen = NamespaceParts.GlobalAttributes;
}
reportUnexpectedToken = true;
break;
}
}
goto default;
default:
var memberOrStatement = isGlobal
? this.ParseMemberDeclarationOrStatement(parentKind)
: this.ParseMemberDeclaration(parentKind);
sawMemberDeclarationOnlyValidWithinTypeDeclaration |= IsMemberDeclarationOnlyValidWithinTypeDeclaration(memberOrStatement);
if (memberOrStatement == null)
{
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBraceOrSemicolon, ref body, ref initialBadNodes);
// eat one token and try to parse declaration or statement again:
var skippedToken = EatToken();
if (reportUnexpectedToken && !skippedToken.ContainsDiagnostics)
{
skippedToken = this.AddError(skippedToken,
IsScript ? ErrorCode.ERR_GlobalDefinitionOrStatementExpected : ErrorCode.ERR_EOFExpected);
// do not report the error multiple times for subsequent tokens:
reportUnexpectedToken = false;
}
this.AddSkippedNamespaceText(ref openBraceOrSemicolon, ref body, ref initialBadNodes, skippedToken);
}
else if (memberOrStatement.Kind == SyntaxKind.IncompleteMember && seen < NamespaceParts.MembersAndStatements)
{
pendingIncompleteMembers.Add(memberOrStatement);
reportUnexpectedToken = true;
}
else
{
// incomplete members must be processed before we add any nodes to the body:
AddIncompleteMembers(ref pendingIncompleteMembers, ref body);
body.Members.Add(adjustStateAndReportStatementOutOfOrder(ref seen, memberOrStatement));
reportUnexpectedToken = true;
}
break;
}
}
}
finally
{
_termState = saveTerm;
// adds pending incomplete nodes:
AddIncompleteMembers(ref pendingIncompleteMembers, ref body);
_pool.Free(pendingIncompleteMembers);
}
MemberDeclarationSyntax adjustStateAndReportStatementOutOfOrder(ref NamespaceParts seen, MemberDeclarationSyntax memberOrStatement)
{
switch (memberOrStatement.Kind)
{
case SyntaxKind.GlobalStatement:
if (seen < NamespaceParts.MembersAndStatements)
{
seen = NamespaceParts.MembersAndStatements;
}
else if (seen == NamespaceParts.TypesAndNamespaces)
{
seen = NamespaceParts.TopLevelStatementsAfterTypesAndNamespaces;
if (!IsScript)
{
memberOrStatement = this.AddError(memberOrStatement, ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType);
}
}
break;
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.FileScopedNamespaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.RecordDeclaration:
case SyntaxKind.RecordStructDeclaration:
if (seen < NamespaceParts.TypesAndNamespaces)
{
seen = NamespaceParts.TypesAndNamespaces;
}
break;
default:
if (seen < NamespaceParts.MembersAndStatements)
{
seen = NamespaceParts.MembersAndStatements;
}
break;
}
return memberOrStatement;
}
void parseUsingDirective(
ref SyntaxToken? openBrace,
ref NamespaceBodyBuilder body,
ref SyntaxListBuilder? initialBadNodes,
ref NamespaceParts seen,
ref SyntaxListBuilder<MemberDeclarationSyntax> pendingIncompleteMembers)
{
// incomplete members must be processed before we add any nodes to the body:
ReduceIncompleteMembers(ref pendingIncompleteMembers, ref openBrace, ref body, ref initialBadNodes);
var @using = this.ParseUsingDirective();
if (seen > NamespaceParts.Usings)
{
@using = this.AddError(@using, ErrorCode.ERR_UsingAfterElements);
this.AddSkippedNamespaceText(ref openBrace, ref body, ref initialBadNodes, @using);
}
else
{
body.Usings.Add(@using);
seen = NamespaceParts.Usings;
}
}
}
private static void AddIncompleteMembers(ref SyntaxListBuilder<MemberDeclarationSyntax> incompleteMembers, ref NamespaceBodyBuilder body)
{
if (incompleteMembers.Count > 0)
{
body.Members.AddRange(incompleteMembers);
incompleteMembers.Clear();
}
}
private void ReduceIncompleteMembers(
ref SyntaxListBuilder<MemberDeclarationSyntax> incompleteMembers,
ref SyntaxToken? openBraceOrSemicolon,
ref NamespaceBodyBuilder body,
ref SyntaxListBuilder? initialBadNodes)
{
for (int i = 0; i < incompleteMembers.Count; i++)
this.AddSkippedNamespaceText(ref openBraceOrSemicolon, ref body, ref initialBadNodes, incompleteMembers[i]);
incompleteMembers.Clear();
}
private bool IsPossibleNamespaceMemberDeclaration()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.ExternKeyword:
case SyntaxKind.UsingKeyword:
case SyntaxKind.NamespaceKeyword:
return true;
case SyntaxKind.IdentifierToken:
return IsPartialInNamespaceMemberDeclaration();
default:
return IsPossibleStartOfTypeDeclaration(this.CurrentToken.Kind);
}
}
private bool IsPartialInNamespaceMemberDeclaration()
{
if (this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword)
{
if (this.IsPartialType())
{
return true;
}
else if (this.PeekToken(1).Kind == SyntaxKind.NamespaceKeyword)
{
return true;
}
}
return false;
}
public bool IsEndOfNamespace()
{
return this.CurrentToken.Kind == SyntaxKind.CloseBraceToken;
}
public bool IsGobalAttributesTerminator()
{
return this.IsEndOfNamespace()
|| this.IsPossibleNamespaceMemberDeclaration();
}
private bool IsNamespaceMemberStartOrStop()
{
return this.IsEndOfNamespace()
|| this.IsPossibleNamespaceMemberDeclaration();
}
/// <summary>
/// Returns true if the lookahead tokens compose extern alias directive.
/// </summary>
private bool ScanExternAliasDirective()
{
// The check also includes the ending semicolon so that we can disambiguate among:
// extern alias goo;
// extern alias goo();
// extern alias goo { get; }
return this.CurrentToken.Kind == SyntaxKind.ExternKeyword
&& this.PeekToken(1) is { Kind: SyntaxKind.IdentifierToken, ContextualKind: SyntaxKind.AliasKeyword }
&& this.PeekToken(2).Kind == SyntaxKind.IdentifierToken
&& this.PeekToken(3).Kind == SyntaxKind.SemicolonToken;
}
private ExternAliasDirectiveSyntax ParseExternAliasDirective()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.ExternAliasDirective)
{
return (ExternAliasDirectiveSyntax)this.EatNode();
}
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ExternKeyword);
return _syntaxFactory.ExternAliasDirective(
this.EatToken(SyntaxKind.ExternKeyword),
this.EatContextualToken(SyntaxKind.AliasKeyword),
this.ParseIdentifierToken(),
this.EatToken(SyntaxKind.SemicolonToken));
}
private NameEqualsSyntax ParseNameEquals()
{
Debug.Assert(this.IsNamedAssignment());
return _syntaxFactory.NameEquals(
_syntaxFactory.IdentifierName(this.ParseIdentifierToken()),
this.EatToken(SyntaxKind.EqualsToken));
}
private UsingDirectiveSyntax ParseUsingDirective()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.UsingDirective)
{
return (UsingDirectiveSyntax)this.EatNode();
}
var globalToken = this.CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword
? ConvertToKeyword(this.EatToken())
: null;
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.UsingKeyword);
var usingToken = this.EatToken(SyntaxKind.UsingKeyword);
var staticToken = this.TryEatToken(SyntaxKind.StaticKeyword);
var unsafeToken = this.TryEatToken(SyntaxKind.UnsafeKeyword);
// if the user wrote `using unsafe static` skip the `static` and tell them it needs to be `using static unsafe`.
if (staticToken is null && unsafeToken != null && this.CurrentToken.Kind == SyntaxKind.StaticKeyword)
{
// create a missing 'static' token so that later binding does recognize what the user wanted.
staticToken = SyntaxFactory.MissingToken(SyntaxKind.StaticKeyword);
unsafeToken = AddTrailingSkippedSyntax(unsafeToken, AddError(this.EatToken(), ErrorCode.ERR_BadStaticAfterUnsafe));
}
var alias = this.IsNamedAssignment() ? ParseNameEquals() : null;
TypeSyntax type;
SyntaxToken semicolon;
var isAliasToFunctionPointer = alias != null && this.CurrentToken.Kind == SyntaxKind.DelegateKeyword;
if (!isAliasToFunctionPointer && IsPossibleNamespaceMemberDeclaration())
{
//We're worried about the case where someone already has a correct program
//and they've gone back to add a using directive, but have not finished the
//new directive. e.g.
//
// using
// namespace Goo {
// //...
// }
//
//If the token we see after "using" could be its own top-level construct, then
//we just want to insert a missing identifier and semicolon and then return to
//parsing at the top-level.
//
//NB: there's no way this could be true for a set of tokens that form a valid
//using directive, so there's no danger in checking the error case first.
type = WithAdditionalDiagnostics(CreateMissingIdentifierName(), GetExpectedTokenError(SyntaxKind.IdentifierToken, this.CurrentToken.Kind));
semicolon = SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken);
}
else
{
// In the case where we don't have an alias, only parse out a name for this using-directive. This is
// worse for error recovery, but it means all code that consumes a using-directive can keep on assuming
// it has a name when there is no alias. Only code that specifically has to process aliases then has to
// deal with getting arbitrary types back.
type = alias == null ? this.ParseQualifiedName() : this.ParseType();
// If we can see a semicolon ahead, then the current token was probably supposed to be an identifier
if (type.IsMissing && this.PeekToken(1).Kind == SyntaxKind.SemicolonToken)
type = AddTrailingSkippedSyntax(type, this.EatToken());
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
return _syntaxFactory.UsingDirective(globalToken, usingToken, staticToken, unsafeToken, alias, type, semicolon);
}
private bool IsPossibleGlobalAttributeDeclaration()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBracketToken
&& IsGlobalAttributeTarget(this.PeekToken(1))
&& this.PeekToken(2).Kind == SyntaxKind.ColonToken;
}
private static bool IsGlobalAttributeTarget(SyntaxToken token)
{
switch (token.ToAttributeLocation())
{
case AttributeLocation.Assembly:
case AttributeLocation.Module:
return true;
default:
return false;
}
}
private bool IsPossibleAttributeDeclaration()
{
// Have to at least start with `[` to be an attribute
if (this.CurrentToken.Kind != SyntaxKind.OpenBracketToken)
return false;
using (this.GetDisposableResetPoint(resetOnDispose: true))
{
// Eat the `[`
EatToken();
// `[ id` could definitely begin an attribute.
if (this.IsTrueIdentifier())
return true;
// `[ word: ...` could definitely start an attribute.
if (IsAttributeTarget())
return true;
// If we see `[lit` (like `[0`) then this is def not an attribute, and should be parsed as a collection
// expr. Note: this heuristic can be added to in the future.
if (SyntaxFacts.IsLiteralExpression(this.CurrentToken.Kind))
return false;
return true;
}
}
private SyntaxList<AttributeListSyntax> ParseAttributeDeclarations(bool inExpressionContext)
{
var saveTerm = _termState;
_termState |= TerminatorState.IsAttributeDeclarationTerminator;
// An attribute can never appear *inside* an attribute argument (since a lambda expression cannot be used as
// a constant argument value). However, during parsing we can end up in a state where we're trying to
// exactly that, through a path of Attribute->Argument->Expression->Attribute (since attributes can not be
// on lambda expressions).
//
// Worse, when we are in a deeply ambiguous (or gibberish) scenario, where we see lots of code with `... [
// ... [ ... ] ... ] ...`, we can get into exponential speculative parsing where we try `[ ... ]` both as an
// attribute *and* a collection expression.
//
// Since we cannot ever legally have an attribute within an attribute, we bail out here immediately
// syntactically. This does mean we won't parse something like: `[X([Y]() => {})]` without errors, but that
// is not semantically legal anyway.
if (saveTerm == _termState)
return default;
var attributes = _pool.Allocate<AttributeListSyntax>();
while (this.IsPossibleAttributeDeclaration())
{
var attributeDeclaration = this.TryParseAttributeDeclaration(inExpressionContext);
if (attributeDeclaration is null)
break;
attributes.Add(attributeDeclaration);
}
_termState = saveTerm;
return _pool.ToListAndFree(attributes);
}
private bool IsAttributeDeclarationTerminator()
{
return this.CurrentToken.Kind == SyntaxKind.CloseBracketToken
|| this.IsPossibleAttributeDeclaration(); // start of a new one...
}
private bool IsAttributeTarget()
=> IsSomeWord(this.CurrentToken.Kind) && this.PeekToken(1).Kind == SyntaxKind.ColonToken;
private AttributeListSyntax? TryParseAttributeDeclaration(bool inExpressionContext)
{
if (this.IsIncrementalAndFactoryContextMatches &&
this.CurrentNodeKind == SyntaxKind.AttributeList &&
!inExpressionContext)
{
return (AttributeListSyntax)this.EatNode();
}
// May have to reset if we discover this is not an attribute but is instead a collection expression.
using var resetPoint = GetDisposableResetPoint(resetOnDispose: false);
var openBracket = this.EatToken(SyntaxKind.OpenBracketToken);
// Check for optional location :
var location = IsAttributeTarget()
? _syntaxFactory.AttributeTargetSpecifier(ConvertToKeyword(this.EatToken()), this.EatToken(SyntaxKind.ColonToken))
: null;
var attributes = this.ParseCommaSeparatedSyntaxList(
ref openBracket,
SyntaxKind.CloseBracketToken,
static @this => @this.IsPossibleAttribute(),
static @this => @this.ParseAttribute(),
skipBadAttributeListTokens,
allowTrailingSeparator: true,
requireOneElement: true,
allowSemicolonAsSeparator: false);
var closeBracket = this.EatToken(SyntaxKind.CloseBracketToken);
if (inExpressionContext && shouldParseAsCollectionExpression())
{
// we're in an expression and we've seen `[A, B].` This is actually the start of a collection expression
// that someone is explicitly accessing a member off of.
resetPoint.Reset();
return null;
}
return _syntaxFactory.AttributeList(openBracket, location, attributes, closeBracket);
bool shouldParseAsCollectionExpression()
{
// `[A, B].` is a member access off of a collection expression.
if (this.CurrentToken.Kind == SyntaxKind.DotToken)
return true;
// `[A, B]->` is a member access off of a collection expression. Note: this will always be illegal
// semantically (as a collection expression has the natural type List<> which is not a pointer type). But
// we leave that check to binding.
if (this.CurrentToken.Kind == SyntaxKind.MinusGreaterThanToken)
return true;
// `[A, B]?.` The `?` is unnecessary (as a collection expression is always non-null), but is still
// syntactically legal.
if (this.CurrentToken.Kind == SyntaxKind.QuestionToken &&
this.PeekToken(1).Kind == SyntaxKind.DotToken)
{
return true;
}
return false;
}
static PostSkipAction skipBadAttributeListTokens(
LanguageParser @this, ref SyntaxToken openBracket, SeparatedSyntaxListBuilder<AttributeSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openBracket, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleAttribute(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private bool IsPossibleAttribute()
{
return this.IsTrueIdentifier();
}
private AttributeSyntax ParseAttribute()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.Attribute)
{
return (AttributeSyntax)this.EatNode();
}
return _syntaxFactory.Attribute(
this.ParseQualifiedName(),
this.ParseAttributeArgumentList());
}
internal AttributeArgumentListSyntax? ParseAttributeArgumentList()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.AttributeArgumentList)
{
return (AttributeArgumentListSyntax)this.EatNode();
}
if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
return null;
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var argNodes = this.ParseCommaSeparatedSyntaxList(
ref openParen,
SyntaxKind.CloseParenToken,
static @this => @this.IsPossibleAttributeArgument(),
static @this => @this.ParseAttributeArgument(),
immediatelyAbort,
skipBadAttributeArgumentTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.AttributeArgumentList(
openParen,
argNodes,
this.EatToken(SyntaxKind.CloseParenToken));
static PostSkipAction skipBadAttributeArgumentTokens(
LanguageParser @this, ref SyntaxToken openParen, SeparatedSyntaxListBuilder<AttributeArgumentSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openParen, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleAttributeArgument(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
static bool immediatelyAbort(AttributeArgumentSyntax argument)
{
// We can be very thrown off by incomplete strings in an attribute argument (especially as the lexer
// will restart on the next line with the contents of the string then being interpreted as more
// arguments). Bail out in this case to prevent going off the rails.
if (argument.expression is LiteralExpressionSyntax { Kind: SyntaxKind.StringLiteralExpression, Token: var literalToken } &&
literalToken.GetDiagnostics().Contains(d => d.Code == (int)ErrorCode.ERR_NewlineInConst))
{
return true;
}
if (argument.expression is InterpolatedStringExpressionSyntax { StringStartToken.Kind: SyntaxKind.InterpolatedStringStartToken, StringEndToken.IsMissing: true })
return true;
return false;
}
}
private bool IsPossibleAttributeArgument()
{
return this.IsPossibleExpression();
}
private AttributeArgumentSyntax ParseAttributeArgument()
{
// Need to parse both "real" named arguments and attribute-style named arguments.
// We track attribute-style named arguments only with fShouldHaveName.
NameEqualsSyntax? nameEquals = null;
NameColonSyntax? nameColon = null;
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
switch (this.PeekToken(1).Kind)
{
case SyntaxKind.EqualsToken:
nameEquals = _syntaxFactory.NameEquals(
_syntaxFactory.IdentifierName(this.ParseIdentifierToken()),
this.EatToken(SyntaxKind.EqualsToken));
break;
case SyntaxKind.ColonToken:
nameColon = _syntaxFactory.NameColon(
this.ParseIdentifierName(),
this.EatToken(SyntaxKind.ColonToken));
break;
}
}
return _syntaxFactory.AttributeArgument(
nameEquals, nameColon, this.ParseExpressionCore());
}
private static DeclarationModifiers GetModifierExcludingScoped(SyntaxToken token)
=> GetModifierExcludingScoped(token.Kind, token.ContextualKind);
internal static DeclarationModifiers GetModifierExcludingScoped(SyntaxKind kind, SyntaxKind contextualKind)
{
switch (kind)
{
case SyntaxKind.PublicKeyword:
return DeclarationModifiers.Public;
case SyntaxKind.InternalKeyword:
return DeclarationModifiers.Internal;
case SyntaxKind.ProtectedKeyword:
return DeclarationModifiers.Protected;
case SyntaxKind.PrivateKeyword:
return DeclarationModifiers.Private;
case SyntaxKind.SealedKeyword:
return DeclarationModifiers.Sealed;
case SyntaxKind.AbstractKeyword:
return DeclarationModifiers.Abstract;
case SyntaxKind.StaticKeyword:
return DeclarationModifiers.Static;
case SyntaxKind.VirtualKeyword:
return DeclarationModifiers.Virtual;
case SyntaxKind.ExternKeyword:
return DeclarationModifiers.Extern;
case SyntaxKind.NewKeyword:
return DeclarationModifiers.New;
case SyntaxKind.OverrideKeyword:
return DeclarationModifiers.Override;
case SyntaxKind.ReadOnlyKeyword:
return DeclarationModifiers.ReadOnly;
case SyntaxKind.VolatileKeyword:
return DeclarationModifiers.Volatile;
case SyntaxKind.UnsafeKeyword:
return DeclarationModifiers.Unsafe;
case SyntaxKind.PartialKeyword:
return DeclarationModifiers.Partial;
case SyntaxKind.AsyncKeyword:
return DeclarationModifiers.Async;
case SyntaxKind.RefKeyword:
return DeclarationModifiers.Ref;
case SyntaxKind.IdentifierToken:
switch (contextualKind)
{
case SyntaxKind.PartialKeyword:
return DeclarationModifiers.Partial;
case SyntaxKind.AsyncKeyword:
return DeclarationModifiers.Async;
case SyntaxKind.RequiredKeyword:
return DeclarationModifiers.Required;
case SyntaxKind.FileKeyword:
return DeclarationModifiers.File;
}
goto default;
default:
return DeclarationModifiers.None;
}
}
private void ParseModifiers(SyntaxListBuilder tokens, bool forAccessors, bool forTopLevelStatements, out bool isPossibleTypeDeclaration)
{
Debug.Assert(!(forAccessors && forTopLevelStatements));
isPossibleTypeDeclaration = true;
while (true)
{
var newMod = GetModifierExcludingScoped(this.CurrentToken);
Debug.Assert(newMod != DeclarationModifiers.Scoped);
if (newMod == DeclarationModifiers.None)
{
if (!forAccessors)
{
SyntaxToken scopedKeyword = ParsePossibleScopedKeyword(isFunctionPointerParameter: false);
if (scopedKeyword != null)
{
isPossibleTypeDeclaration = false;
tokens.Add(scopedKeyword);
}
}
break;
}
SyntaxToken modTok;
switch (newMod)
{
case DeclarationModifiers.Partial:
var nextToken = PeekToken(1);
if (this.IsPartialType() || this.IsPartialMember())
{
// Standard legal cases.
modTok = ConvertToKeyword(this.EatToken());
}
else if (nextToken.Kind == SyntaxKind.NamespaceKeyword)
{
// Error reported in binding
modTok = ConvertToKeyword(this.EatToken());
}
else if (
nextToken.Kind is SyntaxKind.EnumKeyword or SyntaxKind.DelegateKeyword ||
(IsPossibleStartOfTypeDeclaration(nextToken.Kind) && GetModifierExcludingScoped(nextToken) != DeclarationModifiers.None))
{
// Error reported in ModifierUtils.
modTok = ConvertToKeyword(this.EatToken());
}
else
{
return;
}
break;
case DeclarationModifiers.Ref:
// 'ref' is only a modifier if used on a ref struct
// it must be either immediately before the 'struct'
// keyword, or immediately before 'partial struct' if
// this is a partial ref struct declaration
{
var next = PeekToken(1);
if (isStructOrRecordKeyword(next) ||
(next.ContextualKind == SyntaxKind.PartialKeyword &&
isStructOrRecordKeyword(PeekToken(2))))
{
modTok = this.EatToken();
}
else if (forAccessors && this.IsPossibleAccessorModifier())
{
// Accept ref as a modifier for properties and event accessors, to produce an error later during binding.
modTok = this.EatToken();
}
else
{
return;
}
break;
}
case DeclarationModifiers.File:
if ((!IsFeatureEnabled(MessageID.IDS_FeatureFileTypes) || forTopLevelStatements) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false))
{
return;
}
// LangVersion errors for 'file' modifier are given during binding.
modTok = ConvertToKeyword(EatToken());
break;
case DeclarationModifiers.Async:
if (!ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false))
{
return;
}
modTok = ConvertToKeyword(this.EatToken());
break;
case DeclarationModifiers.Required:
// In C# 11, required in a modifier position is always a keyword if not escaped. Otherwise, we reuse the async detection
// machinery to make a conservative guess as to whether the user meant required to be a keyword, so that they get a good langver
// diagnostic and all the machinery to upgrade their project kicks in. The only exception to this rule is top level statements,
// where the user could conceivably have a local named required. For these locations, we need to disambiguate as well.
if ((!IsFeatureEnabled(MessageID.IDS_FeatureRequiredMembers) || forTopLevelStatements) && !ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: false))
{
return;
}
modTok = ConvertToKeyword(this.EatToken());
break;
default:
modTok = this.EatToken();
break;
}
Debug.Assert(modTok.Kind is not (SyntaxKind.OutKeyword or SyntaxKind.InKeyword));
tokens.Add(modTok);
}
bool isStructOrRecordKeyword(SyntaxToken token)
{
if (token.Kind == SyntaxKind.StructKeyword)
{
return true;
}
if (token.ContextualKind == SyntaxKind.RecordKeyword)
{
// This is an unusual use of LangVersion. Normally we only produce errors when the langversion
// does not support a feature, but in this case we are effectively making a language breaking
// change to consider "record" a type declaration in all ambiguous cases. To avoid breaking
// older code that is not using C# 9 we conditionally parse based on langversion
return IsFeatureEnabled(MessageID.IDS_FeatureRecords);
}
return false;
}
}
private bool ShouldContextualKeywordBeTreatedAsModifier(bool parsingStatementNotDeclaration)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.IdentifierToken && GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None);
// Adapted from CParser::IsAsyncMethod.
if (IsNonContextualModifier(PeekToken(1)))
{
// If the next token is a (non-contextual) modifier keyword, then this token is
// definitely a modifier
return true;
}
// Some of our helpers start at the current token, so we'll have to advance for their
// sake and then backtrack when we're done. Don't leave this block without releasing
// the reset point.
using var _ = GetDisposableResetPoint(resetOnDispose: true);
this.EatToken(); //move past contextual token
if (!parsingStatementNotDeclaration &&
(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword))
{
this.EatToken(); // "partial" doesn't affect our decision, so look past it.
}
// ... 'TOKEN' [partial] <typedecl> ...
// ... 'TOKEN' [partial] <event> ...
// ... 'TOKEN' [partial] <implicit> <operator> ...
// ... 'TOKEN' [partial] <explicit> <operator> ...
// ... 'TOKEN' [partial] <typename> <operator> ...
// ... 'TOKEN' [partial] <typename> <membername> ...
// DEVNOTE: Although we parse async user defined conversions, operators, etc. here,
// anything other than async methods are detected as erroneous later, during the define phase
// Generally wherever we refer to 'async' here, it can also be 'required' or 'file'.
if (!parsingStatementNotDeclaration)
{
var currentTokenKind = this.CurrentToken.Kind;
if (IsTypeModifierOrTypeKeyword(currentTokenKind) ||
currentTokenKind == SyntaxKind.EventKeyword ||
(currentTokenKind is SyntaxKind.ExplicitKeyword or SyntaxKind.ImplicitKeyword && PeekToken(1).Kind == SyntaxKind.OperatorKeyword))
{
return true;
}
}
if (ScanType() != ScanTypeFlags.NotType)
{
// We've seen "TOKEN TypeName". Now we have to determine if we should we treat
// 'TOKEN' as a modifier. Or is the user actually writing something like
// "public TOKEN Goo" where 'TOKEN' is actually the return type.
if (IsPossibleMemberName())
{
// we have: "TOKEN Type X" or "TOKEN Type this", 'TOKEN' is definitely a
// modifier here.
return true;
}
var currentTokenKind = this.CurrentToken.Kind;
// The file ends with "TOKEN TypeName", it's not legal code, and it's much
// more likely that this is meant to be a modifier.
if (currentTokenKind == SyntaxKind.EndOfFileToken)
{
return true;
}
// "TOKEN TypeName }". In this case, we just have an incomplete member, and
// we should definitely default to 'TOKEN' being considered a return type here.
if (currentTokenKind == SyntaxKind.CloseBraceToken)
{
return true;
}
// "TOKEN TypeName void". In this case, we just have an incomplete member before
// an existing member. Treat this 'TOKEN' as a keyword.
if (SyntaxFacts.IsPredefinedType(this.CurrentToken.Kind))
{
return true;
}
// "TOKEN TypeName public". In this case, we just have an incomplete member before
// an existing member. Treat this 'TOKEN' as a keyword.
if (IsNonContextualModifier(this.CurrentToken))
{
return true;
}
// "TOKEN TypeName class". In this case, we just have an incomplete member before
// an existing type declaration. Treat this 'TOKEN' as a keyword.
if (IsTypeDeclarationStart())
{
return true;
}
// "TOKEN TypeName namespace". In this case, we just have an incomplete member before
// an existing namespace declaration. Treat this 'TOKEN' as a keyword.
if (currentTokenKind == SyntaxKind.NamespaceKeyword)
{
return true;
}
if (!parsingStatementNotDeclaration && currentTokenKind == SyntaxKind.OperatorKeyword)
{
return true;
}
}
return false;
}
private static bool IsNonContextualModifier(SyntaxToken nextToken)
{
return !SyntaxFacts.IsContextualKeyword(nextToken.ContextualKind) && GetModifierExcludingScoped(nextToken) != DeclarationModifiers.None;
}
private bool IsPartialType()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword);
var nextToken = this.PeekToken(1);
switch (nextToken.Kind)
{
case SyntaxKind.StructKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.InterfaceKeyword:
return true;
}
if (nextToken.ContextualKind == SyntaxKind.RecordKeyword)
{
// This is an unusual use of LangVersion. Normally we only produce errors when the langversion
// does not support a feature, but in this case we are effectively making a language breaking
// change to consider "record" a type declaration in all ambiguous cases. To avoid breaking
// older code that is not using C# 9 we conditionally parse based on langversion
return IsFeatureEnabled(MessageID.IDS_FeatureRecords);
}
return false;
}
private bool IsPartialMember()
{
// note(cyrusn): this could have been written like so:
//
// return
// this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword &&
// this.PeekToken(1).Kind == SyntaxKind.VoidKeyword;
//
// However, we want to be lenient and allow the user to write
// 'partial' in most modifier lists. We will then provide them with
// a more specific message later in binding that they are doing
// something wrong.
//
// Some might argue that the simple check would suffice.
// However, we'd like to maintain behavior with
// previously shipped versions, and so we're keeping this code.
// Here we check for:
// partial ReturnType MemberName
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword);
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
this.EatToken(); // partial
if (this.ScanType() == ScanTypeFlags.NotType)
{
return false;
}
return IsPossibleMemberName();
}
private bool IsPossibleMemberName()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.IdentifierToken:
if (this.CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword && this.PeekToken(1).Kind == SyntaxKind.UsingKeyword)
{
return false;
}
return true;
case SyntaxKind.ThisKeyword:
return true;
default:
return false;
}
}
private MemberDeclarationSyntax ParseTypeDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);
cancellationToken.ThrowIfCancellationRequested();
switch (this.CurrentToken.Kind)
{
case SyntaxKind.ClassKeyword:
return this.ParseClassOrStructOrInterfaceDeclaration(attributes, modifiers);
case SyntaxKind.StructKeyword:
return this.ParseClassOrStructOrInterfaceDeclaration(attributes, modifiers);
case SyntaxKind.InterfaceKeyword:
return this.ParseClassOrStructOrInterfaceDeclaration(attributes, modifiers);
case SyntaxKind.DelegateKeyword:
return this.ParseDelegateDeclaration(attributes, modifiers);
case SyntaxKind.EnumKeyword:
return this.ParseEnumDeclaration(attributes, modifiers);
case SyntaxKind.IdentifierToken:
Debug.Assert(CurrentToken.ContextualKind == SyntaxKind.RecordKeyword);
return ParseClassOrStructOrInterfaceDeclaration(attributes, modifiers);
default:
throw ExceptionUtilities.UnexpectedValue(this.CurrentToken.Kind);
}
}
private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword or SyntaxKind.InterfaceKeyword ||
this.CurrentToken.ContextualKind == SyntaxKind.RecordKeyword);
// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);
if (!tryScanRecordStart(out var keyword, out var recordModifier))
{
keyword = ConvertToKeyword(this.EatToken());
}
var outerSaveTerm = _termState;
_termState |= TerminatorState.IsEndOfRecordOrClassOrStructOrInterfaceSignature;
var saveTerm = _termState;
_termState |= TerminatorState.IsPossibleAggregateClauseStartOrStop;
var name = this.ParseIdentifierToken();
var typeParameters = this.ParseTypeParameterList();
var paramList = CurrentToken.Kind == SyntaxKind.OpenParenToken
? ParseParenthesizedParameterList() : null;
var baseList = this.ParseBaseList();
_termState = saveTerm;
// Parse class body
bool parseMembers = true;
SyntaxListBuilder<MemberDeclarationSyntax> members = default;
SyntaxListBuilder<TypeParameterConstraintClauseSyntax> constraints = default;
try
{
if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
constraints = _pool.Allocate<TypeParameterConstraintClauseSyntax>();
this.ParseTypeParameterConstraintClauses(constraints);
}
_termState = outerSaveTerm;
SyntaxToken semicolon;
SyntaxToken? openBrace;
SyntaxToken? closeBrace;
if (CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = EatToken(SyntaxKind.SemicolonToken);
openBrace = null;
closeBrace = null;
}
else
{
openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
// ignore members if missing open curly
if (openBrace.IsMissing)
{
parseMembers = false;
}
// even if we saw a { or think we should parse members bail out early since
// we know namespaces can't be nested inside types
if (parseMembers)
{
members = _pool.Allocate<MemberDeclarationSyntax>();
while (true)
{
SyntaxKind kind = this.CurrentToken.Kind;
if (CanStartMember(kind))
{
// This token can start a member -- go parse it
var saveTerm2 = _termState;
_termState |= TerminatorState.IsPossibleMemberStartOrStop;
var member = this.ParseMemberDeclaration(keyword.Kind);
if (member != null)
{
// statements are accepted here, a semantic error will be reported later
members.Add(member);
}
else
{
// we get here if we couldn't parse the lookahead as a statement or a declaration (we haven't consumed any tokens):
this.SkipBadMemberListTokens(ref openBrace, members);
}
_termState = saveTerm2;
}
else if (kind is SyntaxKind.CloseBraceToken or SyntaxKind.EndOfFileToken || this.IsTerminator())
{
// This marks the end of members of this class
break;
}
else
{
// Error -- try to sync up with intended reality
this.SkipBadMemberListTokens(ref openBrace, members);
}
}
}
if (openBrace.IsMissing)
{
closeBrace = SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken);
closeBrace = WithAdditionalDiagnostics(closeBrace, this.GetExpectedTokenError(SyntaxKind.CloseBraceToken, this.CurrentToken.Kind));
}
else
{
closeBrace = this.EatToken(SyntaxKind.CloseBraceToken);
}
semicolon = TryEatToken(SyntaxKind.SemicolonToken);
}
return constructTypeDeclaration(_syntaxFactory, attributes, modifiers, keyword, recordModifier, name, typeParameters, paramList, baseList, constraints, openBrace, members, closeBrace, semicolon);
}
finally
{
if (!members.IsNull)
{
_pool.Free(members);
}
if (!constraints.IsNull)
{
_pool.Free(constraints);
}
}
bool tryScanRecordStart([NotNullWhen(true)] out SyntaxToken? keyword, out SyntaxToken? recordModifier)
{
if (this.CurrentToken.ContextualKind == SyntaxKind.RecordKeyword)
{
keyword = ConvertToKeyword(this.EatToken());
recordModifier = this.CurrentToken.Kind is SyntaxKind.ClassKeyword or SyntaxKind.StructKeyword
? EatToken()
: null;
return true;
}
if (this.CurrentToken.Kind is SyntaxKind.StructKeyword or SyntaxKind.ClassKeyword &&
this.PeekToken(1).ContextualKind == SyntaxKind.RecordKeyword &&
this.PeekToken(2).Kind is SyntaxKind.IdentifierToken)
{
// Provide a specific diagnostic on `struct record S` or `class record C`
var misplacedToken = this.EatToken();
// Parse out 'record' but place 'struct/class' as leading skipped trivia on it.
keyword = AddLeadingSkippedSyntax(
this.AddError(ConvertToKeyword(this.EatToken()), ErrorCode.ERR_MisplacedRecord),
misplacedToken);
// Treat `struct record` as a RecordStructDeclaration, and `class record` as a RecordDeclaration.
recordModifier = SyntaxFactory.MissingToken(misplacedToken.Kind);
return true;
}
keyword = null;
recordModifier = null;
return false;
}
static TypeDeclarationSyntax constructTypeDeclaration(ContextAwareSyntax syntaxFactory, SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers, SyntaxToken keyword, SyntaxToken? recordModifier,
SyntaxToken name, TypeParameterListSyntax typeParameters, ParameterListSyntax? paramList, BaseListSyntax baseList, SyntaxListBuilder<TypeParameterConstraintClauseSyntax> constraints,
SyntaxToken? openBrace, SyntaxListBuilder<MemberDeclarationSyntax> members, SyntaxToken? closeBrace, SyntaxToken semicolon)
{
var modifiersList = (SyntaxList<SyntaxToken>)modifiers.ToList();
var membersList = (SyntaxList<MemberDeclarationSyntax>)members;
var constraintsList = (SyntaxList<TypeParameterConstraintClauseSyntax>)constraints;
switch (keyword.Kind)
{
case SyntaxKind.ClassKeyword:
return syntaxFactory.ClassDeclaration(
attributes,
modifiersList,
keyword,
name,
typeParameters,
paramList,
baseList,
constraintsList,
openBrace,
membersList,
closeBrace,
semicolon);
case SyntaxKind.StructKeyword:
return syntaxFactory.StructDeclaration(
attributes,
modifiersList,
keyword,
name,
typeParameters,
paramList,
baseList,
constraintsList,
openBrace,
membersList,
closeBrace,
semicolon);
case SyntaxKind.InterfaceKeyword:
return syntaxFactory.InterfaceDeclaration(
attributes,
modifiersList,
keyword,
name,
typeParameters,
paramList,
baseList,
constraintsList,
openBrace,
membersList,
closeBrace,
semicolon);
case SyntaxKind.RecordKeyword:
// record struct ...
// record ...
// record class ...
SyntaxKind declarationKind = recordModifier?.Kind == SyntaxKind.StructKeyword ? SyntaxKind.RecordStructDeclaration : SyntaxKind.RecordDeclaration;
return syntaxFactory.RecordDeclaration(
declarationKind,
attributes,
modifiers.ToList(),
keyword,
classOrStructKeyword: recordModifier,
name,
typeParameters,
paramList,
baseList,
constraints,
openBrace,
members,
closeBrace,
semicolon);
default:
throw ExceptionUtilities.UnexpectedValue(keyword.Kind);
}
}
}
#nullable disable
private void SkipBadMemberListTokens(ref SyntaxToken openBrace, SyntaxListBuilder members)
{
if (members.Count > 0)
{
var tmp = members[^1];
this.SkipBadMemberListTokens(ref tmp);
members[^1] = tmp;
}
else
{
GreenNode tmp = openBrace;
this.SkipBadMemberListTokens(ref tmp);
openBrace = (SyntaxToken)tmp;
}
}
private void SkipBadMemberListTokens(ref GreenNode previousNode)
{
int curlyCount = 0;
var tokens = _pool.Allocate();
bool done = false;
// always consume at least one token.
var token = this.EatToken();
token = this.AddError(token, ErrorCode.ERR_InvalidMemberDecl, token.Text);
tokens.Add(token);
while (!done)
{
SyntaxKind kind = this.CurrentToken.Kind;
// If this token can start a member, we're done
if (CanStartMember(kind) &&
!(kind == SyntaxKind.DelegateKeyword && this.PeekToken(1).Kind is SyntaxKind.OpenBraceToken or SyntaxKind.OpenParenToken))
{
done = true;
continue;
}
// <UNDONE> UNDONE: Seems like this makes sense,
// but if this token can start a namespace element, but not a member, then
// perhaps we should bail back up to parsing a namespace body somehow...</UNDONE>
// Watch curlies and look for end of file/close curly
switch (kind)
{
case SyntaxKind.OpenBraceToken:
curlyCount++;
break;
case SyntaxKind.CloseBraceToken:
if (curlyCount-- == 0)
{
done = true;
continue;
}
break;
case SyntaxKind.EndOfFileToken:
done = true;
continue;
default:
break;
}
tokens.Add(this.EatToken());
}
previousNode = AddTrailingSkippedSyntax(
(CSharpSyntaxNode)previousNode,
_pool.ToTokenListAndFree(tokens).Node);
}
private bool IsPossibleMemberStartOrStop()
{
return this.IsPossibleMemberStart() || this.CurrentToken.Kind == SyntaxKind.CloseBraceToken;
}
private bool IsPossibleAggregateClauseStartOrStop()
{
return this.CurrentToken.Kind is SyntaxKind.ColonToken or SyntaxKind.OpenBraceToken
|| this.IsCurrentTokenWhereOfConstraintClause();
}
private BaseListSyntax ParseBaseList()
{
// We are only called from ParseClassOrStructOrInterfaceDeclaration which unilaterally sets this.
Debug.Assert((_termState & TerminatorState.IsEndOfRecordOrClassOrStructOrInterfaceSignature) != 0);
var colon = this.TryEatToken(SyntaxKind.ColonToken);
if (colon == null)
return null;
var list = _pool.AllocateSeparated<BaseTypeSyntax>();
// Grammar requires at least one base type follow the colon.
var firstType = this.ParseType();
list.Add(this.CurrentToken.Kind == SyntaxKind.OpenParenToken
? _syntaxFactory.PrimaryConstructorBaseType(firstType, this.ParseParenthesizedArgumentList())
: _syntaxFactory.SimpleBaseType(firstType));
// Parse any optional base types that follow.
while (true)
{
if (this.CurrentToken.Kind is SyntaxKind.OpenBraceToken or SyntaxKind.SemicolonToken ||
this.IsCurrentTokenWhereOfConstraintClause())
{
break;
}
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
list.Add(_syntaxFactory.SimpleBaseType(this.ParseType()));
continue;
}
// Error recovery. Code had an element in the base list, but wasn't followed by a comma or the start of
// any production that normally follows in a type declaration. See if this is just a case of a missing
// comma between types in the base list.
//
// Note: if we see something that looks more like a modifier than a type (like 'file') do not try to
// consume it as a type here, as we want to use that to better determine what member is actually following
// this incomplete type declaration.
if (GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None)
{
break;
}
if (this.IsPossibleType())
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
list.Add(_syntaxFactory.SimpleBaseType(this.ParseType()));
continue;
}
if (skipBadBaseListTokens(ref colon, list, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
return _syntaxFactory.BaseList(colon, _pool.ToListAndFree(list));
PostSkipAction skipBadBaseListTokens(ref SyntaxToken colon, SeparatedSyntaxListBuilder<BaseTypeSyntax> list, SyntaxKind expected)
{
return this.SkipBadSeparatedListTokensWithExpectedKind(ref colon, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleAttribute(),
static (p, _) => p.CurrentToken.Kind == SyntaxKind.OpenBraceToken || p.IsCurrentTokenWhereOfConstraintClause(),
expected);
}
}
private bool IsCurrentTokenWhereOfConstraintClause()
{
return
this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword &&
this.PeekToken(1).Kind == SyntaxKind.IdentifierToken &&
this.PeekToken(2).Kind == SyntaxKind.ColonToken;
}
private void ParseTypeParameterConstraintClauses(SyntaxListBuilder list)
{
while (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
list.Add(this.ParseTypeParameterConstraintClause());
}
}
private TypeParameterConstraintClauseSyntax ParseTypeParameterConstraintClause()
{
var where = this.EatContextualToken(SyntaxKind.WhereKeyword);
var name = !IsTrueIdentifier()
? this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_IdentifierExpected)
: this.ParseIdentifierName();
var colon = this.EatToken(SyntaxKind.ColonToken);
var bounds = _pool.AllocateSeparated<TypeParameterConstraintSyntax>();
// first bound
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken || this.IsCurrentTokenWhereOfConstraintClause())
{
bounds.Add(_syntaxFactory.TypeConstraint(this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_TypeExpected)));
}
else
{
TypeParameterConstraintSyntax constraint = this.ParseTypeParameterConstraint();
bounds.Add(constraint);
// remaining bounds
while (true)
{
bool haveComma;
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
|| ((_termState & TerminatorState.IsEndOfRecordOrClassOrStructOrInterfaceSignature) != 0 && this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
|| this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken
|| this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
break;
}
else if (haveComma = (this.CurrentToken.Kind == SyntaxKind.CommaToken) || this.IsPossibleTypeParameterConstraint())
{
SyntaxToken separatorToken = this.EatToken(SyntaxKind.CommaToken);
if (constraint.Kind == SyntaxKind.AllowsConstraintClause && haveComma && !this.IsPossibleTypeParameterConstraint())
{
AddTrailingSkippedSyntax(bounds, this.AddError(separatorToken, ErrorCode.ERR_UnexpectedToken, SyntaxFacts.GetText(SyntaxKind.CommaToken)));
break;
}
bounds.AddSeparator(separatorToken);
if (this.IsCurrentTokenWhereOfConstraintClause())
{
bounds.Add(_syntaxFactory.TypeConstraint(this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_TypeExpected)));
break;
}
else
{
constraint = this.ParseTypeParameterConstraint();
bounds.Add(constraint);
}
}
else if (skipBadTypeParameterConstraintTokens(bounds, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
}
return _syntaxFactory.TypeParameterConstraintClause(
where,
name,
colon,
_pool.ToListAndFree(bounds));
PostSkipAction skipBadTypeParameterConstraintTokens(SeparatedSyntaxListBuilder<TypeParameterConstraintSyntax> list, SyntaxKind expected)
{
CSharpSyntaxNode tmp = null;
Debug.Assert(list.Count > 0);
return this.SkipBadSeparatedListTokensWithExpectedKind(ref tmp, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleTypeParameterConstraint(),
static (p, _) => p.CurrentToken.Kind == SyntaxKind.OpenBraceToken || p.IsCurrentTokenWhereOfConstraintClause(),
expected);
}
}
private bool IsPossibleTypeParameterConstraint()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.NewKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.StructKeyword:
case SyntaxKind.DefaultKeyword:
return true;
case SyntaxKind.IdentifierToken:
return (this.CurrentToken.ContextualKind == SyntaxKind.AllowsKeyword && PeekToken(1).Kind == SyntaxKind.RefKeyword) || this.IsTrueIdentifier();
default:
return IsPredefinedType(this.CurrentToken.Kind);
}
}
private TypeParameterConstraintSyntax ParseTypeParameterConstraint()
{
return this.CurrentToken.Kind switch
{
SyntaxKind.NewKeyword =>
_syntaxFactory.ConstructorConstraint(
newKeyword: this.EatToken(),
this.EatToken(SyntaxKind.OpenParenToken),
this.EatToken(SyntaxKind.CloseParenToken)),
SyntaxKind.StructKeyword =>
_syntaxFactory.ClassOrStructConstraint(
SyntaxKind.StructConstraint,
classOrStructKeyword: this.EatToken(),
this.CurrentToken.Kind == SyntaxKind.QuestionToken
? this.AddError(this.EatToken(), ErrorCode.ERR_UnexpectedToken, SyntaxFacts.GetText(SyntaxKind.QuestionToken))
: null),
SyntaxKind.ClassKeyword =>
_syntaxFactory.ClassOrStructConstraint(
SyntaxKind.ClassConstraint,
classOrStructKeyword: this.EatToken(),
this.TryEatToken(SyntaxKind.QuestionToken)),
SyntaxKind.DefaultKeyword =>
_syntaxFactory.DefaultConstraint(defaultKeyword: this.EatToken()),
SyntaxKind.EnumKeyword =>
_syntaxFactory.TypeConstraint(AddTrailingSkippedSyntax(
this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_NoEnumConstraint),
this.EatToken())),
// Produce a specific diagnostic for `where T : delegate`
// but not `where T : delegate*<...>
SyntaxKind.DelegateKeyword =>
PeekToken(1).Kind == SyntaxKind.AsteriskToken
? _syntaxFactory.TypeConstraint(this.ParseType())
: _syntaxFactory.TypeConstraint(AddTrailingSkippedSyntax(
this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_NoDelegateConstraint),
this.EatToken())),
_ => parseTypeOrAllowsConstraint(),
};
TypeParameterConstraintSyntax parseTypeOrAllowsConstraint()
{
if (this.CurrentToken.ContextualKind == SyntaxKind.AllowsKeyword &&
PeekToken(1).Kind == SyntaxKind.RefKeyword)
{
var allows = this.EatContextualToken(SyntaxKind.AllowsKeyword);
var bounds = _pool.AllocateSeparated<AllowsConstraintSyntax>();
while (true)
{
bounds.Add(
_syntaxFactory.RefStructConstraint(
this.EatToken(SyntaxKind.RefKeyword),
this.EatToken(SyntaxKind.StructKeyword)));
if (this.CurrentToken.Kind == SyntaxKind.CommaToken && PeekToken(1).Kind == SyntaxKind.RefKeyword)
{
bounds.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
continue;
}
break;
}
return _syntaxFactory.AllowsConstraintClause(allows, _pool.ToListAndFree(bounds));
}
return _syntaxFactory.TypeConstraint(this.ParseType());
}
}
private bool IsPossibleMemberStart()
{
return CanStartMember(this.CurrentToken.Kind);
}
private static bool CanStartMember(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.AbstractKeyword:
case SyntaxKind.BoolKeyword:
case SyntaxKind.ByteKeyword:
case SyntaxKind.CharKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DecimalKeyword:
case SyntaxKind.DelegateKeyword:
case SyntaxKind.DoubleKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.EventKeyword:
case SyntaxKind.ExternKeyword:
case SyntaxKind.FixedKeyword:
case SyntaxKind.FloatKeyword:
case SyntaxKind.IntKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.InternalKeyword:
case SyntaxKind.LongKeyword:
case SyntaxKind.NewKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.OverrideKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.ReadOnlyKeyword:
case SyntaxKind.SByteKeyword:
case SyntaxKind.SealedKeyword:
case SyntaxKind.ShortKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.StructKeyword:
case SyntaxKind.UIntKeyword:
case SyntaxKind.ULongKeyword:
case SyntaxKind.UnsafeKeyword:
case SyntaxKind.UShortKeyword:
case SyntaxKind.VirtualKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.VolatileKeyword:
case SyntaxKind.IdentifierToken:
case SyntaxKind.TildeToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.ImplicitKeyword:
case SyntaxKind.ExplicitKeyword:
case SyntaxKind.OpenParenToken: //tuple
case SyntaxKind.RefKeyword:
return true;
default:
return false;
}
}
private bool IsTypeDeclarationStart()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.ClassKeyword:
case SyntaxKind.DelegateKeyword when !IsFunctionPointerStart():
case SyntaxKind.EnumKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.StructKeyword:
return true;
case SyntaxKind.IdentifierToken:
if (CurrentToken.ContextualKind == SyntaxKind.RecordKeyword)
{
// This is an unusual use of LangVersion. Normally we only produce errors when the langversion
// does not support a feature, but in this case we are effectively making a language breaking
// change to consider "record" a type declaration in all ambiguous cases. To avoid breaking
// older code that is not using C# 9 we conditionally parse based on langversion
return IsFeatureEnabled(MessageID.IDS_FeatureRecords);
}
return false;
default:
return false;
}
}
private bool CanReuseMemberDeclaration(SyntaxKind kind, bool isGlobal)
{
switch (kind)
{
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.EventFieldDeclaration:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.EventDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.OperatorDeclaration:
case SyntaxKind.ConversionOperatorDeclaration:
case SyntaxKind.DestructorDeclaration:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.NamespaceDeclaration:
case SyntaxKind.FileScopedNamespaceDeclaration:
case SyntaxKind.RecordDeclaration:
case SyntaxKind.RecordStructDeclaration:
return true;
case SyntaxKind.FieldDeclaration:
case SyntaxKind.MethodDeclaration:
if (!isGlobal || IsScript)
{
return true;
}
// We can reuse original nodes if they came from the global context as well.
return (this.CurrentNode.Parent is Syntax.CompilationUnitSyntax);
case SyntaxKind.GlobalStatement:
return isGlobal;
default:
return false;
}
}
public MemberDeclarationSyntax ParseMemberDeclaration()
{
// Use a parent kind that causes inclusion of only member declarations that could appear in a struct
// e.g. including fixed member declarations, but not statements.
const SyntaxKind parentKind = SyntaxKind.StructDeclaration;
return ParseWithStackGuard(
static @this => @this.ParseMemberDeclaration(parentKind),
createEmptyNodeFunc);
// Creates a dummy declaration node to which we can attach a stack overflow message
static MemberDeclarationSyntax createEmptyNodeFunc(LanguageParser @this)
{
return @this._syntaxFactory.IncompleteMember(
new SyntaxList<AttributeListSyntax>(),
new SyntaxList<SyntaxToken>(),
@this.CreateMissingIdentifierName());
}
}
// Returns null if we can't parse anything (even partially).
internal MemberDeclarationSyntax ParseMemberDeclarationOrStatement(SyntaxKind parentKind)
{
_recursionDepth++;
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
var result = ParseMemberDeclarationOrStatementCore(parentKind);
_recursionDepth--;
return result;
}
/// <summary>
/// Changes in this function around member parsing should be mirrored in <see cref="ParseMemberDeclarationCore"/>.
/// Try keeping structure of both functions similar to simplify this task. The split was made to
/// reduce the stack usage during recursive parsing.
/// </summary>
/// <returns>Returns null if we can't parse anything (even partially).</returns>
private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind parentKind)
{
// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);
Debug.Assert(parentKind == SyntaxKind.CompilationUnit);
cancellationToken.ThrowIfCancellationRequested();
// don't reuse members if they were previously declared under a different type keyword kind
if (this.IsIncrementalAndFactoryContextMatches && CanReuseMemberDeclaration(CurrentNodeKind, isGlobal: true))
return (MemberDeclarationSyntax)this.EatNode();
var saveTermState = _termState;
var attributes = this.ParseStatementAttributeDeclarations();
bool haveAttributes = attributes.Count > 0;
var afterAttributesPoint = this.GetResetPoint();
var modifiers = _pool.Allocate();
try
{
//
// Check for the following cases to disambiguate between member declarations and expressions.
// Doing this before parsing modifiers simplifies further analysis since some of these keywords can act as modifiers as well.
//
// unsafe { ... }
// fixed (...) { ... }
// delegate (...) { ... }
// delegate { ... }
// new { ... }
// new[] { ... }
// new T (...)
// new T [...]
//
if (!haveAttributes || !IsScript)
{
bool wasInAsync = IsInAsync;
if (!IsScript)
{
IsInAsync = true; // We are implicitly in an async context
}
try
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.UnsafeKeyword:
if (this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken)
{
return _syntaxFactory.GlobalStatement(ParseUnsafeStatement(attributes));
}
break;
case SyntaxKind.FixedKeyword:
if (this.PeekToken(1).Kind == SyntaxKind.OpenParenToken)
{
return _syntaxFactory.GlobalStatement(ParseFixedStatement(attributes));
}
break;
case SyntaxKind.DelegateKeyword:
switch (this.PeekToken(1).Kind)
{
case SyntaxKind.OpenParenToken:
case SyntaxKind.OpenBraceToken:
return _syntaxFactory.GlobalStatement(ParseExpressionStatement(attributes));
}
break;
case SyntaxKind.NewKeyword:
if (IsPossibleNewExpression())
{
return _syntaxFactory.GlobalStatement(ParseExpressionStatement(attributes));
}
break;
}
}
finally
{
IsInAsync = wasInAsync;
}
}
// All modifiers that might start an expression are processed above.
bool isPossibleTypeDeclaration;
this.ParseModifiers(modifiers, forAccessors: false, forTopLevelStatements: true, out isPossibleTypeDeclaration);
bool haveModifiers = (modifiers.Count > 0);
MemberDeclarationSyntax result;
// Check for constructor form
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken && this.PeekToken(1).Kind == SyntaxKind.OpenParenToken)
{
// Script:
// Constructor definitions are not allowed. We parse them as method calls with semicolon missing error:
//
// Script(...) { ... }
// ^
// missing ';'
//
// Unless modifiers or attributes are present this is more likely to be a method call than a method definition.
if (haveAttributes || haveModifiers)
{
var voidType = _syntaxFactory.PredefinedType(
this.AddError(SyntaxFactory.MissingToken(SyntaxKind.VoidKeyword), ErrorCode.ERR_MemberNeedsType));
if (!IsScript)
{
if (tryParseLocalDeclarationStatementFromStartPoint<LocalFunctionStatementSyntax>(attributes, ref afterAttributesPoint, out result))
{
return result;
}
}
else
{
var identifier = this.EatToken();
return this.ParseMethodDeclaration(attributes, modifiers, voidType, explicitInterfaceOpt: null, identifier: identifier, typeParameterList: null);
}
}
}
// Destructors are disallowed in global code, skipping check for them.
// TODO: better error messages for script
// Check for constant
if (this.CurrentToken.Kind == SyntaxKind.ConstKeyword)
{
if (!IsScript &&
tryParseLocalDeclarationStatementFromStartPoint<LocalDeclarationStatementSyntax>(attributes, ref afterAttributesPoint, out result))
{
return result;
}
// Prefers const field over const local variable decl
return this.ParseConstantFieldDeclaration(attributes, modifiers, parentKind);
}
// Check for event.
if (this.CurrentToken.Kind == SyntaxKind.EventKeyword)
{
return this.ParseEventDeclaration(attributes, modifiers, parentKind);
}
// check for fixed size buffers.
if (this.CurrentToken.Kind == SyntaxKind.FixedKeyword)
{
return this.ParseFixedSizeBufferDeclaration(attributes, modifiers, parentKind);
}
// Check for conversion operators (implicit/explicit)
result = this.TryParseConversionOperatorDeclaration(attributes, modifiers);
if (result is not null)
{
return result;
}
if (this.CurrentToken.Kind == SyntaxKind.NamespaceKeyword)
{
return ParseNamespaceDeclaration(attributes, modifiers);
}
// It's valid to have a type declaration here -- check for those
if (isPossibleTypeDeclaration && IsTypeDeclarationStart())
{
return this.ParseTypeDeclaration(attributes, modifiers);
}
TypeSyntax type = ParseReturnType();
var afterTypeResetPoint = this.GetResetPoint();
try
{
// Try as a regular statement rather than a member declaration, if appropriate.
if ((!haveAttributes || !IsScript) && !haveModifiers && (type.Kind == SyntaxKind.RefType || !IsOperatorStart(out _, advanceParser: false)))
{
this.Reset(ref afterAttributesPoint);
if (this.CurrentToken.Kind is not SyntaxKind.CloseBraceToken and not SyntaxKind.EndOfFileToken &&
this.IsPossibleStatement())
{
var saveTerm = _termState;
_termState |= TerminatorState.IsPossibleStatementStartOrStop; // partial statements can abort if a new statement starts
bool wasInAsync = IsInAsync;
if (!IsScript)
{
IsInAsync = true; // We are implicitly in an async context
}
// In Script we don't allow local declaration statements at the top level. We want
// to fall out below and parse them instead as fields. For top-level statements, we allow
// them, but want to try properties , etc. first.
var statement = this.ParseStatementCore(attributes, isGlobal: true);
IsInAsync = wasInAsync;
_termState = saveTerm;
if (isAcceptableNonDeclarationStatement(statement, IsScript))
{
return _syntaxFactory.GlobalStatement(statement);
}
}
this.Reset(ref afterTypeResetPoint);
}
// Everything that's left -- methods, fields, properties, locals,
// indexers, and non-conversion operators -- starts with a type
// (possibly void).
// Check for misplaced modifiers. if we see any, then consider this member
// terminated and restart parsing.
if (IsMisplacedModifier(modifiers, attributes, type, out result))
{
return result;
}
parse_member_name:;
// If we've seen the ref keyword, we know we must have an indexer, method, property, or local.
bool typeIsRef = type.IsRef;
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt;
// Check here for operators
// Allow old-style implicit/explicit casting operator syntax, just so we can give a better error
if (!typeIsRef && IsOperatorStart(out explicitInterfaceOpt))
{
return this.ParseOperatorDeclaration(attributes, modifiers, type, explicitInterfaceOpt);
}
if ((!typeIsRef || !IsScript) && IsFieldDeclaration(isEvent: false, isGlobalScriptLevel: true))
{
var saveTerm = _termState;
if ((!haveAttributes && !haveModifiers) || !IsScript)
{
// if we are at top-level then statements can occur
_termState |= TerminatorState.IsPossibleStatementStartOrStop;
if (!IsScript)
{
this.Reset(ref afterAttributesPoint);
if (tryParseLocalDeclarationStatement<LocalDeclarationStatementSyntax>(attributes, out result))
{
return result;
}
this.Reset(ref afterTypeResetPoint);
}
}
if (!typeIsRef)
{
return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind);
}
else
{
_termState = saveTerm;
}
}
// At this point we can either have indexers, methods, or
// properties (or something unknown). Try to break apart
// the following name and determine what to do from there.
SyntaxToken identifierOrThisOpt;
TypeParameterListSyntax typeParameterListOpt;
this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterListOpt, isEvent: false);
if (!haveModifiers && !haveAttributes && !IsScript &&
explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null &&
!type.IsMissing && type.Kind != SyntaxKind.RefType &&
!isFollowedByPossibleUsingDirective() &&
tryParseLocalDeclarationStatementFromStartPoint<LocalDeclarationStatementSyntax>(attributes, ref afterAttributesPoint, out result))
{
return result;
}
// First, check if we got absolutely nothing. If so, then
// We need to consume a bad member and try again.
if (IsNoneOrIncompleteMember(parentKind, attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result))
{
return result;
}
// If the modifiers did not include "async", and the type we got was "async", and there was an
// error in the identifier or its type parameters, then the user is probably in the midst of typing
// an async method. In that case we reconsider "async" to be a modifier, and treat the identifier
// (with the type parameters) as the type (with type arguments). Then we go back to looking for
// the member name again.
// For example, if we get
// async Task<
// then we want async to be a modifier and Task<MISSING> to be a type.
if (ReconsideredTypeAsAsyncModifier(ref modifiers, ref type, ref afterTypeResetPoint, ref explicitInterfaceOpt, ref identifierOrThisOpt, ref typeParameterListOpt))
{
goto parse_member_name;
}
Debug.Assert(identifierOrThisOpt != null);
if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result))
{
return result;
}
if (!IsScript)
{
if (explicitInterfaceOpt is null &&
tryParseLocalDeclarationStatementFromStartPoint<LocalFunctionStatementSyntax>(attributes, ref afterAttributesPoint, out result))
{
return result;
}
if (!haveModifiers &&
tryParseStatement(attributes, ref afterAttributesPoint, out result))
{
return result;
}
}
// treat anything else as a method.
return this.ParseMethodDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt);
}
finally
{
this.Release(ref afterTypeResetPoint);
}
}
finally
{
_pool.Free(modifiers);
_termState = saveTermState;
this.Release(ref afterAttributesPoint);
}
bool tryParseLocalDeclarationStatement<DeclarationSyntax>(SyntaxList<AttributeListSyntax> attributes, out MemberDeclarationSyntax result) where DeclarationSyntax : StatementSyntax
{
bool wasInAsync = IsInAsync;
IsInAsync = true; // We are implicitly in an async context
int lastTokenPosition = -1;
IsMakingProgress(ref lastTokenPosition);
var topLevelStatement = ParseLocalDeclarationStatement(attributes);
IsInAsync = wasInAsync;
if (topLevelStatement is DeclarationSyntax declaration && IsMakingProgress(ref lastTokenPosition, assertIfFalse: false))
{
result = _syntaxFactory.GlobalStatement(declaration);
return true;
}
result = null;
return false;
}
bool tryParseStatement(SyntaxList<AttributeListSyntax> attributes, ref ResetPoint afterAttributesPoint, out MemberDeclarationSyntax result)
{
using var resetOnFailurePoint = this.GetDisposableResetPoint(resetOnDispose: false);
this.Reset(ref afterAttributesPoint);
if (this.IsPossibleStatement())
{
var saveTerm = _termState;
_termState |= TerminatorState.IsPossibleStatementStartOrStop; // partial statements can abort if a new statement starts
bool wasInAsync = IsInAsync;
IsInAsync = true; // We are implicitly in an async context
var statement = this.ParseStatementCore(attributes, isGlobal: true);
IsInAsync = wasInAsync;
_termState = saveTerm;
if (statement is not null)
{
result = _syntaxFactory.GlobalStatement(statement);
return true;
}
}
resetOnFailurePoint.Reset();
result = null;
return false;
}
bool tryParseLocalDeclarationStatementFromStartPoint<DeclarationSyntax>(SyntaxList<AttributeListSyntax> attributes, ref ResetPoint startPoint, out MemberDeclarationSyntax result) where DeclarationSyntax : StatementSyntax
{
using var resetOnFailurePoint = this.GetDisposableResetPoint(resetOnDispose: false);
this.Reset(ref startPoint);
if (tryParseLocalDeclarationStatement<DeclarationSyntax>(attributes, out result))
{
return true;
}
resetOnFailurePoint.Reset();
return false;
}
static bool isAcceptableNonDeclarationStatement(StatementSyntax statement, bool isScript)
{
switch (statement?.Kind)
{
case null:
case SyntaxKind.LocalFunctionStatement:
case SyntaxKind.ExpressionStatement when
!isScript &&
// Do not parse a single identifier as an expression statement in a Simple Program, this could be a beginning of a keyword and
// we want completion to offer it.
statement is ExpressionStatementSyntax { Expression.Kind: SyntaxKind.IdentifierName, SemicolonToken.IsMissing: true }:
return false;
case SyntaxKind.LocalDeclarationStatement:
return !isScript && statement is LocalDeclarationStatementSyntax { UsingKeyword: not null };
default:
return true;
}
}
bool isFollowedByPossibleUsingDirective()
{
if (CurrentToken.Kind == SyntaxKind.UsingKeyword)
{
return !IsPossibleTopLevelUsingLocalDeclarationStatement();
}
if (CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword && this.PeekToken(1).Kind == SyntaxKind.UsingKeyword)
{
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// Skip 'global' keyword
EatToken();
return !IsPossibleTopLevelUsingLocalDeclarationStatement();
}
return false;
}
}
private bool IsMisplacedModifier(SyntaxListBuilder modifiers, SyntaxList<AttributeListSyntax> attributes, TypeSyntax type, out MemberDeclarationSyntax result)
{
if (GetModifierExcludingScoped(this.CurrentToken) != DeclarationModifiers.None &&
this.CurrentToken.ContextualKind is not (SyntaxKind.PartialKeyword or SyntaxKind.AsyncKeyword or SyntaxKind.RequiredKeyword or SyntaxKind.FileKeyword) &&
IsComplete(type))
{
var misplacedModifier = this.CurrentToken;
type = this.AddError(
type,
type.FullWidth + misplacedModifier.GetLeadingTriviaWidth(),
misplacedModifier.Width,
ErrorCode.ERR_BadModifierLocation,
misplacedModifier.Text);
result = _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type);
return true;
}
result = null;
return false;
}
private bool IsNoneOrIncompleteMember(SyntaxKind parentKind, SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers, TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, SyntaxToken identifierOrThisOpt, TypeParameterListSyntax typeParameterListOpt,
out MemberDeclarationSyntax result)
{
if (explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null)
{
if (attributes.Count == 0 && modifiers.Count == 0 && type.IsMissing && type.Kind != SyntaxKind.RefType)
{
// we haven't advanced, the caller needs to consume the tokens ahead
result = null;
return true;
}
var incompleteMember = _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type.IsMissing ? null : type);
if (ContainsErrorDiagnostic(incompleteMember))
{
result = incompleteMember;
}
else if (parentKind is SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration ||
parentKind == SyntaxKind.CompilationUnit && !IsScript)
{
result = this.AddErrorToLastToken(incompleteMember, ErrorCode.ERR_NamespaceUnexpected);
}
else
{
//the error position should indicate CurrentToken
result = this.AddError(
incompleteMember,
incompleteMember.FullWidth + this.CurrentToken.GetLeadingTriviaWidth(),
this.CurrentToken.Width,
ErrorCode.ERR_InvalidMemberDecl,
this.CurrentToken.Text);
}
return true;
}
result = null;
return false;
}
private bool ReconsideredTypeAsAsyncModifier(ref SyntaxListBuilder modifiers, ref TypeSyntax type, ref ResetPoint afterTypeResetPoint,
ref ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, ref SyntaxToken identifierOrThisOpt,
ref TypeParameterListSyntax typeParameterListOpt)
{
if (type.Kind != SyntaxKind.RefType &&
identifierOrThisOpt != null &&
(typeParameterListOpt != null && typeParameterListOpt.ContainsDiagnostics
|| this.CurrentToken.Kind is not SyntaxKind.OpenParenToken and not SyntaxKind.OpenBraceToken and not SyntaxKind.EqualsGreaterThanToken) &&
ReconsiderTypeAsAsyncModifier(ref modifiers, type, identifierOrThisOpt))
{
this.Reset(ref afterTypeResetPoint);
explicitInterfaceOpt = null;
identifierOrThisOpt = null;
typeParameterListOpt = null;
this.Release(ref afterTypeResetPoint);
type = ParseReturnType();
afterTypeResetPoint = this.GetResetPoint();
return true;
}
return false;
}
private bool TryParseIndexerOrPropertyDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers, TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, SyntaxToken identifierOrThisOpt,
TypeParameterListSyntax typeParameterListOpt, out MemberDeclarationSyntax result)
{
if (identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword)
{
result = this.ParseIndexerDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt);
return true;
}
// `{` or `=>` definitely start a property. Also allow
// `; {` and `; =>` as error recovery for a misplaced semicolon.
if (IsStartOfPropertyBody(this.CurrentToken.Kind) ||
(this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind)))
{
result = this.ParsePropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt);
return true;
}
result = null;
return false;
}
private static bool IsStartOfPropertyBody(SyntaxKind kind)
=> kind is SyntaxKind.OpenBraceToken or SyntaxKind.EqualsGreaterThanToken;
// Returns null if we can't parse anything (even partially).
internal MemberDeclarationSyntax ParseMemberDeclaration(SyntaxKind parentKind)
{
_recursionDepth++;
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
var result = ParseMemberDeclarationCore(parentKind);
_recursionDepth--;
return result;
}
/// <summary>
/// Changes in this function should be mirrored in <see cref="ParseMemberDeclarationOrStatementCore"/>.
/// Try keeping structure of both functions similar to simplify this task. The split was made to
/// reduce the stack usage during recursive parsing.
/// </summary>
/// <returns>Returns null if we can't parse anything (even partially).</returns>
private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind)
{
// "top-level" expressions and statements should never occur inside an asynchronous context
Debug.Assert(!IsInAsync);
Debug.Assert(parentKind != SyntaxKind.CompilationUnit);
cancellationToken.ThrowIfCancellationRequested();
// don't reuse members if they were previously declared under a different type keyword kind
if (this.IsIncrementalAndFactoryContextMatches && CanReuseMemberDeclaration(CurrentNodeKind, isGlobal: false))
return (MemberDeclarationSyntax)this.EatNode();
var modifiers = _pool.Allocate();
var saveTermState = _termState;
try
{
var attributes = this.ParseAttributeDeclarations(inExpressionContext: false);
bool isPossibleTypeDeclaration;
this.ParseModifiers(modifiers, forAccessors: false, forTopLevelStatements: false, out isPossibleTypeDeclaration);
// Check for constructor form
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken && this.PeekToken(1).Kind == SyntaxKind.OpenParenToken)
{
return this.ParseConstructorDeclaration(attributes, modifiers);
}
// Check for destructor form
if (this.CurrentToken.Kind == SyntaxKind.TildeToken)
{
return this.ParseDestructorDeclaration(attributes, modifiers);
}
// Check for constant
if (this.CurrentToken.Kind == SyntaxKind.ConstKeyword)
{
return this.ParseConstantFieldDeclaration(attributes, modifiers, parentKind);
}
// Check for event.
if (this.CurrentToken.Kind == SyntaxKind.EventKeyword)
{
return this.ParseEventDeclaration(attributes, modifiers, parentKind);
}
// check for fixed size buffers.
if (this.CurrentToken.Kind == SyntaxKind.FixedKeyword)
{
return this.ParseFixedSizeBufferDeclaration(attributes, modifiers, parentKind);
}
// Check for conversion operators (implicit/explicit)
MemberDeclarationSyntax result = this.TryParseConversionOperatorDeclaration(attributes, modifiers);
if (result is not null)
{
return result;
}
// Namespaces should be handled by the caller, not checking for them
// It's valid to have a type declaration here -- check for those
if (isPossibleTypeDeclaration && IsTypeDeclarationStart())
{
return this.ParseTypeDeclaration(attributes, modifiers);
}
// Everything that's left -- methods, fields, properties,
// indexers, and non-conversion operators -- starts with a type
// (possibly void).
TypeSyntax type = ParseReturnType();
var afterTypeResetPoint = this.GetResetPoint();
try
{
// Check for misplaced modifiers. if we see any, then consider this member
// terminated and restart parsing.
if (IsMisplacedModifier(modifiers, attributes, type, out result))
{
return result;
}
parse_member_name:;
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt;
// If we've seen the ref keyword, we know we must have an indexer, method, field, or property.
if (type.Kind != SyntaxKind.RefType)
{
// Check here for operators
// Allow old-style implicit/explicit casting operator syntax, just so we can give a better error
if (IsOperatorStart(out explicitInterfaceOpt))
{
return this.ParseOperatorDeclaration(attributes, modifiers, type, explicitInterfaceOpt);
}
}
if (IsFieldDeclaration(isEvent: false, isGlobalScriptLevel: false))
{
return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind);
}
// At this point we can either have indexers, methods, or
// properties (or something unknown). Try to break apart
// the following name and determine what to do from there.
SyntaxToken identifierOrThisOpt;
TypeParameterListSyntax typeParameterListOpt;
this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterListOpt, isEvent: false);
// First, check if we got absolutely nothing. If so, then
// We need to consume a bad member and try again.
if (IsNoneOrIncompleteMember(parentKind, attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result))
{
return result;
}
// If the modifiers did not include "async", and the type we got was "async", and there was an
// error in the identifier or its type parameters, then the user is probably in the midst of typing
// an async method. In that case we reconsider "async" to be a modifier, and treat the identifier
// (with the type parameters) as the type (with type arguments). Then we go back to looking for
// the member name again.
// For example, if we get
// async Task<
// then we want async to be a modifier and Task<MISSING> to be a type.
if (ReconsideredTypeAsAsyncModifier(ref modifiers, ref type, ref afterTypeResetPoint, ref explicitInterfaceOpt, ref identifierOrThisOpt, ref typeParameterListOpt))
{
goto parse_member_name;
}
Debug.Assert(identifierOrThisOpt != null);
if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result))
{
return result;
}
// treat anything else as a method.
return this.ParseMethodDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt);
}
finally
{
this.Release(ref afterTypeResetPoint);
}
}
finally
{
_pool.Free(modifiers);
_termState = saveTermState;
}
}
// if the modifiers do not contain async or replace and the type is the identifier "async" or "replace", then
// add that identifier to the modifiers and assign a new type from the identifierOrThisOpt and the
// type parameter list
private static bool ReconsiderTypeAsAsyncModifier(
ref SyntaxListBuilder modifiers,
TypeSyntax type,
SyntaxToken identifierOrThisOpt)
{
if (type.Kind != SyntaxKind.IdentifierName)
return false;
if (identifierOrThisOpt.Kind != SyntaxKind.IdentifierToken)
return false;
var identifier = ((IdentifierNameSyntax)type).Identifier;
var contextualKind = identifier.ContextualKind;
if (contextualKind != SyntaxKind.AsyncKeyword ||
modifiers.Any((int)contextualKind))
{
return false;
}
modifiers.Add(ConvertToKeyword(identifier));
return true;
}
private bool IsFieldDeclaration(bool isEvent, bool isGlobalScriptLevel)
{
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
return false;
}
if (this.CurrentToken.ContextualKind == SyntaxKind.GlobalKeyword && this.PeekToken(1).Kind == SyntaxKind.UsingKeyword)
{
return false;
}
// Treat this as a field, unless we have anything following that
// makes us:
// a) explicit
// b) generic
// c) a property
// d) a method (unless we already know we're parsing an event)
var kind = this.PeekToken(1).Kind;
// Error recovery, don't allow a misplaced semicolon after the name in a property to throw off the entire parse.
//
// e.g. `public int MyProperty; { get; set; }` should still be parsed as a property with a skipped token.
if (!isGlobalScriptLevel &&
kind == SyntaxKind.SemicolonToken &&
IsStartOfPropertyBody(this.PeekToken(2).Kind))
{
return false;
}
switch (kind)
{
case SyntaxKind.DotToken: // Goo. explicit
case SyntaxKind.ColonColonToken: // Goo:: explicit
case SyntaxKind.LessThanToken: // Goo< explicit or generic method
case SyntaxKind.OpenBraceToken: // Goo { property
case SyntaxKind.EqualsGreaterThanToken: // Goo => property
return false;
case SyntaxKind.OpenParenToken: // Goo( method
return isEvent;
default:
return true;
}
}
private bool IsOperatorKeyword()
{
return this.CurrentToken.Kind is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword or SyntaxKind.OperatorKeyword;
}
public static bool IsComplete(CSharpSyntaxNode node)
{
if (node == null)
{
return false;
}
foreach (var child in node.ChildNodesAndTokens().Reverse())
{
if (child is not SyntaxToken token)
{
return IsComplete((CSharpSyntaxNode)child);
}
if (token.IsMissing)
{
return false;
}
if (token.Kind != SyntaxKind.None)
{
return true;
}
// if token was optional, consider the next one..
}
return true;
}
private ConstructorDeclarationSyntax ParseConstructorDeclaration(
SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
var name = this.ParseIdentifierToken();
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfMethodSignature;
try
{
var paramList = this.ParseParenthesizedParameterList();
var initializer = this.CurrentToken.Kind == SyntaxKind.ColonToken
? this.ParseConstructorInitializer()
: null;
this.ParseBlockAndExpressionBodiesWithSemicolon(out var body, out var expressionBody, out var semicolon);
return _syntaxFactory.ConstructorDeclaration(attributes, modifiers.ToList(), name, paramList, initializer, body, expressionBody, semicolon);
}
finally
{
_termState = saveTerm;
}
}
private ConstructorInitializerSyntax ParseConstructorInitializer()
{
var colon = this.EatToken(SyntaxKind.ColonToken);
var reportError = true;
var kind = this.CurrentToken.Kind == SyntaxKind.BaseKeyword
? SyntaxKind.BaseConstructorInitializer
: SyntaxKind.ThisConstructorInitializer;
SyntaxToken token;
if (this.CurrentToken.Kind is SyntaxKind.BaseKeyword or SyntaxKind.ThisKeyword)
{
token = this.EatToken();
}
else
{
token = this.EatToken(SyntaxKind.ThisKeyword, ErrorCode.ERR_ThisOrBaseExpected);
// No need to report further errors at this point:
reportError = false;
}
var argumentList = this.CurrentToken.Kind == SyntaxKind.OpenParenToken
? this.ParseParenthesizedArgumentList()
: _syntaxFactory.ArgumentList(
this.EatToken(SyntaxKind.OpenParenToken, reportError),
arguments: default,
this.EatToken(SyntaxKind.CloseParenToken, reportError));
return _syntaxFactory.ConstructorInitializer(kind, colon, token, argumentList);
}
private DestructorDeclarationSyntax ParseDestructorDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.TildeToken);
var tilde = this.EatToken(SyntaxKind.TildeToken);
var name = this.ParseIdentifierToken();
var parameterList = _syntaxFactory.ParameterList(
this.EatToken(SyntaxKind.OpenParenToken),
default(SeparatedSyntaxList<ParameterSyntax>),
this.EatToken(SyntaxKind.CloseParenToken));
this.ParseBlockAndExpressionBodiesWithSemicolon(
out BlockSyntax body, out ArrowExpressionClauseSyntax expressionBody, out SyntaxToken semicolon);
return _syntaxFactory.DestructorDeclaration(attributes, modifiers.ToList(), tilde, name, parameterList, body, expressionBody, semicolon);
}
/// <summary>
/// Parses any block or expression bodies that are present. Also parses
/// the trailing semicolon if one is present.
/// </summary>
private void ParseBlockAndExpressionBodiesWithSemicolon(
out BlockSyntax blockBody,
out ArrowExpressionClauseSyntax expressionBody,
out SyntaxToken semicolon,
bool parseSemicolonAfterBlock = true)
{
// Check for 'forward' declarations with no block of any kind
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
blockBody = null;
expressionBody = null;
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
return;
}
blockBody = this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
? this.ParseMethodOrAccessorBodyBlock(attributes: default, isAccessorBody: false)
: null;
expressionBody = this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken
? this.ParseArrowExpressionClause()
: null;
// Expression-bodies need semicolons and native behavior
// expects a semicolon if there is no body
if (expressionBody != null || blockBody == null)
{
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
// Check for bad semicolon after block body
else if (parseSemicolonAfterBlock && this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = this.EatTokenWithPrejudice(ErrorCode.ERR_UnexpectedSemicolon);
}
else
{
semicolon = null;
}
}
private bool IsEndOfTypeParameterList()
{
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
// void Goo<T (
return true;
}
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
// class C<T :
return true;
}
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
// class C<T {
return true;
}
if (IsCurrentTokenWhereOfConstraintClause())
{
// class C<T where T :
return true;
}
return false;
}
private bool IsEndOfMethodSignature()
=> this.CurrentToken.Kind is SyntaxKind.SemicolonToken or SyntaxKind.OpenBraceToken;
private bool IsEndOfRecordOrClassOrStructOrInterfaceSignature()
{
return this.CurrentToken.Kind is SyntaxKind.SemicolonToken or SyntaxKind.OpenBraceToken;
}
private bool IsEndOfNameInExplicitInterface()
=> this.CurrentToken.Kind is SyntaxKind.DotToken or SyntaxKind.ColonColonToken;
private bool IsEndOfFunctionPointerParameterList(bool errored)
=> this.CurrentToken.Kind == (errored ? SyntaxKind.CloseParenToken : SyntaxKind.GreaterThanToken);
private bool IsEndOfFunctionPointerCallingConvention()
=> this.CurrentToken.Kind == SyntaxKind.CloseBracketToken;
private MethodDeclarationSyntax ParseMethodDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt,
SyntaxToken identifier,
TypeParameterListSyntax typeParameterList)
{
// Parse the name (it could be qualified)
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfMethodSignature;
var paramList = this.ParseParenthesizedParameterList();
var constraints = default(SyntaxListBuilder<TypeParameterConstraintClauseSyntax>);
if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
constraints = _pool.Allocate<TypeParameterConstraintClauseSyntax>();
this.ParseTypeParameterConstraintClauses(constraints);
}
else if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
// Use else if, rather than if, because if we see both a constructor initializer and a constraint clause, we're too lost to recover.
var colonToken = this.CurrentToken;
var initializer = this.ParseConstructorInitializer();
initializer = this.AddErrorToFirstToken(initializer, ErrorCode.ERR_UnexpectedToken, colonToken.Text);
paramList = AddTrailingSkippedSyntax(paramList, initializer);
// CONSIDER: Parsing an invalid constructor initializer could, conceivably, get us way
// off track. If this becomes a problem, an alternative approach would be to generalize
// EatTokenWithPrejudice in such a way that we can just skip everything until we recognize
// our context again (perhaps an open brace).
}
_termState = saveTerm;
// Method declarations cannot be nested or placed inside async lambdas, and so cannot occur in an
// asynchronous context. Therefore the IsInAsync state of the parent scope is not saved and
// restored, just assumed to be false and reset accordingly after parsing the method body.
Debug.Assert(!IsInAsync);
IsInAsync = modifiers.Any((int)SyntaxKind.AsyncKeyword);
this.ParseBlockAndExpressionBodiesWithSemicolon(out var blockBody, out var expressionBody, out var semicolon);
IsInAsync = false;
return _syntaxFactory.MethodDeclaration(
attributes,
modifiers.ToList(),
type,
explicitInterfaceOpt,
identifier,
typeParameterList,
paramList,
_pool.ToListAndFree(constraints),
blockBody,
expressionBody,
semicolon);
}
private TypeSyntax ParseReturnType()
{
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfReturnType;
var type = this.ParseTypeOrVoid();
_termState = saveTerm;
return type;
}
private bool IsEndOfReturnType()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.OpenParenToken:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.SemicolonToken:
return true;
default:
return false;
}
}
private ConversionOperatorDeclarationSyntax TryParseConversionOperatorDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
var point = GetResetPoint();
try
{
bool haveExplicitInterfaceName = false;
if (this.CurrentToken.Kind is not (SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword))
{
SyntaxKind separatorKind = SyntaxKind.None;
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
// Scan possible ExplicitInterfaceSpecifier
while (true)
{
// now, scan past the next name. if it's followed by a dot then
// it's part of the explicit name we're building up. Otherwise,
// it should be an operator token
if (this.CurrentToken.Kind == SyntaxKind.OperatorKeyword)
{
// We're past any explicit interface portion
break;
}
else
{
using var scanNamePartPoint = GetDisposableResetPoint(resetOnDispose: false);
int lastTokenPosition = -1;
IsMakingProgress(ref lastTokenPosition, assertIfFalse: true);
ScanNamedTypePart();
if (IsDotOrColonColon() ||
(IsMakingProgress(ref lastTokenPosition, assertIfFalse: false) && this.CurrentToken.Kind != SyntaxKind.OpenParenToken))
{
haveExplicitInterfaceName = true;
if (IsDotOrColonColon())
{
separatorKind = this.CurrentToken.Kind;
EatToken();
}
else
{
separatorKind = SyntaxKind.None;
}
}
else
{
scanNamePartPoint.Reset();
// We're past any explicit interface portion
break;
}
}
}
}
bool possibleConversion;
if (this.CurrentToken.Kind != SyntaxKind.OperatorKeyword ||
(haveExplicitInterfaceName && separatorKind is not SyntaxKind.DotToken))
{
possibleConversion = false;
}
else if (this.PeekToken(1).Kind is SyntaxKind.CheckedKeyword or SyntaxKind.UncheckedKeyword)
{
possibleConversion = !SyntaxFacts.IsAnyOverloadableOperator(this.PeekToken(2).Kind);
}
else
{
possibleConversion = !SyntaxFacts.IsAnyOverloadableOperator(this.PeekToken(1).Kind);
}
this.Reset(ref point);
if (!possibleConversion)
{
return null;
}
}
var style = this.CurrentToken.Kind is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword
? this.EatToken()
: this.EatToken(SyntaxKind.ExplicitKeyword);
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt = tryParseExplicitInterfaceSpecifier();
Debug.Assert(!style.IsMissing || haveExplicitInterfaceName == explicitInterfaceOpt is not null);
SyntaxToken opKeyword;
TypeSyntax type;
if (!style.IsMissing && explicitInterfaceOpt is not null && this.CurrentToken.Kind != SyntaxKind.OperatorKeyword && style.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia))
{
// Not likely an explicit interface implementation. Likely a beginning of the next member on the next line.
this.Reset(ref point);
style = this.EatToken();
explicitInterfaceOpt = null;
opKeyword = this.EatToken(SyntaxKind.OperatorKeyword);
type = this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_IdentifierExpected);
return _syntaxFactory.ConversionOperatorDeclaration(
attributes,
modifiers.ToList(),
style,
explicitInterfaceOpt,
opKeyword,
checkedKeyword: null,
type,
_syntaxFactory.ParameterList(
SyntaxFactory.MissingToken(SyntaxKind.OpenParenToken),
parameters: default,
SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken)),
body: null,
expressionBody: null,
semicolonToken: SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken));
}
opKeyword = this.EatToken(SyntaxKind.OperatorKeyword);
var checkedKeyword = TryEatCheckedOrHandleUnchecked(ref opKeyword);
this.Release(ref point);
point = GetResetPoint();
bool couldBeParameterList = this.CurrentToken.Kind == SyntaxKind.OpenParenToken;
type = this.ParseType();
if (couldBeParameterList && type is TupleTypeSyntax { Elements: { Count: 2, SeparatorCount: 1 } } tupleType &&
tupleType.Elements.GetSeparator(0).IsMissing && tupleType.Elements[1].IsMissing &&
this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
// It looks like the type is missing and we parsed parameter list as the type. Recover.
this.Reset(ref point);
type = ParseIdentifierName();
}
var paramList = this.ParseParenthesizedParameterList();
this.ParseBlockAndExpressionBodiesWithSemicolon(out var blockBody, out var expressionBody, out var semicolon);
return _syntaxFactory.ConversionOperatorDeclaration(
attributes,
modifiers.ToList(),
style,
explicitInterfaceOpt,
opKeyword,
checkedKeyword,
type,
paramList,
blockBody,
expressionBody,
semicolon);
}
finally
{
this.Release(ref point);
}
ExplicitInterfaceSpecifierSyntax tryParseExplicitInterfaceSpecifier()
{
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
return null;
}
NameSyntax explicitInterfaceName = null;
SyntaxToken separator = null;
while (true)
{
// now, scan past the next name. if it's followed by a dot then
// it's part of the explicit name we're building up. Otherwise,
// it should be an operator token
bool isPartOfInterfaceName;
using (GetDisposableResetPoint(resetOnDispose: true))
{
if (this.CurrentToken.Kind == SyntaxKind.OperatorKeyword)
{
isPartOfInterfaceName = false;
}
else
{
int lastTokenPosition = -1;
IsMakingProgress(ref lastTokenPosition, assertIfFalse: true);
ScanNamedTypePart();
isPartOfInterfaceName = IsDotOrColonColon() ||
(IsMakingProgress(ref lastTokenPosition, assertIfFalse: false) && this.CurrentToken.Kind != SyntaxKind.OpenParenToken);
}
}
if (!isPartOfInterfaceName)
{
// We're past any explicit interface portion
if (separator?.Kind == SyntaxKind.ColonColonToken)
{
separator = this.AddError(separator, ErrorCode.ERR_AliasQualAsExpression);
separator = this.ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
break;
}
else
{
// If we saw a . or :: then we must have something explicit.
AccumulateExplicitInterfaceName(ref explicitInterfaceName, ref separator);
}
}
if (explicitInterfaceName is null)
{
return null;
}
if (separator.Kind != SyntaxKind.DotToken)
{
separator = WithAdditionalDiagnostics(separator, GetExpectedTokenError(SyntaxKind.DotToken, separator.Kind, separator.GetLeadingTriviaWidth(), separator.Width));
separator = ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
return _syntaxFactory.ExplicitInterfaceSpecifier(explicitInterfaceName, separator);
}
}
private SyntaxToken TryEatCheckedOrHandleUnchecked(ref SyntaxToken operatorKeyword)
{
if (CurrentToken.Kind == SyntaxKind.UncheckedKeyword)
{
// if we encounter `operator unchecked`, we place the `unchecked` as skipped trivia on `operator`
var misplacedToken = this.AddError(this.EatToken(), ErrorCode.ERR_MisplacedUnchecked);
operatorKeyword = AddTrailingSkippedSyntax(operatorKeyword, misplacedToken);
return null;
}
return TryEatToken(SyntaxKind.CheckedKeyword);
}
private MemberDeclarationSyntax ParseOperatorDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt)
{
// We can get here after seeing `explicit` or `implicit` or `operator`. `ret-type explicit op ...` is not
// legal though.
var firstToken = this.CurrentToken;
if (firstToken.Kind is SyntaxKind.ExplicitKeyword or SyntaxKind.ImplicitKeyword &&
this.PeekToken(1).Kind is SyntaxKind.OperatorKeyword)
{
var conversionOperator = TryParseConversionOperatorDeclaration(attributes, modifiers);
if (conversionOperator is not null)
{
// We need to ensure the type syntax the user provided gets an appropriate error and is placed as
// leading skipped trivia for the explicit/implicit keyword.
var newImplicitOrExplicitKeyword = AddLeadingSkippedSyntax(
conversionOperator.ImplicitOrExplicitKeyword,
AddError(type, ErrorCode.ERR_BadOperatorSyntax, firstToken.Text));
return conversionOperator.Update(
conversionOperator.AttributeLists,
conversionOperator.Modifiers,
newImplicitOrExplicitKeyword,
conversionOperator.ExplicitInterfaceSpecifier,
conversionOperator.OperatorKeyword,
conversionOperator.CheckedKeyword,
conversionOperator.Type,
conversionOperator.ParameterList,
conversionOperator.Body,
conversionOperator.ExpressionBody,
conversionOperator.SemicolonToken);
}
}
var opKeyword = this.EatToken(SyntaxKind.OperatorKeyword);
var checkedKeyword = TryEatCheckedOrHandleUnchecked(ref opKeyword);
SyntaxToken opToken;
int opTokenErrorOffset;
int opTokenErrorWidth;
if (SyntaxFacts.IsAnyOverloadableOperator(this.CurrentToken.Kind))
{
opToken = this.EatToken();
Debug.Assert(!opToken.IsMissing);
opTokenErrorOffset = opToken.GetLeadingTriviaWidth();
opTokenErrorWidth = opToken.Width;
}
else
{
if (this.CurrentToken.Kind is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword)
{
// Grab the offset and width before we consume the invalid keyword and change our position.
GetDiagnosticSpanForMissingToken(out opTokenErrorOffset, out opTokenErrorWidth);
opToken = this.ConvertToMissingWithTrailingTrivia(this.EatToken(), SyntaxKind.PlusToken);
Debug.Assert(opToken.IsMissing); //Which is why we used GetDiagnosticSpanForMissingToken above.
Debug.Assert(type != null); // How could it be? The only caller got it from ParseReturnType.
if (type.IsMissing)
{
SyntaxDiagnosticInfo diagInfo = MakeError(opTokenErrorOffset, opTokenErrorWidth, ErrorCode.ERR_BadOperatorSyntax, SyntaxFacts.GetText(SyntaxKind.PlusToken));
opToken = WithAdditionalDiagnostics(opToken, diagInfo);
}
else
{
// Dev10 puts this error on the type (if there is one).
type = this.AddError(type, ErrorCode.ERR_BadOperatorSyntax, SyntaxFacts.GetText(SyntaxKind.PlusToken));
}
}
else
{
// Consume whatever follows the operator keyword as the operator token. If it is not we'll add an
// error below (when we can guess the arity). Handle .. as well so we can give the user a good
// message if they do `operator ..`
opToken = IsAtDotDotToken() ? EatDotDotToken() : EatToken();
Debug.Assert(!opToken.IsMissing);
opTokenErrorOffset = opToken.GetLeadingTriviaWidth();
opTokenErrorWidth = opToken.Width;
}
}
// check for >> and >>>
var opKind = opToken.Kind;
var tk = this.CurrentToken;
if (opToken.Kind == SyntaxKind.GreaterThanToken && tk.Kind == SyntaxKind.GreaterThanToken &&
NoTriviaBetween(opToken, tk)) // no trailing trivia and no leading trivia
{
var opToken2 = this.EatToken();
tk = this.CurrentToken;
if (tk.Kind == SyntaxKind.GreaterThanToken &&
NoTriviaBetween(opToken2, tk)) // no trailing trivia and no leading trivia
{
opToken2 = this.EatToken();
opToken = SyntaxFactory.Token(opToken.GetLeadingTrivia(), SyntaxKind.GreaterThanGreaterThanGreaterThanToken, opToken2.GetTrailingTrivia());
}
else
{
opToken = SyntaxFactory.Token(opToken.GetLeadingTrivia(), SyntaxKind.GreaterThanGreaterThanToken, opToken2.GetTrailingTrivia());
}
}
var paramList = this.ParseParenthesizedParameterList();
switch (paramList.Parameters.Count)
{
case 1:
if (opToken.IsMissing || !SyntaxFacts.IsOverloadableUnaryOperator(opKind))
{
SyntaxDiagnosticInfo diagInfo = MakeError(opTokenErrorOffset, opTokenErrorWidth, ErrorCode.ERR_OvlUnaryOperatorExpected);
opToken = WithAdditionalDiagnostics(opToken, diagInfo);
}
break;
case 2:
if (opToken.IsMissing || !SyntaxFacts.IsOverloadableBinaryOperator(opKind))
{
SyntaxDiagnosticInfo diagInfo = MakeError(opTokenErrorOffset, opTokenErrorWidth, ErrorCode.ERR_OvlBinaryOperatorExpected);
opToken = WithAdditionalDiagnostics(opToken, diagInfo);
}
break;
default:
if (opToken.IsMissing)
{
SyntaxDiagnosticInfo diagInfo = MakeError(opTokenErrorOffset, opTokenErrorWidth, ErrorCode.ERR_OvlOperatorExpected);
opToken = WithAdditionalDiagnostics(opToken, diagInfo);
}
else if (SyntaxFacts.IsOverloadableBinaryOperator(opKind))
{
opToken = this.AddError(opToken, ErrorCode.ERR_BadBinOpArgs, SyntaxFacts.GetText(opKind));
}
else if (SyntaxFacts.IsOverloadableUnaryOperator(opKind))
{
opToken = this.AddError(opToken, ErrorCode.ERR_BadUnOpArgs, SyntaxFacts.GetText(opKind));
}
else
{
opToken = this.AddError(opToken, ErrorCode.ERR_OvlOperatorExpected);
}
break;
}
this.ParseBlockAndExpressionBodiesWithSemicolon(out var blockBody, out var expressionBody, out var semicolon);
// if the operator is invalid, then switch it to plus (which will work either way) so that
// we can finish building the tree
if (!(opKind == SyntaxKind.IsKeyword ||
SyntaxFacts.IsOverloadableUnaryOperator(opKind) ||
SyntaxFacts.IsOverloadableBinaryOperator(opKind)))
{
opToken = ConvertToMissingWithTrailingTrivia(opToken, SyntaxKind.PlusToken);
}
return _syntaxFactory.OperatorDeclaration(
attributes,
modifiers.ToList(),
type,
explicitInterfaceOpt,
opKeyword,
checkedKeyword,
opToken,
paramList,
blockBody,
expressionBody,
semicolon);
}
private IndexerDeclarationSyntax ParseIndexerDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt,
SyntaxToken thisKeyword,
TypeParameterListSyntax typeParameterList)
{
Debug.Assert(thisKeyword.Kind == SyntaxKind.ThisKeyword);
// check to see if the user tried to create a generic indexer.
if (typeParameterList != null)
{
thisKeyword = AddTrailingSkippedSyntax(thisKeyword, typeParameterList);
thisKeyword = this.AddError(thisKeyword, ErrorCode.ERR_UnexpectedGenericName);
}
var parameterList = this.ParseBracketedParameterList();
AccessorListSyntax accessorList = null;
ArrowExpressionClauseSyntax expressionBody = null;
SyntaxToken semicolon = null;
// Try to parse accessor list unless there is an expression
// body and no accessor list
if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken)
{
expressionBody = this.ParseArrowExpressionClause();
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
else
{
accessorList = this.ParseAccessorList(AccessorDeclaringKind.Indexer);
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = this.EatTokenWithPrejudice(ErrorCode.ERR_UnexpectedSemicolon);
}
}
// If the user has erroneously provided both an accessor list
// and an expression body, but no semicolon, we want to parse
// the expression body and report the error (which is done later)
if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken
&& semicolon == null)
{
expressionBody = this.ParseArrowExpressionClause();
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
return _syntaxFactory.IndexerDeclaration(
attributes,
modifiers.ToList(),
type,
explicitInterfaceOpt,
thisKeyword,
parameterList,
accessorList,
expressionBody,
semicolon);
}
private PropertyDeclarationSyntax ParsePropertyDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt,
SyntaxToken identifier,
TypeParameterListSyntax typeParameterList)
{
// check to see if the user tried to create a generic property.
if (typeParameterList != null)
{
identifier = AddTrailingSkippedSyntax(identifier, typeParameterList);
identifier = this.AddError(identifier, ErrorCode.ERR_UnexpectedGenericName);
}
// Error recovery: add an errant semicolon to the identifier token and keep going.
if (this.CurrentToken.Kind is SyntaxKind.SemicolonToken)
{
identifier = AddTrailingSkippedSyntax(identifier, this.EatTokenWithPrejudice(SyntaxKind.OpenBraceToken));
}
// We know we are parsing a property because we have seen either an open brace or an arrow token
Debug.Assert(IsStartOfPropertyBody(this.CurrentToken.Kind));
var accessorList = this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
? this.ParseAccessorList(AccessorDeclaringKind.Property)
: null;
ArrowExpressionClauseSyntax expressionBody = null;
EqualsValueClauseSyntax initializer = null;
// Check for expression body
if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken)
{
using (new FieldKeywordContext(this, isInFieldKeywordContext: true))
{
expressionBody = this.ParseArrowExpressionClause();
}
}
// Check if we have an initializer
else if (this.CurrentToken.Kind == SyntaxKind.EqualsToken)
{
var equals = this.EatToken(SyntaxKind.EqualsToken);
var value = this.ParseVariableInitializer();
initializer = _syntaxFactory.EqualsValueClause(equals, value: value);
}
SyntaxToken semicolon = null;
if (expressionBody != null || initializer != null)
{
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
else if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = this.EatTokenWithPrejudice(ErrorCode.ERR_UnexpectedSemicolon);
}
return _syntaxFactory.PropertyDeclaration(
attributes,
modifiers.ToList(),
type,
explicitInterfaceOpt,
identifier,
accessorList,
expressionBody,
initializer,
semicolon);
}
private readonly ref struct FieldKeywordContext : IDisposable
{
private readonly LanguageParser _parser;
private readonly bool _previousInFieldKeywordContext;
public FieldKeywordContext(LanguageParser parser, bool isInFieldKeywordContext)
{
_parser = parser;
_previousInFieldKeywordContext = parser.IsInFieldKeywordContext;
_parser.IsInFieldKeywordContext = isInFieldKeywordContext;
}
public void Dispose()
{
_parser.IsInFieldKeywordContext = _previousInFieldKeywordContext;
}
}
private enum AccessorDeclaringKind
{
Property,
Indexer,
Event,
}
private AccessorListSyntax ParseAccessorList(AccessorDeclaringKind declaringKind)
{
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var accessors = default(SyntaxList<AccessorDeclarationSyntax>);
if (!openBrace.IsMissing || !this.IsTerminator())
{
// parse property accessors
var builder = _pool.Allocate<AccessorDeclarationSyntax>();
while (true)
{
if (this.CurrentToken.Kind == SyntaxKind.CloseBraceToken)
{
break;
}
else if (this.IsPossibleAccessor())
{
var acc = this.ParseAccessorDeclaration(declaringKind);
builder.Add(acc);
}
else if (this.SkipBadAccessorListTokens(ref openBrace, builder,
declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected) == PostSkipAction.Abort)
{
break;
}
}
accessors = _pool.ToListAndFree(builder);
}
return _syntaxFactory.AccessorList(
openBrace,
accessors,
this.EatToken(SyntaxKind.CloseBraceToken));
}
private ArrowExpressionClauseSyntax ParseArrowExpressionClause()
{
return _syntaxFactory.ArrowExpressionClause(
this.EatToken(SyntaxKind.EqualsGreaterThanToken),
ParsePossibleRefExpression());
}
private ExpressionSyntax ParsePossibleRefExpression()
{
// check for lambda expression with explicit ref return type: `ref int () => { ... }`
var refKeyword = this.CurrentToken.Kind == SyntaxKind.RefKeyword && !this.IsPossibleLambdaExpression(Precedence.Expression)
? this.EatToken()
: null;
var expression = this.ParseExpressionCore();
return refKeyword == null ? expression : _syntaxFactory.RefExpression(refKeyword, expression);
}
private PostSkipAction SkipBadAccessorListTokens(ref SyntaxToken openBrace, SyntaxListBuilder<AccessorDeclarationSyntax> list, ErrorCode error)
{
return this.SkipBadListTokensWithErrorCode(ref openBrace, list,
static p => p.CurrentToken.Kind != SyntaxKind.CloseBraceToken && !p.IsPossibleAccessor(),
static p => p.IsTerminator(),
error);
}
private bool IsPossibleAccessor()
{
return this.CurrentToken.Kind == SyntaxKind.IdentifierToken
|| IsPossibleAttributeDeclaration()
|| SyntaxFacts.GetAccessorDeclarationKind(this.CurrentToken.ContextualKind) != SyntaxKind.None
|| this.CurrentToken.Kind == SyntaxKind.OpenBraceToken // for accessor blocks w/ missing keyword
|| this.CurrentToken.Kind == SyntaxKind.SemicolonToken // for empty body accessors w/ missing keyword
|| IsPossibleAccessorModifier();
}
private bool IsPossibleAccessorModifier()
{
// We only want to accept a modifier as the start of an accessor if the modifiers are
// actually followed by "get/set/add/remove". Otherwise, we might thing think we're
// starting an accessor when we're actually starting a normal class member. For example:
//
// class C {
// public int Prop { get { this.
// private DateTime x;
//
// We don't want to think of the "private" in "private DateTime x" as starting an accessor
// here. If we do, we'll get totally thrown off in parsing the remainder and that will
// throw off the rest of the features that depend on a good syntax tree.
//
// Note: we allow all modifiers here. That's because we want to parse things like
// "abstract get" as an accessor. This way we can provide a good error message
// to the user that this is not allowed.
if (GetModifierExcludingScoped(this.CurrentToken) == DeclarationModifiers.None)
{
return false;
}
var peekIndex = 1;
while (GetModifierExcludingScoped(this.PeekToken(peekIndex)) != DeclarationModifiers.None)
{
peekIndex++;
}
var token = this.PeekToken(peekIndex);
if (token.Kind is SyntaxKind.CloseBraceToken or SyntaxKind.EndOfFileToken)
{
// If we see "{ get { } public }
// then we will think that "public" likely starts an accessor.
return true;
}
switch (token.ContextualKind)
{
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
case SyntaxKind.InitKeyword:
case SyntaxKind.AddKeyword:
case SyntaxKind.RemoveKeyword:
return true;
default:
return false;
}
}
private enum PostSkipAction
{
Continue,
Abort
}
private PostSkipAction SkipBadSeparatedListTokensWithExpectedKind<T, TNode>(
ref T startToken,
SeparatedSyntaxListBuilder<TNode> list,
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, SyntaxKind, bool> abortFunction,
SyntaxKind expected,
SyntaxKind closeKind = SyntaxKind.None)
where T : CSharpSyntaxNode
where TNode : CSharpSyntaxNode
{
// We're going to cheat here and pass the underlying SyntaxListBuilder of "list" to the helper method so that
// it can append skipped trivia to the last element, regardless of whether that element is a node or a token.
GreenNode trailingTrivia;
var action = this.SkipBadListTokensWithExpectedKindHelper(list.UnderlyingBuilder, isNotExpectedFunction, abortFunction, expected, closeKind, out trailingTrivia);
if (trailingTrivia != null)
{
startToken = AddTrailingSkippedSyntax(startToken, trailingTrivia);
}
return action;
}
private PostSkipAction SkipBadListTokensWithErrorCode<T, TNode>(
ref T startToken,
SyntaxListBuilder<TNode> list,
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, bool> abortFunction,
ErrorCode error)
where T : CSharpSyntaxNode
where TNode : CSharpSyntaxNode
{
GreenNode trailingTrivia;
var action = this.SkipBadListTokensWithErrorCodeHelper(list, isNotExpectedFunction, abortFunction, error, out trailingTrivia);
if (trailingTrivia != null)
{
startToken = AddTrailingSkippedSyntax(startToken, trailingTrivia);
}
return action;
}
/// <remarks>
/// WARNING: it is possible that "list" is really the underlying builder of a SeparateSyntaxListBuilder,
/// so it is important that we not add anything to the list.
/// </remarks>
private PostSkipAction SkipBadListTokensWithExpectedKindHelper(
SyntaxListBuilder list,
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, SyntaxKind, bool> abortFunction,
SyntaxKind expected,
SyntaxKind closeKind,
out GreenNode trailingTrivia)
{
if (list.Count == 0)
{
return SkipBadTokensWithExpectedKind(isNotExpectedFunction, abortFunction, expected, closeKind, out trailingTrivia);
}
else
{
GreenNode lastItemTrailingTrivia;
var action = SkipBadTokensWithExpectedKind(isNotExpectedFunction, abortFunction, expected, closeKind, out lastItemTrailingTrivia);
if (lastItemTrailingTrivia != null)
{
AddTrailingSkippedSyntax(list, lastItemTrailingTrivia);
}
trailingTrivia = null;
return action;
}
}
private PostSkipAction SkipBadListTokensWithErrorCodeHelper<TNode>(
SyntaxListBuilder<TNode> list,
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, bool> abortFunction,
ErrorCode error,
out GreenNode trailingTrivia) where TNode : CSharpSyntaxNode
{
if (list.Count == 0)
{
return SkipBadTokensWithErrorCode(isNotExpectedFunction, abortFunction, error, out trailingTrivia);
}
else
{
GreenNode lastItemTrailingTrivia;
var action = SkipBadTokensWithErrorCode(isNotExpectedFunction, abortFunction, error, out lastItemTrailingTrivia);
if (lastItemTrailingTrivia != null)
{
AddTrailingSkippedSyntax(list, lastItemTrailingTrivia);
}
trailingTrivia = null;
return action;
}
}
private PostSkipAction SkipBadTokensWithExpectedKind(
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, SyntaxKind, bool> abortFunction,
SyntaxKind expected,
SyntaxKind closeKind,
out GreenNode trailingTrivia)
{
var nodes = _pool.Allocate();
bool first = true;
var action = PostSkipAction.Continue;
while (isNotExpectedFunction(this))
{
if (abortFunction(this, closeKind) || this.IsTerminator())
{
action = PostSkipAction.Abort;
break;
}
var token = (first && !this.CurrentToken.ContainsDiagnostics) ? this.EatTokenWithPrejudice(expected) : this.EatToken();
first = false;
nodes.Add(token);
}
trailingTrivia = _pool.ToTokenListAndFree(nodes).Node;
return action;
}
private PostSkipAction SkipBadTokensWithErrorCode(
Func<LanguageParser, bool> isNotExpectedFunction,
Func<LanguageParser, bool> abortFunction,
ErrorCode errorCode,
out GreenNode trailingTrivia)
{
var nodes = _pool.Allocate();
bool first = true;
var action = PostSkipAction.Continue;
while (isNotExpectedFunction(this))
{
if (abortFunction(this))
{
action = PostSkipAction.Abort;
break;
}
var token = (first && !this.CurrentToken.ContainsDiagnostics) ? this.EatTokenWithPrejudice(errorCode) : this.EatToken();
first = false;
nodes.Add(token);
}
trailingTrivia = _pool.ToTokenListAndFree(nodes).Node;
return action;
}
private AccessorDeclarationSyntax ParseAccessorDeclaration(AccessorDeclaringKind declaringKind)
{
if (this.IsIncrementalAndFactoryContextMatches && SyntaxFacts.IsAccessorDeclaration(this.CurrentNodeKind))
{
return (AccessorDeclarationSyntax)this.EatNode();
}
using var __ = new FieldKeywordContext(this, isInFieldKeywordContext: declaringKind is AccessorDeclaringKind.Property);
var accMods = _pool.Allocate();
var accAttrs = this.ParseAttributeDeclarations(inExpressionContext: false);
this.ParseModifiers(accMods, forAccessors: true, forTopLevelStatements: false, isPossibleTypeDeclaration: out _);
var accessorName = this.EatToken(SyntaxKind.IdentifierToken,
declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected);
var accessorKind = GetAccessorKind(accessorName);
// Only convert the identifier to a keyword if it's a valid one. Otherwise any
// other contextual keyword (like 'partial') will be converted into a keyword
// and will be invalid.
if (accessorKind == SyntaxKind.UnknownAccessorDeclaration)
{
// We'll have an UnknownAccessorDeclaration either because we didn't have
// an IdentifierToken or because we have an IdentifierToken which is not
// add/remove/get/set. In the former case, we'll already have reported
// an error and will have a missing token. But in the latter case we need
// to report that the identifier is incorrect.
if (!accessorName.IsMissing)
{
accessorName = this.AddError(accessorName,
declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected);
}
else
{
Debug.Assert(accessorName.ContainsDiagnostics);
}
}
else
{
accessorName = ConvertToKeyword(accessorName);
}
BlockSyntax blockBody = null;
ArrowExpressionClauseSyntax expressionBody = null;
SyntaxToken semicolon = null;
bool currentTokenIsSemicolon = this.CurrentToken.Kind == SyntaxKind.SemicolonToken;
bool currentTokenIsArrow = this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken;
bool currentTokenIsOpenBraceToken = this.CurrentToken.Kind == SyntaxKind.OpenBraceToken;
if (currentTokenIsOpenBraceToken || currentTokenIsArrow)
{
this.ParseBlockAndExpressionBodiesWithSemicolon(
out blockBody, out expressionBody, out semicolon);
}
else if (currentTokenIsSemicolon)
{
semicolon = EatAccessorSemicolon();
}
else
{
// We didn't get something we recognized. If we got an accessor type we
// recognized (i.e. get/set/init/add/remove) then try to parse out a block.
// Only do this if it doesn't seem like we're at the end of the accessor/property.
// for example, if we have "get set", don't actually try to parse out the
// block. Otherwise we'll consume the 'set'. In that case, just end the
// current accessor with a semicolon so we can properly consume the next
// in the calling method's loop.
if (accessorKind != SyntaxKind.UnknownAccessorDeclaration)
{
if (!IsTerminator())
{
blockBody = this.ParseMethodOrAccessorBodyBlock(attributes: default, isAccessorBody: true);
}
else
{
semicolon = EatAccessorSemicolon();
}
}
else
{
// Don't bother eating anything if we didn't even have a valid accessor.
// It will just lead to more errors. Note: we will have already produced
// a good error by now.
Debug.Assert(accessorName.ContainsDiagnostics);
}
}
return _syntaxFactory.AccessorDeclaration(
accessorKind,
accAttrs,
_pool.ToTokenListAndFree(accMods),
accessorName,
blockBody,
expressionBody,
semicolon);
}
private SyntaxToken EatAccessorSemicolon()
=> this.EatToken(SyntaxKind.SemicolonToken,
IsFeatureEnabled(MessageID.IDS_FeatureExpressionBodiedAccessor)
? ErrorCode.ERR_SemiOrLBraceOrArrowExpected
: ErrorCode.ERR_SemiOrLBraceExpected);
private static SyntaxKind GetAccessorKind(SyntaxToken accessorName)
{
return accessorName.ContextualKind switch
{
SyntaxKind.GetKeyword => SyntaxKind.GetAccessorDeclaration,
SyntaxKind.SetKeyword => SyntaxKind.SetAccessorDeclaration,
SyntaxKind.InitKeyword => SyntaxKind.InitAccessorDeclaration,
SyntaxKind.AddKeyword => SyntaxKind.AddAccessorDeclaration,
SyntaxKind.RemoveKeyword => SyntaxKind.RemoveAccessorDeclaration,
_ => SyntaxKind.UnknownAccessorDeclaration,
};
}
internal ParameterListSyntax ParseParenthesizedParameterList()
{
if (this.IsIncrementalAndFactoryContextMatches && CanReuseParameterList(this.CurrentNode as CSharp.Syntax.ParameterListSyntax))
{
return (ParameterListSyntax)this.EatNode();
}
var parameters = this.ParseParameterList(out var open, out var close, SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken);
return _syntaxFactory.ParameterList(open, parameters, close);
}
internal BracketedParameterListSyntax ParseBracketedParameterList()
{
if (this.IsIncrementalAndFactoryContextMatches && CanReuseBracketedParameterList(this.CurrentNode as CSharp.Syntax.BracketedParameterListSyntax))
{
return (BracketedParameterListSyntax)this.EatNode();
}
var parameters = this.ParseParameterList(out var open, out var close, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
return _syntaxFactory.BracketedParameterList(open, parameters, close);
}
private static bool CanReuseParameterList(CSharp.Syntax.ParameterListSyntax list)
{
if (list == null)
{
return false;
}
if (list.OpenParenToken.IsMissing)
{
return false;
}
if (list.CloseParenToken.IsMissing)
{
return false;
}
foreach (var parameter in list.Parameters)
{
if (!CanReuseParameter(parameter))
{
return false;
}
}
return true;
}
private static bool CanReuseBracketedParameterList(CSharp.Syntax.BracketedParameterListSyntax list)
{
if (list == null)
{
return false;
}
if (list.OpenBracketToken.IsMissing)
{
return false;
}
if (list.CloseBracketToken.IsMissing)
{
return false;
}
foreach (var parameter in list.Parameters)
{
if (!CanReuseParameter(parameter))
{
return false;
}
}
return true;
}
private SeparatedSyntaxList<ParameterSyntax> ParseParameterList(
out SyntaxToken open,
out SyntaxToken close,
SyntaxKind openKind,
SyntaxKind closeKind)
{
open = this.EatToken(openKind);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfParameterList;
var parameters = ParseCommaSeparatedSyntaxList(
ref open,
closeKind,
static @this => @this.IsPossibleParameter(),
static @this => @this.ParseParameter(),
skipBadParameterListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
_termState = saveTerm;
close = this.EatToken(closeKind);
return parameters;
static PostSkipAction skipBadParameterListTokens(
LanguageParser @this, ref SyntaxToken open, SeparatedSyntaxListBuilder<ParameterSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleParameter(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private bool IsEndOfParameterList()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken or SyntaxKind.SemicolonToken;
}
private bool IsPossibleParameter()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.OpenBracketToken: // attribute
case SyntaxKind.ArgListKeyword:
case SyntaxKind.OpenParenToken: // tuple
case SyntaxKind.DelegateKeyword when IsFunctionPointerStart(): // Function pointer type
return true;
case SyntaxKind.IdentifierToken:
return this.IsTrueIdentifier();
default:
return IsParameterModifierExcludingScoped(this.CurrentToken) || IsPossibleScopedKeyword(isFunctionPointerParameter: false) || IsPredefinedType(this.CurrentToken.Kind);
}
}
private static bool CanReuseParameter(CSharp.Syntax.ParameterSyntax parameter)
{
if (parameter == null)
{
return false;
}
// cannot reuse a node that possibly ends in an expression
if (parameter.Default != null)
{
return false;
}
// cannot reuse lambda parameters as normal parameters (parsed with
// different rules)
CSharp.CSharpSyntaxNode parent = parameter.Parent;
if (parent != null)
{
if (parent.Kind() == SyntaxKind.SimpleLambdaExpression)
{
return false;
}
CSharp.CSharpSyntaxNode grandparent = parent.Parent;
if (grandparent != null && grandparent.Kind() == SyntaxKind.ParenthesizedLambdaExpression)
{
Debug.Assert(parent.Kind() == SyntaxKind.ParameterList);
return false;
}
}
return true;
}
#nullable enable
private ParameterSyntax ParseParameter()
{
if (this.IsIncrementalAndFactoryContextMatches && CanReuseParameter(this.CurrentNode as CSharp.Syntax.ParameterSyntax))
{
return (ParameterSyntax)this.EatNode();
}
var attributes = this.ParseAttributeDeclarations(inExpressionContext: false);
var modifiers = _pool.Allocate();
this.ParseParameterModifiers(modifiers, isFunctionPointerParameter: false);
if (this.CurrentToken.Kind == SyntaxKind.ArgListKeyword)
{
// We store an __arglist parameter as a parameter with null type and whose
// .Identifier has the kind ArgListKeyword.
return _syntaxFactory.Parameter(
attributes, modifiers.ToList(), type: null, this.EatToken(SyntaxKind.ArgListKeyword), @default: null);
}
var type = this.ParseType(mode: ParseTypeMode.Parameter);
SyntaxToken identifier;
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken && IsCurrentTokenWhereOfConstraintClause())
{
identifier = this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected);
}
else
{
identifier = this.ParseIdentifierToken();
}
// When the user type "int goo[]", give them a useful error
if (this.CurrentToken.Kind is SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind is SyntaxKind.CloseBracketToken)
{
identifier = AddTrailingSkippedSyntax(identifier, SyntaxList.List(
this.AddError(this.EatToken(), ErrorCode.ERR_BadArraySyntax),
this.EatToken()));
}
var equalsToken = TryEatToken(SyntaxKind.EqualsToken);
return _syntaxFactory.Parameter(
attributes,
_pool.ToTokenListAndFree(modifiers),
type,
identifier,
equalsToken == null ? null : _syntaxFactory.EqualsValueClause(equalsToken, this.ParseExpressionCore()));
}
internal static bool NoTriviaBetween(SyntaxToken token1, SyntaxToken token2)
=> token1.GetTrailingTriviaWidth() == 0 && token2.GetLeadingTriviaWidth() == 0;
#nullable disable
private static bool IsParameterModifierExcludingScoped(SyntaxToken token)
{
switch (token.Kind)
{
case SyntaxKind.ThisKeyword:
case SyntaxKind.RefKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.ParamsKeyword:
case SyntaxKind.ReadOnlyKeyword:
return true;
}
return false;
}
private void ParseParameterModifiers(SyntaxListBuilder modifiers, bool isFunctionPointerParameter)
{
bool tryScoped = true;
while (IsParameterModifierExcludingScoped(this.CurrentToken))
{
if (this.CurrentToken.Kind is SyntaxKind.RefKeyword or SyntaxKind.OutKeyword or SyntaxKind.InKeyword or SyntaxKind.ReadOnlyKeyword)
{
tryScoped = false;
}
modifiers.Add(this.EatToken());
}
if (tryScoped)
{
SyntaxToken scopedKeyword = ParsePossibleScopedKeyword(isFunctionPointerParameter);
if (scopedKeyword != null)
{
modifiers.Add(scopedKeyword);
// Look if ref/out/in/readonly are next
while (this.CurrentToken.Kind is (SyntaxKind.RefKeyword or SyntaxKind.OutKeyword or SyntaxKind.InKeyword or SyntaxKind.ReadOnlyKeyword))
{
modifiers.Add(this.EatToken());
}
}
}
}
private FieldDeclarationSyntax ParseFixedSizeBufferDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
SyntaxKind parentKind)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.FixedKeyword);
modifiers.Add(this.EatToken());
var type = this.ParseType();
return _syntaxFactory.FieldDeclaration(
attributes, modifiers.ToList(),
_syntaxFactory.VariableDeclaration(
type, this.ParseFieldDeclarationVariableDeclarators(type, VariableFlags.Fixed, parentKind)),
this.EatToken(SyntaxKind.SemicolonToken));
}
private MemberDeclarationSyntax ParseEventDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
SyntaxKind parentKind)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.EventKeyword);
var eventToken = this.EatToken();
var type = this.ParseType();
return IsFieldDeclaration(isEvent: true, isGlobalScriptLevel: parentKind == SyntaxKind.CompilationUnit)
? this.ParseEventFieldDeclaration(attributes, modifiers, eventToken, type, parentKind)
: this.ParseEventDeclarationWithAccessors(attributes, modifiers, eventToken, type);
}
private EventDeclarationSyntax ParseEventDeclarationWithAccessors(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
SyntaxToken eventToken,
TypeSyntax type)
{
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt;
SyntaxToken identifierOrThisOpt;
TypeParameterListSyntax typeParameterList;
this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterList, isEvent: true);
// If we got an explicitInterfaceOpt but not an identifier, then we're in the special
// case for ERR_ExplicitEventFieldImpl (see ParseMemberName for details).
if (explicitInterfaceOpt != null && this.CurrentToken.Kind is not SyntaxKind.OpenBraceToken and not SyntaxKind.SemicolonToken)
{
Debug.Assert(typeParameterList == null, "Exit condition of ParseMemberName in this scenario");
return _syntaxFactory.EventDeclaration(
attributes,
modifiers.ToList(),
eventToken,
type,
//already has an appropriate error attached
explicitInterfaceOpt,
// No need for a diagnostic, ParseMemberName has already added one.
identifierOrThisOpt == null ? CreateMissingIdentifierToken() : identifierOrThisOpt,
_syntaxFactory.AccessorList(
SyntaxFactory.MissingToken(SyntaxKind.OpenBraceToken),
default(SyntaxList<AccessorDeclarationSyntax>),
SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken)),
semicolonToken: null);
}
SyntaxToken identifier;
if (identifierOrThisOpt == null)
{
identifier = CreateMissingIdentifierToken();
}
else if (identifierOrThisOpt.Kind != SyntaxKind.IdentifierToken)
{
Debug.Assert(identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword);
identifier = ConvertToMissingWithTrailingTrivia(identifierOrThisOpt, SyntaxKind.IdentifierToken);
}
else
{
identifier = identifierOrThisOpt;
}
Debug.Assert(identifier != null);
Debug.Assert(identifier.Kind == SyntaxKind.IdentifierToken);
if (identifier.IsMissing && !type.IsMissing)
{
identifier = this.AddError(identifier, ErrorCode.ERR_IdentifierExpected);
}
if (typeParameterList != null) // check to see if the user tried to create a generic event.
{
identifier = AddTrailingSkippedSyntax(identifier, typeParameterList);
identifier = this.AddError(identifier, ErrorCode.ERR_UnexpectedGenericName);
}
AccessorListSyntax accessorList = null;
SyntaxToken semicolon = null;
if (explicitInterfaceOpt != null && this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
else
{
accessorList = this.ParseAccessorList(AccessorDeclaringKind.Event);
}
var decl = _syntaxFactory.EventDeclaration(
attributes,
modifiers.ToList(),
eventToken,
type,
explicitInterfaceOpt,
identifier,
accessorList,
semicolon);
decl = EatUnexpectedTrailingSemicolon(decl);
return decl;
}
private TNode EatUnexpectedTrailingSemicolon<TNode>(TNode decl) where TNode : CSharpSyntaxNode
{
// allow for case of one unexpected semicolon...
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
var semi = this.EatToken();
semi = this.AddError(semi, ErrorCode.ERR_UnexpectedSemicolon);
decl = AddTrailingSkippedSyntax(decl, semi);
}
return decl;
}
private FieldDeclarationSyntax ParseNormalFieldDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
TypeSyntax type,
SyntaxKind parentKind)
{
var variables = this.ParseFieldDeclarationVariableDeclarators(type, flags: VariableFlags.LocalOrField, parentKind);
// Make 'scoped' part of the type when it is the last token in the modifiers list
if (modifiers is [.., SyntaxToken { Kind: SyntaxKind.ScopedKeyword } scopedKeyword])
{
type = _syntaxFactory.ScopedType(scopedKeyword, type);
modifiers.RemoveLast();
}
return _syntaxFactory.FieldDeclaration(
attributes,
modifiers.ToList(),
_syntaxFactory.VariableDeclaration(type, variables),
this.EatToken(SyntaxKind.SemicolonToken));
}
private EventFieldDeclarationSyntax ParseEventFieldDeclaration(
SyntaxList<AttributeListSyntax> attributes,
SyntaxListBuilder modifiers,
SyntaxToken eventToken,
TypeSyntax type,
SyntaxKind parentKind)
{
// An attribute specified on an event declaration that omits event accessors can apply
// to the event being declared, to the associated field (if the event is not abstract),
// or to the associated add and remove methods. In the absence of an
// attribute-target-specifier, the attribute applies to the event. The presence of the
// event attribute-target-specifier indicates that the attribute applies to the event;
// the presence of the field attribute-target-specifier indicates that the attribute
// applies to the field; and the presence of the method attribute-target-specifier
// indicates that the attribute applies to the methods.
//
// NOTE(cyrusn): We allow more than the above here. Specifically, even if the event is
// abstract, we allow the attribute to specify that it belongs to a field. Later, in the
// semantic pass, we will disallow this.
var variables = this.ParseFieldDeclarationVariableDeclarators(type, flags: 0, parentKind);
if (this.CurrentToken.Kind == SyntaxKind.DotToken)
{
// Better error message for confusing event situation.
eventToken = this.AddError(eventToken, ErrorCode.ERR_ExplicitEventFieldImpl);
}
return _syntaxFactory.EventFieldDeclaration(
attributes,
modifiers.ToList(),
eventToken,
_syntaxFactory.VariableDeclaration(type, variables),
this.EatToken(SyntaxKind.SemicolonToken));
}
private bool IsEndOfFieldDeclaration()
{
return this.CurrentToken.Kind == SyntaxKind.SemicolonToken;
}
private SeparatedSyntaxList<VariableDeclaratorSyntax> ParseFieldDeclarationVariableDeclarators(
TypeSyntax type, VariableFlags flags, SyntaxKind parentKind)
{
// Although we try parse variable declarations in contexts where they are not allowed (non-interactive top-level or a namespace)
// the reported errors should take into consideration whether or not one expects them in the current context.
bool variableDeclarationsExpected =
parentKind is not SyntaxKind.NamespaceDeclaration and not SyntaxKind.FileScopedNamespaceDeclaration &&
(parentKind != SyntaxKind.CompilationUnit || IsScript);
var variables = _pool.AllocateSeparated<VariableDeclaratorSyntax>();
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfFieldDeclaration;
ParseVariableDeclarators(
type,
flags,
variables,
variableDeclarationsExpected,
allowLocalFunctions: false,
// A field declaration doesn't have a `(...)` construct. So no need to stop if we hit a close paren
// after a declarator. Let normal error recovery kick in.
stopOnCloseParen: false,
attributes: default,
mods: default,
out var localFunction);
Debug.Assert(localFunction == null);
_termState = saveTerm;
return _pool.ToListAndFree(variables);
}
private void ParseVariableDeclarators(
TypeSyntax type,
VariableFlags flags,
SeparatedSyntaxListBuilder<VariableDeclaratorSyntax> variables,
bool variableDeclarationsExpected,
bool allowLocalFunctions,
bool stopOnCloseParen,
SyntaxList<AttributeListSyntax> attributes,
SyntaxList<SyntaxToken> mods,
out LocalFunctionStatementSyntax localFunction)
{
variables.Add(
this.ParseVariableDeclarator(
type,
flags,
isFirst: true,
allowLocalFunctions: allowLocalFunctions,
attributes: attributes,
mods: mods,
localFunction: out localFunction));
if (localFunction != null)
{
// ParseVariableDeclarator returns null, so it is not added to variables
Debug.Assert(variables.Count == 0);
return;
}
while (true)
{
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
break;
}
else if (stopOnCloseParen && this.CurrentToken.Kind == SyntaxKind.CloseParenToken)
{
break;
}
else if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
// If we see `for (int i = 0, j < ...` then we do not want to consume j as the next declarator.
//
// Legal forms here are `for (int i = 0, j; ...` or `for (int i = 0, j = ...` or `for (int i = 0, j)`.
//
// We also accept: `for (int i = 0, ;` as that's likely an intermediary state prior to writing the next
// variable.
//
// Anything else we'll treat as as more likely to be the following conditional.
if (flags.HasFlag(VariableFlags.ForStatement) && this.PeekToken(1).Kind != SyntaxKind.SemicolonToken)
{
// `int i = 0, ...` where what follows is not an identifier. Don't treat this as the start of a
// second variable.
if (!IsTrueIdentifier(this.PeekToken(1)))
break;
// `int i = 0, j ...` where what follows is not something that continues a variable declaration.
// In this case, treat that `j` as the start of the condition expression instead.
if (this.PeekToken(2).Kind is not (SyntaxKind.SemicolonToken or SyntaxKind.EqualsToken or SyntaxKind.CloseParenToken))
break;
}
variables.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
variables.Add(
this.ParseVariableDeclarator(
type,
flags,
isFirst: false,
allowLocalFunctions: false,
attributes: attributes,
mods: mods,
localFunction: out localFunction));
Debug.Assert(localFunction is null);
}
else if (!variableDeclarationsExpected || this.SkipBadVariableListTokens(variables, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
}
private PostSkipAction SkipBadVariableListTokens(SeparatedSyntaxListBuilder<VariableDeclaratorSyntax> list, SyntaxKind expected)
{
CSharpSyntaxNode tmp = null;
Debug.Assert(list.Count > 0);
return this.SkipBadSeparatedListTokensWithExpectedKind(ref tmp, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken,
static (p, _) => p.CurrentToken.Kind == SyntaxKind.SemicolonToken,
expected);
}
[Flags]
private enum VariableFlags
{
None = 0,
Fixed = 0x01,
Const = 0x02,
LocalOrField = 0x04,
ForStatement = 0x08,
}
private static SyntaxTokenList GetOriginalModifiers(CSharp.CSharpSyntaxNode decl)
{
if (decl != null)
{
switch (decl.Kind())
{
case SyntaxKind.FieldDeclaration:
return ((CSharp.Syntax.FieldDeclarationSyntax)decl).Modifiers;
case SyntaxKind.MethodDeclaration:
return ((CSharp.Syntax.MethodDeclarationSyntax)decl).Modifiers;
case SyntaxKind.ConstructorDeclaration:
return ((CSharp.Syntax.ConstructorDeclarationSyntax)decl).Modifiers;
case SyntaxKind.DestructorDeclaration:
return ((CSharp.Syntax.DestructorDeclarationSyntax)decl).Modifiers;
case SyntaxKind.PropertyDeclaration:
return ((CSharp.Syntax.PropertyDeclarationSyntax)decl).Modifiers;
case SyntaxKind.EventFieldDeclaration:
return ((CSharp.Syntax.EventFieldDeclarationSyntax)decl).Modifiers;
case SyntaxKind.AddAccessorDeclaration:
case SyntaxKind.RemoveAccessorDeclaration:
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
case SyntaxKind.InitAccessorDeclaration:
return ((CSharp.Syntax.AccessorDeclarationSyntax)decl).Modifiers;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.RecordDeclaration:
case SyntaxKind.RecordStructDeclaration:
return ((CSharp.Syntax.TypeDeclarationSyntax)decl).Modifiers;
case SyntaxKind.DelegateDeclaration:
return ((CSharp.Syntax.DelegateDeclarationSyntax)decl).Modifiers;
}
}
return default(SyntaxTokenList);
}
private static bool WasFirstVariable(CSharp.Syntax.VariableDeclaratorSyntax variable)
{
if (GetOldParent(variable) is CSharp.Syntax.VariableDeclarationSyntax parent)
{
return parent.Variables[0] == variable;
}
return false;
}
private static VariableFlags GetOriginalVariableFlags(CSharp.Syntax.VariableDeclaratorSyntax old)
{
var parent = GetOldParent(old);
var mods = GetOriginalModifiers(parent);
VariableFlags flags = default(VariableFlags);
if (mods.Any(SyntaxKind.FixedKeyword))
{
flags |= VariableFlags.Fixed;
}
if (mods.Any(SyntaxKind.ConstKeyword))
{
flags |= VariableFlags.Const;
}
if (parent != null && (parent.Kind() == SyntaxKind.VariableDeclaration || parent.Kind() == SyntaxKind.LocalDeclarationStatement))
{
flags |= VariableFlags.LocalOrField;
}
return flags;
}
private static bool CanReuseVariableDeclarator(CSharp.Syntax.VariableDeclaratorSyntax old, VariableFlags flags, bool isFirst)
{
if (old == null)
{
return false;
}
SyntaxKind oldKind;
return (flags == GetOriginalVariableFlags(old))
&& (isFirst == WasFirstVariable(old))
&& old.Initializer == null // can't reuse node that possibly ends in an expression
&& (oldKind = GetOldParent(old).Kind()) != SyntaxKind.VariableDeclaration // or in a method body
&& oldKind != SyntaxKind.LocalDeclarationStatement;
}
private VariableDeclaratorSyntax ParseVariableDeclarator(
TypeSyntax parentType,
VariableFlags flags,
bool isFirst,
bool allowLocalFunctions,
SyntaxList<AttributeListSyntax> attributes,
SyntaxList<SyntaxToken> mods,
out LocalFunctionStatementSyntax localFunction,
bool isExpressionContext = false)
{
if (this.IsIncrementalAndFactoryContextMatches && CanReuseVariableDeclarator(this.CurrentNode as CSharp.Syntax.VariableDeclaratorSyntax, flags, isFirst))
{
localFunction = null;
return (VariableDeclaratorSyntax)this.EatNode();
}
if (!isExpressionContext)
{
// Check for the common pattern of:
//
// C //<-- here
// Console.WriteLine();
//
// Standard greedy parsing will assume that this should be parsed as a variable
// declaration: "C Console". We want to avoid that as it can confused parts of the
// system further up. So, if we see certain things following the identifier, then we can
// assume it's not the actual name.
//
// So, if we're after a newline and we see a name followed by the list below, then we
// assume that we're accidentally consuming too far into the next statement.
//
// <dot>, <arrow>, any binary operator (except =), <question>. None of these characters
// are allowed in a normal variable declaration. This also provides a more useful error
// message to the user. Instead of telling them that a semicolon is expected after the
// following token, then instead get a useful message about an identifier being missing.
// The above list prevents:
//
// C //<-- here
// Console.WriteLine();
//
// C //<-- here
// Console->WriteLine();
//
// C
// A + B;
//
// C
// A ? B : D;
//
// C
// A()
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
var currentTokenKind = this.CurrentToken.Kind;
if (currentTokenKind == SyntaxKind.IdentifierToken && !parentType.IsMissing)
{
var isAfterNewLine = parentType.GetLastToken().TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia);
if (isAfterNewLine)
{
int offset, width;
this.GetDiagnosticSpanForMissingToken(out offset, out width);
this.EatToken();
currentTokenKind = this.CurrentToken.Kind;
var isNonEqualsBinaryToken =
currentTokenKind != SyntaxKind.EqualsToken &&
SyntaxFacts.IsBinaryExpressionOperatorToken(currentTokenKind);
if (currentTokenKind is SyntaxKind.DotToken or SyntaxKind.OpenParenToken or SyntaxKind.MinusGreaterThanToken ||
isNonEqualsBinaryToken)
{
var isPossibleLocalFunctionToken = currentTokenKind is SyntaxKind.OpenParenToken or SyntaxKind.LessThanToken;
// Make sure this isn't a local function
if (!isPossibleLocalFunctionToken || !IsLocalFunctionAfterIdentifier())
{
var missingIdentifier = CreateMissingIdentifierToken();
missingIdentifier = this.AddError(missingIdentifier, offset, width, ErrorCode.ERR_IdentifierExpected);
localFunction = null;
return _syntaxFactory.VariableDeclarator(missingIdentifier, null, null);
}
}
}
}
}
// NOTE: Diverges from Dev10.
//
// When we see parse an identifier and we see the partial contextual keyword, we check
// to see whether it is already attached to a partial class or partial method
// declaration. However, in the specific case of variable declarators, Dev10
// specifically treats it as a variable name, even if it could be interpreted as a
// keyword.
var name = this.ParseIdentifierToken();
BracketedArgumentListSyntax argumentList = null;
EqualsValueClauseSyntax initializer = null;
TerminatorState saveTerm = _termState;
bool isFixed = (flags & VariableFlags.Fixed) != 0;
bool isConst = (flags & VariableFlags.Const) != 0;
bool isLocalOrField = (flags & VariableFlags.LocalOrField) != 0;
// Give better error message in the case where the user did something like:
//
// X x = 1, Y y = 2;
// using (X x = expr1, Y y = expr2) ...
//
// The superfluous type name is treated as variable (it is an identifier) and a missing ',' is injected after it.
if (!isFirst && this.IsTrueIdentifier())
{
name = this.AddError(name, ErrorCode.ERR_MultiTypeInDeclaration);
}
switch (this.CurrentToken.Kind)
{
case SyntaxKind.EqualsToken:
if (isFixed)
{
goto default;
}
var equals = this.EatToken();
// check for lambda expression with explicit ref return type: `ref int () => { ... }`
var refKeyword = isLocalOrField && !isConst && this.CurrentToken.Kind == SyntaxKind.RefKeyword && !this.IsPossibleLambdaExpression(Precedence.Expression)
? this.EatToken()
: null;
var init = this.ParseVariableInitializer();
initializer = _syntaxFactory.EqualsValueClause(
equals,
refKeyword == null ? init : _syntaxFactory.RefExpression(refKeyword, init));
break;
case SyntaxKind.LessThanToken:
if (allowLocalFunctions && isFirst)
{
localFunction = TryParseLocalFunctionStatementBody(attributes, mods, parentType, name);
if (localFunction != null)
{
return null;
}
}
goto default;
case SyntaxKind.OpenParenToken:
if (allowLocalFunctions && isFirst)
{
localFunction = TryParseLocalFunctionStatementBody(attributes, mods, parentType, name);
if (localFunction != null)
{
return null;
}
}
// Special case for accidental use of C-style constructors
// Fake up something to hold the arguments.
_termState |= TerminatorState.IsPossibleEndOfVariableDeclaration;
argumentList = this.ParseBracketedArgumentList();
_termState = saveTerm;
argumentList = this.AddError(argumentList, ErrorCode.ERR_BadVarDecl);
break;
case SyntaxKind.OpenBracketToken:
bool sawNonOmittedSize;
_termState |= TerminatorState.IsPossibleEndOfVariableDeclaration;
var specifier = this.ParseArrayRankSpecifier(sawNonOmittedSize: out sawNonOmittedSize);
_termState = saveTerm;
var open = specifier.OpenBracketToken;
var sizes = specifier.Sizes;
var close = specifier.CloseBracketToken;
if (isFixed && !sawNonOmittedSize)
{
close = this.AddError(close, ErrorCode.ERR_ValueExpected);
}
var args = _pool.AllocateSeparated<ArgumentSyntax>();
var withSeps = sizes.GetWithSeparators();
foreach (var item in withSeps)
{
if (item is ExpressionSyntax expression)
{
bool isOmitted = expression.Kind == SyntaxKind.OmittedArraySizeExpression;
if (!isFixed && !isOmitted)
{
expression = this.AddError(expression, ErrorCode.ERR_ArraySizeInDeclaration);
}
args.Add(_syntaxFactory.Argument(null, refKindKeyword: null, expression));
}
else
{
args.AddSeparator((SyntaxToken)item);
}
}
argumentList = _syntaxFactory.BracketedArgumentList(open, _pool.ToListAndFree(args), close);
if (!isFixed)
{
argumentList = this.AddError(argumentList, ErrorCode.ERR_CStyleArray);
// If we have "int x[] = new int[10];" then parse the initializer.
if (this.CurrentToken.Kind == SyntaxKind.EqualsToken)
{
goto case SyntaxKind.EqualsToken;
}
}
break;
default:
if (isConst)
{
name = this.AddError(name, ErrorCode.ERR_ConstValueRequired); // Error here for missing constant initializers
}
else if (isFixed)
{
if (parentType.Kind == SyntaxKind.ArrayType)
{
// They accidentally put the array before the identifier
name = this.AddError(name, ErrorCode.ERR_FixedDimsRequired);
}
else
{
goto case SyntaxKind.OpenBracketToken;
}
}
break;
}
localFunction = null;
return _syntaxFactory.VariableDeclarator(name, argumentList, initializer);
}
// Is there a local function after an eaten identifier?
private bool IsLocalFunctionAfterIdentifier()
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.OpenParenToken or SyntaxKind.LessThanToken);
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
var typeParameterListOpt = this.ParseTypeParameterList();
var paramList = ParseParenthesizedParameterList();
if (!paramList.IsMissing &&
(this.CurrentToken.Kind is SyntaxKind.OpenBraceToken or SyntaxKind.EqualsGreaterThanToken ||
this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword))
{
return true;
}
return false;
}
private bool IsPossibleEndOfVariableDeclaration()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.CommaToken:
case SyntaxKind.SemicolonToken:
return true;
default:
return false;
}
}
private ExpressionSyntax ParseVariableInitializer()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
? this.ParseArrayInitializer()
: this.ParseExpressionCore();
}
private bool IsPossibleVariableInitializer()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBraceToken || this.IsPossibleExpression();
}
private FieldDeclarationSyntax ParseConstantFieldDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers, SyntaxKind parentKind)
{
modifiers.Add(this.EatToken(SyntaxKind.ConstKeyword));
var type = this.ParseType();
return _syntaxFactory.FieldDeclaration(
attributes,
modifiers.ToList(),
_syntaxFactory.VariableDeclaration(
type,
this.ParseFieldDeclarationVariableDeclarators(type, VariableFlags.Const, parentKind)),
this.EatToken(SyntaxKind.SemicolonToken));
}
private DelegateDeclarationSyntax ParseDelegateDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.DelegateKeyword);
var delegateToken = this.EatToken(SyntaxKind.DelegateKeyword);
var type = this.ParseReturnType();
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfMethodSignature;
var name = this.ParseIdentifierToken();
var typeParameters = this.ParseTypeParameterList();
var parameterList = this.ParseParenthesizedParameterList();
var constraints = default(SyntaxListBuilder<TypeParameterConstraintClauseSyntax>);
if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
constraints = _pool.Allocate<TypeParameterConstraintClauseSyntax>();
this.ParseTypeParameterConstraintClauses(constraints);
}
_termState = saveTerm;
return _syntaxFactory.DelegateDeclaration(
attributes,
modifiers.ToList(),
delegateToken,
type,
name,
typeParameters,
parameterList,
_pool.ToListAndFree(constraints),
this.EatToken(SyntaxKind.SemicolonToken));
}
private EnumDeclarationSyntax ParseEnumDeclaration(SyntaxList<AttributeListSyntax> attributes, SyntaxListBuilder modifiers)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.EnumKeyword);
var enumToken = this.EatToken(SyntaxKind.EnumKeyword);
var name = this.ParseIdentifierToken();
// check to see if the user tried to create a generic enum.
var typeParameters = this.ParseTypeParameterList();
if (typeParameters != null)
{
name = AddTrailingSkippedSyntax(name, typeParameters);
name = this.AddError(name, ErrorCode.ERR_UnexpectedGenericName);
}
BaseListSyntax baseList = null;
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
var colon = this.EatToken(SyntaxKind.ColonToken);
var type = this.ParseType();
var tmpList = _pool.AllocateSeparated<BaseTypeSyntax>();
tmpList.Add(_syntaxFactory.SimpleBaseType(type));
baseList = _syntaxFactory.BaseList(
colon,
_pool.ToListAndFree(tmpList));
}
var members = default(SeparatedSyntaxList<EnumMemberDeclarationSyntax>);
SyntaxToken semicolon;
SyntaxToken openBrace;
SyntaxToken closeBrace;
if (CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
semicolon = EatToken(SyntaxKind.SemicolonToken);
openBrace = null;
closeBrace = null;
}
else
{
openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
if (!openBrace.IsMissing)
{
// It's not uncommon for people to use semicolons to separate out enum members. So be resilient to
// that, successfully consuming them as separators, while telling the user it needs to be a comma
// instead.
members = this.ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsPossibleEnumMemberDeclaration(),
static @this => @this.ParseEnumMemberDeclaration(),
skipBadEnumMemberListTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: true);
}
closeBrace = this.EatToken(SyntaxKind.CloseBraceToken);
semicolon = TryEatToken(SyntaxKind.SemicolonToken);
}
return _syntaxFactory.EnumDeclaration(
attributes,
modifiers.ToList(),
enumToken,
name,
baseList,
openBrace,
members,
closeBrace,
semicolon);
static PostSkipAction skipBadEnumMemberListTokens(
LanguageParser @this, ref SyntaxToken openBrace, SeparatedSyntaxListBuilder<EnumMemberDeclarationSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openBrace, list,
static p => p.CurrentToken.Kind is not SyntaxKind.CommaToken and not SyntaxKind.SemicolonToken && !p.IsPossibleEnumMemberDeclaration(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private EnumMemberDeclarationSyntax ParseEnumMemberDeclaration()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.EnumMemberDeclaration)
{
return (EnumMemberDeclarationSyntax)this.EatNode();
}
var memberAttrs = this.ParseAttributeDeclarations(inExpressionContext: false);
var memberName = this.ParseIdentifierToken();
EqualsValueClauseSyntax equalsValue = null;
if (this.CurrentToken.Kind == SyntaxKind.EqualsToken)
{
//an identifier is a valid expression
equalsValue = _syntaxFactory.EqualsValueClause(
this.EatToken(SyntaxKind.EqualsToken),
this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.CloseBraceToken
? this.ParseIdentifierName(ErrorCode.ERR_ConstantExpected)
: this.ParseExpressionCore());
}
return _syntaxFactory.EnumMemberDeclaration(memberAttrs, modifiers: default, memberName, equalsValue);
}
private bool IsPossibleEnumMemberDeclaration()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBracketToken || this.IsTrueIdentifier();
}
private bool IsDotOrColonColon()
{
return this.CurrentToken.Kind is SyntaxKind.DotToken or SyntaxKind.ColonColonToken;
}
// This is public and parses open types. You probably don't want to use it.
public NameSyntax ParseName()
{
return this.ParseQualifiedName();
}
private IdentifierNameSyntax CreateMissingIdentifierName()
{
return _syntaxFactory.IdentifierName(CreateMissingIdentifierToken());
}
private static SyntaxToken CreateMissingIdentifierToken()
{
return SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken);
}
[Flags]
private enum NameOptions
{
None = 0,
InExpression = 1 << 0, // Used to influence parser ambiguity around "<" and generics vs. expressions. Used in ParseSimpleName.
InTypeList = 1 << 1, // Allows attributes to appear within the generic type argument list. Used during ParseInstantiation.
PossiblePattern = 1 << 2, // Used to influence parser ambiguity around "<" and generics vs. expressions on the right of 'is'
AfterIs = 1 << 3,
DefinitePattern = 1 << 4,
AfterOut = 1 << 5,
AfterTupleComma = 1 << 6,
FirstElementOfPossibleTupleLiteral = 1 << 7,
}
/// <summary>
/// True if current identifier token is not really some contextual keyword
/// </summary>
/// <returns></returns>
private bool IsTrueIdentifier()
{
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
if (!IsCurrentTokenPartialKeywordOfPartialMethodOrType() &&
!IsCurrentTokenQueryKeywordInQuery() &&
!IsCurrentTokenWhereOfConstraintClause())
{
return true;
}
}
return false;
}
/// <summary>
/// True if the given token is not really some contextual keyword.
/// This method is for use in executable code, as it treats <c>partial</c> as an identifier.
/// </summary>
private bool IsTrueIdentifier(SyntaxToken token)
{
return
token.Kind == SyntaxKind.IdentifierToken &&
!(this.IsInQuery && IsTokenQueryContextualKeyword(token));
}
private IdentifierNameSyntax ParseIdentifierName(ErrorCode code = ErrorCode.ERR_IdentifierExpected)
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.IdentifierName)
{
if (!SyntaxFacts.IsContextualKeyword(((CSharp.Syntax.IdentifierNameSyntax)this.CurrentNode).Identifier.Kind()))
{
return (IdentifierNameSyntax)this.EatNode();
}
}
return SyntaxFactory.IdentifierName(
ParseIdentifierToken(code));
}
private SyntaxToken ParseIdentifierToken(ErrorCode code = ErrorCode.ERR_IdentifierExpected)
{
var ctk = this.CurrentToken.Kind;
if (ctk == SyntaxKind.IdentifierToken)
{
// Error tolerance for IntelliSense. Consider the following case: [EditorBrowsable( partial class Goo {
// } Because we're parsing an attribute argument we'll end up consuming the "partial" identifier and
// we'll eventually end up in a pretty confused state. Because of that it becomes very difficult to
// show the correct parameter help in this case. So, when we see "partial" we check if it's being used
// as an identifier or as a contextual keyword. If it's the latter then we bail out. See
// Bug: vswhidbey/542125
if (IsCurrentTokenPartialKeywordOfPartialMethodOrType() || IsCurrentTokenQueryKeywordInQuery())
{
var result = CreateMissingIdentifierToken();
result = this.AddError(result, ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text);
return result;
}
SyntaxToken identifierToken = this.EatToken();
if (this.IsInAsync && identifierToken.ContextualKind == SyntaxKind.AwaitKeyword)
{
identifierToken = this.AddError(identifierToken, ErrorCode.ERR_BadAwaitAsIdentifier);
}
return identifierToken;
}
else
{
return this.AddError(CreateMissingIdentifierToken(), code);
}
}
private bool IsCurrentTokenQueryKeywordInQuery()
{
return this.IsInQuery && this.IsCurrentTokenQueryContextualKeyword;
}
private bool IsCurrentTokenPartialKeywordOfPartialMethodOrType()
{
if (this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword)
{
if (this.IsPartialType() || this.IsPartialMember())
{
return true;
}
}
return false;
}
private bool IsCurrentTokenFieldInKeywordContext()
{
return CurrentToken.ContextualKind == SyntaxKind.FieldKeyword &&
IsInFieldKeywordContext &&
IsFeatureEnabled(MessageID.IDS_FeatureFieldKeyword);
}
private TypeParameterListSyntax ParseTypeParameterList()
{
if (this.CurrentToken.Kind != SyntaxKind.LessThanToken)
{
return null;
}
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfTypeParameterList;
var open = this.EatToken(SyntaxKind.LessThanToken);
var parameters = this.ParseCommaSeparatedSyntaxList(
ref open,
SyntaxKind.GreaterThanToken,
static @this => @this.IsStartOfTypeParameter(),
static @this => @this.ParseTypeParameter(),
skipBadTypeParameterListTokens,
allowTrailingSeparator: false,
requireOneElement: true,
allowSemicolonAsSeparator: false);
_termState = saveTerm;
return _syntaxFactory.TypeParameterList(
open,
parameters,
this.EatToken(SyntaxKind.GreaterThanToken));
static PostSkipAction skipBadTypeParameterListTokens(
LanguageParser @this, ref SyntaxToken open, SeparatedSyntaxListBuilder<TypeParameterSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken,
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private bool IsStartOfTypeParameter()
{
if (this.IsCurrentTokenWhereOfConstraintClause())
return false;
// possible attributes
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind != SyntaxKind.CloseBracketToken)
return true;
// Variance.
if (this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
return true;
return IsTrueIdentifier();
}
private TypeParameterSyntax ParseTypeParameter()
{
if (this.IsCurrentTokenWhereOfConstraintClause())
{
return _syntaxFactory.TypeParameter(
default(SyntaxList<AttributeListSyntax>),
varianceKeyword: null,
this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected));
}
var attrs = default(SyntaxList<AttributeListSyntax>);
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind != SyntaxKind.CloseBracketToken)
{
var saveTerm = _termState;
_termState = TerminatorState.IsEndOfTypeArgumentList;
attrs = this.ParseAttributeDeclarations(inExpressionContext: false);
_termState = saveTerm;
}
return _syntaxFactory.TypeParameter(
attrs,
this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword ? EatToken() : null,
this.ParseIdentifierToken());
}
// Parses the parts of the names between Dots and ColonColons.
private SimpleNameSyntax ParseSimpleName(NameOptions options = NameOptions.None)
{
var id = this.ParseIdentifierName();
if (id.Identifier.IsMissing)
{
return id;
}
// You can pass ignore generics if you don't even want the parser to consider generics at all.
// The name parsing will then stop at the first "<". It doesn't make sense to pass both Generic and IgnoreGeneric.
SimpleNameSyntax name = id;
if (this.CurrentToken.Kind == SyntaxKind.LessThanToken)
{
ScanTypeArgumentListKind kind;
using (this.GetDisposableResetPoint(resetOnDispose: true))
{
kind = this.ScanTypeArgumentList(options);
}
if (kind == ScanTypeArgumentListKind.DefiniteTypeArgumentList || (kind == ScanTypeArgumentListKind.PossibleTypeArgumentList && (options & NameOptions.InTypeList) != 0))
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.LessThanToken);
var types = _pool.AllocateSeparated<TypeSyntax>();
this.ParseTypeArgumentList(out var open, types, out var close);
name = _syntaxFactory.GenericName(
id.Identifier,
_syntaxFactory.TypeArgumentList(
open,
_pool.ToListAndFree(types),
close));
}
}
return name;
}
private enum ScanTypeArgumentListKind
{
NotTypeArgumentList,
PossibleTypeArgumentList,
DefiniteTypeArgumentList
}
private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options)
{
if (this.CurrentToken.Kind != SyntaxKind.LessThanToken)
{
return ScanTypeArgumentListKind.NotTypeArgumentList;
}
if ((options & NameOptions.InExpression) == 0)
{
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
// We're in an expression context, and we have a < token. This could be a
// type argument list, or it could just be a relational expression.
//
// Scan just the type argument list portion (i.e. the part from < to > ) to
// see what we think it could be. This will give us one of three possibilities:
//
// result == ScanTypeFlags.NotType.
//
// This is absolutely not a type-argument-list. Just return that result immediately.
//
// result != ScanTypeFlags.NotType && isDefinitelyTypeArgumentList.
//
// This is absolutely a type-argument-list. Just return that result immediately
//
// result != ScanTypeFlags.NotType && !isDefinitelyTypeArgumentList.
//
// This could be a type-argument list, or it could be an expression. Need to see
// what came after the last '>' to find out which it is.
// Scan for a type argument list. If we think it's a type argument list
// then assume it is unless we see specific tokens following it.
ScanTypeFlags possibleTypeArgumentFlags = ScanPossibleTypeArgumentList(
out var greaterThanToken, out bool isDefinitelyTypeArgumentList);
if (possibleTypeArgumentFlags == ScanTypeFlags.NotType)
{
return ScanTypeArgumentListKind.NotTypeArgumentList;
}
if (isDefinitelyTypeArgumentList)
{
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
// If we did not definitively determine from immediate syntax that it was or
// was not a type argument list, we must have scanned the entire thing up through
// the closing greater-than token. In that case we will disambiguate based on the
// token that follows it.
Debug.Assert(greaterThanToken.Kind == SyntaxKind.GreaterThanToken);
switch (this.CurrentToken.Kind)
{
case SyntaxKind.OpenParenToken:
case SyntaxKind.CloseParenToken:
case SyntaxKind.CloseBracketToken:
case SyntaxKind.CloseBraceToken:
case SyntaxKind.ColonToken:
case SyntaxKind.SemicolonToken:
case SyntaxKind.CommaToken:
case SyntaxKind.DotToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.BarToken:
case SyntaxKind.CaretToken:
// These tokens are from 7.5.4.2 Grammar Ambiguities
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.AmpersandAmpersandToken: // e.g. `e is A<B> && e`
case SyntaxKind.BarBarToken: // e.g. `e is A<B> || e`
case SyntaxKind.AmpersandToken: // e.g. `e is A<B> & e`
case SyntaxKind.OpenBracketToken: // e.g. `e is A<B>[]`
case SyntaxKind.LessThanToken: // e.g. `e is A<B> < C`
case SyntaxKind.LessThanEqualsToken: // e.g. `e is A<B> <= C`
case SyntaxKind.GreaterThanEqualsToken: // e.g. `e is A<B> >= C`
case SyntaxKind.IsKeyword: // e.g. `e is A<B> is bool`
case SyntaxKind.AsKeyword: // e.g. `e is A<B> as bool`
// These tokens were added to 7.5.4.2 Grammar Ambiguities in C# 7.0
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.OpenBraceToken: // e.g. `e is A<B> {}`
// This token was added to 7.5.4.2 Grammar Ambiguities in C# 8.0
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.GreaterThanToken when ((options & NameOptions.AfterIs) != 0) && this.PeekToken(1).Kind != SyntaxKind.GreaterThanToken:
// This token is added to 7.5.4.2 Grammar Ambiguities in C#7 for the special case in which
// the possible generic is following an `is` keyword, e.g. `e is A<B> > C`.
// We test one further token ahead because a right-shift operator `>>` looks like a pair of greater-than
// tokens at this stage, but we don't intend to be handling the right-shift operator.
// The upshot is that we retain compatibility with the two previous behaviors:
// `(x is A<B>>C)` is parsed as `(x is A<B>) > C`
// `A<B>>C` elsewhere is parsed as `A < (B >> C)`
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.IdentifierToken:
// C#7: In certain contexts, we treat *identifier* as a disambiguating token. Those
// contexts are where the sequence of tokens being disambiguated is immediately preceded by one
// of the keywords is, case, or out, or arises while parsing the first element of a tuple literal
// (in which case the tokens are preceded by `(` and the identifier is followed by a `,`) or a
// subsequent element of a tuple literal (in which case the tokens are preceded by `,` and the
// identifier is followed by a `,` or `)`).
// In C#8 (or whenever recursive patterns are introduced) we also treat an identifier as a
// disambiguating token if we're parsing the type of a pattern.
// Note that we treat query contextual keywords (which appear here as identifiers) as disambiguating tokens as well.
if ((options & (NameOptions.AfterIs | NameOptions.DefinitePattern | NameOptions.AfterOut)) != 0 ||
(options & NameOptions.AfterTupleComma) != 0 && this.PeekToken(1).Kind is SyntaxKind.CommaToken or SyntaxKind.CloseParenToken ||
(options & NameOptions.FirstElementOfPossibleTupleLiteral) != 0 && this.PeekToken(1).Kind == SyntaxKind.CommaToken)
{
// we allow 'G<T,U> x' as a pattern-matching operation and a declaration expression in a tuple.
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
return ScanTypeArgumentListKind.PossibleTypeArgumentList;
case SyntaxKind.EndOfFileToken: // e.g. `e is A<B>`
// This is useful for parsing expressions in isolation
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.EqualsGreaterThanToken: // e.g. `e switch { A<B> => 1 }`
// This token was added to 7.5.4.2 Grammar Ambiguities in C# 9.0
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
default:
return ScanTypeArgumentListKind.PossibleTypeArgumentList;
}
}
private ScanTypeFlags ScanPossibleTypeArgumentList(
out SyntaxToken greaterThanToken,
out bool isDefinitelyTypeArgumentList)
{
isDefinitelyTypeArgumentList = false;
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.LessThanToken);
// If we have `X<>`, or `X<,>` or `X<,,,,,,>` then none of these are legal expression or types. However,
// it seems likelier that they are invalid open-types in an expression context, versus expressions
// missing values (note that we only support this when the open name does have the final `>` token).
if (IsOpenName())
{
isDefinitelyTypeArgumentList = true;
var start = this.EatToken();
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
this.EatToken();
greaterThanToken = this.EatToken();
Debug.Assert(start.Kind == SyntaxKind.LessThanToken);
Debug.Assert(greaterThanToken.Kind == SyntaxKind.GreaterThanToken);
return ScanTypeFlags.GenericTypeOrMethod;
}
ScanTypeFlags result = ScanTypeFlags.GenericTypeOrExpression;
ScanTypeFlags lastScannedType;
do
{
this.EatToken();
// Type arguments cannot contain attributes, so if this is an open square, we early out and assume it is not a type argument
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
greaterThanToken = null;
return ScanTypeFlags.NotType;
}
if (this.CurrentToken.Kind == SyntaxKind.GreaterThanToken)
{
greaterThanToken = EatToken();
return result;
}
// Allow for any chain of errant commas in the generic name. like `Dictionary<,int>` or
// `Dictionary<int,,>` We still want to think of these as generics, just with missing type-arguments, vs
// some invalid tree-expression that we would otherwise form.
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
lastScannedType = default;
continue;
}
lastScannedType = this.ScanType(out _);
switch (lastScannedType)
{
case ScanTypeFlags.NotType:
greaterThanToken = null;
return ScanTypeFlags.NotType;
case ScanTypeFlags.MustBeType:
// We're currently scanning a possible type-argument list. But we're
// not sure if this is actually a type argument list, or is maybe some
// complex relational expression with <'s and >'s. One thing we can
// tell though is that if we have a predefined type (like 'int' or 'string')
// before a comma or > then this is definitely a type argument list. i.e.
// if you have:
//
// var v = ImmutableDictionary<int,
//
// then there's no legal interpretation of this as an expression (since a
// standalone predefined type is not a valid simple term. Contrast that
// with :
//
// var v = ImmutableDictionary<Int32,
//
// Here this might actually be a relational expression and the comma is meant
// to separate out the variable declarator 'v' from the next variable.
//
// Note: we check if we got 'MustBeType' which triggers for predefined types,
// (int, string, etc.), or array types (Goo[], A<T>[][] etc.), or pointer types
// of things that must be types (int*, void**, etc.).
isDefinitelyTypeArgumentList = isDefinitelyTypeArgumentList || this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.GreaterThanToken;
result = ScanTypeFlags.GenericTypeOrMethod;
break;
// case ScanTypeFlags.TupleType:
// It would be nice if we saw a tuple to state that we definitely had a
// type argument list. However, there are cases where this would not be
// true. For example:
//
// public class C
// {
// public static void Main()
// {
// XX X = default;
// int a = 1, b = 2;
// bool z = X < (a, b), w = false;
// }
// }
//
// struct XX
// {
// public static bool operator <(XX x, (int a, int b) arg) => true;
// public static bool operator >(XX x, (int a, int b) arg) => false;
// }
case ScanTypeFlags.NullableType:
// See above. If we have `X<Y?,` or `X<Y?>` then this is definitely a type argument list.
isDefinitelyTypeArgumentList = isDefinitelyTypeArgumentList || this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.GreaterThanToken;
if (isDefinitelyTypeArgumentList)
{
result = ScanTypeFlags.GenericTypeOrMethod;
}
// Note: we intentionally fall out without setting 'result'.
// Seeing a nullable type (not followed by a , or > ) is not enough
// information for us to determine what this is yet. i.e. the user may have:
//
// X < Y ? Z : W
//
// We'd see a nullable type here, but this is definitely not a type arg list.
break;
case ScanTypeFlags.GenericTypeOrExpression:
// See above. If we have X<Y<Z>, then this would definitely be a type argument list.
// However, if we have X<Y<Z>> then this might not be type argument list. This could just
// be some sort of expression where we're comparing, and then shifting values.
if (!isDefinitelyTypeArgumentList)
{
isDefinitelyTypeArgumentList = this.CurrentToken.Kind == SyntaxKind.CommaToken;
result = ScanTypeFlags.GenericTypeOrMethod;
}
break;
case ScanTypeFlags.GenericTypeOrMethod:
result = ScanTypeFlags.GenericTypeOrMethod;
break;
case ScanTypeFlags.NonGenericTypeOrExpression:
// Explicitly keeping this case in the switch for clarity. We parsed out another portion of the
// type argument list that looks like it's a non-generic-type-or-expr (the simplest case just
// being "X"). That changes nothing here wrt determining what type of entity we have here, so
// just fall through and see if we're followed by a "," (in which case keep going), or a ">", in
// which case we're done.
break;
}
}
while (this.CurrentToken.Kind == SyntaxKind.CommaToken);
if (this.CurrentToken.Kind != SyntaxKind.GreaterThanToken)
{
// Error recovery after missing > token:
// In the case of an identifier, we assume that there could be a missing > token
// For example, we have reached C in X<A, B C
if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken)
{
greaterThanToken = this.EatToken(SyntaxKind.GreaterThanToken);
return result;
}
// As for tuples, we do not expect direct invocation right after the parenthesis
// EXAMPLE: X<(string, string)(), where we imply a missing > token between )(
// as the user probably wants to invoke X by X<(string, string)>()
if (lastScannedType is ScanTypeFlags.TupleType && this.CurrentToken.Kind is SyntaxKind.OpenParenToken)
{
greaterThanToken = this.EatToken(SyntaxKind.GreaterThanToken);
return result;
}
greaterThanToken = null;
return ScanTypeFlags.NotType;
}
greaterThanToken = this.EatToken();
// If we have `X<Y>)` then this would definitely be a type argument list.
isDefinitelyTypeArgumentList = isDefinitelyTypeArgumentList || this.CurrentToken.Kind is SyntaxKind.CloseParenToken;
if (isDefinitelyTypeArgumentList)
{
result = ScanTypeFlags.GenericTypeOrMethod;
}
return result;
}
// ParseInstantiation: Parses the generic argument/parameter parts of the name.
private void ParseTypeArgumentList(out SyntaxToken open, SeparatedSyntaxListBuilder<TypeSyntax> types, out SyntaxToken close)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.LessThanToken);
var isOpenName = this.IsOpenName();
open = this.EatToken(SyntaxKind.LessThanToken);
open = CheckFeatureAvailability(open, MessageID.IDS_FeatureGenerics);
if (isOpenName)
{
// NOTE: trivia will be attached to comma, not omitted type argument
var omittedTypeArgumentInstance = _syntaxFactory.OmittedTypeArgument(SyntaxFactory.Token(SyntaxKind.OmittedTypeArgumentToken));
types.Add(omittedTypeArgumentInstance);
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
types.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
types.Add(omittedTypeArgumentInstance);
}
close = this.EatToken(SyntaxKind.GreaterThanToken);
return;
}
// first type
types.Add(this.ParseTypeArgument());
// remaining types & commas
while (true)
{
if (this.CurrentToken.Kind == SyntaxKind.GreaterThanToken)
{
break;
}
// We prefer early terminating the argument list over parsing until exhaustion
// for better error recovery
if (tokenBreaksTypeArgumentList(this.CurrentToken))
{
break;
}
// We are currently past parsing a type and we encounter an unexpected identifier token
// followed by tokens that are not part of a type argument list
// Example: List<(string a, string b) Method() { }
// current token: ^^^^^^
if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken && tokenBreaksTypeArgumentList(this.PeekToken(1)))
{
break;
}
// This is for the case where we are in a this[] accessor, and the last one of the parameters in the parameter list
// is missing a > on its type
// Example: X this[IEnumerable<string parameter] =>
// current token: ^^^^^^^^^
if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken
&& this.PeekToken(1).Kind is SyntaxKind.CloseBracketToken)
{
break;
}
if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleType())
{
types.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
types.Add(this.ParseTypeArgument());
}
else if (this.SkipBadTypeArgumentListTokens(types, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
close = this.EatToken(SyntaxKind.GreaterThanToken);
static bool tokenBreaksTypeArgumentList(SyntaxToken token)
{
var contextualKind = SyntaxFacts.GetContextualKeywordKind(token.ValueText);
switch (contextualKind)
{
// Example: x is IEnumerable<string or IList<int>
case SyntaxKind.OrKeyword:
// Example: x is IEnumerable<string and IDisposable
case SyntaxKind.AndKeyword:
return true;
}
switch (token.Kind)
{
// Example: Method<string(argument)
// Note: We would do a bad job handling a tuple argument with a missing comma,
// like: Method<string (int x, int y)>
// but since we do not look as far as possible to determine whether it is
// a tuple type or an argument list, we resort to considering it as an
// argument list
case SyntaxKind.OpenParenToken:
// Example: IEnumerable<string Method<T>() --- (< in <T>)
case SyntaxKind.LessThanToken:
// Example: Method(IEnumerable<string parameter)
case SyntaxKind.CloseParenToken:
// Example: IEnumerable<string field;
case SyntaxKind.SemicolonToken:
// Example: IEnumerable<string Property { get; set; }
case SyntaxKind.OpenBraceToken:
// Example:
// {
// IEnumerable<string field
// }
case SyntaxKind.CloseBraceToken:
// Examples:
// - IEnumerable<string field = null;
// - Method(IEnumerable<string parameter = null)
case SyntaxKind.EqualsToken:
// Example: IEnumerable<string Property => null;
case SyntaxKind.EqualsGreaterThanToken:
// Example: IEnumerable<string this[string key] { get; set; }
case SyntaxKind.ThisKeyword:
// Example: static IEnumerable<string operator +(A left, A right);
case SyntaxKind.OperatorKeyword:
return true;
}
return false;
}
}
private PostSkipAction SkipBadTypeArgumentListTokens(SeparatedSyntaxListBuilder<TypeSyntax> list, SyntaxKind expected)
{
CSharpSyntaxNode tmp = null;
Debug.Assert(list.Count > 0);
return this.SkipBadSeparatedListTokensWithExpectedKind(ref tmp, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleType(),
static (p, _) => p.CurrentToken.Kind == SyntaxKind.GreaterThanToken,
expected);
}
// Parses the individual generic parameter/arguments in a name.
private TypeSyntax ParseTypeArgument()
{
var attrs = default(SyntaxList<AttributeListSyntax>);
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind != SyntaxKind.CloseBracketToken)
{
// Here, if we see a "[" that looks like it has something in it, we parse
// it as an attribute and then later put an error on the whole type if
// it turns out that attributes are not allowed.
// TODO: should there be another flag that controls this behavior? we have
// "allowAttrs" but should there also be a "recognizeAttrs" that we can
// set to false in an expression context?
var saveTerm = _termState;
_termState = TerminatorState.IsEndOfTypeArgumentList;
attrs = this.ParseAttributeDeclarations(inExpressionContext: false);
_termState = saveTerm;
}
// Recognize the variance syntax, but give an error as it's only appropriate in a type parameter list.
var varianceToken = this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword
? this.AddError(this.EatToken(), ErrorCode.ERR_IllegalVarianceSyntax)
: null;
var result = this.ParseType();
// Consider the case where someone supplies an invalid type argument
// Such as Action<0> or Action<static>. In this case we generate a missing
// identifier in ParseType, but if we continue as is we'll immediately start to
// interpret 0 as the start of a new expression when we can tell it's most likely
// meant to be part of the type list.
//
// To solve this we check if the current token is not comma or greater than and
// the next token is a comma or greater than. If so we assume that the found
// token is part of this expression and we attempt to recover. This does open
// the door for cases where we have an incomplete line to be interpretted as
// a single expression. For example:
//
// Action< // Incomplete line
// a>b;
//
// However, this only happens when the following expression is of the form a>...
// or a,... which means this case should happen less frequently than what we're
// trying to solve here so we err on the side of better error messages
// for the majority of cases.
if (result.IsMissing &&
this.CurrentToken.Kind is not SyntaxKind.CommaToken and not SyntaxKind.GreaterThanToken &&
this.PeekToken(1).Kind is SyntaxKind.CommaToken or SyntaxKind.GreaterThanToken)
{
// Eat the current token and add it as skipped so we recover
result = AddTrailingSkippedSyntax(result, this.EatToken());
}
if (varianceToken != null)
{
result = AddLeadingSkippedSyntax(result, varianceToken);
}
if (attrs.Count > 0)
{
result = AddLeadingSkippedSyntax(result, attrs.Node);
result = this.AddError(result, ErrorCode.ERR_TypeExpected);
}
return result;
}
private bool IsEndOfTypeArgumentList()
=> this.CurrentToken.Kind == SyntaxKind.GreaterThanToken;
private bool IsOpenName()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.LessThanToken);
var n = 1;
while (this.PeekToken(n).Kind == SyntaxKind.CommaToken)
n++;
return this.PeekToken(n).Kind == SyntaxKind.GreaterThanToken;
}
private void ParseMemberName(
out ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt,
out SyntaxToken identifierOrThisOpt,
out TypeParameterListSyntax typeParameterListOpt,
bool isEvent)
{
identifierOrThisOpt = null;
explicitInterfaceOpt = null;
typeParameterListOpt = null;
if (!IsPossibleMemberName())
{
// No clue what this is. Just bail. Our caller will have to
// move forward and try again.
return;
}
NameSyntax explicitInterfaceName = null;
SyntaxToken separator = null;
ResetPoint beforeIdentifierPoint = default(ResetPoint);
bool beforeIdentifierPointSet = false;
try
{
while (true)
{
// Check if we got 'this'. If so, then we have an indexer.
// Note: we parse out type parameters here as well so that
// we can give a useful error about illegal generic indexers.
if (this.CurrentToken.Kind == SyntaxKind.ThisKeyword)
{
beforeIdentifierPoint = GetResetPoint();
beforeIdentifierPointSet = true;
identifierOrThisOpt = this.EatToken();
typeParameterListOpt = this.ParseTypeParameterList();
break;
}
// now, scan past the next name. if it's followed by a dot then
// it's part of the explicit name we're building up. Otherwise,
// it's the name of the member.
bool isMemberName;
using (GetDisposableResetPoint(resetOnDispose: true))
{
ScanNamedTypePart();
isMemberName = !IsDotOrColonColon();
}
if (isMemberName)
{
// We're past any explicit interface portion and We've
// gotten to the member name.
beforeIdentifierPoint = GetResetPoint();
beforeIdentifierPointSet = true;
if (separator != null && separator.Kind == SyntaxKind.ColonColonToken)
{
separator = this.AddError(separator, ErrorCode.ERR_AliasQualAsExpression);
separator = this.ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
identifierOrThisOpt = this.ParseIdentifierToken();
typeParameterListOpt = this.ParseTypeParameterList();
break;
}
else
{
// If we saw a . or :: then we must have something explicit.
AccumulateExplicitInterfaceName(ref explicitInterfaceName, ref separator);
}
}
if (explicitInterfaceName != null)
{
if (separator.Kind != SyntaxKind.DotToken)
{
separator = WithAdditionalDiagnostics(separator, GetExpectedTokenError(SyntaxKind.DotToken, separator.Kind, separator.GetLeadingTriviaWidth(), separator.Width));
separator = ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
if (isEvent && this.CurrentToken.Kind is not SyntaxKind.OpenBraceToken and not SyntaxKind.SemicolonToken)
{
// CS0071: If you're explicitly implementing an event field, you have to use the accessor form
//
// Good:
// event EventDelegate Parent.E
// {
// add { ... }
// remove { ... }
// }
//
// Bad:
// event EventDelegate Parent.
// E( //(or anything that is not the semicolon
//
// To recover: rollback to before the name of the field was parsed (just the part after the last
// dot), insert a missing identifier for the field name, insert missing accessors, and then treat
// the event name that's actually there as the beginning of a new member. e.g.
//
// event EventDelegate Parent./*Missing nodes here*/
//
// E(
//
// Rationale: The identifier could be the name of a type at the beginning of an existing member
// declaration (above which someone has started to type an explicit event implementation).
//
// In case the dot doesn't follow with an end line or E ends with a semicolon, the error recovery
// is skipped. In that case the rationale above does not fit very well.
explicitInterfaceOpt = _syntaxFactory.ExplicitInterfaceSpecifier(
explicitInterfaceName,
AddError(separator, ErrorCode.ERR_ExplicitEventFieldImpl));
if (separator.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia))
{
Debug.Assert(beforeIdentifierPointSet);
Reset(ref beforeIdentifierPoint);
//clear fields that were populated after the reset point
identifierOrThisOpt = null;
typeParameterListOpt = null;
}
}
else
{
explicitInterfaceOpt = _syntaxFactory.ExplicitInterfaceSpecifier(explicitInterfaceName, separator);
}
}
}
finally
{
if (beforeIdentifierPointSet)
{
Release(ref beforeIdentifierPoint);
}
}
}
private void AccumulateExplicitInterfaceName(ref NameSyntax explicitInterfaceName, ref SyntaxToken separator)
{
// first parse the upcoming name portion.
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfNameInExplicitInterface;
if (explicitInterfaceName == null)
{
// If this is the first time, then just get the next simple
// name and store it as the explicit interface name.
explicitInterfaceName = this.ParseSimpleName(NameOptions.InTypeList);
// Now, get the next separator.
separator = this.CurrentToken.Kind == SyntaxKind.ColonColonToken
? this.EatToken() // fine after the first identifier
: this.EatToken(SyntaxKind.DotToken);
}
else
{
// Parse out the next part and combine it with the
// current explicit name to form the new explicit name.
var tmp = this.ParseQualifiedNameRight(NameOptions.InTypeList, explicitInterfaceName, separator);
Debug.Assert(!ReferenceEquals(tmp, explicitInterfaceName), "We should have consumed something and updated explicitInterfaceName");
explicitInterfaceName = tmp;
// Now, get the next separator.
if (this.CurrentToken.Kind == SyntaxKind.ColonColonToken)
{
separator = this.EatToken();
separator = this.AddError(separator, ErrorCode.ERR_UnexpectedAliasedName);
separator = this.ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
else
{
separator = this.EatToken(SyntaxKind.DotToken);
}
}
_termState = saveTerm;
}
/// <summary>
/// This is an adjusted version of <see cref="ParseMemberName"/>.
/// When it returns true, it stops at operator keyword (<see cref="IsOperatorKeyword"/>).
/// When it returns false, it does not advance in the token stream.
/// </summary>
private bool IsOperatorStart(out ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, bool advanceParser = true)
{
explicitInterfaceOpt = null;
if (IsOperatorKeyword())
{
return true;
}
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
return false;
}
NameSyntax explicitInterfaceName = null;
SyntaxToken separator = null;
using var beforeIdentifierPoint = GetDisposableResetPoint(resetOnDispose: false);
while (true)
{
// now, scan past the next name. if it's followed by a dot then
// it's part of the explicit name we're building up. Otherwise,
// it should be an operator token
bool isPartOfInterfaceName;
using (GetDisposableResetPoint(resetOnDispose: true))
{
if (IsOperatorKeyword())
{
isPartOfInterfaceName = false;
}
else
{
ScanNamedTypePart();
// If we have part of the interface name, but no dot before the operator token, then
// for the purpose of error recovery, treat this as an operator start with a
// missing dot token.
isPartOfInterfaceName = IsDotOrColonColon() || IsOperatorKeyword();
}
}
if (!isPartOfInterfaceName)
{
// We're past any explicit interface portion
if (separator != null && separator.Kind == SyntaxKind.ColonColonToken)
{
separator = this.AddError(separator, ErrorCode.ERR_AliasQualAsExpression);
separator = this.ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
break;
}
else
{
// If we saw a . or :: then we must have something explicit.
AccumulateExplicitInterfaceName(ref explicitInterfaceName, ref separator);
}
}
if (!IsOperatorKeyword() || explicitInterfaceName is null)
{
beforeIdentifierPoint.Reset();
return false;
}
if (!advanceParser)
{
beforeIdentifierPoint.Reset();
return true;
}
if (separator.Kind != SyntaxKind.DotToken)
{
separator = WithAdditionalDiagnostics(separator, GetExpectedTokenError(SyntaxKind.DotToken, separator.Kind, separator.GetLeadingTriviaWidth(), separator.Width));
separator = ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
}
explicitInterfaceOpt = _syntaxFactory.ExplicitInterfaceSpecifier(explicitInterfaceName, separator);
return true;
}
private NameSyntax ParseAliasQualifiedName(NameOptions allowedParts = NameOptions.None)
{
var name = this.ParseSimpleName(allowedParts);
return this.CurrentToken.Kind == SyntaxKind.ColonColonToken
? ParseQualifiedNameRight(allowedParts, name, this.EatToken())
: name;
}
private NameSyntax ParseQualifiedName(NameOptions options = NameOptions.None)
{
NameSyntax name = this.ParseAliasQualifiedName(options);
// Handle .. tokens for error recovery purposes.
while (IsDotOrColonColon())
{
if (this.PeekToken(1).Kind == SyntaxKind.ThisKeyword)
{
break;
}
var separator = this.EatToken();
name = ParseQualifiedNameRight(options, name, separator);
}
return name;
}
private NameSyntax ParseQualifiedNameRight(
NameOptions options,
NameSyntax left,
SyntaxToken separator)
{
Debug.Assert(separator.Kind is SyntaxKind.DotToken or SyntaxKind.ColonColonToken);
var right = this.ParseSimpleName(options);
switch (separator.Kind)
{
case SyntaxKind.DotToken:
return _syntaxFactory.QualifiedName(left, separator, right);
case SyntaxKind.ColonColonToken:
if (left.Kind != SyntaxKind.IdentifierName)
{
separator = this.AddError(separator, ErrorCode.ERR_UnexpectedAliasedName);
}
// If the left hand side is not an identifier name then the user has done
// something like Goo.Bar::Blah. We've already made an error node for the
// ::, so just pretend that they typed Goo.Bar.Blah and continue on.
if (left is not IdentifierNameSyntax identifierLeft)
{
separator = this.ConvertToMissingWithTrailingTrivia(separator, SyntaxKind.DotToken);
return _syntaxFactory.QualifiedName(left, separator, right);
}
else
{
if (identifierLeft.Identifier.ContextualKind == SyntaxKind.GlobalKeyword)
{
identifierLeft = _syntaxFactory.IdentifierName(ConvertToKeyword(identifierLeft.Identifier));
}
// If the name on the right had errors or warnings then we need to preserve
// them in the tree.
return WithAdditionalDiagnostics(_syntaxFactory.AliasQualifiedName(identifierLeft, separator, right), left.GetDiagnostics());
}
default:
throw ExceptionUtilities.Unreachable();
}
}
private SyntaxToken ConvertToMissingWithTrailingTrivia(SyntaxToken token, SyntaxKind expectedKind)
{
var newToken = SyntaxFactory.MissingToken(expectedKind);
newToken = AddTrailingSkippedSyntax(newToken, token);
return newToken;
}
private enum ScanTypeFlags
{
/// <summary>
/// Definitely not a type name.
/// </summary>
NotType,
/// <summary>
/// Definitely a type name: either a predefined type (int, string, etc.) or an array
/// type (ending with a [] brackets), or a pointer type (ending with *s), or a function
/// pointer type (ending with > in valid cases, or a *, ), or calling convention
/// identifier, in invalid cases).
/// </summary>
MustBeType,
/// <summary>
/// Might be a generic (qualified) type name or a method name.
/// </summary>
GenericTypeOrMethod,
/// <summary>
/// Might be a generic (qualified) type name or an expression or a method name.
/// </summary>
GenericTypeOrExpression,
/// <summary>
/// Might be a non-generic (qualified) type name or an expression.
/// </summary>
NonGenericTypeOrExpression,
/// <summary>
/// A type name with alias prefix (Alias::Name). Note that Alias::Name.X would not fall under this. This
/// only is returned for exactly Alias::Name.
/// </summary>
AliasQualifiedName,
/// <summary>
/// Nullable type (ending with ?).
/// </summary>
NullableType,
/// <summary>
/// Might be a pointer type or a multiplication.
/// </summary>
PointerOrMultiplication,
/// <summary>
/// Might be a tuple type.
/// </summary>
TupleType,
}
private bool IsPossibleType()
{
var tk = this.CurrentToken.Kind;
return IsPredefinedType(tk) || this.IsTrueIdentifier();
}
private ScanTypeFlags ScanType(bool forPattern = false)
{
return ScanType(out _, forPattern);
}
private ScanTypeFlags ScanType(out SyntaxToken lastTokenOfType, bool forPattern = false)
{
return ScanType(forPattern ? ParseTypeMode.DefinitePattern : ParseTypeMode.Normal, out lastTokenOfType);
}
private void ScanNamedTypePart()
{
ScanNamedTypePart(out _);
}
private ScanTypeFlags ScanNamedTypePart(out SyntaxToken lastTokenOfType)
{
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken || !this.IsTrueIdentifier())
{
lastTokenOfType = null;
return ScanTypeFlags.NotType;
}
lastTokenOfType = this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.LessThanToken)
{
return this.ScanPossibleTypeArgumentList(out lastTokenOfType, out _);
}
else
{
return ScanTypeFlags.NonGenericTypeOrExpression;
}
}
private ScanTypeFlags ScanType(ParseTypeMode mode, out SyntaxToken lastTokenOfType)
{
Debug.Assert(mode != ParseTypeMode.NewExpression);
ScanTypeFlags result;
if (this.CurrentToken.Kind == SyntaxKind.RefKeyword)
{
// in a ref local or ref return, we treat "ref" and "ref readonly" as part of the type
this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword)
{
this.EatToken();
}
}
// Handle :: as well for error case of an alias used without a preceding identifier.
if (this.CurrentToken.Kind is SyntaxKind.IdentifierToken or SyntaxKind.ColonColonToken)
{
bool isAlias;
if (this.CurrentToken.Kind is SyntaxKind.ColonColonToken)
{
result = ScanTypeFlags.NonGenericTypeOrExpression;
// Definitely seems like an alias if we're starting with a ::
isAlias = true;
// We set this to null to appease the flow checker. It will always be the case that this will be
// set to an appropriate value inside the `for` loop below. We'll consume the :: there and then
// call ScanNamedTypePart which will always set this to a valid value.
lastTokenOfType = null;
}
else
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.IdentifierToken);
// We're an alias if we start with an: id::
isAlias = this.PeekToken(1).Kind == SyntaxKind.ColonColonToken;
result = this.ScanNamedTypePart(out lastTokenOfType);
if (result == ScanTypeFlags.NotType)
{
return ScanTypeFlags.NotType;
}
Debug.Assert(result is ScanTypeFlags.GenericTypeOrExpression or ScanTypeFlags.GenericTypeOrMethod or ScanTypeFlags.NonGenericTypeOrExpression);
}
// Scan a name
for (bool firstLoop = true; IsDotOrColonColon(); firstLoop = false)
{
// If we consume any more dots or colons, don't consider us an alias anymore. For dots, we now have
// x::y.z (which is now back to a normal expr/type, not an alias), and for colons that means we have
// x::y::z or x.y::z both of which are effectively gibberish.
if (!firstLoop)
{
isAlias = false;
}
this.EatToken();
result = this.ScanNamedTypePart(out lastTokenOfType);
if (result == ScanTypeFlags.NotType)
{
return ScanTypeFlags.NotType;
}
Debug.Assert(result is ScanTypeFlags.GenericTypeOrExpression or ScanTypeFlags.GenericTypeOrMethod or ScanTypeFlags.NonGenericTypeOrExpression);
}
if (isAlias)
{
result = ScanTypeFlags.AliasQualifiedName;
}
}
else if (IsPredefinedType(this.CurrentToken.Kind))
{
// Simple type...
lastTokenOfType = this.EatToken();
result = ScanTypeFlags.MustBeType;
}
else if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
lastTokenOfType = this.EatToken();
result = this.ScanTupleType(out lastTokenOfType);
if (result == ScanTypeFlags.NotType || mode == ParseTypeMode.DefinitePattern && this.CurrentToken.Kind != SyntaxKind.OpenBracketToken)
{
// A tuple type can appear in a pattern only if it is the element type of an array type.
return ScanTypeFlags.NotType;
}
}
else if (IsFunctionPointerStart())
{
result = ScanFunctionPointerType(out lastTokenOfType);
}
else
{
// Can't be a type!
lastTokenOfType = null;
return ScanTypeFlags.NotType;
}
int lastTokenPosition = -1;
while (IsMakingProgress(ref lastTokenPosition))
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.QuestionToken
when lastTokenOfType.Kind is not SyntaxKind.QuestionToken // don't allow `Type??`
and not SyntaxKind.AsteriskToken: // don't allow `Type*?`
lastTokenOfType = this.EatToken();
result = ScanTypeFlags.NullableType;
break;
case SyntaxKind.AsteriskToken:
// Check for pointer type(s)
switch (mode)
{
case ParseTypeMode.FirstElementOfPossibleTupleLiteral:
case ParseTypeMode.AfterTupleComma:
// We are parsing the type for a declaration expression in a tuple, which does
// not permit pointer types except as an element type of an array type.
// In that context a `*` is parsed as a multiplication.
if (PointerTypeModsFollowedByRankAndDimensionSpecifier())
{
goto default;
}
goto done;
case ParseTypeMode.DefinitePattern:
// pointer type syntax is not supported in patterns.
goto done;
default:
lastTokenOfType = this.EatToken();
if (result is ScanTypeFlags.GenericTypeOrExpression or ScanTypeFlags.NonGenericTypeOrExpression)
{
result = ScanTypeFlags.PointerOrMultiplication;
}
else if (result == ScanTypeFlags.GenericTypeOrMethod)
{
result = ScanTypeFlags.MustBeType;
}
break;
}
break;
case SyntaxKind.OpenBracketToken:
// Check for array types.
this.EatToken();
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
this.EatToken();
}
if (this.CurrentToken.Kind != SyntaxKind.CloseBracketToken)
{
lastTokenOfType = null;
return ScanTypeFlags.NotType;
}
lastTokenOfType = this.EatToken();
result = ScanTypeFlags.MustBeType;
break;
default:
goto done;
}
}
done:
return result;
}
/// <summary>
/// Returns TupleType when a possible tuple type is found.
/// Note that this is not MustBeType, so that the caller can consider deconstruction syntaxes.
/// The caller is expected to have consumed the opening paren.
/// </summary>
private ScanTypeFlags ScanTupleType(out SyntaxToken lastTokenOfType)
{
var tupleElementType = ScanType(out lastTokenOfType);
if (tupleElementType != ScanTypeFlags.NotType)
{
if (IsTrueIdentifier())
{
lastTokenOfType = this.EatToken();
}
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
do
{
lastTokenOfType = this.EatToken();
tupleElementType = ScanType(out lastTokenOfType);
if (tupleElementType == ScanTypeFlags.NotType)
{
lastTokenOfType = this.EatToken();
return ScanTypeFlags.NotType;
}
if (IsTrueIdentifier())
{
lastTokenOfType = this.EatToken();
}
}
while (this.CurrentToken.Kind == SyntaxKind.CommaToken);
if (this.CurrentToken.Kind == SyntaxKind.CloseParenToken)
{
lastTokenOfType = this.EatToken();
return ScanTypeFlags.TupleType;
}
}
}
// Can't be a type!
lastTokenOfType = null;
return ScanTypeFlags.NotType;
}
#nullable enable
private ScanTypeFlags ScanFunctionPointerType(out SyntaxToken lastTokenOfType)
{
Debug.Assert(IsFunctionPointerStart());
_ = EatToken(SyntaxKind.DelegateKeyword);
lastTokenOfType = EatToken(SyntaxKind.AsteriskToken);
TerminatorState saveTerm;
if (CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
var peek1 = PeekToken(1);
switch (CurrentToken)
{
case { ContextualKind: SyntaxKind.ManagedKeyword }:
case { ContextualKind: SyntaxKind.UnmanagedKeyword }:
case var _ when IsPossibleFunctionPointerParameterListStart(peek1):
case var _ when peek1.Kind == SyntaxKind.OpenBracketToken:
lastTokenOfType = EatToken();
break;
default:
// Whatever is next, it's probably not part of the type. We know that delegate* must be
// a function pointer start, however, so say the asterisk is the last element and bail
return ScanTypeFlags.MustBeType;
}
if (CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
lastTokenOfType = EatToken(SyntaxKind.OpenBracketToken);
saveTerm = _termState;
_termState |= TerminatorState.IsEndOfFunctionPointerCallingConvention;
try
{
while (true)
{
lastTokenOfType = TryEatToken(SyntaxKind.IdentifierToken) ?? lastTokenOfType;
if (skipBadFunctionPointerTokens() == PostSkipAction.Abort)
{
break;
}
Debug.Assert(CurrentToken.Kind == SyntaxKind.CommaToken);
lastTokenOfType = EatToken();
}
lastTokenOfType = TryEatToken(SyntaxKind.CloseBracketToken) ?? lastTokenOfType;
}
finally
{
_termState = saveTerm;
}
}
}
if (!IsPossibleFunctionPointerParameterListStart(CurrentToken))
{
// Even though this function pointer type is incomplete, we know that it
// must be the start of a type, as there is no other possible interpretation
// of delegate*. By always treating it as a type, we ensure that any disambiguation
// done in later parsing treats this as a type, which will produce better
// errors at later stages.
return ScanTypeFlags.MustBeType;
}
var validStartingToken = EatToken().Kind == SyntaxKind.LessThanToken;
saveTerm = _termState;
_termState |= validStartingToken ? TerminatorState.IsEndOfFunctionPointerParameterList : TerminatorState.IsEndOfFunctionPointerParameterListErrored;
var ignoredModifiers = _pool.Allocate<SyntaxToken>();
try
{
do
{
ParseParameterModifiers(ignoredModifiers, isFunctionPointerParameter: true);
ignoredModifiers.Clear();
_ = ScanType(out _);
if (skipBadFunctionPointerTokens() == PostSkipAction.Abort)
{
break;
}
_ = EatToken(SyntaxKind.CommaToken);
}
while (true);
}
finally
{
_termState = saveTerm;
_pool.Free(ignoredModifiers);
}
if (!validStartingToken && CurrentToken.Kind == SyntaxKind.CloseParenToken)
{
lastTokenOfType = EatTokenAsKind(SyntaxKind.GreaterThanToken);
}
else
{
lastTokenOfType = EatToken(SyntaxKind.GreaterThanToken);
}
return ScanTypeFlags.MustBeType;
PostSkipAction skipBadFunctionPointerTokens()
{
return SkipBadTokensWithExpectedKind(
isNotExpectedFunction: static p => p.CurrentToken.Kind != SyntaxKind.CommaToken,
abortFunction: static (p, _) => p.IsTerminator(),
expected: SyntaxKind.CommaToken,
closeKind: SyntaxKind.None,
trailingTrivia: out _);
}
}
#nullable disable
private static bool IsPredefinedType(SyntaxKind keyword)
{
return SyntaxFacts.IsPredefinedType(keyword);
}
public TypeSyntax ParseTypeName()
{
return ParseType();
}
private TypeSyntax ParseTypeOrVoid()
{
if (this.CurrentToken.Kind == SyntaxKind.VoidKeyword && this.PeekToken(1).Kind != SyntaxKind.AsteriskToken)
{
// Must be 'void' type, so create such a type node and return it.
return _syntaxFactory.PredefinedType(this.EatToken());
}
return this.ParseType();
}
private enum ParseTypeMode
{
Normal,
Parameter,
AfterIs,
DefinitePattern,
AfterOut,
AfterRef,
AfterTupleComma,
AsExpression,
NewExpression,
FirstElementOfPossibleTupleLiteral,
}
private TypeSyntax ParseType(ParseTypeMode mode = ParseTypeMode.Normal)
{
if (this.CurrentToken.Kind == SyntaxKind.RefKeyword)
{
return _syntaxFactory.RefType(
this.EatToken(),
this.CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword ? this.EatToken() : null,
ParseTypeCore(ParseTypeMode.AfterRef));
}
return ParseTypeCore(mode);
}
private TypeSyntax ParseTypeCore(ParseTypeMode mode)
{
NameOptions nameOptions;
switch (mode)
{
case ParseTypeMode.AfterIs:
nameOptions = NameOptions.InExpression | NameOptions.AfterIs | NameOptions.PossiblePattern;
break;
case ParseTypeMode.DefinitePattern:
nameOptions = NameOptions.InExpression | NameOptions.DefinitePattern | NameOptions.PossiblePattern;
break;
case ParseTypeMode.AfterOut:
nameOptions = NameOptions.InExpression | NameOptions.AfterOut;
break;
case ParseTypeMode.AfterTupleComma:
nameOptions = NameOptions.InExpression | NameOptions.AfterTupleComma;
break;
case ParseTypeMode.FirstElementOfPossibleTupleLiteral:
nameOptions = NameOptions.InExpression | NameOptions.FirstElementOfPossibleTupleLiteral;
break;
case ParseTypeMode.NewExpression:
case ParseTypeMode.AsExpression:
case ParseTypeMode.Normal:
case ParseTypeMode.Parameter:
case ParseTypeMode.AfterRef:
nameOptions = NameOptions.None;
break;
default:
throw ExceptionUtilities.UnexpectedValue(mode);
}
var type = this.ParseUnderlyingType(mode, options: nameOptions);
Debug.Assert(type != null);
int lastTokenPosition = -1;
while (IsMakingProgress(ref lastTokenPosition))
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.QuestionToken:
{
var question = TryEatNullableQualifierIfApplicable(type, mode);
if (question != null)
{
type = _syntaxFactory.NullableType(type, question);
continue;
}
// token not consumed
break;
}
case SyntaxKind.AsteriskToken:
switch (mode)
{
case ParseTypeMode.AfterIs:
case ParseTypeMode.DefinitePattern:
case ParseTypeMode.AfterTupleComma:
case ParseTypeMode.FirstElementOfPossibleTupleLiteral:
// these contexts do not permit a pointer type except as an element type of an array.
if (PointerTypeModsFollowedByRankAndDimensionSpecifier())
{
type = this.ParsePointerTypeMods(type);
continue;
}
break;
case ParseTypeMode.Normal:
case ParseTypeMode.Parameter:
case ParseTypeMode.AfterOut:
case ParseTypeMode.AfterRef:
case ParseTypeMode.AsExpression:
case ParseTypeMode.NewExpression:
type = this.ParsePointerTypeMods(type);
continue;
}
// token not consumed
break;
case SyntaxKind.OpenBracketToken:
// Now check for arrays.
{
var ranks = _pool.Allocate<ArrayRankSpecifierSyntax>();
do
{
ranks.Add(this.ParseArrayRankSpecifier(out _));
}
while (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken);
type = _syntaxFactory.ArrayType(type, _pool.ToListAndFree(ranks));
continue;
}
default:
// token not consumed
break;
}
// token not consumed
break;
}
Debug.Assert(type != null);
return type;
}
private SyntaxToken TryEatNullableQualifierIfApplicable(
TypeSyntax typeParsedSoFar, ParseTypeMode mode)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.QuestionToken);
// These are the fast tests for (in)applicability. More expensive tests are follow.
//
// If we already have `x?` or `x*` then do not parse out a nullable type if we see `x??` or `x*?`. These
// are never legal as types in the language, so we can fast bail out.
if (typeParsedSoFar.Kind is SyntaxKind.NullableType or SyntaxKind.PointerType)
return null;
using var outerResetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
var questionToken = this.EatToken();
if (!canFollowNullableType())
{
// Restore current token index
outerResetPoint.Reset();
return null;
}
return questionToken;
bool canFollowNullableType()
{
if (mode == ParseTypeMode.AfterIs && this.CurrentToken.Kind is SyntaxKind.OpenBracketToken)
{
// T?[
//
// This could be a array of nullable types (e.g. `is T?[]` or `is T?[,]`) or it's a
// conditional with a collection expression or lambda (e.g. `is T ? [...] :` or `is T ? [Attr]() => ...`)
//
// Note: `is T?[]` could be the start of either. So we have to look to see if we have a
// `:` to know which case we're in.
switch (this.PeekToken(1).Kind)
{
// `is T?[,]`. Definitely an array of nullable type.
case SyntaxKind.CommaToken:
return true;
// `is T?[]`. Could be an array of a nullable type, or a conditional. Have to
// see if it is followed by `:` to find out. If there is a colon, it's a
// conditional.
case SyntaxKind.CloseBracketToken:
{
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// Consume the expression after the `?`.
var whenTrue = this.ParsePossibleRefExpression();
// Now see if we have a ':' following. If so, this is a conditional. If not, it's a nullable type.
return this.CurrentToken.Kind != SyntaxKind.ColonToken;
}
// `is T ? [...`. Not an array. This is a conditional with a collection expr
// or attributed lambda.
default:
return false;
}
}
switch (mode)
{
case ParseTypeMode.AfterIs:
case ParseTypeMode.DefinitePattern:
case ParseTypeMode.AsExpression:
// We are currently after `?` token after a nullable type pattern and need to decide how to
// parse what we see next. In the case of an identifier (e.g. `x ? a` there are two ways we can
// see things
//
// 1. As a start of conditional expression, e.g. `var a = obj is string ? a : b`
// 2. As a designation of a nullable-typed pattern, e.g. `if (obj is string? str)`
//
// Since nullable types (no matter reference or value types) are not valid in patterns by
// default we are biased towards the first option and consider case 2 only for error recovery
// purposes (if we parse here as nullable type pattern an error will be reported during
// binding). This condition checks for simple cases, where we better use option 2 and parse a
// nullable-typed pattern
if (IsTrueIdentifier(this.CurrentToken))
{
// 1. `async` can start a simple lambda in a conditional expression
// (e.g. `x is Y ? async a => ...`). The correct behavior is to treat `async` as a keyword
// 2. In a non-async method, `await` is a simple identifier. However, if we see `x ? await`
// it's almost certainly the start of an `await expression` in a conditional expression
// (e.g. `x is Y ? await ...`), not a nullable type pattern (since users would not use
// 'await' as the name of a variable). So just treat this as a conditional expression.
// 3. `from` most likely starts a linq query: `x is Y ? from item in collection select item : ...`
if (this.CurrentToken.ContextualKind is SyntaxKind.AsyncKeyword or SyntaxKind.AwaitKeyword or SyntaxKind.FromKeyword)
return false;
var nextToken = PeekToken(1);
// Cases like `x is Y ? someRecord with { } : ...`
if (nextToken.ContextualKind == SyntaxKind.WithKeyword)
return false;
var nextTokenKind = nextToken.Kind;
// These token either 100% end a pattern or start a new one:
// A literal token starts a new pattern. Can occur in list pattern with missing separation
// `,`. For example, in `x is [int[]? arr 5]` we'd prefer to parse this as a missing `,`
// after `arr`
if (SyntaxFacts.IsLiteral(nextTokenKind))
return true;
// A predefined type is basically the same case: `x is [string[]? slice char ch]`. We'd
// prefer to parse this as a missing `,` after `slice`.
if (SyntaxFacts.IsPredefinedType(nextTokenKind))
return true;
// `)`, `]` and `}` obviously end a pattern. For example:
// `if (x is int? i)`, `indexable[x is string? s]`, `x is { Prop: Type? typeVar }`
if (nextTokenKind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken or SyntaxKind.CloseBraceToken)
return true;
// `{` starts a new pattern. For example: `x is A? { ...`. Note, that `[` and `(` are not
// in the list because they can start an invocation/indexer
if (nextTokenKind == SyntaxKind.OpenBraceToken)
return true;
// `,` ends a pattern in list/property pattern. For example `x is { Prop1: Type1? type, Prop2: Type2 }` or
// `x is [Type1? type, ...]`
if (nextTokenKind == SyntaxKind.CommaToken)
return true;
// `;` ends a pattern if it finishes an expression statement: var y = x is bool? b;
if (nextTokenKind == SyntaxKind.SemicolonToken)
return true;
// EndOfFileToken is obviously the end of parsing. We are better parsing a pattern rather
// than an unfinished conditional expression
if (nextTokenKind == SyntaxKind.EndOfFileToken)
return true;
return false;
}
// If nothing from above worked permit the nullable qualifier if it is followed by a token that
// could not start an expression. If we have `T?[]` we do want to treat that as an array of
// nullables (following existing parsing), not a conditional that returns a list.
if (this.CurrentToken.Kind is SyntaxKind.OpenBracketToken)
return true;
return !CanStartExpression();
case ParseTypeMode.NewExpression:
// A nullable qualifier is permitted as part of the type in a `new` expression. e.g. `new
// int?()` is allowed. It creates a null value of type `Nullable<int>`. Similarly `new int? {}`
// is allowed.
return
this.CurrentToken.Kind is SyntaxKind.OpenParenToken or // ctor parameters
SyntaxKind.OpenBracketToken or // array type
SyntaxKind.OpenBraceToken; // object initializer
default:
return true;
}
}
}
private bool PointerTypeModsFollowedByRankAndDimensionSpecifier()
{
// Are pointer specifiers (if any) followed by an array specifier?
for (int i = 0; ; i++)
{
switch (this.PeekToken(i).Kind)
{
case SyntaxKind.AsteriskToken:
continue;
case SyntaxKind.OpenBracketToken:
return true;
default:
return false;
}
}
}
private ArrayRankSpecifierSyntax ParseArrayRankSpecifier(out bool sawNonOmittedSize)
{
sawNonOmittedSize = false;
bool sawOmittedSize = false;
var open = this.EatToken(SyntaxKind.OpenBracketToken);
var list = _pool.AllocateSeparated<ExpressionSyntax>();
var omittedArraySizeExpressionInstance = _syntaxFactory.OmittedArraySizeExpression(SyntaxFactory.Token(SyntaxKind.OmittedArraySizeExpressionToken));
int lastTokenPosition = -1;
while (IsMakingProgress(ref lastTokenPosition) && this.CurrentToken.Kind != SyntaxKind.CloseBracketToken)
{
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
// NOTE: trivia will be attached to comma, not omitted array size
sawOmittedSize = true;
list.Add(omittedArraySizeExpressionInstance);
list.AddSeparator(this.EatToken());
}
else if (this.IsPossibleExpression())
{
var size = this.ParseExpressionCore();
sawNonOmittedSize = true;
list.Add(size);
if (this.CurrentToken.Kind != SyntaxKind.CloseBracketToken)
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
}
}
else if (this.SkipBadArrayRankSpecifierTokens(ref open, list, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
// Don't end on a comma.
// If the omitted size would be the only element, then skip it unless sizes were expected.
if (((list.Count & 1) == 0))
{
sawOmittedSize = true;
list.Add(omittedArraySizeExpressionInstance);
}
// Never mix omitted and non-omitted array sizes. If there were non-omitted array sizes,
// then convert all of the omitted array sizes to missing identifiers.
if (sawOmittedSize && sawNonOmittedSize)
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].RawKind == (int)SyntaxKind.OmittedArraySizeExpression)
{
int width = list[i].Width;
int offset = list[i].GetLeadingTriviaWidth();
list[i] = this.AddError(this.CreateMissingIdentifierName(), offset, width, ErrorCode.ERR_ValueExpected);
}
}
}
return _syntaxFactory.ArrayRankSpecifier(
open,
_pool.ToListAndFree(list),
this.EatToken(SyntaxKind.CloseBracketToken));
}
private TupleTypeSyntax ParseTupleType()
{
var open = this.EatToken(SyntaxKind.OpenParenToken);
var list = _pool.AllocateSeparated<TupleElementSyntax>();
if (this.CurrentToken.Kind != SyntaxKind.CloseParenToken)
{
list.Add(ParseTupleElement());
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
list.Add(ParseTupleElement());
}
}
if (list.Count < 2)
{
if (list.Count < 1)
{
list.Add(_syntaxFactory.TupleElement(this.CreateMissingIdentifierName(), identifier: null));
}
list.AddSeparator(SyntaxFactory.MissingToken(SyntaxKind.CommaToken));
var missing = this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_TupleTooFewElements);
list.Add(_syntaxFactory.TupleElement(missing, identifier: null));
}
return _syntaxFactory.TupleType(
open,
_pool.ToListAndFree(list),
this.EatToken(SyntaxKind.CloseParenToken));
}
private TupleElementSyntax ParseTupleElement()
{
return _syntaxFactory.TupleElement(
ParseType(),
IsTrueIdentifier() ? this.ParseIdentifierToken() : null);
}
private PostSkipAction SkipBadArrayRankSpecifierTokens(ref SyntaxToken openBracket, SeparatedSyntaxListBuilder<ExpressionSyntax> list, SyntaxKind expected)
{
return this.SkipBadSeparatedListTokensWithExpectedKind(ref openBracket, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleExpression(),
static (p, _) => p.CurrentToken.Kind == SyntaxKind.CloseBracketToken,
expected);
}
private TypeSyntax ParseUnderlyingType(ParseTypeMode mode, NameOptions options = NameOptions.None)
{
if (IsPredefinedType(this.CurrentToken.Kind))
{
// This is a predefined type
var token = this.EatToken();
if (token.Kind == SyntaxKind.VoidKeyword && this.CurrentToken.Kind != SyntaxKind.AsteriskToken)
{
token = this.AddError(token, mode == ParseTypeMode.Parameter ? ErrorCode.ERR_NoVoidParameter : ErrorCode.ERR_NoVoidHere);
}
return _syntaxFactory.PredefinedType(token);
}
// The :: case is for error recovery.
if (IsTrueIdentifier() || this.CurrentToken.Kind == SyntaxKind.ColonColonToken)
{
return this.ParseQualifiedName(options);
}
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
return this.ParseTupleType();
}
else if (IsFunctionPointerStart())
{
return ParseFunctionPointerTypeSyntax();
}
return this.AddError(
this.CreateMissingIdentifierName(),
mode == ParseTypeMode.NewExpression ? ErrorCode.ERR_BadNewExpr : ErrorCode.ERR_TypeExpected);
}
#nullable enable
private FunctionPointerTypeSyntax ParseFunctionPointerTypeSyntax()
{
Debug.Assert(IsFunctionPointerStart());
var @delegate = EatToken(SyntaxKind.DelegateKeyword);
var asterisk = EatToken(SyntaxKind.AsteriskToken);
FunctionPointerCallingConventionSyntax? callingConvention = parseCallingConvention();
if (!IsPossibleFunctionPointerParameterListStart(CurrentToken))
{
var lessThanTokenError = WithAdditionalDiagnostics(SyntaxFactory.MissingToken(SyntaxKind.LessThanToken), GetExpectedTokenError(SyntaxKind.LessThanToken, SyntaxKind.None));
var missingTypes = _pool.AllocateSeparated<FunctionPointerParameterSyntax>();
var missingType = SyntaxFactory.FunctionPointerParameter(attributeLists: default, modifiers: default, CreateMissingIdentifierName());
missingTypes.Add(missingType);
// Handle the simple case of delegate*>. We don't try to deal with any variation of delegate*invalid>, as
// we don't know for sure that the expression isn't a relational with something else.
return SyntaxFactory.FunctionPointerType(
@delegate,
asterisk,
callingConvention,
SyntaxFactory.FunctionPointerParameterList(
lessThanTokenError,
_pool.ToListAndFree(missingTypes),
TryEatToken(SyntaxKind.GreaterThanToken) ?? SyntaxFactory.MissingToken(SyntaxKind.GreaterThanToken)));
}
var lessThanToken = EatTokenAsKind(SyntaxKind.LessThanToken);
var saveTerm = _termState;
_termState |= (lessThanToken.IsMissing ? TerminatorState.IsEndOfFunctionPointerParameterListErrored : TerminatorState.IsEndOfFunctionPointerParameterList);
var types = _pool.AllocateSeparated<FunctionPointerParameterSyntax>();
try
{
while (true)
{
var modifiers = _pool.Allocate<SyntaxToken>();
ParseParameterModifiers(modifiers, isFunctionPointerParameter: true);
types.Add(SyntaxFactory.FunctionPointerParameter(
attributeLists: default,
_pool.ToTokenListAndFree(modifiers),
ParseTypeOrVoid()));
if (skipBadFunctionPointerTokens(types) == PostSkipAction.Abort)
{
break;
}
Debug.Assert(CurrentToken.Kind == SyntaxKind.CommaToken);
types.AddSeparator(EatToken(SyntaxKind.CommaToken));
}
return SyntaxFactory.FunctionPointerType(
@delegate,
asterisk,
callingConvention,
SyntaxFactory.FunctionPointerParameterList(
lessThanToken,
_pool.ToListAndFree(types),
lessThanToken.IsMissing && CurrentToken.Kind == SyntaxKind.CloseParenToken
? EatTokenAsKind(SyntaxKind.GreaterThanToken)
: EatToken(SyntaxKind.GreaterThanToken)));
}
finally
{
_termState = saveTerm;
}
PostSkipAction skipBadFunctionPointerTokens<T>(SeparatedSyntaxListBuilder<T> list) where T : CSharpSyntaxNode
{
CSharpSyntaxNode? tmp = null;
Debug.Assert(list.Count > 0);
return SkipBadSeparatedListTokensWithExpectedKind(ref tmp,
list,
isNotExpectedFunction: static p => p.CurrentToken.Kind != SyntaxKind.CommaToken,
// this.IsTerminator() (called by our caller) is the only thing that aborts parsing.
abortFunction: static (p, _) => false,
expected: SyntaxKind.CommaToken);
}
FunctionPointerCallingConventionSyntax? parseCallingConvention()
{
if (CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
SyntaxToken managedSpecifier;
SyntaxToken peek1 = PeekToken(1);
switch (CurrentToken)
{
case { ContextualKind: SyntaxKind.ManagedKeyword }:
case { ContextualKind: SyntaxKind.UnmanagedKeyword }:
managedSpecifier = EatContextualToken(CurrentToken.ContextualKind);
break;
case var _ when IsPossibleFunctionPointerParameterListStart(peek1):
// If there's a possible parameter list next, treat this as a bad identifier that should have been managed or unmanaged
managedSpecifier = EatTokenAsKind(SyntaxKind.ManagedKeyword);
break;
case var _ when peek1.Kind == SyntaxKind.OpenBracketToken:
// If there's an open brace next, treat this as a bad identifier that should have been unmanaged
managedSpecifier = EatTokenAsKind(SyntaxKind.UnmanagedKeyword);
break;
default:
// Whatever is next, it's probably not a calling convention or a function pointer type.
// Bail out
return null;
}
FunctionPointerUnmanagedCallingConventionListSyntax? unmanagedCallingConventions = null;
if (CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
var openBracket = EatToken(SyntaxKind.OpenBracketToken);
var callingConventionModifiers = _pool.AllocateSeparated<FunctionPointerUnmanagedCallingConventionSyntax>();
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfFunctionPointerCallingConvention;
try
{
while (true)
{
callingConventionModifiers.Add(SyntaxFactory.FunctionPointerUnmanagedCallingConvention(EatToken(SyntaxKind.IdentifierToken)));
if (skipBadFunctionPointerTokens(callingConventionModifiers) == PostSkipAction.Abort)
{
break;
}
Debug.Assert(CurrentToken.Kind == SyntaxKind.CommaToken);
callingConventionModifiers.AddSeparator(EatToken(SyntaxKind.CommaToken));
}
var closeBracket = EatToken(SyntaxKind.CloseBracketToken);
unmanagedCallingConventions = SyntaxFactory.FunctionPointerUnmanagedCallingConventionList(
openBracket,
_pool.ToListAndFree(callingConventionModifiers), closeBracket);
}
finally
{
_termState = saveTerm;
}
}
if (managedSpecifier.Kind == SyntaxKind.ManagedKeyword && unmanagedCallingConventions != null)
{
// 'managed' calling convention cannot be combined with unmanaged calling convention specifiers.
unmanagedCallingConventions = AddError(unmanagedCallingConventions, ErrorCode.ERR_CannotSpecifyManagedWithUnmanagedSpecifiers);
}
return SyntaxFactory.FunctionPointerCallingConvention(managedSpecifier, unmanagedCallingConventions);
}
return null;
}
}
private bool IsFunctionPointerStart()
=> CurrentToken.Kind == SyntaxKind.DelegateKeyword && PeekToken(1).Kind == SyntaxKind.AsteriskToken;
private static bool IsPossibleFunctionPointerParameterListStart(SyntaxToken token)
// We consider both ( and < to be possible starts, in order to make error recovery more graceful
// in the scenario where a user accidentally surrounds their function pointer type list with parens.
=> token.Kind == SyntaxKind.LessThanToken || token.Kind == SyntaxKind.OpenParenToken;
#nullable disable
private TypeSyntax ParsePointerTypeMods(TypeSyntax type)
{
// Check for pointer types
while (this.CurrentToken.Kind == SyntaxKind.AsteriskToken)
{
type = _syntaxFactory.PointerType(type, this.EatToken());
}
return type;
}
public StatementSyntax ParseStatement()
{
return ParseWithStackGuard(
static @this => @this.ParsePossiblyAttributedStatement() ?? @this.ParseExpressionStatement(attributes: default),
static @this => SyntaxFactory.EmptyStatement(attributeLists: default, SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)));
}
private StatementSyntax ParsePossiblyAttributedStatement()
=> ParseStatementCore(ParseStatementAttributeDeclarations(), isGlobal: false);
private SyntaxList<AttributeListSyntax> ParseStatementAttributeDeclarations()
{
if (this.CurrentToken.Kind != SyntaxKind.OpenBracketToken)
return default;
// See if we should treat this as a collection expression. At the top-level or statement-level, this should
// only be considered a collection if followed by a `.`, `?` or `!` (indicating it's a value, not an
// attribute).
var resetPoint = GetResetPoint();
// Grab the first part as a collection expression.
ParseCollectionExpression();
// Continue consuming element access expressions for `[x][y]...`. We have to determine if this is a
// collection expression being indexed into, or if it's a sequence of attributes.
var hadBracketArgumentList = false;
while (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
ParseBracketedArgumentList();
hadBracketArgumentList = true;
}
// Check the next token to see if it indicates the `[...]` sequence we have is a term or not. This is the
// same set of tokens that ParsePostFixExpression looks for.
//
// Note `SyntaxKind.DotToken` handles both the `[...].Name` case as well as the `[...]..Range` case.
var isCollectionExpression = this.CurrentToken.Kind
is SyntaxKind.DotToken
or SyntaxKind.QuestionToken
or SyntaxKind.ExclamationToken
or SyntaxKind.PlusPlusToken
or SyntaxKind.MinusMinusToken
or SyntaxKind.MinusGreaterThanToken;
// Now look for another set of items that indicate that we're not an attribute, but instead are a collection
// expression misplaced in an invalid top level expression-statement. (like `[] + b`). These are
// technically invalid. But checking for this allows us to parse effectively to then give a good semantic
// error later on. These cases came from: ParseExpressionContinued
isCollectionExpression = isCollectionExpression
|| IsExpectedBinaryOperator(this.CurrentToken.Kind)
|| IsExpectedAssignmentOperator(this.CurrentToken.Kind)
|| (this.CurrentToken.ContextualKind is SyntaxKind.SwitchKeyword or SyntaxKind.WithKeyword && this.PeekToken(1).Kind is SyntaxKind.OpenBraceToken);
if (!isCollectionExpression &&
hadBracketArgumentList &&
this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
// There are a few things that could be happening here:
//
// First is that we have an actual collection expression that we're invoking. For example:
//
// `[() => {}][rand.NextInt() % x]();`
//
// Second would be the start of a local function that returns a tuple. For example:
//
// `[Attr] (A, B) LocalFunc() { }
//
// Have to figure out what the parenthesized thing is in order to parse this. By parsing out a type
// and looking for an identifier next, we handle the cases of:
//
// `[Attr] (A, B) LocalFunc() { }
// `[Attr] (A, B)[] LocalFunc() { }
// `[Attr] (A, B)[,] LocalFunc() { }
// `[Attr] (A, B)? LocalFunc() { }
// `[Attr] (A, B)* LocalFunc() { }
//
// etc.
//
// Note: we do not accept the naked `[...](...)` as an invocation of a collection expression. Collection
// literals never have a type that itself could possibly be invoked, so this ensures a more natural parse
// with what users may be expecting here.
var returnType = this.ParseReturnType();
isCollectionExpression = ContainsErrorDiagnostic(returnType) || !IsTrueIdentifier();
}
// If this was a collection expression, not an attribute declaration, return no attributes so that the
// caller will parse this out as a collection expression. Otherwise re-parse the code as the actual
// attribute declarations.
this.Reset(ref resetPoint);
var attributes = isCollectionExpression ? default : ParseAttributeDeclarations(inExpressionContext: true);
this.Release(ref resetPoint);
return attributes;
}
/// <param name="isGlobal">If we're being called while parsing a C# top-level statements (Script or Simple Program).
/// At the top level in Script, we allow most statements *except* for local-decls/local-funcs.
/// Those will instead be parsed out as script-fields/methods.</param>
private StatementSyntax ParseStatementCore(SyntaxList<AttributeListSyntax> attributes, bool isGlobal)
{
if (TryReuseStatement(attributes, isGlobal) is { } reused)
{
return reused;
}
ResetPoint resetPointBeforeStatement = this.GetResetPoint();
try
{
_recursionDepth++;
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
StatementSyntax result;
// Main switch to handle processing almost any statement.
switch (this.CurrentToken.Kind)
{
case SyntaxKind.FixedKeyword:
return this.ParseFixedStatement(attributes);
case SyntaxKind.BreakKeyword:
return this.ParseBreakStatement(attributes);
case SyntaxKind.ContinueKeyword:
return this.ParseContinueStatement(attributes);
case SyntaxKind.TryKeyword:
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
return this.ParseTryStatement(attributes);
case SyntaxKind.CheckedKeyword:
case SyntaxKind.UncheckedKeyword:
return this.ParseCheckedStatement(attributes);
case SyntaxKind.DoKeyword:
return this.ParseDoStatement(attributes);
case SyntaxKind.ForKeyword:
return this.ParseForOrForEachStatement(attributes);
case SyntaxKind.ForEachKeyword:
return this.ParseForEachStatement(attributes, awaitTokenOpt: null);
case SyntaxKind.GotoKeyword:
return this.ParseGotoStatement(attributes);
case SyntaxKind.IfKeyword:
return this.ParseIfStatement(attributes);
case SyntaxKind.ElseKeyword:
// Including 'else' keyword to handle 'else without if' error cases
return this.ParseMisplacedElse(attributes);
case SyntaxKind.LockKeyword:
return this.ParseLockStatement(attributes);
case SyntaxKind.ReturnKeyword:
return this.ParseReturnStatement(attributes);
case SyntaxKind.SwitchKeyword:
case SyntaxKind.CaseKeyword: // error recovery case.
return this.ParseSwitchStatement(attributes);
case SyntaxKind.ThrowKeyword:
return this.ParseThrowStatement(attributes);
case SyntaxKind.UnsafeKeyword:
result = TryParseStatementStartingWithUnsafe(attributes);
if (result != null)
return result;
break;
case SyntaxKind.UsingKeyword:
return ParseStatementStartingWithUsing(attributes);
case SyntaxKind.WhileKeyword:
return this.ParseWhileStatement(attributes);
case SyntaxKind.OpenBraceToken:
return this.ParseBlock(attributes);
case SyntaxKind.SemicolonToken:
return _syntaxFactory.EmptyStatement(attributes, this.EatToken());
case SyntaxKind.IdentifierToken:
result = TryParseStatementStartingWithIdentifier(attributes, isGlobal);
if (result != null)
return result;
break;
}
return ParseStatementCoreRest(attributes, isGlobal, ref resetPointBeforeStatement);
}
finally
{
_recursionDepth--;
this.Release(ref resetPointBeforeStatement);
}
}
private StatementSyntax TryReuseStatement(SyntaxList<AttributeListSyntax> attributes, bool isGlobal)
{
if (this.IsIncrementalAndFactoryContextMatches &&
this.CurrentNode is Syntax.StatementSyntax &&
!isGlobal && // Top-level statements are reused by ParseMemberDeclarationOrStatementCore when possible.
attributes.Count == 0)
{
return (StatementSyntax)this.EatNode();
}
return null;
}
private StatementSyntax ParseStatementCoreRest(SyntaxList<AttributeListSyntax> attributes, bool isGlobal, ref ResetPoint resetPointBeforeStatement)
{
isGlobal = isGlobal && IsScript;
if (!this.IsPossibleLocalDeclarationStatement(isGlobal))
{
return this.ParseExpressionStatement(attributes);
}
if (isGlobal)
{
// if we're at the global script level, then we don't support local-decls or
// local-funcs. The caller instead will look for those and parse them as
// fields/methods in the global script scope.
return null;
}
bool beginsWithAwait = this.CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword;
var result = ParseLocalDeclarationStatement(attributes);
// didn't get any sort of statement. This was something else entirely
// (like just a `}`). No need to retry anything here. Just reset back
// to where we started from and bail entirely from parsing a statement.
if (result == null)
{
this.Reset(ref resetPointBeforeStatement);
return null;
}
if (result.ContainsDiagnostics &&
beginsWithAwait &&
!IsInAsync)
{
// Local decl had issues. We were also starting with 'await' in a non-async
// context. Retry parsing this as if we were in an 'async' context as it's much
// more likely that this was a misplace await-expr' than a local decl.
//
// The user will still get a later binding error about an await-expr in a non-async
// context.
this.Reset(ref resetPointBeforeStatement);
IsInAsync = true;
result = ParseExpressionStatement(attributes);
IsInAsync = false;
}
// Didn't want to retry as an `await expr`. Just return what we actually
// produced.
return result;
}
private StatementSyntax TryParseStatementStartingWithIdentifier(SyntaxList<AttributeListSyntax> attributes, bool isGlobal)
{
if (this.CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword &&
this.PeekToken(1).Kind == SyntaxKind.ForEachKeyword)
{
return this.ParseForEachStatement(attributes, this.EatContextualToken(SyntaxKind.AwaitKeyword));
}
else if (IsPossibleAwaitUsing())
{
if (PeekToken(2).Kind == SyntaxKind.OpenParenToken)
{
// `await using Type ...` is handled below in ParseLocalDeclarationStatement
return this.ParseUsingStatement(attributes, this.EatContextualToken(SyntaxKind.AwaitKeyword));
}
}
else if (this.IsPossibleLabeledStatement())
{
return this.ParseLabeledStatement(attributes);
}
else if (this.IsPossibleYieldStatement())
{
return this.ParseYieldStatement(attributes);
}
else if (this.IsPossibleAwaitExpressionStatement())
{
return this.ParseExpressionStatement(attributes);
}
else if (this.IsQueryExpression(mayBeVariableDeclaration: true, mayBeMemberDeclaration: isGlobal && IsScript))
{
return this.ParseExpressionStatement(attributes, this.ParseQueryExpression(0));
}
return null;
}
private StatementSyntax ParseStatementStartingWithUsing(SyntaxList<AttributeListSyntax> attributes)
=> PeekToken(1).Kind == SyntaxKind.OpenParenToken ? ParseUsingStatement(attributes) : ParseLocalDeclarationStatement(attributes);
// Checking for brace to disambiguate between unsafe statement and unsafe local function
private StatementSyntax TryParseStatementStartingWithUnsafe(SyntaxList<AttributeListSyntax> attributes)
=> IsPossibleUnsafeStatement() ? ParseUnsafeStatement(attributes) : null;
private bool IsPossibleAwaitUsing()
=> CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword && PeekToken(1).Kind == SyntaxKind.UsingKeyword;
private bool IsPossibleLabeledStatement()
{
return this.PeekToken(1).Kind == SyntaxKind.ColonToken && this.IsTrueIdentifier();
}
private bool IsPossibleUnsafeStatement()
{
return this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken;
}
private bool IsPossibleYieldStatement()
{
return this.CurrentToken.ContextualKind == SyntaxKind.YieldKeyword &&
this.PeekToken(1).Kind is SyntaxKind.ReturnKeyword or SyntaxKind.BreakKeyword;
}
private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel)
{
// This method decides whether to parse a statement as a
// declaration or as an expression statement. In the old
// compiler it would simply call IsLocalDeclaration.
var tk = this.CurrentToken.Kind;
if (tk == SyntaxKind.RefKeyword ||
IsDeclarationModifier(tk) || // treat `static int x = 2;` as a local variable declaration
(SyntaxFacts.IsPredefinedType(tk) &&
this.PeekToken(1).Kind is not SyntaxKind.DotToken // e.g. `int.Parse()` is an expression
and not SyntaxKind.OpenParenToken)) // e.g. `int (x, y)` is an error decl expression
{
return true;
}
// note: `using (` and `await using (` are already handled in ParseStatementCore.
if (tk == SyntaxKind.UsingKeyword)
{
Debug.Assert(PeekToken(1).Kind != SyntaxKind.OpenParenToken);
return true;
}
if (IsPossibleAwaitUsing())
{
Debug.Assert(PeekToken(2).Kind != SyntaxKind.OpenParenToken);
return true;
}
if (IsPossibleScopedKeyword(isFunctionPointerParameter: false))
{
return true;
}
tk = this.CurrentToken.ContextualKind;
var isPossibleModifier =
IsAdditionalLocalFunctionModifier(tk)
&& (tk is not (SyntaxKind.AsyncKeyword or SyntaxKind.ScopedKeyword) || ShouldContextualKeywordBeTreatedAsModifier(parsingStatementNotDeclaration: true));
if (isPossibleModifier)
{
return true;
}
return IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(isGlobalScriptLevel);
}
private bool IsPossibleScopedKeyword(bool isFunctionPointerParameter)
{
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
return ParsePossibleScopedKeyword(isFunctionPointerParameter) != null;
}
private bool IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(bool isGlobalScriptLevel)
{
bool? typedIdentifier = IsPossibleTypedIdentifierStart(this.CurrentToken, this.PeekToken(1), allowThisKeyword: false);
if (typedIdentifier != null)
{
return typedIdentifier.Value;
}
// It's common to have code like the following:
//
// Task.
// await Task.Delay()
//
// In this case we don't want to parse this as a local declaration like:
//
// Task.await Task
//
// This does not represent user intent, and it causes all sorts of problems to higher
// layers. This is because both the parse tree is strange, and the symbol tables have
// entries that throw things off (like a bogus 'Task' local).
//
// Note that we explicitly do this check when we see that the code spreads over multiple
// lines. We don't want this if the user has actually written "X.Y z"
var tk = this.CurrentToken.ContextualKind;
if (tk == SyntaxKind.IdentifierToken)
{
var token1 = PeekToken(1);
if (token1.Kind == SyntaxKind.DotToken &&
token1.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia))
{
if (PeekToken(2).Kind == SyntaxKind.IdentifierToken &&
PeekToken(3).Kind == SyntaxKind.IdentifierToken)
{
// We have something like:
//
// X.
// Y z
//
// This is only a local declaration if we have:
//
// X.Y z;
// X.Y z = ...
// X.Y z, ...
// X.Y z( ... (local function)
// X.Y z<W... (local function)
//
var token4Kind = PeekToken(4).Kind;
if (token4Kind != SyntaxKind.SemicolonToken &&
token4Kind != SyntaxKind.EqualsToken &&
token4Kind != SyntaxKind.CommaToken &&
token4Kind != SyntaxKind.OpenParenToken &&
token4Kind != SyntaxKind.LessThanToken)
{
return false;
}
}
}
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
ScanTypeFlags st = this.ScanType();
// We could always return true for st == AliasQualName in addition to MustBeType on the first line, however, we want it to return false in the case where
// CurrentToken.Kind != SyntaxKind.Identifier so that error cases, like: A::N(), are not parsed as variable declarations and instead are parsed as A.N() where we can give
// a better error message saying "did you meant to use a '.'?"
if (st == ScanTypeFlags.MustBeType && this.CurrentToken.Kind is not SyntaxKind.DotToken and not SyntaxKind.OpenParenToken)
{
return true;
}
if (st == ScanTypeFlags.NotType || this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
return false;
}
// T? and T* might start an expression, we need to parse further to disambiguate:
if (isGlobalScriptLevel)
{
if (st == ScanTypeFlags.PointerOrMultiplication)
{
return false;
}
else if (st == ScanTypeFlags.NullableType)
{
return IsPossibleDeclarationStatementFollowingNullableType(isGlobalScriptLevel);
}
}
return true;
}
private bool IsPossibleTopLevelUsingLocalDeclarationStatement()
{
if (this.CurrentToken.Kind != SyntaxKind.UsingKeyword)
{
return false;
}
var tk = PeekToken(1).Kind;
if (tk == SyntaxKind.RefKeyword)
{
return true;
}
if (IsDeclarationModifier(tk)) // treat `const int x = 2;` as a local variable declaration
{
if (tk != SyntaxKind.StaticKeyword) // For `static` we still need to make sure we have a typed identifier after it, because `using static type;` is a valid using directive.
{
return true;
}
}
else if (SyntaxFacts.IsPredefinedType(tk))
{
return true;
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// Skip 'using' keyword
EatToken();
if (IsPossibleScopedKeyword(isFunctionPointerParameter: false))
{
return true;
}
if (tk == SyntaxKind.StaticKeyword)
{
// Skip 'static' keyword
EatToken();
}
return IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(isGlobalScriptLevel: false);
}
// Looks ahead for a declaration of a field, property or method declaration following a nullable type T?.
private bool IsPossibleDeclarationStatementFollowingNullableType(bool isGlobalScriptLevel)
{
if (IsFieldDeclaration(isEvent: false, isGlobalScriptLevel))
{
return IsPossibleFieldDeclarationFollowingNullableType();
}
ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt;
SyntaxToken identifierOrThisOpt;
TypeParameterListSyntax typeParameterListOpt;
this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterListOpt, isEvent: false);
if (explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null)
{
return false;
}
// looks like a property:
// T? Goo {
//
// Importantly, we don't consider `T? Goo =>` to be the start of a property. This is because it's legal to write:
// T ? Goo => Goo : Bar => Bar
if (this.CurrentToken.Kind is SyntaxKind.OpenBraceToken)
{
return true;
}
// don't accept indexers:
if (identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword)
{
return false;
}
return IsPossibleMethodDeclarationFollowingNullableType();
}
// At least one variable declaration terminated by a semicolon or a comma.
// idf;
// idf,
// idf = <expr>;
// idf = <expr>,
private bool IsPossibleFieldDeclarationFollowingNullableType()
{
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
return false;
}
this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.EqualsToken)
{
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfFieldDeclaration;
this.EatToken();
this.ParseVariableInitializer();
_termState = saveTerm;
}
return this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.SemicolonToken;
}
private bool IsPossibleMethodDeclarationFollowingNullableType()
{
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfMethodSignature;
var paramList = this.ParseParenthesizedParameterList();
_termState = saveTerm;
var separatedParameters = paramList.Parameters.GetWithSeparators();
// parsed full signature:
if (!paramList.CloseParenToken.IsMissing)
{
// (...) {
// (...) where
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken || this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
return true;
}
// disambiguates conditional expressions
// (...) :
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
return false;
}
}
// no parameters, just an open paren followed by a token that doesn't belong to a parameter definition:
if (separatedParameters.Count == 0)
{
return false;
}
var parameter = (ParameterSyntax)separatedParameters[0];
// has an attribute:
// ([Attr]
if (parameter.AttributeLists.Count > 0)
{
return true;
}
// has params modifier:
// (params
for (int i = 0; i < parameter.Modifiers.Count; i++)
{
if (parameter.Modifiers[i].Kind == SyntaxKind.ParamsKeyword)
{
return true;
}
}
if (parameter.Type == null)
{
// has arglist:
// (__arglist
if (parameter.Identifier.Kind == SyntaxKind.ArgListKeyword)
{
return true;
}
}
else if (parameter.Type.Kind == SyntaxKind.NullableType)
{
// nullable type with modifiers
// (ref T?
// (out T?
if (parameter.Modifiers.Count > 0)
{
return true;
}
// nullable type, identifier, and separator or closing parent
// (T ? idf,
// (T ? idf)
if (!parameter.Identifier.IsMissing &&
(separatedParameters.Count >= 2 && !separatedParameters[1].IsMissing ||
separatedParameters.Count == 1 && !paramList.CloseParenToken.IsMissing))
{
return true;
}
}
else if (parameter.Type.Kind == SyntaxKind.IdentifierName &&
((IdentifierNameSyntax)parameter.Type).Identifier.ContextualKind == SyntaxKind.FromKeyword)
{
// assume that "from" is meant to be a query start ("from" bound to a type is rare):
// (from
return false;
}
else
{
// has a name and a non-nullable type:
// (T idf
// (ref T idf
// (out T idf
if (!parameter.Identifier.IsMissing)
{
return true;
}
}
return false;
}
private bool IsPossibleNewExpression()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.NewKeyword);
// skip new
SyntaxToken nextToken = PeekToken(1);
// new { }
// new [ ]
switch (nextToken.Kind)
{
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken:
return true;
}
//
// Declaration with new modifier vs. new expression
// Parse it as an expression if the type is not followed by an identifier or this keyword.
//
// Member declarations:
// new T Idf ...
// new T this ...
// new partial Idf ("partial" as a type name)
// new partial this ("partial" as a type name)
// new partial T Idf
// new partial T this
// new <modifier>
// new <class|interface|struct|enum>
// new partial <class|interface|struct|enum>
//
// New expressions:
// new T []
// new T { }
// new <non-type>
// new partial []
//
if (SyntaxFacts.GetBaseTypeDeclarationKind(nextToken.Kind) != SyntaxKind.None)
{
return false;
}
DeclarationModifiers modifier = GetModifierExcludingScoped(nextToken);
if (modifier == DeclarationModifiers.Partial)
{
if (SyntaxFacts.IsPredefinedType(PeekToken(2).Kind))
{
return false;
}
// class, struct, enum, interface keywords, but also other modifiers that are not allowed after
// partial keyword but start class declaration, so we can assume the user just swapped them.
if (IsTypeModifierOrTypeKeyword(PeekToken(2).Kind))
{
return false;
}
}
else if (modifier != DeclarationModifiers.None)
{
return false;
}
bool? typedIdentifier = IsPossibleTypedIdentifierStart(nextToken, PeekToken(2), allowThisKeyword: true);
if (typedIdentifier != null)
{
// new Idf Idf
// new Idf .
// new partial T
// new partial .
return !typedIdentifier.Value;
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// skips new keyword
EatToken();
ScanTypeFlags st = this.ScanType();
return !IsPossibleMemberName() || st == ScanTypeFlags.NotType;
}
/// <returns>
/// true if the current token can be the first token of a typed identifier (a type name followed by an identifier),
/// false if it definitely can't be,
/// null if we need to scan further to find out.
/// </returns>
private bool? IsPossibleTypedIdentifierStart(SyntaxToken current, SyntaxToken next, bool allowThisKeyword)
{
if (IsTrueIdentifier(current))
{
switch (next.Kind)
{
// tokens that can be in type names...
case SyntaxKind.DotToken:
case SyntaxKind.AsteriskToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.ColonColonToken:
return null;
case SyntaxKind.OpenParenToken:
if (current.IsIdentifierVar())
{
// potentially either a tuple type in a local declaration (true), or
// a tuple lvalue in a deconstruction assignment (false).
return null;
}
else
{
return false;
}
case SyntaxKind.IdentifierToken:
return IsTrueIdentifier(next);
case SyntaxKind.ThisKeyword:
return allowThisKeyword;
default:
return false;
}
}
return null;
}
private BlockSyntax ParsePossiblyAttributedBlock() => ParseBlock(this.ParseAttributeDeclarations(inExpressionContext: false));
/// <summary>
/// Used to parse the block-body for a method or accessor. For blocks that appear *inside*
/// method bodies, call <see cref="ParseBlock"/>.
/// </summary>
/// <param name="isAccessorBody">If is true, then we produce a special diagnostic if the
/// open brace is missing.</param>
private BlockSyntax ParseMethodOrAccessorBodyBlock(SyntaxList<AttributeListSyntax> attributes, bool isAccessorBody)
{
// Check again for incremental re-use. This way if a method signature is edited we can
// still quickly re-sync on the body.
if (this.IsIncrementalAndFactoryContextMatches &&
this.CurrentNodeKind == SyntaxKind.Block &&
attributes.Count == 0)
{
return (BlockSyntax)this.EatNode();
}
// There's a special error code for a missing token after an accessor keyword
CSharpSyntaxNode openBrace = isAccessorBody && this.CurrentToken.Kind != SyntaxKind.OpenBraceToken
? this.AddError(
SyntaxFactory.MissingToken(SyntaxKind.OpenBraceToken),
IsFeatureEnabled(MessageID.IDS_FeatureExpressionBodiedAccessor)
? ErrorCode.ERR_SemiOrLBraceOrArrowExpected
: ErrorCode.ERR_SemiOrLBraceExpected)
: this.EatToken(SyntaxKind.OpenBraceToken);
var statements = _pool.Allocate<StatementSyntax>();
this.ParseStatements(ref openBrace, statements, stopOnSwitchSections: false);
var block = _syntaxFactory.Block(
attributes,
(SyntaxToken)openBrace,
// Force creation a many-children list, even if only 1, 2, or 3 elements in the statement list.
IsLargeEnoughNonEmptyStatementList(statements)
? new SyntaxList<StatementSyntax>(SyntaxList.List(((SyntaxListBuilder)statements).ToArray()))
: statements,
this.EatToken(SyntaxKind.CloseBraceToken));
_pool.Free(statements);
return block;
}
/// <summary>
/// Used to parse normal blocks that appear inside method bodies. For the top level block
/// of a method/accessor use <see cref="ParseMethodOrAccessorBodyBlock"/>.
/// </summary>
private BlockSyntax ParseBlock(SyntaxList<AttributeListSyntax> attributes)
{
// Check again for incremental re-use, since ParseBlock is called from a bunch of places
// other than ParseStatementCore()
// Also, if our caller produced any attributes, we don't want to reuse an existing block syntax
// directly as we don't want to lose those attributes
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.Block && attributes.Count == 0)
return (BlockSyntax)this.EatNode();
CSharpSyntaxNode openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var statements = _pool.Allocate<StatementSyntax>();
this.ParseStatements(ref openBrace, statements, stopOnSwitchSections: false);
return _syntaxFactory.Block(
attributes,
(SyntaxToken)openBrace,
_pool.ToListAndFree(statements),
this.EatToken(SyntaxKind.CloseBraceToken));
}
// Is this statement list non-empty, and large enough to make using weak children beneficial?
private static bool IsLargeEnoughNonEmptyStatementList(SyntaxListBuilder<StatementSyntax> statements)
{
if (statements.Count == 0)
{
return false;
}
else if (statements.Count == 1)
{
// If we have a single statement, it might be small, like "return null", or large,
// like a loop or if or switch with many statements inside. Use the width as a proxy for
// how big it is. If it's small, its better to forgo a many children list anyway, since the
// weak reference would consume as much memory as is saved.
return statements[0].Width > 60;
}
else
{
// For 2 or more statements, go ahead and create a many-children lists.
return true;
}
}
private void ParseStatements(ref CSharpSyntaxNode previousNode, SyntaxListBuilder<StatementSyntax> statements, bool stopOnSwitchSections)
{
var saveTerm = _termState;
_termState |= TerminatorState.IsPossibleStatementStartOrStop; // partial statements can abort if a new statement starts
if (stopOnSwitchSections)
{
_termState |= TerminatorState.IsSwitchSectionStart;
}
int lastTokenPosition = -1;
while (this.CurrentToken.Kind is not SyntaxKind.CloseBraceToken and not SyntaxKind.EndOfFileToken
&& !(stopOnSwitchSections && this.IsPossibleSwitchSection())
&& IsMakingProgress(ref lastTokenPosition))
{
if (this.IsPossibleStatement())
{
var statement = this.ParsePossiblyAttributedStatement();
if (statement != null)
{
statements.Add(statement);
continue;
}
}
GreenNode trailingTrivia;
var action = this.SkipBadStatementListTokens(statements, SyntaxKind.CloseBraceToken, out trailingTrivia);
if (trailingTrivia != null)
{
previousNode = AddTrailingSkippedSyntax(previousNode, trailingTrivia);
}
if (action == PostSkipAction.Abort)
{
break;
}
}
_termState = saveTerm;
}
private bool IsPossibleStatementStartOrStop()
{
return this.CurrentToken.Kind == SyntaxKind.SemicolonToken
|| this.IsPossibleStatement();
}
private PostSkipAction SkipBadStatementListTokens(SyntaxListBuilder<StatementSyntax> statements, SyntaxKind expected, out GreenNode trailingTrivia)
{
return this.SkipBadListTokensWithExpectedKindHelper(
statements,
// We know we have a bad statement, so it can't be a local
// function, meaning we shouldn't consider accessibility
// modifiers to be the start of a statement
static p => !p.IsPossibleStatement(),
static (p, _) => p.CurrentToken.Kind == SyntaxKind.CloseBraceToken,
expected,
closeKind: SyntaxKind.None,
out trailingTrivia);
}
private bool IsPossibleStatement()
{
var tk = this.CurrentToken.Kind;
switch (tk)
{
case SyntaxKind.FixedKeyword:
case SyntaxKind.BreakKeyword:
case SyntaxKind.ContinueKeyword:
case SyntaxKind.TryKeyword:
case SyntaxKind.CheckedKeyword:
case SyntaxKind.UncheckedKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DoKeyword:
case SyntaxKind.ForKeyword:
case SyntaxKind.ForEachKeyword:
case SyntaxKind.GotoKeyword:
case SyntaxKind.IfKeyword:
case SyntaxKind.ElseKeyword:
case SyntaxKind.LockKeyword:
case SyntaxKind.ReturnKeyword:
case SyntaxKind.SwitchKeyword:
case SyntaxKind.ThrowKeyword:
case SyntaxKind.UnsafeKeyword:
case SyntaxKind.UsingKeyword:
case SyntaxKind.WhileKeyword:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.SemicolonToken:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ReadOnlyKeyword:
case SyntaxKind.VolatileKeyword:
case SyntaxKind.RefKeyword:
case SyntaxKind.ExternKeyword:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.CaseKeyword: // for parsing an errant case without a switch.
return true;
case SyntaxKind.IdentifierToken:
return IsTrueIdentifier();
default:
return IsPredefinedType(tk)
|| IsPossibleExpression();
}
}
private FixedStatementSyntax ParseFixedStatement(SyntaxList<AttributeListSyntax> attributes)
{
var @fixed = this.EatToken(SyntaxKind.FixedKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfFixedStatement;
var decl = ParseParenthesizedVariableDeclaration(VariableFlags.None, scopedKeyword: null);
_termState = saveTerm;
return _syntaxFactory.FixedStatement(
attributes,
@fixed,
openParen,
decl,
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseEmbeddedStatement());
}
private bool IsEndOfFixedStatement()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.OpenBraceToken or SyntaxKind.SemicolonToken;
}
private StatementSyntax ParseEmbeddedStatement()
{
// ParseEmbeddedStatement is called through many recursive statement parsing cases. We
// keep the body exceptionally simple, and we optimize for the common case, to ensure it
// is inlined into the callers. Otherwise the overhead of this single method can have a
// deep impact on the number of recursive calls we can make (more than a hundred during
// empirical testing).
return parseEmbeddedStatementRest(this.ParsePossiblyAttributedStatement());
StatementSyntax parseEmbeddedStatementRest(StatementSyntax statement)
{
if (statement == null)
{
// The consumers of embedded statements are expecting to receive a non-null statement
// yet there are several error conditions that can lead ParseStatementCore to return
// null. When that occurs create an error empty Statement and return it to the caller.
return SyntaxFactory.EmptyStatement(attributeLists: default, EatToken(SyntaxKind.SemicolonToken));
}
// In scripts, stand-alone expression statements may not be followed by semicolons.
// ParseExpressionStatement hides the error.
// However, embedded expression statements are required to be followed by semicolon.
if (statement.Kind == SyntaxKind.ExpressionStatement &&
IsScript)
{
var expressionStatementSyntax = (ExpressionStatementSyntax)statement;
var semicolonToken = expressionStatementSyntax.SemicolonToken;
// Do not add a new error if the same error was already added.
if (semicolonToken.IsMissing &&
!semicolonToken.GetDiagnostics().Contains(diagnosticInfo => (ErrorCode)diagnosticInfo.Code == ErrorCode.ERR_SemicolonExpected))
{
semicolonToken = this.AddError(semicolonToken, ErrorCode.ERR_SemicolonExpected);
return expressionStatementSyntax.Update(expressionStatementSyntax.AttributeLists, expressionStatementSyntax.Expression, semicolonToken);
}
}
return statement;
}
}
private BreakStatementSyntax ParseBreakStatement(SyntaxList<AttributeListSyntax> attributes)
{
return _syntaxFactory.BreakStatement(
attributes,
this.EatToken(SyntaxKind.BreakKeyword),
this.EatToken(SyntaxKind.SemicolonToken));
}
private ContinueStatementSyntax ParseContinueStatement(SyntaxList<AttributeListSyntax> attributes)
{
return _syntaxFactory.ContinueStatement(
attributes,
this.EatToken(SyntaxKind.ContinueKeyword),
this.EatToken(SyntaxKind.SemicolonToken));
}
private TryStatementSyntax ParseTryStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.TryKeyword or SyntaxKind.CatchKeyword or SyntaxKind.FinallyKeyword);
// We are called into on try/catch/finally, so eating the try may actually fail.
var @try = this.EatToken(SyntaxKind.TryKeyword);
BlockSyntax tryBlock;
if (@try.IsMissing)
{
// If there was no actual `try`, then we got here because of a misplaced `catch`/`finally`. In that
// case just synthesize a fully missing try-block. We will already have issued a diagnostic on the
// `try` keyword, so we don't need to issue any more.
Debug.Assert(@try.ContainsDiagnostics);
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.CatchKeyword or SyntaxKind.FinallyKeyword);
tryBlock = missingBlock();
}
else
{
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfTryBlock;
tryBlock = this.ParsePossiblyAttributedBlock();
_termState = saveTerm;
}
SyntaxListBuilder<CatchClauseSyntax> catchClauses = default;
FinallyClauseSyntax finallyClause = null;
if (this.CurrentToken.Kind == SyntaxKind.CatchKeyword)
{
catchClauses = _pool.Allocate<CatchClauseSyntax>();
while (this.CurrentToken.Kind == SyntaxKind.CatchKeyword)
{
catchClauses.Add(this.ParseCatchClause());
}
}
if (this.CurrentToken.Kind == SyntaxKind.FinallyKeyword)
{
finallyClause = _syntaxFactory.FinallyClause(
this.EatToken(),
this.ParsePossiblyAttributedBlock());
}
if (catchClauses.IsNull && finallyClause == null)
{
if (!ContainsErrorDiagnostic(tryBlock))
tryBlock = this.AddErrorToLastToken(tryBlock, ErrorCode.ERR_ExpectedEndTry);
// synthesize missing tokens for "finally { }":
finallyClause = _syntaxFactory.FinallyClause(
SyntaxFactory.MissingToken(SyntaxKind.FinallyKeyword),
missingBlock());
}
return _syntaxFactory.TryStatement(
attributes,
@try,
tryBlock,
_pool.ToListAndFree(catchClauses),
finallyClause);
BlockSyntax missingBlock()
=> _syntaxFactory.Block(
attributeLists: default,
SyntaxFactory.MissingToken(SyntaxKind.OpenBraceToken),
statements: default,
SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken));
}
private bool IsEndOfTryBlock()
{
return this.CurrentToken.Kind is SyntaxKind.CloseBraceToken or SyntaxKind.CatchKeyword or SyntaxKind.FinallyKeyword;
}
private CatchClauseSyntax ParseCatchClause()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.CatchKeyword);
var @catch = this.EatToken();
CatchDeclarationSyntax decl = null;
var saveTerm = _termState;
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
var openParen = this.EatToken();
_termState |= TerminatorState.IsEndOfCatchClause;
var type = this.ParseType();
SyntaxToken name = null;
if (this.IsTrueIdentifier())
{
name = this.ParseIdentifierToken();
}
_termState = saveTerm;
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
decl = _syntaxFactory.CatchDeclaration(openParen, type, name, closeParen);
}
CatchFilterClauseSyntax filter = null;
var keywordKind = this.CurrentToken.ContextualKind;
if (keywordKind == SyntaxKind.WhenKeyword || keywordKind == SyntaxKind.IfKeyword)
{
var whenKeyword = this.EatContextualToken(SyntaxKind.WhenKeyword);
if (keywordKind == SyntaxKind.IfKeyword)
{
// The initial design of C# exception filters called for the use of the
// "if" keyword in this position. We've since changed to "when", but
// the error recovery experience for early adopters (and for old source
// stored in the symbol server) will be better if we consume "if" as
// though it were "when".
whenKeyword = AddTrailingSkippedSyntax(whenKeyword, EatToken());
}
_termState |= TerminatorState.IsEndOfFilterClause;
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var filterExpression = this.ParseExpressionCore();
_termState = saveTerm;
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
filter = _syntaxFactory.CatchFilterClause(whenKeyword, openParen, filterExpression, closeParen);
}
_termState |= TerminatorState.IsEndOfCatchBlock;
var block = this.ParsePossiblyAttributedBlock();
_termState = saveTerm;
return _syntaxFactory.CatchClause(@catch, decl, filter, block);
}
private bool IsEndOfCatchClause()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken
or SyntaxKind.OpenBraceToken
or SyntaxKind.CloseBraceToken
or SyntaxKind.CatchKeyword
or SyntaxKind.FinallyKeyword;
}
private bool IsEndOfFilterClause()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken
or SyntaxKind.OpenBraceToken
or SyntaxKind.CloseBraceToken
or SyntaxKind.CatchKeyword
or SyntaxKind.FinallyKeyword;
}
private bool IsEndOfCatchBlock()
{
return this.CurrentToken.Kind is SyntaxKind.CloseBraceToken
or SyntaxKind.CatchKeyword
or SyntaxKind.FinallyKeyword;
}
private StatementSyntax ParseCheckedStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.CheckedKeyword or SyntaxKind.UncheckedKeyword);
if (this.PeekToken(1).Kind == SyntaxKind.OpenParenToken)
{
return this.ParseExpressionStatement(attributes);
}
var keyword = this.EatToken();
return _syntaxFactory.CheckedStatement(
SyntaxFacts.GetCheckStatement(keyword.Kind),
attributes,
keyword,
this.ParsePossiblyAttributedBlock());
}
private DoStatementSyntax ParseDoStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.DoKeyword);
var @do = this.EatToken(SyntaxKind.DoKeyword);
var statement = this.ParseEmbeddedStatement();
var @while = this.EatToken(SyntaxKind.WhileKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfDoWhileExpression;
var expression = this.ParseExpressionCore();
_termState = saveTerm;
return _syntaxFactory.DoStatement(
attributes,
@do,
statement,
@while,
openParen,
expression,
this.EatToken(SyntaxKind.CloseParenToken),
this.EatToken(SyntaxKind.SemicolonToken));
}
private bool IsEndOfDoWhileExpression()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.SemicolonToken;
}
private StatementSyntax ParseForOrForEachStatement(SyntaxList<AttributeListSyntax> attributes)
{
// Check if the user wrote the following accidentally:
//
// for (SomeType t in
//
// instead of
//
// foreach (SomeType t in
//
// In that case, parse it as a foreach, but given the appropriate message that a
// 'foreach' keyword was expected.
using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ForKeyword);
this.EatToken();
if (this.EatToken().Kind == SyntaxKind.OpenParenToken &&
this.ScanType() != ScanTypeFlags.NotType &&
this.EatToken().Kind == SyntaxKind.IdentifierToken &&
this.EatToken().Kind == SyntaxKind.InKeyword)
{
// Looks like a foreach statement. Parse it that way instead
resetPoint.Reset();
return this.ParseForEachStatement(attributes, awaitTokenOpt: null);
}
else
{
// Normal for statement.
resetPoint.Reset();
return this.ParseForStatement(attributes);
}
}
private ForStatementSyntax ParseForStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ForKeyword);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfForStatementArgument;
var forToken = this.EatToken(SyntaxKind.ForKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var (variableDeclaration, initializers) = eatVariableDeclarationOrInitializers();
var firstSemicolonToken = eatCommaOrSemicolon();
var condition = this.CurrentToken.Kind is not SyntaxKind.SemicolonToken and not SyntaxKind.CommaToken
? this.ParseExpressionCore()
: null;
// Used to place skipped tokens we run into when parsing the incrementors list.
var secondSemicolonToken = eatCommaOrSemicolon();
// Do allow semicolons (with diagnostics) in the incrementors list. This allows us to consume
// accidental extra incrementors that should have been separated by commas.
var incrementors = this.CurrentToken.Kind != SyntaxKind.CloseParenToken
? parseForStatementExpressionList(ref secondSemicolonToken, allowSemicolonAsSeparator: true)
: default;
var forStatement = _syntaxFactory.ForStatement(
attributes,
forToken,
openParen,
variableDeclaration,
initializers,
firstSemicolonToken,
condition,
secondSemicolonToken,
incrementors,
eatUnexpectedTokensAndCloseParenToken(),
ParseEmbeddedStatement());
_termState = saveTerm;
return forStatement;
(VariableDeclarationSyntax variableDeclaration, SeparatedSyntaxList<ExpressionSyntax> initializers) eatVariableDeclarationOrInitializers()
{
using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
// Here can be either a declaration or an expression statement list. Scan
// for a declaration first.
bool isDeclaration = false;
if (this.CurrentToken.ContextualKind == SyntaxKind.ScopedKeyword)
{
if (this.PeekToken(1).Kind == SyntaxKind.RefKeyword)
{
isDeclaration = true;
}
else
{
this.EatToken();
isDeclaration = ScanType() != ScanTypeFlags.NotType && this.CurrentToken.Kind == SyntaxKind.IdentifierToken;
resetPoint.Reset();
}
}
else if (this.CurrentToken.Kind == SyntaxKind.RefKeyword)
{
isDeclaration = true;
}
if (!isDeclaration)
{
isDeclaration = !this.IsQueryExpression(mayBeVariableDeclaration: true, mayBeMemberDeclaration: false) &&
this.ScanType() != ScanTypeFlags.NotType &&
this.IsTrueIdentifier();
resetPoint.Reset();
}
if (isDeclaration)
{
return (ParseParenthesizedVariableDeclaration(VariableFlags.ForStatement, ParsePossibleScopedKeyword(isFunctionPointerParameter: false)), initializers: default);
}
else if (this.CurrentToken.Kind != SyntaxKind.SemicolonToken)
{
// Not a type followed by an identifier, so it must be the initializer expression list.
//
// Do not consume semicolons here as they are used to separate the initializers out from the
// condition of the for loop.
return (variableDeclaration: null, parseForStatementExpressionList(ref openParen, allowSemicolonAsSeparator: false));
}
else
{
return default;
}
}
SyntaxToken eatCommaOrSemicolon()
=> this.CurrentToken.Kind is SyntaxKind.CommaToken
? this.EatTokenAsKind(SyntaxKind.SemicolonToken)
: this.EatToken(SyntaxKind.SemicolonToken);
SyntaxToken eatUnexpectedTokensAndCloseParenToken()
{
var skippedTokens = _pool.Allocate();
while (this.CurrentToken.Kind is SyntaxKind.SemicolonToken or SyntaxKind.CommaToken)
skippedTokens.Add(this.EatTokenWithPrejudice(SyntaxKind.CloseParenToken));
var result = this.EatToken(SyntaxKind.CloseParenToken);
return AddLeadingSkippedSyntax(result, _pool.ToTokenListAndFree(skippedTokens).Node);
}
// Parses out a sequence of expressions. Both for the initializer section (the `for (initializer1,
// initializer2, ...` section), as well as the incrementor section (the `for (;; incrementor1, incrementor2,
// ...` section).
SeparatedSyntaxList<ExpressionSyntax> parseForStatementExpressionList(ref SyntaxToken startToken, bool allowSemicolonAsSeparator)
=> ParseCommaSeparatedSyntaxList(
ref startToken,
SyntaxKind.CloseParenToken,
static @this => @this.IsPossibleExpression(),
static @this => @this.ParseExpressionCore(),
skipBadForStatementExpressionListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator);
static PostSkipAction skipBadForStatementExpressionListTokens(
LanguageParser @this, ref SyntaxToken startToken, SeparatedSyntaxListBuilder<ExpressionSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
if (@this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.SemicolonToken)
return PostSkipAction.Abort;
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref startToken, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleExpression(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind || p.CurrentToken.Kind == SyntaxKind.SemicolonToken,
expectedKind, closeKind);
}
}
private bool IsEndOfForStatementArgument()
{
return this.CurrentToken.Kind is SyntaxKind.SemicolonToken or SyntaxKind.CloseParenToken or SyntaxKind.OpenBraceToken;
}
private CommonForEachStatementSyntax ParseForEachStatement(
SyntaxList<AttributeListSyntax> attributes, SyntaxToken awaitTokenOpt)
{
// Can be a 'for' keyword if the user typed: 'for (SomeType t in'
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ForEachKeyword || this.CurrentToken.Kind == SyntaxKind.ForKeyword);
// Syntax for foreach is either:
// foreach [await] ( <type> <identifier> in <expr> ) <embedded-statement>
// or
// foreach [await] ( <deconstruction-declaration> in <expr> ) <embedded-statement>
SyntaxToken @foreach;
// If we're at a 'for', then consume it and attach
// it as skipped text to the missing 'foreach' token.
if (this.CurrentToken.Kind == SyntaxKind.ForKeyword)
{
var skippedForToken = this.EatToken();
skippedForToken = this.AddError(skippedForToken, ErrorCode.ERR_SyntaxError, SyntaxFacts.GetText(SyntaxKind.ForEachKeyword));
@foreach = ConvertToMissingWithTrailingTrivia(skippedForToken, SyntaxKind.ForEachKeyword);
}
else
{
@foreach = this.EatToken(SyntaxKind.ForEachKeyword);
}
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var variable = ParseExpressionOrDeclaration(ParseTypeMode.Normal, permitTupleDesignation: true);
var @in = this.EatToken(SyntaxKind.InKeyword, ErrorCode.ERR_InExpected);
if (!IsValidForeachVariable(variable))
{
@in = this.AddError(@in, ErrorCode.ERR_BadForeachDecl);
}
var expression = this.ParseExpressionCore();
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
var statement = this.ParseEmbeddedStatement();
if (variable is DeclarationExpressionSyntax decl)
{
if (decl.designation.Kind != SyntaxKind.ParenthesizedVariableDesignation)
{
// if we see a foreach declaration that isn't a deconstruction, we use the old form of foreach syntax node.
SyntaxToken identifier;
switch (decl.designation.Kind)
{
case SyntaxKind.SingleVariableDesignation:
identifier = ((SingleVariableDesignationSyntax)decl.designation).identifier;
break;
case SyntaxKind.DiscardDesignation:
// revert the identifier from its contextual underscore back to an identifier.
var discard = ((DiscardDesignationSyntax)decl.designation).underscoreToken;
Debug.Assert(discard.Kind == SyntaxKind.UnderscoreToken);
identifier = SyntaxToken.WithValue(SyntaxKind.IdentifierToken, discard.LeadingTrivia.Node, discard.Text, discard.ValueText, discard.TrailingTrivia.Node);
break;
default:
throw ExceptionUtilities.UnexpectedValue(decl.designation.Kind);
}
return _syntaxFactory.ForEachStatement(attributes, awaitTokenOpt, @foreach, openParen, decl.Type, identifier, @in, expression, closeParen, statement);
}
}
return _syntaxFactory.ForEachVariableStatement(attributes, awaitTokenOpt, @foreach, openParen, variable, @in, expression, closeParen, statement);
}
//
// Parse an expression where a declaration expression would be permitted. This is suitable for use after
// the `out` keyword in an argument list, or in the elements of a tuple literal (because they may
// be on the left-hand-side of a positional subpattern). The first element of a tuple is handled slightly
// differently, as we check for the comma before concluding that the identifier should cause a
// disambiguation. For example, for the input `(A < B , C > D)`, we treat this as a tuple with
// two elements, because if we considered the `A<B,C>` to be a type, it wouldn't be a tuple at
// all. Since we don't have such a thing as a one-element tuple (even for positional subpattern), the
// absence of the comma after the `D` means we don't treat the `D` as contributing to the
// disambiguation of the expression/type. More formally, ...
//
// If a sequence of tokens can be parsed(in context) as a* simple-name* (§7.6.3), *member-access* (§7.6.5),
// or* pointer-member-access* (§18.5.2) ending with a* type-argument-list* (§4.4.1), the token immediately
// following the closing `>` token is examined, to see if it is
// - One of `( ) ] } : ; , . ? == != | ^ && || & [`; or
// - One of the relational operators `< > <= >= is as`; or
// - A contextual query keyword appearing inside a query expression; or
// - In certain contexts, we treat *identifier* as a disambiguating token.Those contexts are where the
// sequence of tokens being disambiguated is immediately preceded by one of the keywords `is`, `case`
// or `out`, or arises while parsing the first element of a tuple literal(in which case the tokens are
// preceded by `(` or `:` and the identifier is followed by a `,`) or a subsequent element of a tuple literal.
//
// If the following token is among this list, or an identifier in such a context, then the *type-argument-list* is
// retained as part of the *simple-name*, *member-access* or *pointer-member-access* and any other possible parse
// of the sequence of tokens is discarded.Otherwise, the *type-argument-list* is not considered to be part of the
// *simple-name*, *member-access* or *pointer-member-access*, even if there is no other possible parse of the
// sequence of tokens.Note that these rules are not applied when parsing a *type-argument-list* in a *namespace-or-type-name* (§3.8).
//
// See also ScanTypeArgumentList where these disambiguation rules are encoded.
//
private ExpressionSyntax ParseExpressionOrDeclaration(ParseTypeMode mode, bool permitTupleDesignation)
{
return IsPossibleDeclarationExpression(mode, permitTupleDesignation, out var isScoped)
? this.ParseDeclarationExpression(mode, isScoped)
: this.ParseSubExpression(Precedence.Expression);
}
private bool IsPossibleDeclarationExpression(ParseTypeMode mode, bool permitTupleDesignation, out bool isScoped)
{
Debug.Assert(mode is ParseTypeMode.Normal or ParseTypeMode.FirstElementOfPossibleTupleLiteral or ParseTypeMode.AfterTupleComma);
isScoped = false;
if (this.IsInAsync && this.CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword)
{
// can't be a declaration expression.
return false;
}
using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: true);
if (this.CurrentToken.ContextualKind == SyntaxKind.ScopedKeyword)
{
this.EatToken();
if (ScanType() != ScanTypeFlags.NotType && this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
switch (mode)
{
case ParseTypeMode.FirstElementOfPossibleTupleLiteral:
if (this.PeekToken(1).Kind == SyntaxKind.CommaToken)
{
isScoped = true;
return true;
}
break;
case ParseTypeMode.AfterTupleComma:
if (this.PeekToken(1).Kind is SyntaxKind.CommaToken or SyntaxKind.CloseParenToken)
{
isScoped = true;
return true;
}
break;
default:
// The other case where we disambiguate between a declaration and expression is before the `in` of a foreach loop.
// There we err on the side of accepting a declaration.
isScoped = true;
return true;
}
}
resetPoint.Reset();
}
bool typeIsVar = IsVarType();
SyntaxToken lastTokenOfType;
if (ScanType(mode, out lastTokenOfType) == ScanTypeFlags.NotType)
{
return false;
}
// check for a designation
if (!ScanDesignation(permitTupleDesignation && (typeIsVar || IsPredefinedType(lastTokenOfType.Kind))))
{
return false;
}
switch (mode)
{
case ParseTypeMode.FirstElementOfPossibleTupleLiteral:
return this.CurrentToken.Kind == SyntaxKind.CommaToken;
case ParseTypeMode.AfterTupleComma:
return this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.CloseParenToken;
default:
// The other case where we disambiguate between a declaration and expression is before the `in` of a foreach loop.
// There we err on the side of accepting a declaration.
return true;
}
}
/// <summary>
/// Is the following set of tokens, interpreted as a type, the type <c>var</c>?
/// </summary>
private bool IsVarType()
{
if (!this.CurrentToken.IsIdentifierVar())
{
return false;
}
switch (this.PeekToken(1).Kind)
{
case SyntaxKind.DotToken:
case SyntaxKind.ColonColonToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.AsteriskToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.LessThanToken:
return false;
default:
return true;
}
}
private static bool IsValidForeachVariable(ExpressionSyntax variable)
{
switch (variable.Kind)
{
case SyntaxKind.DeclarationExpression:
// e.g. `foreach (var (x, y) in e)`
return true;
case SyntaxKind.TupleExpression:
// e.g. `foreach ((var x, var y) in e)`
return true;
case SyntaxKind.IdentifierName:
// e.g. `foreach (_ in e)`
return ((IdentifierNameSyntax)variable).Identifier.ContextualKind == SyntaxKind.UnderscoreToken;
default:
return false;
}
}
private GotoStatementSyntax ParseGotoStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.GotoKeyword);
var @goto = this.EatToken(SyntaxKind.GotoKeyword);
SyntaxToken caseOrDefault = null;
ExpressionSyntax arg = null;
SyntaxKind kind;
if (this.CurrentToken.Kind is SyntaxKind.CaseKeyword or SyntaxKind.DefaultKeyword)
{
caseOrDefault = this.EatToken();
if (caseOrDefault.Kind == SyntaxKind.CaseKeyword)
{
kind = SyntaxKind.GotoCaseStatement;
arg = this.ParseExpressionCore();
}
else
{
kind = SyntaxKind.GotoDefaultStatement;
}
}
else
{
kind = SyntaxKind.GotoStatement;
arg = this.ParseIdentifierName();
}
return _syntaxFactory.GotoStatement(
kind, attributes, @goto, caseOrDefault, arg, this.EatToken(SyntaxKind.SemicolonToken));
}
private IfStatementSyntax ParseIfStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.IfKeyword);
var stack = ArrayBuilder<(SyntaxToken, SyntaxToken, ExpressionSyntax, SyntaxToken, StatementSyntax, SyntaxToken)>.GetInstance();
StatementSyntax alternative = null;
while (true)
{
var ifKeyword = this.EatToken(SyntaxKind.IfKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var condition = this.ParseExpressionCore();
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
var consequence = this.ParseEmbeddedStatement();
var elseKeyword = this.CurrentToken.Kind != SyntaxKind.ElseKeyword ?
null :
this.EatToken(SyntaxKind.ElseKeyword);
stack.Push((ifKeyword, openParen, condition, closeParen, consequence, elseKeyword));
if (elseKeyword is null)
{
alternative = null;
break;
}
if (this.CurrentToken.Kind != SyntaxKind.IfKeyword)
{
alternative = this.ParseEmbeddedStatement();
break;
}
alternative = TryReuseStatement(attributes: default, isGlobal: false);
if (alternative is not null)
{
break;
}
}
IfStatementSyntax ifStatement;
do
{
var (ifKeyword, openParen, condition, closeParen, consequence, elseKeyword) = stack.Pop();
var elseClause = alternative is null ?
null :
_syntaxFactory.ElseClause(
elseKeyword,
alternative);
ifStatement = _syntaxFactory.IfStatement(
attributeLists: stack.Any() ? default : attributes,
ifKeyword,
openParen,
condition,
closeParen,
consequence,
elseClause);
alternative = ifStatement;
}
while (stack.Any());
stack.Free();
return ifStatement;
}
private IfStatementSyntax ParseMisplacedElse(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ElseKeyword);
return _syntaxFactory.IfStatement(
attributes,
this.EatToken(SyntaxKind.IfKeyword, ErrorCode.ERR_ElseCannotStartStatement),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseExpressionCore(),
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseExpressionStatement(attributes: default),
this.ParseElseClauseOpt());
}
private ElseClauseSyntax ParseElseClauseOpt()
{
return this.CurrentToken.Kind != SyntaxKind.ElseKeyword
? null
: _syntaxFactory.ElseClause(
this.EatToken(SyntaxKind.ElseKeyword),
this.ParseEmbeddedStatement());
}
private LockStatementSyntax ParseLockStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.LockKeyword);
return _syntaxFactory.LockStatement(
attributes,
this.EatToken(SyntaxKind.LockKeyword),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseExpressionCore(),
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseEmbeddedStatement());
}
private ReturnStatementSyntax ParseReturnStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ReturnKeyword);
return _syntaxFactory.ReturnStatement(
attributes,
this.EatToken(SyntaxKind.ReturnKeyword),
this.CurrentToken.Kind != SyntaxKind.SemicolonToken ? this.ParsePossibleRefExpression() : null,
this.EatToken(SyntaxKind.SemicolonToken));
}
private YieldStatementSyntax ParseYieldStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.YieldKeyword);
var yieldToken = ConvertToKeyword(this.EatToken());
SyntaxToken returnOrBreak;
ExpressionSyntax arg = null;
SyntaxKind kind;
if (this.CurrentToken.Kind == SyntaxKind.BreakKeyword)
{
kind = SyntaxKind.YieldBreakStatement;
returnOrBreak = this.EatToken();
}
else
{
kind = SyntaxKind.YieldReturnStatement;
returnOrBreak = this.EatToken(SyntaxKind.ReturnKeyword);
if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken)
{
returnOrBreak = this.AddError(returnOrBreak, ErrorCode.ERR_EmptyYield);
}
else
{
arg = this.ParseExpressionCore();
}
}
return _syntaxFactory.YieldStatement(
kind,
attributes,
yieldToken,
returnOrBreak,
arg,
this.EatToken(SyntaxKind.SemicolonToken));
}
private SwitchStatementSyntax ParseSwitchStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.SwitchKeyword or SyntaxKind.CaseKeyword);
parseSwitchHeader(out var switchKeyword, out var openParen, out var expression, out var closeParen, out var openBrace);
var sections = _pool.Allocate<SwitchSectionSyntax>();
while (this.IsPossibleSwitchSection())
sections.Add(this.ParseSwitchSection());
return _syntaxFactory.SwitchStatement(
attributes,
switchKeyword,
openParen,
expression,
closeParen,
openBrace,
_pool.ToListAndFree(sections),
this.EatToken(SyntaxKind.CloseBraceToken));
void parseSwitchHeader(
out SyntaxToken switchKeyword,
out SyntaxToken openParen,
out ExpressionSyntax expression,
out SyntaxToken closeParen,
out SyntaxToken openBrace)
{
if (this.CurrentToken.Kind is SyntaxKind.CaseKeyword)
{
// try to eat a 'switch' so the user gets a good error message about what's wrong. then directly
// creating missing tokens for the rest so they don't get cascading errors.
switchKeyword = EatToken(SyntaxKind.SwitchKeyword);
openParen = SyntaxFactory.MissingToken(SyntaxKind.OpenParenToken);
expression = CreateMissingIdentifierName();
closeParen = SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken);
openBrace = SyntaxFactory.MissingToken(SyntaxKind.OpenBraceToken);
}
else
{
switchKeyword = this.EatToken(SyntaxKind.SwitchKeyword);
expression = this.ParseExpressionCore();
if (expression.Kind == SyntaxKind.ParenthesizedExpression)
{
var parenExpression = (ParenthesizedExpressionSyntax)expression;
openParen = parenExpression.OpenParenToken;
expression = parenExpression.Expression;
closeParen = parenExpression.CloseParenToken;
Debug.Assert(parenExpression.GetDiagnostics().Length == 0);
}
else if (expression.Kind == SyntaxKind.TupleExpression)
{
// As a special case, when a tuple literal is the governing expression of
// a switch statement we permit the switch statement's own parentheses to be omitted.
// LDM 2018-04-04.
openParen = closeParen = null;
}
else
{
// Some other expression has appeared without parens. Give a syntax error.
openParen = SyntaxFactory.MissingToken(SyntaxKind.OpenParenToken);
expression = this.AddError(expression, ErrorCode.ERR_SwitchGoverningExpressionRequiresParens);
closeParen = SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken);
}
openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
}
}
}
private bool IsPossibleSwitchSection()
{
return this.CurrentToken.Kind == SyntaxKind.CaseKeyword ||
(this.CurrentToken.Kind == SyntaxKind.DefaultKeyword && this.PeekToken(1).Kind != SyntaxKind.OpenParenToken);
}
private SwitchSectionSyntax ParseSwitchSection()
{
Debug.Assert(this.IsPossibleSwitchSection());
// First, parse case label(s)
var labels = _pool.Allocate<SwitchLabelSyntax>();
var statements = _pool.Allocate<StatementSyntax>();
do
{
SwitchLabelSyntax label;
if (this.CurrentToken.Kind == SyntaxKind.CaseKeyword)
{
var caseKeyword = this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
label = _syntaxFactory.CaseSwitchLabel(
caseKeyword,
ParseIdentifierName(ErrorCode.ERR_ConstantExpected),
this.EatToken(SyntaxKind.ColonToken));
}
else
{
var node = ParseExpressionOrPatternForSwitchStatement();
// if there is a 'when' token, we treat a case expression as a constant pattern.
if (this.CurrentToken.ContextualKind == SyntaxKind.WhenKeyword && node is ExpressionSyntax ex)
node = _syntaxFactory.ConstantPattern(ex);
if (node.Kind == SyntaxKind.DiscardPattern)
node = this.AddError(node, ErrorCode.ERR_DiscardPatternInSwitchStatement);
if (node is PatternSyntax pat)
{
label = _syntaxFactory.CasePatternSwitchLabel(
caseKeyword,
pat,
ParseWhenClause(Precedence.Expression),
this.EatToken(SyntaxKind.ColonToken));
}
else
{
label = _syntaxFactory.CaseSwitchLabel(
caseKeyword,
(ExpressionSyntax)node,
this.EatToken(SyntaxKind.ColonToken));
}
}
}
else
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.DefaultKeyword);
label = _syntaxFactory.DefaultSwitchLabel(
this.EatToken(SyntaxKind.DefaultKeyword),
this.EatToken(SyntaxKind.ColonToken));
}
labels.Add(label);
}
while (IsPossibleSwitchSection());
// Next, parse statement list stopping for new sections
CSharpSyntaxNode tmp = labels[^1];
this.ParseStatements(ref tmp, statements, stopOnSwitchSections: true);
labels[^1] = (SwitchLabelSyntax)tmp;
return _syntaxFactory.SwitchSection(
_pool.ToListAndFree(labels),
_pool.ToListAndFree(statements));
}
private ThrowStatementSyntax ParseThrowStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.ThrowKeyword);
return _syntaxFactory.ThrowStatement(
attributes,
this.EatToken(SyntaxKind.ThrowKeyword),
this.CurrentToken.Kind != SyntaxKind.SemicolonToken ? this.ParseExpressionCore() : null,
this.EatToken(SyntaxKind.SemicolonToken));
}
private UnsafeStatementSyntax ParseUnsafeStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.UnsafeKeyword);
return _syntaxFactory.UnsafeStatement(
attributes,
this.EatToken(SyntaxKind.UnsafeKeyword),
this.ParsePossiblyAttributedBlock());
}
private UsingStatementSyntax ParseUsingStatement(SyntaxList<AttributeListSyntax> attributes, SyntaxToken awaitTokenOpt = null)
{
var @using = this.EatToken(SyntaxKind.UsingKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
VariableDeclarationSyntax declaration = null;
ExpressionSyntax expression = null;
var resetPoint = this.GetResetPoint();
ParseUsingExpression(ref declaration, ref expression, ref resetPoint);
this.Release(ref resetPoint);
return _syntaxFactory.UsingStatement(
attributes,
awaitTokenOpt,
@using,
openParen,
declaration,
expression,
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseEmbeddedStatement());
}
private void ParseUsingExpression(ref VariableDeclarationSyntax declaration, ref ExpressionSyntax expression, ref ResetPoint resetPoint)
{
if (this.IsAwaitExpression())
{
expression = this.ParseExpressionCore();
return;
}
// Now, this can be either an expression or a decl list
ScanTypeFlags st;
if (this.IsQueryExpression(mayBeVariableDeclaration: true, mayBeMemberDeclaration: false))
{
st = ScanTypeFlags.NotType;
}
else
{
SyntaxToken scopedKeyword = ParsePossibleScopedKeyword(isFunctionPointerParameter: false);
if (scopedKeyword != null)
{
declaration = ParseParenthesizedVariableDeclaration(VariableFlags.None, scopedKeyword);
return;
}
else
{
st = this.ScanType();
}
}
if (st == ScanTypeFlags.NullableType)
{
// We need to handle:
// * using (f ? x = a : x = b)
// * using (f ? x = a)
// * using (f ? x, y)
if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken)
{
this.Reset(ref resetPoint);
expression = this.ParseExpressionCore();
}
else
{
switch (this.PeekToken(1).Kind)
{
default:
this.Reset(ref resetPoint);
expression = this.ParseExpressionCore();
break;
case SyntaxKind.CommaToken:
case SyntaxKind.CloseParenToken:
this.Reset(ref resetPoint);
declaration = ParseParenthesizedVariableDeclaration(VariableFlags.None, scopedKeyword: null);
break;
case SyntaxKind.EqualsToken:
// Parse it as a decl. If the next token is a : and only one variable was parsed,
// convert the whole thing to ?: expression.
this.Reset(ref resetPoint);
declaration = ParseParenthesizedVariableDeclaration(VariableFlags.None, scopedKeyword: null);
// We may have non-nullable types in error scenarios.
if (this.CurrentToken.Kind == SyntaxKind.ColonToken &&
declaration.Type.Kind == SyntaxKind.NullableType &&
SyntaxFacts.IsName(((NullableTypeSyntax)declaration.Type).ElementType.Kind) &&
declaration.Variables.Count == 1)
{
// We have "name? id = expr :" so need to convert to a ?: expression.
this.Reset(ref resetPoint);
declaration = null;
expression = this.ParseExpressionCore();
}
break;
}
}
}
else if (IsUsingStatementVariableDeclaration(st))
{
this.Reset(ref resetPoint);
declaration = ParseParenthesizedVariableDeclaration(VariableFlags.None, scopedKeyword: null);
}
else
{
// Must be an expression statement
this.Reset(ref resetPoint);
expression = this.ParseExpressionCore();
}
}
private bool IsUsingStatementVariableDeclaration(ScanTypeFlags st)
{
Debug.Assert(st != ScanTypeFlags.NullableType);
bool condition1 = st == ScanTypeFlags.MustBeType && this.CurrentToken.Kind != SyntaxKind.DotToken;
bool condition2 = st != ScanTypeFlags.NotType && this.CurrentToken.Kind == SyntaxKind.IdentifierToken;
bool condition3 = st == ScanTypeFlags.NonGenericTypeOrExpression || this.PeekToken(1).Kind == SyntaxKind.EqualsToken;
return condition1 || (condition2 && condition3);
}
private WhileStatementSyntax ParseWhileStatement(SyntaxList<AttributeListSyntax> attributes)
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.WhileKeyword);
return _syntaxFactory.WhileStatement(
attributes,
this.EatToken(SyntaxKind.WhileKeyword),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseExpressionCore(),
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseEmbeddedStatement());
}
private LabeledStatementSyntax ParseLabeledStatement(SyntaxList<AttributeListSyntax> attributes)
{
// We have an identifier followed by a colon. But if the identifier is a contextual keyword in a query context,
// ParseIdentifier will result in a missing name and Eat(Colon) will fail. We won't make forward progress.
Debug.Assert(this.IsTrueIdentifier() && this.PeekToken(1).Kind == SyntaxKind.ColonToken);
return _syntaxFactory.LabeledStatement(
attributes,
this.ParseIdentifierToken(),
this.EatToken(SyntaxKind.ColonToken),
this.ParsePossiblyAttributedStatement() ?? SyntaxFactory.EmptyStatement(attributeLists: default, EatToken(SyntaxKind.SemicolonToken)));
}
/// <summary>
/// Parses any kind of local declaration statement: local variable or local function.
/// </summary>
private StatementSyntax ParseLocalDeclarationStatement(SyntaxList<AttributeListSyntax> attributes)
{
SyntaxToken awaitKeyword, usingKeyword;
bool canParseAsLocalFunction = false;
if (IsPossibleAwaitUsing())
{
awaitKeyword = this.EatContextualToken(SyntaxKind.AwaitKeyword);
usingKeyword = EatToken();
}
else if (this.CurrentToken.Kind == SyntaxKind.UsingKeyword)
{
awaitKeyword = null;
usingKeyword = EatToken();
}
else
{
awaitKeyword = null;
usingKeyword = null;
canParseAsLocalFunction = true;
}
var mods = _pool.Allocate();
this.ParseDeclarationModifiers(mods, isUsingDeclaration: usingKeyword is not null);
var variables = _pool.AllocateSeparated<VariableDeclaratorSyntax>();
try
{
SyntaxToken scopedKeyword = ParsePossibleScopedKeyword(isFunctionPointerParameter: false);
// For local functions, 'scoped' is a modifier in LocalFunctionStatementSyntax
if (scopedKeyword != null)
{
mods.Add(scopedKeyword);
}
this.ParseLocalDeclaration(variables,
allowLocalFunctions: canParseAsLocalFunction,
// A local declaration doesn't have a `(...)` construct. So no need to stop if we hit a close paren
// after a declarator. Let normal error recovery kick in.
stopOnCloseParen: false,
attributes,
mods.ToList(),
scopedKeyword: null,
initialFlags: VariableFlags.None,
out var type,
out var localFunction);
if (localFunction != null)
{
Debug.Assert(variables.Count == 0);
return localFunction;
}
if (canParseAsLocalFunction)
{
// If we find an accessibility modifier but no local function it's likely
// the user forgot a closing brace. Let's back out of statement parsing.
// We check just for a leading accessibility modifier in the syntax because
// SkipBadStatementListTokens will not skip attribute lists.
if (attributes.Count == 0 && mods.Count > 0 && IsAccessibilityModifier(((SyntaxToken)mods[0]).ContextualKind))
{
return null;
}
}
// For locals, 'scoped' is part of ScopedTypeSyntax.
if (scopedKeyword != null)
{
mods.RemoveLast();
type = _syntaxFactory.ScopedType(scopedKeyword, type);
}
// We've already reported all modifiers for local_using_declaration as errors
if (usingKeyword is null)
{
for (int i = 0; i < mods.Count; i++)
{
var mod = (SyntaxToken)mods[i];
if (IsAdditionalLocalFunctionModifier(mod.ContextualKind))
{
mods[i] = this.AddError(mod, ErrorCode.ERR_BadMemberFlag, mod.Text);
}
}
}
return _syntaxFactory.LocalDeclarationStatement(
attributes,
awaitKeyword,
usingKeyword,
mods.ToList(),
_syntaxFactory.VariableDeclaration(type, _pool.ToListAndFree(variables)),
this.EatToken(SyntaxKind.SemicolonToken));
}
finally
{
_pool.Free(mods);
}
}
private SyntaxToken ParsePossibleScopedKeyword(bool isFunctionPointerParameter)
{
if (this.CurrentToken.ContextualKind == SyntaxKind.ScopedKeyword)
{
using var beforeScopedResetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
SyntaxToken scopedKeyword = this.EatContextualToken(SyntaxKind.ScopedKeyword);
if (this.CurrentToken.Kind is not (SyntaxKind.RefKeyword or SyntaxKind.OutKeyword or SyntaxKind.InKeyword))
{
using var afterScopedResetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
if (ScanType() == ScanTypeFlags.NotType ||
(isFunctionPointerParameter
? this.CurrentToken.Kind is not (SyntaxKind.CommaToken or SyntaxKind.GreaterThanToken)
: this.CurrentToken.Kind != SyntaxKind.IdentifierToken))
{
beforeScopedResetPoint.Reset();
return null;
}
afterScopedResetPoint.Reset();
}
return scopedKeyword;
}
return null;
}
private VariableDesignationSyntax ParseDesignation(bool forPattern)
{
// the two forms of designation are
// (1) identifier
// (2) ( designation ... )
// for pattern-matching, we permit the designation list to be empty
VariableDesignationSyntax result;
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var listOfDesignations = _pool.AllocateSeparated<VariableDesignationSyntax>();
bool done = false;
if (forPattern)
{
done = (this.CurrentToken.Kind == SyntaxKind.CloseParenToken);
}
else
{
listOfDesignations.Add(ParseDesignation(forPattern));
listOfDesignations.AddSeparator(EatToken(SyntaxKind.CommaToken));
}
if (!done)
{
while (true)
{
listOfDesignations.Add(ParseDesignation(forPattern));
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
listOfDesignations.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
}
else
{
break;
}
}
}
result = _syntaxFactory.ParenthesizedVariableDesignation(
openParen,
_pool.ToListAndFree(listOfDesignations),
this.EatToken(SyntaxKind.CloseParenToken));
}
else
{
result = ParseSimpleDesignation();
}
return result;
}
/// <summary>
/// Parse a single variable designation (e.g. <c>x</c>) or a wildcard designation (e.g. <c>_</c>)
/// </summary>
/// <returns></returns>
private VariableDesignationSyntax ParseSimpleDesignation()
{
return CurrentToken.ContextualKind == SyntaxKind.UnderscoreToken
? _syntaxFactory.DiscardDesignation(this.EatContextualToken(SyntaxKind.UnderscoreToken))
: _syntaxFactory.SingleVariableDesignation(this.EatToken(SyntaxKind.IdentifierToken));
}
private WhenClauseSyntax ParseWhenClause(Precedence precedence)
{
if (this.CurrentToken.ContextualKind != SyntaxKind.WhenKeyword)
{
return null;
}
return _syntaxFactory.WhenClause(
this.EatContextualToken(SyntaxKind.WhenKeyword),
ParseSubExpression(precedence));
}
#nullable enable
/// <summary>
/// Parse a local variable declaration for constructs where the variable declaration is enclosed in parentheses.
/// Specifically, only for the `fixed (...)` `for(...)` or `using (...)` statements.
/// </summary>
private VariableDeclarationSyntax ParseParenthesizedVariableDeclaration(
VariableFlags initialFlags, SyntaxToken? scopedKeyword)
{
var variables = _pool.AllocateSeparated<VariableDeclaratorSyntax>();
ParseLocalDeclaration(
variables,
allowLocalFunctions: false,
// Always stop on a close paren as the parent `fixed(...)/for(...)/using(...)` statement wants to
// consume it.
stopOnCloseParen: true,
attributes: default,
mods: default,
scopedKeyword,
initialFlags,
out var type,
out var localFunction);
Debug.Assert(localFunction == null);
return _syntaxFactory.VariableDeclaration(
type,
_pool.ToListAndFree(variables));
}
private void ParseLocalDeclaration(
SeparatedSyntaxListBuilder<VariableDeclaratorSyntax> variables,
bool allowLocalFunctions,
bool stopOnCloseParen,
SyntaxList<AttributeListSyntax> attributes,
SyntaxList<SyntaxToken> mods,
SyntaxToken? scopedKeyword,
VariableFlags initialFlags,
out TypeSyntax type,
out LocalFunctionStatementSyntax localFunction)
{
type = allowLocalFunctions ? ParseReturnType() : this.ParseType();
if (scopedKeyword != null)
type = _syntaxFactory.ScopedType(scopedKeyword, type);
VariableFlags flags = initialFlags | VariableFlags.LocalOrField;
if (mods.Any((int)SyntaxKind.ConstKeyword))
{
flags |= VariableFlags.Const;
}
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfDeclarationClause;
this.ParseVariableDeclarators(
type,
flags,
variables,
variableDeclarationsExpected: true,
allowLocalFunctions,
stopOnCloseParen,
attributes,
mods,
out localFunction);
_termState = saveTerm;
if (allowLocalFunctions && localFunction == null && type is PredefinedTypeSyntax { Keyword.Kind: SyntaxKind.VoidKeyword })
{
type = this.AddError(type, ErrorCode.ERR_NoVoidHere);
}
}
#nullable disable
private bool IsEndOfDeclarationClause()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.SemicolonToken:
case SyntaxKind.ColonToken:
return true;
default:
return false;
}
}
private void ParseDeclarationModifiers(SyntaxListBuilder list, bool isUsingDeclaration)
{
SyntaxKind k;
while (IsDeclarationModifier(k = this.CurrentToken.ContextualKind) || IsAdditionalLocalFunctionModifier(k))
{
SyntaxToken mod;
if (k == SyntaxKind.AsyncKeyword)
{
// check for things like "async async()" where async is the type and/or the function name
if (!shouldTreatAsModifier())
{
break;
}
mod = this.EatContextualToken(k);
}
else
{
mod = this.EatToken();
}
if (isUsingDeclaration)
{
mod = this.AddError(mod, ErrorCode.ERR_NoModifiersOnUsing);
}
else if (k is SyntaxKind.ReadOnlyKeyword or SyntaxKind.VolatileKeyword)
{
mod = this.AddError(mod, ErrorCode.ERR_BadMemberFlag, mod.Text);
}
else if (list.Any(mod.RawKind))
{
// check for duplicates, can only be const
mod = this.AddError(mod, ErrorCode.ERR_TypeExpected);
}
list.Add(mod);
}
bool shouldTreatAsModifier()
{
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.IdentifierToken);
do
{
this.EatToken();
if (IsDeclarationModifier(this.CurrentToken.Kind) ||
IsAdditionalLocalFunctionModifier(this.CurrentToken.Kind))
{
return true;
}
using var _2 = this.GetDisposableResetPoint(resetOnDispose: true);
if (ScanType() != ScanTypeFlags.NotType && this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
return true;
}
}
// If current token might be a contextual modifier we need to check ahead the next token after it
// If the next token appears to be a modifier, we treat current token as a modifier as well
// This allows to correctly parse things like local functions with several `async` modifiers
while (IsAdditionalLocalFunctionModifier(this.CurrentToken.ContextualKind));
return false;
}
}
private static bool IsDeclarationModifier(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.ConstKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ReadOnlyKeyword:
case SyntaxKind.VolatileKeyword:
return true;
default:
return false;
}
}
private static bool IsAdditionalLocalFunctionModifier(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.StaticKeyword:
case SyntaxKind.AsyncKeyword:
case SyntaxKind.UnsafeKeyword:
case SyntaxKind.ExternKeyword:
// Not a valid modifier, but we should parse to give a good
// error message
case SyntaxKind.PublicKeyword:
case SyntaxKind.InternalKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
return true;
default:
return false;
}
}
private static bool IsAccessibilityModifier(SyntaxKind kind)
{
switch (kind)
{
// Accessibility modifiers aren't legal in a local function,
// but a common mistake. Parse to give a better error message.
case SyntaxKind.PublicKeyword:
case SyntaxKind.InternalKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
return true;
default:
return false;
}
}
private LocalFunctionStatementSyntax TryParseLocalFunctionStatementBody(
SyntaxList<AttributeListSyntax> attributes,
SyntaxList<SyntaxToken> modifiers,
TypeSyntax type,
SyntaxToken identifier)
{
// This may potentially be an ambiguous parse until very far into the token stream, so we may have to backtrack.
// For example, "await x()" is ambiguous at the current point of parsing (right now we're right after the x).
// The point at which it becomes unambiguous is after the argument list. A "=>" or "{" means its a local function
// (with return type @await), a ";" or other expression-y token means its an await of a function call.
// Note that we could just check if we're in an async context, but that breaks some analyzers, because
// "await f();" would be parsed as a local function statement when really we want a parse error so we can say
// "did you mean to make this method be an async method?" (it's invalid either way, so the spec doesn't care)
var resetPoint = this.GetResetPoint();
// Indicates this must be parsed as a local function, even if there's no body
bool forceLocalFunc = true;
if (type.Kind == SyntaxKind.IdentifierName)
{
var id = ((IdentifierNameSyntax)type).Identifier;
forceLocalFunc = id.ContextualKind != SyntaxKind.AwaitKeyword;
}
bool parentScopeIsInAsync = IsInAsync;
IsInAsync = false;
SyntaxListBuilder badBuilder = null;
for (int i = 0; i < modifiers.Count; i++)
{
var modifier = modifiers[i];
switch (modifier.ContextualKind)
{
case SyntaxKind.AsyncKeyword:
IsInAsync = true;
forceLocalFunc = true;
continue;
case SyntaxKind.UnsafeKeyword:
forceLocalFunc = true;
continue;
case SyntaxKind.ReadOnlyKeyword:
case SyntaxKind.VolatileKeyword:
continue; // already reported earlier, no need to report again
case SyntaxKind.StaticKeyword:
continue;
case SyntaxKind.ExternKeyword:
continue;
default:
modifier = this.AddError(modifier, ErrorCode.ERR_BadMemberFlag, modifier.Text);
break;
}
if (badBuilder == null)
{
badBuilder = _pool.Allocate();
badBuilder.AddRange(modifiers);
}
badBuilder[i] = modifier;
}
if (badBuilder != null)
{
modifiers = badBuilder.ToList();
_pool.Free(badBuilder);
}
TypeParameterListSyntax typeParameterListOpt = this.ParseTypeParameterList();
// "await f<T>()" still makes sense, so don't force accept a local function if there's a type parameter list.
ParameterListSyntax paramList = this.ParseParenthesizedParameterList();
// "await x()" is ambiguous (see note at start of this method), but we assume "await x(await y)" is meant to be a function if it's in a non-async context.
if (!forceLocalFunc)
{
var paramListSyntax = paramList.Parameters;
for (int i = 0; i < paramListSyntax.Count; i++)
{
// "await x(y)" still parses as a parameter list, so check to see if it's a valid parameter (like "x(t y)")
forceLocalFunc |= !paramListSyntax[i].ContainsDiagnostics;
if (forceLocalFunc)
break;
}
}
var constraints = default(SyntaxListBuilder<TypeParameterConstraintClauseSyntax>);
if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword)
{
constraints = _pool.Allocate<TypeParameterConstraintClauseSyntax>();
this.ParseTypeParameterConstraintClauses(constraints);
forceLocalFunc = true;
}
BlockSyntax blockBody;
ArrowExpressionClauseSyntax expressionBody;
SyntaxToken semicolon;
this.ParseBlockAndExpressionBodiesWithSemicolon(out blockBody, out expressionBody, out semicolon, parseSemicolonAfterBlock: false);
IsInAsync = parentScopeIsInAsync;
if (!forceLocalFunc && blockBody == null && expressionBody == null)
{
this.Reset(ref resetPoint);
this.Release(ref resetPoint);
return null;
}
this.Release(ref resetPoint);
return _syntaxFactory.LocalFunctionStatement(
attributes,
modifiers,
type,
identifier,
typeParameterListOpt,
paramList,
constraints,
blockBody,
expressionBody,
semicolon);
}
private ExpressionStatementSyntax ParseExpressionStatement(SyntaxList<AttributeListSyntax> attributes)
{
return ParseExpressionStatement(attributes, this.ParseExpressionCore());
}
private ExpressionStatementSyntax ParseExpressionStatement(SyntaxList<AttributeListSyntax> attributes, ExpressionSyntax expression)
{
SyntaxToken semicolon;
if (IsScript && this.CurrentToken.Kind == SyntaxKind.EndOfFileToken)
{
semicolon = SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken);
}
else
{
// Do not report an error if the expression is not a statement expression.
// The error is reported in semantic analysis.
semicolon = this.EatToken(SyntaxKind.SemicolonToken);
}
return _syntaxFactory.ExpressionStatement(attributes, expression, semicolon);
}
public ExpressionSyntax ParseExpression()
{
return ParseWithStackGuard(
static @this => @this.ParseExpressionCore(),
static @this => @this.CreateMissingIdentifierName());
}
private ExpressionSyntax ParseExpressionCore()
{
return this.ParseSubExpression(Precedence.Expression);
}
/// <summary>
/// Is the current token one that could start an expression?
/// </summary>
private bool CanStartExpression()
{
return IsPossibleExpression(allowBinaryExpressions: false, allowAssignmentExpressions: false);
}
/// <summary>
/// Is the current token one that could be in an expression?
/// </summary>
private bool IsPossibleExpression()
{
return IsPossibleExpression(allowBinaryExpressions: true, allowAssignmentExpressions: true);
}
private bool IsPossibleExpression(bool allowBinaryExpressions, bool allowAssignmentExpressions)
{
SyntaxKind tk = this.CurrentToken.Kind;
switch (tk)
{
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.DefaultKeyword:
case SyntaxKind.SizeOfKeyword:
case SyntaxKind.MakeRefKeyword:
case SyntaxKind.RefTypeKeyword:
case SyntaxKind.CheckedKeyword:
case SyntaxKind.UncheckedKeyword:
case SyntaxKind.RefValueKeyword:
case SyntaxKind.ArgListKeyword:
case SyntaxKind.BaseKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.ThisKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.OpenParenToken:
case SyntaxKind.NumericLiteralToken:
case SyntaxKind.StringLiteralToken:
case SyntaxKind.Utf8StringLiteralToken:
case SyntaxKind.SingleLineRawStringLiteralToken:
case SyntaxKind.Utf8SingleLineRawStringLiteralToken:
case SyntaxKind.MultiLineRawStringLiteralToken:
case SyntaxKind.Utf8MultiLineRawStringLiteralToken:
case SyntaxKind.InterpolatedStringToken:
case SyntaxKind.InterpolatedStringStartToken:
case SyntaxKind.InterpolatedVerbatimStringStartToken:
case SyntaxKind.InterpolatedSingleLineRawStringStartToken:
case SyntaxKind.InterpolatedMultiLineRawStringStartToken:
case SyntaxKind.CharacterLiteralToken:
case SyntaxKind.NewKeyword:
case SyntaxKind.DelegateKeyword:
case SyntaxKind.ColonColonToken: // bad aliased name
case SyntaxKind.ThrowKeyword:
case SyntaxKind.StackAllocKeyword:
case SyntaxKind.RefKeyword:
case SyntaxKind.OpenBracketToken: // attributes on a lambda, or a collection expression.
return true;
case SyntaxKind.DotToken when IsAtDotDotToken():
return true;
case SyntaxKind.StaticKeyword:
return IsPossibleAnonymousMethodExpression() || IsPossibleLambdaExpression(Precedence.Expression);
case SyntaxKind.IdentifierToken:
// Specifically allow the from contextual keyword, because it can always be the start of an
// expression (whether it is used as an identifier or a keyword).
return this.IsTrueIdentifier() || this.CurrentToken.ContextualKind == SyntaxKind.FromKeyword;
default:
return IsPredefinedType(tk)
|| SyntaxFacts.IsAnyUnaryExpression(tk)
|| (allowBinaryExpressions && SyntaxFacts.IsBinaryExpression(tk))
|| (allowAssignmentExpressions && SyntaxFacts.IsAssignmentExpressionOperatorToken(tk));
}
}
private static bool IsInvalidSubExpression(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.BreakKeyword:
case SyntaxKind.CaseKeyword:
case SyntaxKind.CatchKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.ContinueKeyword:
case SyntaxKind.DoKeyword:
case SyntaxKind.FinallyKeyword:
case SyntaxKind.ForKeyword:
case SyntaxKind.ForEachKeyword:
case SyntaxKind.GotoKeyword:
case SyntaxKind.IfKeyword:
case SyntaxKind.ElseKeyword:
case SyntaxKind.LockKeyword:
case SyntaxKind.ReturnKeyword:
case SyntaxKind.SwitchKeyword:
case SyntaxKind.TryKeyword:
case SyntaxKind.UsingKeyword:
case SyntaxKind.WhileKeyword:
return true;
default:
return false;
}
}
internal static bool IsRightAssociative(SyntaxKind op)
{
switch (op)
{
case SyntaxKind.SimpleAssignmentExpression:
case SyntaxKind.AddAssignmentExpression:
case SyntaxKind.SubtractAssignmentExpression:
case SyntaxKind.MultiplyAssignmentExpression:
case SyntaxKind.DivideAssignmentExpression:
case SyntaxKind.ModuloAssignmentExpression:
case SyntaxKind.AndAssignmentExpression:
case SyntaxKind.ExclusiveOrAssignmentExpression:
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.UnsignedRightShiftAssignmentExpression:
case SyntaxKind.CoalesceAssignmentExpression:
case SyntaxKind.CoalesceExpression:
return true;
default:
return false;
}
}
private enum Precedence : uint
{
Expression = 0, // Loosest possible precedence, used to accept all expressions
Assignment = Expression,
Lambda = Assignment, // "The => operator has the same precedence as assignment (=) and is right-associative."
Conditional,
Coalescing,
ConditionalOr,
ConditionalAnd,
LogicalOr,
LogicalXor,
LogicalAnd,
Equality,
Relational,
Shift,
Additive,
Multiplicative,
Switch,
Range,
Unary,
Cast,
PointerIndirection,
AddressOf,
Primary,
}
private static Precedence GetPrecedence(SyntaxKind op)
{
switch (op)
{
case SyntaxKind.QueryExpression:
return Precedence.Expression;
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.AnonymousMethodExpression:
return Precedence.Lambda;
case SyntaxKind.SimpleAssignmentExpression:
case SyntaxKind.AddAssignmentExpression:
case SyntaxKind.SubtractAssignmentExpression:
case SyntaxKind.MultiplyAssignmentExpression:
case SyntaxKind.DivideAssignmentExpression:
case SyntaxKind.ModuloAssignmentExpression:
case SyntaxKind.AndAssignmentExpression:
case SyntaxKind.ExclusiveOrAssignmentExpression:
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.UnsignedRightShiftAssignmentExpression:
case SyntaxKind.CoalesceAssignmentExpression:
return Precedence.Assignment;
case SyntaxKind.CoalesceExpression:
case SyntaxKind.ThrowExpression:
return Precedence.Coalescing;
case SyntaxKind.LogicalOrExpression:
return Precedence.ConditionalOr;
case SyntaxKind.LogicalAndExpression:
return Precedence.ConditionalAnd;
case SyntaxKind.BitwiseOrExpression:
return Precedence.LogicalOr;
case SyntaxKind.ExclusiveOrExpression:
return Precedence.LogicalXor;
case SyntaxKind.BitwiseAndExpression:
return Precedence.LogicalAnd;
case SyntaxKind.EqualsExpression:
case SyntaxKind.NotEqualsExpression:
return Precedence.Equality;
case SyntaxKind.LessThanExpression:
case SyntaxKind.LessThanOrEqualExpression:
case SyntaxKind.GreaterThanExpression:
case SyntaxKind.GreaterThanOrEqualExpression:
case SyntaxKind.IsExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.IsPatternExpression:
return Precedence.Relational;
case SyntaxKind.SwitchExpression:
case SyntaxKind.WithExpression:
return Precedence.Switch;
case SyntaxKind.LeftShiftExpression:
case SyntaxKind.RightShiftExpression:
case SyntaxKind.UnsignedRightShiftExpression:
return Precedence.Shift;
case SyntaxKind.AddExpression:
case SyntaxKind.SubtractExpression:
return Precedence.Additive;
case SyntaxKind.MultiplyExpression:
case SyntaxKind.DivideExpression:
case SyntaxKind.ModuloExpression:
return Precedence.Multiplicative;
case SyntaxKind.UnaryPlusExpression:
case SyntaxKind.UnaryMinusExpression:
case SyntaxKind.BitwiseNotExpression:
case SyntaxKind.LogicalNotExpression:
case SyntaxKind.PreIncrementExpression:
case SyntaxKind.PreDecrementExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.SizeOfExpression:
case SyntaxKind.CheckedExpression:
case SyntaxKind.UncheckedExpression:
case SyntaxKind.MakeRefExpression:
case SyntaxKind.RefValueExpression:
case SyntaxKind.RefTypeExpression:
case SyntaxKind.AwaitExpression:
case SyntaxKind.IndexExpression:
return Precedence.Unary;
case SyntaxKind.CastExpression:
return Precedence.Cast;
case SyntaxKind.PointerIndirectionExpression:
return Precedence.PointerIndirection;
case SyntaxKind.AddressOfExpression:
return Precedence.AddressOf;
case SyntaxKind.RangeExpression:
return Precedence.Range;
case SyntaxKind.ConditionalExpression:
return Precedence.Expression;
case SyntaxKind.AliasQualifiedName:
case SyntaxKind.AnonymousObjectCreationExpression:
case SyntaxKind.ArgListExpression:
case SyntaxKind.ArrayCreationExpression:
case SyntaxKind.BaseExpression:
case SyntaxKind.CharacterLiteralExpression:
case SyntaxKind.CollectionExpression:
case SyntaxKind.ConditionalAccessExpression:
case SyntaxKind.DeclarationExpression:
case SyntaxKind.DefaultExpression:
case SyntaxKind.DefaultLiteralExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.FalseLiteralExpression:
case SyntaxKind.FieldExpression:
case SyntaxKind.GenericName:
case SyntaxKind.IdentifierName:
case SyntaxKind.ImplicitArrayCreationExpression:
case SyntaxKind.ImplicitStackAllocArrayCreationExpression:
case SyntaxKind.ImplicitObjectCreationExpression:
case SyntaxKind.InterpolatedStringExpression:
case SyntaxKind.InvocationExpression:
case SyntaxKind.NullLiteralExpression:
case SyntaxKind.NumericLiteralExpression:
case SyntaxKind.ObjectCreationExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.PointerMemberAccessExpression:
case SyntaxKind.PostDecrementExpression:
case SyntaxKind.PostIncrementExpression:
case SyntaxKind.PredefinedType:
case SyntaxKind.RefExpression:
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.StackAllocArrayCreationExpression:
case SyntaxKind.StringLiteralExpression:
case SyntaxKind.Utf8StringLiteralExpression:
case SyntaxKind.SuppressNullableWarningExpression:
case SyntaxKind.ThisExpression:
case SyntaxKind.TrueLiteralExpression:
case SyntaxKind.TupleExpression:
return Precedence.Primary;
default:
throw ExceptionUtilities.UnexpectedValue(op);
}
}
private static bool IsExpectedPrefixUnaryOperator(SyntaxKind kind)
{
return SyntaxFacts.IsPrefixUnaryExpression(kind) && kind is not SyntaxKind.RefKeyword and not SyntaxKind.OutKeyword;
}
private static bool IsExpectedBinaryOperator(SyntaxKind kind)
{
return SyntaxFacts.IsBinaryExpression(kind);
}
private static bool IsExpectedAssignmentOperator(SyntaxKind kind)
{
return SyntaxFacts.IsAssignmentExpressionOperatorToken(kind);
}
private bool IsPossibleAwaitExpressionStatement()
{
return (this.IsScript || this.IsInAsync) && this.CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword;
}
private bool IsAwaitExpression()
{
if (this.CurrentToken.ContextualKind == SyntaxKind.AwaitKeyword)
{
if (this.IsInAsync)
{
// If we see an await in an async function, parse it as an unop.
return true;
}
// If we see an await followed by a token that cannot follow an identifier, parse await as a unop.
// BindAwait() catches the cases where await successfully parses as a unop but is not in an async
// function, and reports an appropriate ERR_BadAwaitWithoutAsync* error.
var next = PeekToken(1);
switch (next.Kind)
{
case SyntaxKind.IdentifierToken:
return next.ContextualKind != SyntaxKind.WithKeyword;
// Keywords
case SyntaxKind.NewKeyword:
case SyntaxKind.ThisKeyword:
case SyntaxKind.BaseKeyword:
case SyntaxKind.DelegateKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.CheckedKeyword:
case SyntaxKind.UncheckedKeyword:
case SyntaxKind.DefaultKeyword:
// Literals
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.StringLiteralToken:
case SyntaxKind.SingleLineRawStringLiteralToken:
case SyntaxKind.Utf8SingleLineRawStringLiteralToken:
case SyntaxKind.MultiLineRawStringLiteralToken:
case SyntaxKind.Utf8MultiLineRawStringLiteralToken:
case SyntaxKind.InterpolatedStringToken:
case SyntaxKind.Utf8StringLiteralToken:
case SyntaxKind.InterpolatedStringStartToken:
case SyntaxKind.InterpolatedVerbatimStringStartToken:
case SyntaxKind.InterpolatedSingleLineRawStringStartToken:
case SyntaxKind.InterpolatedMultiLineRawStringStartToken:
case SyntaxKind.NumericLiteralToken:
case SyntaxKind.NullKeyword:
case SyntaxKind.CharacterLiteralToken:
return true;
}
}
return false;
}
/// <summary>
/// Parse a subexpression of the enclosing operator of the given precedence.
/// </summary>
private ExpressionSyntax ParseSubExpression(Precedence precedence)
{
_recursionDepth++;
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
var result = ParseSubExpressionCore(precedence);
#if DEBUG
// Ensure every expression kind is handled in GetPrecedence
_ = GetPrecedence(result.Kind);
#endif
_recursionDepth--;
return result;
}
private ExpressionSyntax ParseSubExpressionCore(Precedence precedence)
{
// all of these are tokens that start statements and are invalid
// to start a expression with. if we see one, then we must have
// something like:
//
// return
// if (...
// parse out a missing name node for the expression, and keep on going
var tk = this.CurrentToken.Kind;
if (IsInvalidSubExpression(tk))
return this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_InvalidExprTerm, SyntaxFacts.GetText(tk));
return ParseExpressionContinued(parseUnaryOrPrimaryExpression(precedence), precedence);
// Parses out a unary expression, or something with higher precedence (cast, addressof, primary).
ExpressionSyntax parseUnaryOrPrimaryExpression(Precedence precedence)
{
// Parse a left operand -- possibly preceded by a unary operator.
if (IsExpectedPrefixUnaryOperator(tk))
{
var opKind = SyntaxFacts.GetPrefixUnaryExpression(tk);
return _syntaxFactory.PrefixUnaryExpression(
opKind,
this.EatToken(),
this.ParseSubExpression(GetPrecedence(opKind)));
}
// Check *explicitly* for `..` starting an expression. This *is* the initial term we want to parse out.
// If we have `expr..` though we don't do that here. Instead, we'll parse out 'expr', and the `..`
// portion will be handled in ParseExpressionContinued.
if (IsAtDotDotToken())
{
return _syntaxFactory.RangeExpression(
leftOperand: null,
this.EatDotDotToken(),
CanStartExpression()
? this.ParseSubExpression(Precedence.Range)
: null);
}
if (IsAwaitExpression())
{
return _syntaxFactory.AwaitExpression(
this.EatContextualToken(SyntaxKind.AwaitKeyword),
this.ParseSubExpression(GetPrecedence(SyntaxKind.AwaitExpression)));
}
if (this.IsQueryExpression(mayBeVariableDeclaration: false, mayBeMemberDeclaration: false))
return this.ParseQueryExpression(precedence);
if (this.CurrentToken.ContextualKind == SyntaxKind.FromKeyword && IsInQuery)
{
// If this "from" token wasn't the start of a query then it's not really an expression.
// Consume it so that we don't try to parse it again as the next argument in an
// argument list.
return AddTrailingSkippedSyntax(
this.CreateMissingIdentifierName(),
this.AddError(this.EatToken(), ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text));
}
if (tk == SyntaxKind.ThrowKeyword)
{
var result = ParseThrowExpression();
// we parse a throw expression even at the wrong precedence for better recovery
return precedence <= Precedence.Coalescing
? result
: this.AddError(result, ErrorCode.ERR_InvalidExprTerm, SyntaxFacts.GetText(tk));
}
if (this.IsPossibleDeconstructionLeft(precedence))
return ParseDeclarationExpression(ParseTypeMode.Normal, isScoped: false);
// Not a unary operator - get a primary expression.
return this.ParsePrimaryExpression(precedence);
}
}
#nullable enable
/// <summary>
/// Takes in an initial unary expression or primary expression, and then consumes what follows as long as its
/// precedence is either lower than the <paramref name="precedence"/> we're parsing currently, or equal to that
/// precedence if we have something right-associative <see cref="IsRightAssociative"/>.
/// </summary>
private ExpressionSyntax ParseExpressionContinued(ExpressionSyntax unaryOrPrimaryExpression, Precedence precedence)
{
var currentExpression = unaryOrPrimaryExpression;
// Keep on expanding the left operand as long as what we see fits the precedence we're under.
while (tryExpandExpression(currentExpression, precedence) is ExpressionSyntax expandedExpression)
currentExpression = expandedExpression;
// Finally, consume a conditional expression if precedence allows it.
// https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator:
//
// conditional_expression
// : null_coalescing_expression
// | null_coalescing_expression '?' expression ':' expression
// ;
//
// 1. Only take the conditional part of the expression if we're at or below its precedence.
// 2. When parsing the branches of the expression, parse at the highest precedence again ('expression').
// This allows for things like assignments/lambdas in the branches of the conditional.
if (this.CurrentToken.Kind == SyntaxKind.QuestionToken && precedence <= Precedence.Conditional)
return consumeConditionalExpression(currentExpression);
return currentExpression;
ExpressionSyntax? tryExpandExpression(ExpressionSyntax leftOperand, Precedence precedence)
{
// Look for operators that can follow what we've seen so far, and which are acceptable at this
// precedence level. Examples include binary operator, assignment operators, range operators `..`, as
// well as `switch` and `with` clauses.
var (operatorTokenKind, operatorExpressionKind) = getOperatorTokenAndExpressionKind();
if (operatorTokenKind == SyntaxKind.None)
return null;
var newPrecedence = GetPrecedence(operatorExpressionKind);
// Check the precedence to see if we should "take" this operator. A lower precedence means what's
// coming isn't a child of us, but rather we will be a child of it. So we bail out and let any higher
// up expression parsing consume it with us as the left side.
if (newPrecedence < precedence)
return null;
// Same precedence, but not right-associative -- deal with this "later"
if ((newPrecedence == precedence) && !IsRightAssociative(operatorExpressionKind))
return null;
// Now consume the operator (including consuming multiple tokens in the case of merged operator tokens)
var operatorToken = eatOperatorToken(operatorTokenKind);
if (newPrecedence > GetPrecedence(leftOperand.Kind))
{
// Normally, a left operand with a looser precedence will consume all right operands that
// have a tighter precedence. For example, in the expression `a + b * c`, the `* c` part
// will be consumed as part of the right operand of the addition. However, there are a
// few circumstances in which a tighter precedence is not consumed: that occurs when the
// left hand operator does not have an expression as its right operand. This occurs for
// the is-type operator and the is-pattern operator. Source text such as
// `a is {} + b` should produce a syntax error, as parsing the `+` with an `is`
// expression as its left operand would be a precedence inversion. Similarly, it occurs
// with an anonymous method expression or a lambda expression with a block body. No
// further parsing will find a way to fix things up, so we accept the operator but issue
// a diagnostic.
operatorToken = this.AddError(
operatorToken,
leftOperand.Kind == SyntaxKind.IsPatternExpression ? ErrorCode.ERR_UnexpectedToken : ErrorCode.WRN_PrecedenceInversion,
operatorToken.Text);
}
if (operatorExpressionKind == SyntaxKind.AsExpression)
{
return _syntaxFactory.BinaryExpression(
operatorExpressionKind, leftOperand, operatorToken, this.ParseType(ParseTypeMode.AsExpression));
}
if (operatorExpressionKind == SyntaxKind.IsExpression)
return ParseIsExpression(leftOperand, operatorToken);
if (operatorExpressionKind == SyntaxKind.SwitchExpression)
return ParseSwitchExpression(leftOperand, operatorToken);
if (operatorExpressionKind == SyntaxKind.WithExpression)
return ParseWithExpression(leftOperand, operatorToken);
if (operatorExpressionKind == SyntaxKind.RangeExpression)
{
return _syntaxFactory.RangeExpression(
leftOperand,
operatorToken,
CanStartExpression()
? this.ParseSubExpression(Precedence.Range)
: null);
}
if (IsExpectedAssignmentOperator(operatorToken.Kind))
{
ExpressionSyntax rhs;
if (operatorExpressionKind == SyntaxKind.SimpleAssignmentExpression && CurrentToken.Kind == SyntaxKind.RefKeyword &&
// check for lambda expression with explicit ref return type: `ref int () => { ... }`
!this.IsPossibleLambdaExpression(newPrecedence))
{
rhs = _syntaxFactory.RefExpression(
this.EatToken(),
this.ParseExpressionCore());
}
else
{
rhs = this.ParseSubExpression(newPrecedence);
}
return _syntaxFactory.AssignmentExpression(
operatorExpressionKind, leftOperand, operatorToken, rhs);
}
if (IsExpectedBinaryOperator(operatorToken.Kind))
return _syntaxFactory.BinaryExpression(operatorExpressionKind, leftOperand, operatorToken, this.ParseSubExpression(newPrecedence));
throw ExceptionUtilities.Unreachable();
}
(SyntaxKind operatorTokenKind, SyntaxKind operatorExpressionKind) getOperatorTokenAndExpressionKind()
{
// If the set of expression continuations is updated here, please review ParseStatementAttributeDeclarations
// to see if it may need a similar look-ahead check to determine if something is a collection expression versus
// an attribute.
var token1 = this.CurrentToken;
var token1Kind = token1.ContextualKind;
// Merge two consecutive dots into a DotDotToken
if (IsAtDotDotToken())
return (SyntaxKind.DotDotToken, SyntaxKind.RangeExpression);
// check for >>, >>=, >>> or >>>=
//
// In all those cases, update token1Kind to be the merged token kind. It will then be handled by the code below.
if (token1Kind == SyntaxKind.GreaterThanToken
&& this.PeekToken(1) is { Kind: SyntaxKind.GreaterThanToken or SyntaxKind.GreaterThanEqualsToken } token2
&& NoTriviaBetween(token1, token2)) // check to see if they really are adjacent
{
if (token2.Kind == SyntaxKind.GreaterThanToken)
{
if (this.PeekToken(2) is { Kind: SyntaxKind.GreaterThanToken or SyntaxKind.GreaterThanEqualsToken } token3
&& NoTriviaBetween(token2, token3)) // check to see if they really are adjacent
{
// >>> or >>>=
token1Kind = token3.Kind == SyntaxKind.GreaterThanToken
? SyntaxKind.GreaterThanGreaterThanGreaterThanToken
: SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken;
}
else
{
// >>
token1Kind = SyntaxKind.GreaterThanGreaterThanToken;
}
}
else
{
// >>=
token1Kind = SyntaxKind.GreaterThanGreaterThanEqualsToken;
}
}
if (IsExpectedBinaryOperator(token1Kind))
return (token1Kind, SyntaxFacts.GetBinaryExpression(token1Kind));
if (IsExpectedAssignmentOperator(token1Kind))
return (token1Kind, SyntaxFacts.GetAssignmentExpression(token1Kind));
if (token1Kind == SyntaxKind.SwitchKeyword && this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken)
return (token1Kind, SyntaxKind.SwitchExpression);
if (token1Kind == SyntaxKind.WithKeyword && this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken)
return (token1Kind, SyntaxKind.WithExpression);
// Something that doesn't expand the current expression we're looking at. Bail out and see if we
// can end with a conditional expression.
return (SyntaxKind.None, SyntaxKind.None);
}
SyntaxToken eatOperatorToken(SyntaxKind operatorTokenKind)
{
// Combine tokens into a single token if needed
if (operatorTokenKind is SyntaxKind.DotDotToken)
return EatDotDotToken();
if (operatorTokenKind is SyntaxKind.GreaterThanGreaterThanToken or SyntaxKind.GreaterThanGreaterThanEqualsToken)
{
// >> and >>=
// Two tokens need to be consumed here.
var token1 = EatToken();
var token2 = EatToken();
return SyntaxFactory.Token(
token1.GetLeadingTrivia(),
operatorTokenKind,
token2.GetTrailingTrivia());
}
else if (operatorTokenKind is SyntaxKind.GreaterThanGreaterThanGreaterThanToken or SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken)
{
// >>> and >>>=
// Three tokens need to be consumed here.
var token1 = EatToken();
_ = EatToken();
var token3 = EatToken();
return SyntaxFactory.Token(
token1.GetLeadingTrivia(),
operatorTokenKind,
token3.GetTrailingTrivia());
}
else
{
// Normal operator. Eat as a single token, converting contextual words cases (like 'with') to a keyword.
return this.EatContextualToken(operatorTokenKind);
}
}
ConditionalExpressionSyntax consumeConditionalExpression(ExpressionSyntax leftOperand)
{
// Complex ambiguity with `?` and collection-expressions. Specifically: b?[c]:d
//
// On its own, we want that to be a conditional expression with a collection expression in it. However, for
// back compat, we need to make sure that `a ? b?[c] : d` sees the inner `b?[c]` as a
// conditional-access-expression. So, if after consuming the portion after the initial `?` if we do not
// have the `:` we need, and we can see a `?[` in that portion of the parse, then we retry consuming the
// when-true portion, but this time forcing the prior way of handling `?[`.
var questionToken = this.EatToken();
using var afterQuestionToken = this.GetDisposableResetPoint(resetOnDispose: false);
var whenTrue = this.ParsePossibleRefExpression();
if (this.CurrentToken.Kind != SyntaxKind.ColonToken &&
!this.ForceConditionalAccessExpression &&
containsTernaryCollectionToReinterpret(whenTrue))
{
// Keep track of where we are right now in case the new parse doesn't make things better.
using var originalAfterWhenTrue = this.GetDisposableResetPoint(resetOnDispose: false);
// Go back to right after the `?`
afterQuestionToken.Reset();
// try reparsing with `?[` as a conditional access, not a ternary+collection
this.ForceConditionalAccessExpression = true;
var newWhenTrue = this.ParsePossibleRefExpression();
this.ForceConditionalAccessExpression = false;
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
// if we now are at a colon, this was preferred parse.
whenTrue = newWhenTrue;
}
else
{
// retrying the parse didn't help. Use the original interpretation.
originalAfterWhenTrue.Reset();
}
}
if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken && this.lexer.InterpolationFollowedByColon)
{
// We have an interpolated string with an interpolation that contains a conditional expression.
// Unfortunately, the precedence demands that the colon is considered to signal the start of the
// format string. Without this code, the compiler would complain about a missing colon, and point
// to the colon that is present, which would be confusing. We aim to give a better error message.
var conditionalExpression = _syntaxFactory.ConditionalExpression(
leftOperand,
questionToken,
whenTrue,
SyntaxFactory.MissingToken(SyntaxKind.ColonToken),
_syntaxFactory.IdentifierName(SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken)));
return this.AddError(conditionalExpression, ErrorCode.ERR_ConditionalInInterpolation);
}
else
{
return _syntaxFactory.ConditionalExpression(
leftOperand,
questionToken,
whenTrue,
this.EatToken(SyntaxKind.ColonToken),
this.ParsePossibleRefExpression());
}
}
static bool containsTernaryCollectionToReinterpret(ExpressionSyntax expression)
{
var stack = ArrayBuilder<GreenNode>.GetInstance();
stack.Push(expression);
while (stack.Count > 0)
{
var current = stack.Pop();
if (current is ConditionalExpressionSyntax conditionalExpression &&
conditionalExpression.WhenTrue.GetFirstToken().Kind == SyntaxKind.OpenBracketToken)
{
stack.Free();
return true;
}
// Note: we could consider not recursing into anonymous-methods/lambdas (since we reset the
// ForceConditionalAccessExpression flag when we go into that). However, that adds a bit of
// fragile coupling between these different code blocks that i'd prefer to avoid. In practice
// the extra cost here will almost never occur, so the simplicity is worth it.
foreach (var child in current.ChildNodesAndTokens())
stack.Push(child);
}
stack.Free();
return false;
}
}
/// <summary>Check if we're currently at a .. sequence that can then be parsed out as a <see cref="SyntaxKind.DotDotToken"/>.</summary>
public bool IsAtDotDotToken()
=> IsAtDotDotToken(this.CurrentToken, this.PeekToken(1));
public static bool IsAtDotDotToken(SyntaxToken token1, SyntaxToken token2)
=> token1.Kind == SyntaxKind.DotToken &&
token2.Kind == SyntaxKind.DotToken &&
NoTriviaBetween(token1, token2);
/// <summary>Consume the next two tokens as a <see cref="SyntaxKind.DotDotToken"/>. Note: if three dot tokens
/// are in a row, an error will be placed on the <c>..</c> token to say that is illegal, and single DotDot token
/// will be returned.</summary>
public SyntaxToken EatDotDotToken()
{
Debug.Assert(IsAtDotDotToken());
var token1 = this.EatToken();
var token2 = this.EatToken();
var dotDotToken = SyntaxFactory.Token(token1.GetLeadingTrivia(), SyntaxKind.DotDotToken, token2.GetTrailingTrivia());
if (this.CurrentToken is { Kind: SyntaxKind.DotToken } token3 &&
NoTriviaBetween(token2, token3))
{
// At least three dots directly in a row. Definitely mark that this is always illegal. We do not allow
// `...` at all in case we want to use that syntax in the future.
dotDotToken = AddError(
dotDotToken,
offset: dotDotToken.GetLeadingTriviaWidth(),
length: 0,
ErrorCode.ERR_TripleDotNotAllowed);
// If we have exactly 3 dots in a row, then make the third dot into skipped trivia on the `..` as this
// is likely just a mistyped range/slice and we'll recover better if we don't try to process the 3rd dot
// as a member access or anything like that.
//
// If we have 4 dots in a row (`....`), then don't skip any of them. We'll let the caller handle the
// next two dots as a range/slice/whatever.
if (this.PeekToken(1) is not { Kind: SyntaxKind.DotToken } token4 ||
!NoTriviaBetween(token3, token4))
{
dotDotToken = AddSkippedSyntax(dotDotToken, this.EatToken(), trailing: true);
}
}
return dotDotToken;
}
#nullable disable
private DeclarationExpressionSyntax ParseDeclarationExpression(ParseTypeMode mode, bool isScoped)
{
var scopedKeyword = isScoped
? EatContextualToken(SyntaxKind.ScopedKeyword)
: null;
var type = this.ParseType(mode);
return _syntaxFactory.DeclarationExpression(
scopedKeyword == null ? type : _syntaxFactory.ScopedType(scopedKeyword, type),
ParseDesignation(forPattern: false));
}
private ExpressionSyntax ParseThrowExpression()
{
return _syntaxFactory.ThrowExpression(
this.EatToken(SyntaxKind.ThrowKeyword),
this.ParseSubExpression(Precedence.Coalescing));
}
private ExpressionSyntax ParseIsExpression(ExpressionSyntax leftOperand, SyntaxToken opToken)
{
var node = this.ParseTypeOrPatternForIsOperator();
return node switch
{
PatternSyntax pattern => _syntaxFactory.IsPatternExpression(leftOperand, opToken, pattern),
TypeSyntax type => _syntaxFactory.BinaryExpression(SyntaxKind.IsExpression, leftOperand, opToken, type),
_ => throw ExceptionUtilities.UnexpectedValue(node),
};
}
private ExpressionSyntax ParsePrimaryExpression(Precedence precedence)
{
// Primary expressions:
// x.y, f(x), a[i], x?.y, x?[y], x++, x--, x!, new, typeof, checked, unchecked, default, nameof, delegate, sizeof, stackalloc, x->y
//
// Note that postfix operators (like ++) are still primary expressions, even though their prefix equivalents (`++x`) are unary.
return parsePostFixExpression(parsePrimaryExpressionWithoutPostfix(precedence));
ExpressionSyntax parsePrimaryExpressionWithoutPostfix(Precedence precedence)
{
var tk = this.CurrentToken.Kind;
switch (tk)
{
case SyntaxKind.TypeOfKeyword:
return this.ParseTypeOfExpression();
case SyntaxKind.DefaultKeyword:
return this.ParseDefaultExpression();
case SyntaxKind.SizeOfKeyword:
return this.ParseSizeOfExpression();
case SyntaxKind.MakeRefKeyword:
return this.ParseMakeRefExpression();
case SyntaxKind.RefTypeKeyword:
return this.ParseRefTypeExpression();
case SyntaxKind.CheckedKeyword:
case SyntaxKind.UncheckedKeyword:
return this.ParseCheckedOrUncheckedExpression();
case SyntaxKind.RefValueKeyword:
return this.ParseRefValueExpression();
case SyntaxKind.ColonColonToken:
// misplaced ::
// Calling ParseAliasQualifiedName will cause us to create a missing identifier node that then
// properly consumes the :: and the reset of the alias name afterwards.
return this.ParseAliasQualifiedName(NameOptions.InExpression);
case SyntaxKind.EqualsGreaterThanToken:
return this.ParseLambdaExpression();
case SyntaxKind.StaticKeyword:
if (this.IsPossibleAnonymousMethodExpression())
{
return this.ParseAnonymousMethodExpression();
}
else if (this.IsPossibleLambdaExpression(precedence))
{
return this.ParseLambdaExpression();
}
else
{
return this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text);
}
case SyntaxKind.IdentifierToken:
{
if (this.IsTrueIdentifier())
{
if (this.IsPossibleAnonymousMethodExpression())
{
return this.ParseAnonymousMethodExpression();
}
else if (this.IsPossibleLambdaExpression(precedence) && this.TryParseLambdaExpression() is { } lambda)
{
return lambda;
}
else if (this.IsPossibleDeconstructionLeft(precedence))
{
return ParseDeclarationExpression(ParseTypeMode.Normal, isScoped: false);
}
else if (IsCurrentTokenFieldInKeywordContext() && PeekToken(1).Kind != SyntaxKind.ColonColonToken)
{
return _syntaxFactory.FieldExpression(this.EatContextualToken(SyntaxKind.FieldKeyword));
}
else
{
return this.ParseAliasQualifiedName(NameOptions.InExpression);
}
}
else
{
return this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text);
}
}
case SyntaxKind.OpenBracketToken:
return this.IsPossibleLambdaExpression(precedence)
? this.ParseLambdaExpression()
: this.ParseCollectionExpression();
case SyntaxKind.ThisKeyword:
return _syntaxFactory.ThisExpression(this.EatToken());
case SyntaxKind.BaseKeyword:
return ParseBaseExpression();
case SyntaxKind.ArgListKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.NumericLiteralToken:
case SyntaxKind.StringLiteralToken:
case SyntaxKind.Utf8StringLiteralToken:
case SyntaxKind.SingleLineRawStringLiteralToken:
case SyntaxKind.Utf8SingleLineRawStringLiteralToken:
case SyntaxKind.MultiLineRawStringLiteralToken:
case SyntaxKind.Utf8MultiLineRawStringLiteralToken:
case SyntaxKind.CharacterLiteralToken:
return _syntaxFactory.LiteralExpression(SyntaxFacts.GetLiteralExpression(tk), this.EatToken());
case SyntaxKind.InterpolatedStringStartToken:
case SyntaxKind.InterpolatedVerbatimStringStartToken:
case SyntaxKind.InterpolatedSingleLineRawStringStartToken:
case SyntaxKind.InterpolatedMultiLineRawStringStartToken:
throw new NotImplementedException(); // this should not occur because these tokens are produced and parsed immediately
case SyntaxKind.InterpolatedStringToken:
return this.ParseInterpolatedStringToken();
case SyntaxKind.OpenParenToken:
{
return IsPossibleLambdaExpression(precedence) && this.TryParseLambdaExpression() is { } lambda
? lambda
: this.ParseCastOrParenExpressionOrTuple();
}
case SyntaxKind.NewKeyword:
return this.ParseNewExpression();
case SyntaxKind.StackAllocKeyword:
return this.ParseStackAllocExpression();
case SyntaxKind.DelegateKeyword:
// check for lambda expression with explicit function pointer return type
return this.IsPossibleLambdaExpression(precedence)
? this.ParseLambdaExpression()
: this.ParseAnonymousMethodExpression();
case SyntaxKind.RefKeyword:
// check for lambda expression with explicit ref return type: `ref int () => { ... }`
if (this.IsPossibleLambdaExpression(precedence))
{
return this.ParseLambdaExpression();
}
// ref is not expected to appear in this position.
var refKeyword = this.EatToken();
return this.AddError(_syntaxFactory.RefExpression(refKeyword, this.ParseExpressionCore()), ErrorCode.ERR_InvalidExprTerm, SyntaxFacts.GetText(tk));
default:
if (IsPredefinedType(tk))
{
if (this.IsPossibleLambdaExpression(precedence))
{
return this.ParseLambdaExpression();
}
// check for intrinsic type followed by '.'
var expr = _syntaxFactory.PredefinedType(this.EatToken());
if (this.CurrentToken.Kind != SyntaxKind.DotToken || tk == SyntaxKind.VoidKeyword)
{
expr = this.AddError(expr, ErrorCode.ERR_InvalidExprTerm, SyntaxFacts.GetText(tk));
}
return expr;
}
else
{
var expr = this.CreateMissingIdentifierName();
if (tk == SyntaxKind.EndOfFileToken)
{
expr = this.AddError(expr, ErrorCode.ERR_ExpressionExpected);
}
else
{
expr = this.AddError(expr, ErrorCode.ERR_InvalidExprTerm, SyntaxFacts.GetText(tk));
}
return expr;
}
}
}
ExpressionSyntax parsePostFixExpression(ExpressionSyntax expr)
{
Debug.Assert(expr != null);
while (true)
{
// If the set of postfix expressions is updated here, please review ParseStatementAttributeDeclarations
// to see if it may need a similar look-ahead check to determine if something is a collection expression
// versus an attribute.
switch (this.CurrentToken.Kind)
{
case SyntaxKind.OpenParenToken:
expr = _syntaxFactory.InvocationExpression(expr, this.ParseParenthesizedArgumentList());
continue;
case SyntaxKind.OpenBracketToken:
expr = _syntaxFactory.ElementAccessExpression(expr, this.ParseBracketedArgumentList());
continue;
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
expr = _syntaxFactory.PostfixUnaryExpression(SyntaxFacts.GetPostfixUnaryExpression(this.CurrentToken.Kind), expr, this.EatToken());
continue;
case SyntaxKind.ColonColonToken:
if (this.PeekToken(1).Kind == SyntaxKind.IdentifierToken)
{
expr = _syntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
expr,
// replace :: with missing dot and annotate with skipped text "::" and error
this.ConvertToMissingWithTrailingTrivia(this.AddError(this.EatToken(), ErrorCode.ERR_UnexpectedAliasedName), SyntaxKind.DotToken),
this.ParseSimpleName(NameOptions.InExpression));
}
else
{
// just some random trailing :: ?
expr = AddTrailingSkippedSyntax(expr, this.EatTokenWithPrejudice(SyntaxKind.DotToken));
}
continue;
case SyntaxKind.MinusGreaterThanToken:
expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.PointerMemberAccessExpression, expr, this.EatToken(), this.ParseSimpleName(NameOptions.InExpression));
continue;
case SyntaxKind.DotToken when !IsAtDotDotToken():
// if we have the error situation:
//
// expr.
// X Y
//
// Then we don't want to parse this out as "Expr.X"
//
// It's far more likely the member access expression is simply incomplete and
// there is a new declaration on the next line.
if (this.CurrentToken.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia) &&
this.PeekToken(1).Kind == SyntaxKind.IdentifierToken &&
this.PeekToken(2).ContextualKind == SyntaxKind.IdentifierToken)
{
return _syntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression, expr, this.EatToken(),
this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_IdentifierExpected));
}
expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, this.EatToken(), this.ParseSimpleName(NameOptions.InExpression));
continue;
case SyntaxKind.QuestionToken:
if (CanStartConsequenceExpression())
{
expr = _syntaxFactory.ConditionalAccessExpression(
expr,
this.EatToken(),
ParseConsequenceSyntax());
continue;
}
return expr;
case SyntaxKind.ExclamationToken:
expr = _syntaxFactory.PostfixUnaryExpression(SyntaxKind.SuppressNullableWarningExpression, expr, this.EatToken());
continue;
default:
return expr;
}
}
}
}
private ExpressionSyntax ParseBaseExpression()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.BaseKeyword);
return _syntaxFactory.BaseExpression(this.EatToken());
}
/// <summary>
/// Returns true if...
/// 1. The precedence is less than or equal to Assignment, and
/// 2. The current token is the identifier var or a predefined type, and
/// 3. it is followed by (, and
/// 4. that ( begins a valid parenthesized designation, and
/// 5. the token following that designation is =
/// </summary>
private bool IsPossibleDeconstructionLeft(Precedence precedence)
{
if (precedence > Precedence.Assignment || !(this.CurrentToken.IsIdentifierVar() || IsPredefinedType(this.CurrentToken.Kind)))
{
return false;
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
this.EatToken(); // `var`
return
this.CurrentToken.Kind == SyntaxKind.OpenParenToken && ScanDesignator() &&
this.CurrentToken.Kind == SyntaxKind.EqualsToken;
}
private bool ScanDesignator()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.IdentifierToken:
if (!IsTrueIdentifier())
{
goto default;
}
this.EatToken(); // eat the identifier
return true;
case SyntaxKind.OpenParenToken:
while (true)
{
this.EatToken(); // eat the open paren or comma
if (!ScanDesignator())
{
return false;
}
switch (this.CurrentToken.Kind)
{
case SyntaxKind.CommaToken:
continue;
case SyntaxKind.CloseParenToken:
this.EatToken(); // eat the close paren
return true;
default:
return false;
}
}
default:
return false;
}
}
private bool IsPossibleAnonymousMethodExpression()
{
// Skip past any static/async keywords.
var tokenIndex = 0;
while (this.PeekToken(tokenIndex).Kind == SyntaxKind.StaticKeyword ||
this.PeekToken(tokenIndex).ContextualKind == SyntaxKind.AsyncKeyword)
{
tokenIndex++;
}
return this.PeekToken(tokenIndex).Kind == SyntaxKind.DelegateKeyword &&
this.PeekToken(tokenIndex + 1).Kind != SyntaxKind.AsteriskToken;
}
private bool CanStartConsequenceExpression()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.QuestionToken);
var nextToken = this.PeekToken(1);
var nextTokenKind = nextToken.Kind;
// ?. is always the start of of a consequence expression.
//
// ?.. is a ternary with a range expression as it's 'whenTrue' clause.
if (nextTokenKind == SyntaxKind.DotToken && !IsAtDotDotToken(nextToken, this.PeekToken(2)))
return true;
if (nextTokenKind == SyntaxKind.OpenBracketToken)
{
// could simply be `x?[0]`, or could be `x ? [0] : [1]`.
// Caller only wants us to parse ?[ how it was originally parsed before collection expressions.
if (this.ForceConditionalAccessExpression)
return true;
using var _ = GetDisposableResetPoint(resetOnDispose: true);
// Move past the '?'. Parse what comes next the same way that conditional expressions are parsed.
this.EatToken();
this.ParsePossibleRefExpression();
// If we see a colon, then do not parse this as a conditional-access-expression, pop up to the caller
// and have it reparse this as a conditional-expression instead.
return this.CurrentToken.Kind != SyntaxKind.ColonToken;
}
// Anything else is just a normal ? and indicates the start of a ternary expression.
return false;
}
internal ExpressionSyntax ParseConsequenceSyntax()
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.DotToken or SyntaxKind.OpenBracketToken);
ExpressionSyntax expr = this.CurrentToken.Kind switch
{
SyntaxKind.DotToken => _syntaxFactory.MemberBindingExpression(this.EatToken(), this.ParseSimpleName(NameOptions.InExpression)),
SyntaxKind.OpenBracketToken => _syntaxFactory.ElementBindingExpression(this.ParseBracketedArgumentList()),
_ => throw ExceptionUtilities.Unreachable(),
};
while (true)
{
// Nullable suppression operators should only be consumed by a conditional access
// if there are further conditional operations performed after the suppression
if (isOptionalExclamationsFollowedByConditionalOperation())
{
while (this.CurrentToken.Kind == SyntaxKind.ExclamationToken)
expr = _syntaxFactory.PostfixUnaryExpression(SyntaxKind.SuppressNullableWarningExpression, expr, EatToken());
}
switch (this.CurrentToken.Kind)
{
case SyntaxKind.OpenParenToken:
expr = _syntaxFactory.InvocationExpression(expr, this.ParseParenthesizedArgumentList());
continue;
case SyntaxKind.OpenBracketToken:
expr = _syntaxFactory.ElementAccessExpression(expr, this.ParseBracketedArgumentList());
continue;
case SyntaxKind.DotToken:
expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, this.EatToken(), this.ParseSimpleName(NameOptions.InExpression));
continue;
case SyntaxKind.QuestionToken:
return !CanStartConsequenceExpression()
? expr
: _syntaxFactory.ConditionalAccessExpression(
expr,
operatorToken: this.EatToken(),
ParseConsequenceSyntax());
default:
return expr;
}
}
bool isOptionalExclamationsFollowedByConditionalOperation()
{
var index = 0;
while (this.PeekToken(index).Kind == SyntaxKind.ExclamationToken)
index++;
return this.PeekToken(index).Kind
is SyntaxKind.OpenParenToken
or SyntaxKind.OpenBracketToken
or SyntaxKind.DotToken
or SyntaxKind.QuestionToken;
}
}
internal ArgumentListSyntax ParseParenthesizedArgumentList()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.ArgumentList)
{
return (ArgumentListSyntax)this.EatNode();
}
ParseArgumentList(
openToken: out SyntaxToken openToken,
arguments: out SeparatedSyntaxList<ArgumentSyntax> arguments,
closeToken: out SyntaxToken closeToken,
openKind: SyntaxKind.OpenParenToken,
closeKind: SyntaxKind.CloseParenToken);
return _syntaxFactory.ArgumentList(openToken, arguments, closeToken);
}
internal BracketedArgumentListSyntax ParseBracketedArgumentList()
{
if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.BracketedArgumentList)
{
return (BracketedArgumentListSyntax)this.EatNode();
}
ParseArgumentList(
openToken: out SyntaxToken openToken,
arguments: out SeparatedSyntaxList<ArgumentSyntax> arguments,
closeToken: out SyntaxToken closeToken,
openKind: SyntaxKind.OpenBracketToken,
closeKind: SyntaxKind.CloseBracketToken);
return _syntaxFactory.BracketedArgumentList(openToken, arguments, closeToken);
}
private void ParseArgumentList(
out SyntaxToken openToken,
out SeparatedSyntaxList<ArgumentSyntax> arguments,
out SyntaxToken closeToken,
SyntaxKind openKind,
SyntaxKind closeKind)
{
Debug.Assert(openKind is SyntaxKind.OpenParenToken or SyntaxKind.OpenBracketToken);
Debug.Assert(closeKind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken);
Debug.Assert((openKind == SyntaxKind.OpenParenToken) == (closeKind == SyntaxKind.CloseParenToken));
bool isIndexer = openKind == SyntaxKind.OpenBracketToken;
// convert `[` into `(` or vice versa for error recovery
openToken = this.CurrentToken.Kind is SyntaxKind.OpenParenToken or SyntaxKind.OpenBracketToken
? this.EatTokenAsKind(openKind)
: this.EatToken(openKind);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfArgumentList;
if (this.CurrentToken.Kind != closeKind && this.CurrentToken.Kind != SyntaxKind.SemicolonToken)
{
if (isIndexer)
{
// An indexer always expects at least one value.
arguments = ParseCommaSeparatedSyntaxList(
ref openToken,
SyntaxKind.CloseBracketToken,
static @this => @this.IsPossibleArgumentExpression(),
static @this => @this.ParseArgumentExpression(isIndexer: true),
skipBadArgumentListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
}
else
{
arguments = ParseCommaSeparatedSyntaxList(
ref openToken,
SyntaxKind.CloseParenToken,
static @this => @this.IsPossibleArgumentExpression(),
static @this => @this.ParseArgumentExpression(isIndexer: false),
skipBadArgumentListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
}
}
else if (isIndexer && this.CurrentToken.Kind == closeKind)
{
// An indexer always expects at least one value. And so we need to give an error
// for the case where we see only "[]". ParseArgumentExpression gives it.
var list = _pool.AllocateSeparated<ArgumentSyntax>();
list.Add(this.ParseArgumentExpression(isIndexer));
arguments = _pool.ToListAndFree(list);
}
else
{
arguments = default;
}
_termState = saveTerm;
// convert `]` into `)` or vice versa for error recovery
closeToken = this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken
? this.EatTokenAsKind(closeKind)
: this.EatToken(closeKind);
return;
static PostSkipAction skipBadArgumentListTokens(
LanguageParser @this, ref SyntaxToken open, SeparatedSyntaxListBuilder<ArgumentSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
if (@this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken or SyntaxKind.SemicolonToken)
return PostSkipAction.Abort;
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleArgumentExpression(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind || p.CurrentToken.Kind == SyntaxKind.SemicolonToken,
expectedKind, closeKind);
}
}
private bool IsEndOfArgumentList()
{
return this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken;
}
private bool IsPossibleArgumentExpression()
{
return IsValidArgumentRefKindKeyword(this.CurrentToken.Kind) || this.IsPossibleExpression();
}
private static bool IsValidArgumentRefKindKeyword(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.RefKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.InKeyword:
return true;
default:
return false;
}
}
private ArgumentSyntax ParseArgumentExpression(bool isIndexer)
{
var nameColon = this.CurrentToken.Kind == SyntaxKind.IdentifierToken && this.PeekToken(1).Kind == SyntaxKind.ColonToken
? _syntaxFactory.NameColon(
this.ParseIdentifierName(),
this.EatToken(SyntaxKind.ColonToken))
: null;
SyntaxToken refKindKeyword = null;
if (IsValidArgumentRefKindKeyword(this.CurrentToken.Kind) &&
// check for lambda expression with explicit ref return type: `ref int () => { ... }`
!(this.CurrentToken.Kind == SyntaxKind.RefKeyword &&
this.IsPossibleLambdaExpression(Precedence.Expression)))
{
refKindKeyword = this.EatToken();
}
ExpressionSyntax expression;
if (isIndexer && this.CurrentToken.Kind is SyntaxKind.CommaToken or SyntaxKind.CloseBracketToken)
{
expression = this.ParseIdentifierName(ErrorCode.ERR_ValueExpected);
}
else if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
expression = this.ParseIdentifierName(ErrorCode.ERR_MissingArgument);
}
else
{
// According to Language Specification, section 7.6.7 Element access
// The argument-list of an element-access is not allowed to contain ref or out arguments.
// However, we actually do support ref indexing of indexed properties in COM interop
// scenarios, and when indexing an object of static type "dynamic". So we enforce
// that the ref/out of the argument must match the parameter when binding the argument list.
expression = refKindKeyword?.Kind == SyntaxKind.OutKeyword
? ParseExpressionOrDeclaration(ParseTypeMode.Normal, permitTupleDesignation: false)
: ParseSubExpression(Precedence.Expression);
}
return _syntaxFactory.Argument(nameColon, refKindKeyword, expression);
}
private TypeOfExpressionSyntax ParseTypeOfExpression()
{
return _syntaxFactory.TypeOfExpression(
this.EatToken(),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseTypeOrVoid(),
this.EatToken(SyntaxKind.CloseParenToken));
}
private ExpressionSyntax ParseDefaultExpression()
{
var keyword = this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
return _syntaxFactory.DefaultExpression(
keyword,
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseType(),
this.EatToken(SyntaxKind.CloseParenToken));
}
else
{
return _syntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression, keyword);
}
}
private SizeOfExpressionSyntax ParseSizeOfExpression()
{
return _syntaxFactory.SizeOfExpression(
this.EatToken(),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseType(),
this.EatToken(SyntaxKind.CloseParenToken));
}
private MakeRefExpressionSyntax ParseMakeRefExpression()
{
return _syntaxFactory.MakeRefExpression(
this.EatToken(),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseSubExpression(Precedence.Expression),
this.EatToken(SyntaxKind.CloseParenToken));
}
private RefTypeExpressionSyntax ParseRefTypeExpression()
{
return _syntaxFactory.RefTypeExpression(
this.EatToken(),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseSubExpression(Precedence.Expression),
this.EatToken(SyntaxKind.CloseParenToken));
}
private CheckedExpressionSyntax ParseCheckedOrUncheckedExpression()
{
var checkedOrUnchecked = this.EatToken();
Debug.Assert(checkedOrUnchecked.Kind is SyntaxKind.CheckedKeyword or SyntaxKind.UncheckedKeyword);
var kind = checkedOrUnchecked.Kind == SyntaxKind.CheckedKeyword ? SyntaxKind.CheckedExpression : SyntaxKind.UncheckedExpression;
return _syntaxFactory.CheckedExpression(
kind,
checkedOrUnchecked,
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseSubExpression(Precedence.Expression),
this.EatToken(SyntaxKind.CloseParenToken));
}
private RefValueExpressionSyntax ParseRefValueExpression()
{
return _syntaxFactory.RefValueExpression(
this.EatToken(SyntaxKind.RefValueKeyword),
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseSubExpression(Precedence.Expression),
this.EatToken(SyntaxKind.CommaToken),
this.ParseType(),
this.EatToken(SyntaxKind.CloseParenToken));
}
private bool ScanParenthesizedLambda(Precedence precedence)
{
return ScanParenthesizedImplicitlyTypedLambda(precedence) || ScanExplicitlyTypedLambda(precedence);
}
private bool ScanParenthesizedImplicitlyTypedLambda(Precedence precedence)
{
Debug.Assert(CurrentToken.Kind == SyntaxKind.OpenParenToken);
if (!(precedence <= Precedence.Lambda))
{
return false;
}
// case 1: ( x ,
if (isParenVarCommaSyntax())
{
// Make sure it really looks like a lambda, not just a tuple
int curTk = 3;
while (true)
{
var tk = this.PeekToken(curTk++);
// skip identifiers commas and predefined types in any combination for error recovery
if (tk.Kind is not SyntaxKind.IdentifierToken and not SyntaxKind.CommaToken
&& !SyntaxFacts.IsPredefinedType(tk.Kind)
&& (this.IsInQuery || !IsTokenQueryContextualKeyword(tk)))
{
break;
};
}
// ) =>
return this.PeekToken(curTk - 1).Kind == SyntaxKind.CloseParenToken &&
this.PeekToken(curTk).Kind == SyntaxKind.EqualsGreaterThanToken;
}
// case 2: ( x ) =>
if (IsTrueIdentifier(this.PeekToken(1)))
{
// allow for a) =>
var skipIndex = 2;
// Must have: ) =>
if (this.PeekToken(skipIndex).Kind == SyntaxKind.CloseParenToken
&& this.PeekToken(skipIndex + 1).Kind == SyntaxKind.EqualsGreaterThanToken)
{
return true;
}
}
// case 3: ( ) =>
if (this.PeekToken(1).Kind == SyntaxKind.CloseParenToken
&& this.PeekToken(2).Kind == SyntaxKind.EqualsGreaterThanToken)
{
return true;
}
// case 4: ( params
// This case is interesting in that it is not legal; this error could be caught at parse time but we would rather
// recover from the error and let the semantic analyzer deal with it.
if (this.PeekToken(1).Kind == SyntaxKind.ParamsKeyword)
{
return true;
}
return false;
bool isParenVarCommaSyntax()
{
var token1 = this.PeekToken(1);
// Ensure next token is a variable
if (token1.Kind == SyntaxKind.IdentifierToken)
{
if (!this.IsInQuery || !IsTokenQueryContextualKeyword(token1))
{
// Variable must be directly followed by a comma if not followed by exclamation
var token2 = this.PeekToken(2);
// ( x , [...]
if (token2.Kind == SyntaxKind.CommaToken)
{
return true;
}
}
}
return false;
}
}
private bool ScanExplicitlyTypedLambda(Precedence precedence)
{
Debug.Assert(CurrentToken.Kind == SyntaxKind.OpenParenToken);
if (!(precedence <= Precedence.Lambda))
{
return false;
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// Do we have the following, where the attributes, modifier, and type are
// optional? If so then parse it as a lambda.
// (attributes modifier T x [, ...]) =>
//
// It's not sufficient to assume this is a lambda expression if we see a
// modifier such as `(ref x,` because the caller of this method may have
// scanned past a preceding identifier, and `F (ref x,` might be a call to
// method F rather than a lambda expression with return type F.
// Instead, we need to scan to `=>`.
while (true)
{
// Advance past the open paren or comma.
this.EatToken();
ParseAttributeDeclarations(inExpressionContext: true);
bool hasModifier = false;
if (IsParameterModifierExcludingScoped(this.CurrentToken) || this.CurrentToken.ContextualKind == SyntaxKind.ScopedKeyword)
{
SyntaxListBuilder modifiers = _pool.Allocate();
ParseParameterModifiers(modifiers, isFunctionPointerParameter: false);
hasModifier = modifiers.Count != 0;
_pool.Free(modifiers);
}
if (hasModifier || ShouldParseLambdaParameterType())
{
if (this.ScanType() == ScanTypeFlags.NotType)
{
return false;
}
}
// eat the parameter name.
var identifier = this.IsTrueIdentifier() ? this.EatToken() : CreateMissingIdentifierToken();
var equalsToken = TryEatToken(SyntaxKind.EqualsToken);
// If we have an `=` then parse out a default value. Note: this is not legal, but this allows us to
// to be resilient to the user writing this so we don't go completely off the rails.
if (equalsToken != null)
{
// Note: we don't do this if we have `=[`. Realistically, this is never going to be a lambda
// expression as a `[` can only start an attribute declaration or collection expression, neither of
// which can be a default arg. Checking for this helps us from going off the rails in pathological
// cases with lots of nested tokens that look like the could be anything.
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
return false;
}
this.ParseExpressionCore();
}
switch (this.CurrentToken.Kind)
{
case SyntaxKind.CommaToken:
continue;
case SyntaxKind.CloseParenToken:
return this.PeekToken(1).Kind == SyntaxKind.EqualsGreaterThanToken;
default:
return false;
}
}
}
private ExpressionSyntax ParseCastOrParenExpressionOrTuple()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.OpenParenToken);
using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
// We have a decision to make -- is this a cast, or is it a parenthesized
// expression? Because look-ahead is cheap with our token stream, we check
// to see if this "looks like" a cast (without constructing any parse trees)
// to help us make the decision.
if (this.ScanCast())
{
if (!IsCurrentTokenQueryKeywordInQuery())
{
// Looks like a cast, so parse it as one.
resetPoint.Reset();
return _syntaxFactory.CastExpression(
this.EatToken(SyntaxKind.OpenParenToken),
this.ParseType(),
this.EatToken(SyntaxKind.CloseParenToken),
this.ParseSubExpression(Precedence.Cast));
}
}
// Doesn't look like a cast, so parse this as a parenthesized expression or tuple.
resetPoint.Reset();
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var expression = this.ParseExpressionOrDeclaration(ParseTypeMode.FirstElementOfPossibleTupleLiteral, permitTupleDesignation: true);
// ( <expr>, must be a tuple
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
return ParseTupleExpressionTail(
openParen,
_syntaxFactory.Argument(nameColon: null, refKindKeyword: null, expression));
}
// ( name:
if (expression.Kind == SyntaxKind.IdentifierName && this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
return ParseTupleExpressionTail(
openParen,
_syntaxFactory.Argument(
_syntaxFactory.NameColon((IdentifierNameSyntax)expression, EatToken()),
refKindKeyword: null,
this.ParseExpressionOrDeclaration(ParseTypeMode.FirstElementOfPossibleTupleLiteral, permitTupleDesignation: true)));
}
return _syntaxFactory.ParenthesizedExpression(
openParen,
expression,
this.EatToken(SyntaxKind.CloseParenToken));
}
private TupleExpressionSyntax ParseTupleExpressionTail(SyntaxToken openParen, ArgumentSyntax firstArg)
{
var list = _pool.AllocateSeparated<ArgumentSyntax>();
list.Add(firstArg);
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
var expression = ParseExpressionOrDeclaration(ParseTypeMode.AfterTupleComma, permitTupleDesignation: true);
var argument = expression.Kind != SyntaxKind.IdentifierName || this.CurrentToken.Kind != SyntaxKind.ColonToken
? _syntaxFactory.Argument(nameColon: null, refKindKeyword: null, expression: expression)
: _syntaxFactory.Argument(
_syntaxFactory.NameColon((IdentifierNameSyntax)expression, EatToken()),
refKindKeyword: null,
ParseExpressionOrDeclaration(ParseTypeMode.AfterTupleComma, permitTupleDesignation: true));
list.Add(argument);
}
if (list.Count < 2)
{
list.AddSeparator(SyntaxFactory.MissingToken(SyntaxKind.CommaToken));
list.Add(_syntaxFactory.Argument(
nameColon: null, refKindKeyword: null,
this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_TupleTooFewElements)));
}
return _syntaxFactory.TupleExpression(
openParen,
_pool.ToListAndFree(list),
this.EatToken(SyntaxKind.CloseParenToken));
}
private bool ScanCast(bool forPattern = false)
{
if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
return false;
}
this.EatToken();
var type = this.ScanType(forPattern);
if (type == ScanTypeFlags.NotType)
{
return false;
}
if (this.CurrentToken.Kind != SyntaxKind.CloseParenToken)
{
return false;
}
this.EatToken();
if (forPattern && this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
// In a pattern, an identifier can follow a cast unless it's a binary pattern token.
return !isBinaryPattern();
}
switch (type)
{
// If we have any of the following, we know it must be a cast:
// 1) (Goo*)bar;
// 2) (Goo?)bar;
// 3) "(int)bar" or "(int[])bar"
// 4) (G::Goo)bar
case ScanTypeFlags.PointerOrMultiplication:
case ScanTypeFlags.NullableType:
case ScanTypeFlags.MustBeType:
case ScanTypeFlags.AliasQualifiedName:
// The thing between parens is unambiguously a type.
// In a pattern, we need more lookahead to confirm it is a cast and not
// a parenthesized type pattern. In this case the tokens that
// have both unary and binary operator forms may appear in their unary form
// following a cast.
return !forPattern || this.CurrentToken.Kind switch
{
SyntaxKind.PlusToken or
SyntaxKind.MinusToken or
SyntaxKind.AmpersandToken or
SyntaxKind.AsteriskToken
=> true,
// `(X)..` must be a cast of a range expression, not a member access of some arbitrary expression.
SyntaxKind.DotToken when IsAtDotDotToken()
=> true,
var tk
=> CanFollowCast(tk)
};
case ScanTypeFlags.GenericTypeOrMethod:
case ScanTypeFlags.TupleType:
// If we have `(X<Y>)[...` then we know this must be a cast of a collection expression, not an index
// into some expr. As most collections are generic, the common case is not ambiguous.
//
// Things are still ambiguous if you have `(X)[...` and for back compat we still parse that as
// indexing into an expression. The user can still write `(X)([...` in this case though to get cast
// parsing. As non-generic casts are the rare case for collection expressions, this gives a good
// balance of back compat and user ease for the normal case.
return this.CurrentToken.Kind == SyntaxKind.OpenBracketToken || CanFollowCast(this.CurrentToken.Kind);
case ScanTypeFlags.GenericTypeOrExpression:
case ScanTypeFlags.NonGenericTypeOrExpression:
// if we have `(A)[]` then treat that always as a cast of an empty collection expression. `[]` is not
// legal on the RHS in any other circumstances for a parenthesized expr.
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken &&
this.PeekToken(1).Kind == SyntaxKind.CloseBracketToken)
{
return true;
}
// check for ambiguous type or expression followed by disambiguating token. i.e.
//
// "(A)b" is a cast. But "(A)+b" is not a cast.
return CanFollowCast(this.CurrentToken.Kind);
default:
throw ExceptionUtilities.UnexpectedValue(type);
}
bool isBinaryPattern()
{
if (!isBinaryPatternKeyword())
{
return false;
}
bool lastTokenIsBinaryOperator = true;
EatToken();
while (isBinaryPatternKeyword())
{
// If we see a subsequent binary pattern token, it can't be an operator.
// Later, it will be parsed as an identifier.
lastTokenIsBinaryOperator = !lastTokenIsBinaryOperator;
EatToken();
}
// In case a combinator token is used as a constant, we explicitly check that a pattern is NOT followed.
// Such as `(e is (int)or or >= 0)` versus `(e is (int) or or)`
return lastTokenIsBinaryOperator == IsPossibleSubpatternElement();
}
bool isBinaryPatternKeyword()
{
return this.CurrentToken.ContextualKind is SyntaxKind.OrKeyword or SyntaxKind.AndKeyword;
}
}
/// <summary>
/// Tokens that match the following are considered a possible lambda expression:
/// <code>attribute-list* ('async' | 'static')* type? ('(' | identifier) ...</code>
/// For better error recovery 'static =>' is also considered a possible lambda expression.
/// </summary>
private bool IsPossibleLambdaExpression(Precedence precedence)
{
if (precedence > Precedence.Lambda)
{
return false;
}
var token1 = this.PeekToken(1);
// x =>
//
// Def a lambda.
if (token1.Kind == SyntaxKind.EqualsGreaterThanToken)
{
return true;
}
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
// A lambda could be starting with attributes, attempt to skip past them and check after that point.
if (CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
// Subtle case to deal with. Consider:
//
// [X, () => {} vs:
// [X] () => {}
//
// The former is a collection expression, the latter an attributed-lambda. However, we will likely
// successfully parse out `[X,` as an incomplete attribute, and thus think the former is the latter. So,
// to ensure proper parsing of the collection expressions, bail out if the attribute is not complete.
var attributeDeclarations = ParseAttributeDeclarations(inExpressionContext: true);
if (attributeDeclarations is [.., { CloseBracketToken.IsMissing: true }])
return false;
}
bool seenStatic;
if (this.CurrentToken.Kind == SyntaxKind.StaticKeyword)
{
EatToken();
seenStatic = true;
}
else if (this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword &&
this.PeekToken(1).Kind == SyntaxKind.StaticKeyword)
{
EatToken();
EatToken();
seenStatic = true;
}
else
{
seenStatic = false;
}
if (seenStatic)
{
if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken)
{
// 1. `static =>`
// 2. `async static =>`
// This is an error case, but we have enough code in front of us to be certain
// the user was trying to write a static lambda.
return true;
}
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
// 1. `static (...
// 2. `async static (...
return true;
}
}
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken &&
this.PeekToken(1).Kind == SyntaxKind.EqualsGreaterThanToken)
{
// 1. `a => ...`
// 1. `static a => ...`
// 2. `async static a => ...`
return true;
}
// Have checked all the static forms. And have checked for the basic `a => a` form.
// At this point we have must be on 'async' or an explicit return type for this to still be a lambda.
if (this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword &&
IsAnonymousFunctionAsyncModifier())
{
EatToken();
}
using (var nestedResetPoint = this.GetDisposableResetPoint(resetOnDispose: false))
{
var st = ScanType();
if (st == ScanTypeFlags.NotType || this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
nestedResetPoint.Reset();
}
}
// However, just because we're on `async` doesn't mean we're a lambda. We might have
// something lambda-like like:
//
// async a => ... // or
// async (a) => ...
//
// Or we could have something that isn't a lambda like:
//
// async ();
// 'async <identifier> => ...' looks like an async simple lambda
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken &&
this.PeekToken(1).Kind == SyntaxKind.EqualsGreaterThanToken)
{
// async a => ...
return true;
}
// Non-simple async lambda must be of the form 'async (...'
if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
return false;
}
// Check whether looks like implicitly or explicitly typed lambda
return ScanParenthesizedLambda(precedence);
}
private static bool CanFollowCast(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.AsKeyword:
case SyntaxKind.IsKeyword:
case SyntaxKind.SemicolonToken:
case SyntaxKind.CloseParenToken:
case SyntaxKind.CloseBracketToken:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CommaToken:
case SyntaxKind.EqualsToken:
case SyntaxKind.PlusEqualsToken:
case SyntaxKind.MinusEqualsToken:
case SyntaxKind.AsteriskEqualsToken:
case SyntaxKind.SlashEqualsToken:
case SyntaxKind.PercentEqualsToken:
case SyntaxKind.AmpersandEqualsToken:
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.BarEqualsToken:
case SyntaxKind.LessThanLessThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.ColonToken:
case SyntaxKind.BarBarToken:
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.BarToken:
case SyntaxKind.CaretToken:
case SyntaxKind.AmpersandToken:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.LessThanEqualsToken:
case SyntaxKind.GreaterThanToken:
case SyntaxKind.GreaterThanEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.AsteriskToken:
case SyntaxKind.SlashToken:
case SyntaxKind.PercentToken:
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.DotToken:
case SyntaxKind.MinusGreaterThanToken:
case SyntaxKind.QuestionQuestionToken:
case SyntaxKind.EndOfFileToken:
case SyntaxKind.SwitchKeyword:
case SyntaxKind.EqualsGreaterThanToken:
return false;
default:
return true;
}
}
private ExpressionSyntax ParseNewExpression()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.NewKeyword);
if (this.IsAnonymousType())
{
return this.ParseAnonymousTypeExpression();
}
else if (this.IsImplicitlyTypedArray())
{
return this.ParseImplicitlyTypedArrayCreation();
}
else
{
// assume object creation as default case
return this.ParseArrayOrObjectCreationExpression();
}
}
private CollectionExpressionSyntax ParseCollectionExpression()
{
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.OpenBracketToken);
var openBracket = this.EatToken(SyntaxKind.OpenBracketToken);
var list = this.ParseCommaSeparatedSyntaxList(
ref openBracket,
SyntaxKind.CloseBracketToken,
static @this => @this.IsPossibleCollectionElement(),
static @this => @this.ParseCollectionElement(),
skipBadCollectionElementTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.CollectionExpression(
openBracket,
list,
this.EatToken(SyntaxKind.CloseBracketToken));
static PostSkipAction skipBadCollectionElementTokens(
LanguageParser @this, ref SyntaxToken openBracket, SeparatedSyntaxListBuilder<CollectionElementSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openBracket, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleCollectionElement(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private bool IsPossibleCollectionElement()
{
return this.IsPossibleExpression();
}
private CollectionElementSyntax ParseCollectionElement()
{
return IsAtDotDotToken()
? _syntaxFactory.SpreadElement(this.EatDotDotToken(), this.ParseExpressionCore())
: _syntaxFactory.ExpressionElement(this.ParseExpressionCore());
}
private bool IsAnonymousType()
{
return this.CurrentToken.Kind == SyntaxKind.NewKeyword && this.PeekToken(1).Kind == SyntaxKind.OpenBraceToken;
}
private AnonymousObjectCreationExpressionSyntax ParseAnonymousTypeExpression()
{
Debug.Assert(IsAnonymousType());
var @new = this.EatToken(SyntaxKind.NewKeyword);
Debug.Assert(this.CurrentToken.Kind == SyntaxKind.OpenBraceToken);
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var expressions = ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsPossibleExpression(),
static @this => @this.ParseAnonymousTypeMemberInitializer(),
SkipBadInitializerListTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.AnonymousObjectCreationExpression(
@new,
openBrace,
expressions,
this.EatToken(SyntaxKind.CloseBraceToken));
}
private AnonymousObjectMemberDeclaratorSyntax ParseAnonymousTypeMemberInitializer()
{
return _syntaxFactory.AnonymousObjectMemberDeclarator(
this.IsNamedAssignment() ? ParseNameEquals() : null,
this.ParseExpressionCore());
}
private bool IsInitializerMember()
{
return this.IsComplexElementInitializer() ||
this.IsNamedAssignment() ||
this.IsDictionaryInitializer() ||
this.IsPossibleExpression();
}
private bool IsComplexElementInitializer()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBraceToken;
}
private bool IsNamedAssignment()
{
return IsTrueIdentifier() && this.PeekToken(1).Kind == SyntaxKind.EqualsToken;
}
private bool IsDictionaryInitializer()
{
return this.CurrentToken.Kind == SyntaxKind.OpenBracketToken;
}
private ExpressionSyntax ParseArrayOrObjectCreationExpression()
{
SyntaxToken @new = this.EatToken(SyntaxKind.NewKeyword);
TypeSyntax type = null;
InitializerExpressionSyntax initializer = null;
if (!IsImplicitObjectCreation())
{
type = this.ParseType(ParseTypeMode.NewExpression);
if (type.Kind == SyntaxKind.ArrayType)
{
// Check for an initializer.
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
initializer = this.ParseArrayInitializer();
}
return _syntaxFactory.ArrayCreationExpression(@new, (ArrayTypeSyntax)type, initializer);
}
}
ArgumentListSyntax argumentList = null;
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
argumentList = this.ParseParenthesizedArgumentList();
}
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
initializer = this.ParseObjectOrCollectionInitializer();
}
// we need one or the other. also, don't bother reporting this if we already complained about the new type.
if (argumentList == null && initializer == null)
{
argumentList = _syntaxFactory.ArgumentList(
this.EatToken(SyntaxKind.OpenParenToken, ErrorCode.ERR_BadNewExpr, reportError: type?.ContainsDiagnostics == false),
default(SeparatedSyntaxList<ArgumentSyntax>),
SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken));
}
return type is null
? _syntaxFactory.ImplicitObjectCreationExpression(@new, argumentList, initializer)
: _syntaxFactory.ObjectCreationExpression(@new, type, argumentList, initializer);
}
private bool IsImplicitObjectCreation()
{
// The caller is expected to have consumed the new keyword.
if (this.CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
return false;
}
using var _1 = this.GetDisposableResetPoint(resetOnDispose: true);
this.EatToken(); // open paren
ScanTypeFlags scanTypeFlags = ScanTupleType(out _);
if (scanTypeFlags != ScanTypeFlags.NotType)
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.QuestionToken: // e.g. `new(a, b)?()`
case SyntaxKind.OpenBracketToken: // e.g. `new(a, b)[]`
case SyntaxKind.OpenParenToken: // e.g. `new(a, b)()` for better error recovery
return false;
}
}
return true;
}
#nullable enable
private WithExpressionSyntax ParseWithExpression(ExpressionSyntax receiverExpression, SyntaxToken withKeyword)
{
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var list = this.ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsPossibleExpression(),
static @this => @this.ParseExpressionCore(),
SkipBadInitializerListTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.WithExpression(
receiverExpression,
withKeyword,
_syntaxFactory.InitializerExpression(
SyntaxKind.WithInitializerExpression,
openBrace,
list,
this.EatToken(SyntaxKind.CloseBraceToken)));
}
#nullable disable
private InitializerExpressionSyntax ParseObjectOrCollectionInitializer()
{
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var initializers = this.ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsInitializerMember(),
static @this => @this.ParseObjectOrCollectionInitializerMember(),
SkipBadInitializerListTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: true);
var kind = isObjectInitializer(initializers) ? SyntaxKind.ObjectInitializerExpression : SyntaxKind.CollectionInitializerExpression;
return _syntaxFactory.InitializerExpression(
kind,
openBrace,
initializers,
this.EatToken(SyntaxKind.CloseBraceToken));
static bool isObjectInitializer(SeparatedSyntaxList<ExpressionSyntax> initializers)
{
// Empty initializer list must be parsed as an object initializer.
if (initializers.Count == 0)
return true;
// We have at least one initializer expression. If at least one initializer expression is a named
// assignment, this is an object initializer. Otherwise, this is a collection initializer.
for (int i = 0, n = initializers.Count; i < n; i++)
{
if (initializers[i] is AssignmentExpressionSyntax
{
Kind: SyntaxKind.SimpleAssignmentExpression,
Left.Kind: SyntaxKind.IdentifierName or SyntaxKind.ImplicitElementAccess,
})
{
return true;
}
}
return false;
}
}
private ExpressionSyntax ParseObjectOrCollectionInitializerMember()
{
if (this.IsComplexElementInitializer())
{
// { ... }
return this.ParseComplexElementInitializer();
}
else if (IsDictionaryInitializer())
{
// [...] = { ... }
// [...] = ref <expr>
// [...] = <expr>
return this.ParseDictionaryInitializer();
}
else if (this.IsNamedAssignment())
{
// Name = { ... }
// Name = ref <expr>
// Name = <expr>
return this.ParseObjectInitializerNamedAssignment();
}
else
{
// <expr>
// ref <expr>
return this.ParsePossibleRefExpression();
}
}
private static PostSkipAction SkipBadInitializerListTokens<T>(
LanguageParser @this, ref SyntaxToken startToken, SeparatedSyntaxListBuilder<T> list, SyntaxKind expectedKind, SyntaxKind closeKind)
where T : CSharpSyntaxNode
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref startToken, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleExpression(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
private AssignmentExpressionSyntax ParseObjectInitializerNamedAssignment()
{
return _syntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
this.ParseIdentifierName(),
this.EatToken(SyntaxKind.EqualsToken),
this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
? this.ParseObjectOrCollectionInitializer()
: this.ParsePossibleRefExpression());
}
private AssignmentExpressionSyntax ParseDictionaryInitializer()
{
return _syntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_syntaxFactory.ImplicitElementAccess(this.ParseBracketedArgumentList()),
this.EatToken(SyntaxKind.EqualsToken),
this.CurrentToken.Kind == SyntaxKind.OpenBraceToken
? this.ParseObjectOrCollectionInitializer()
: this.ParsePossibleRefExpression());
}
private InitializerExpressionSyntax ParseComplexElementInitializer()
{
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var initializers = this.ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsPossibleExpression(),
static @this => @this.ParseExpressionCore(),
SkipBadInitializerListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.InitializerExpression(
SyntaxKind.ComplexElementInitializerExpression,
openBrace,
initializers,
this.EatToken(SyntaxKind.CloseBraceToken));
}
private bool IsImplicitlyTypedArray()
{
Debug.Assert(this.CurrentToken.Kind is SyntaxKind.NewKeyword or SyntaxKind.StackAllocKeyword);
return this.PeekToken(1).Kind == SyntaxKind.OpenBracketToken;
}
private ImplicitArrayCreationExpressionSyntax ParseImplicitlyTypedArrayCreation()
{
var @new = this.EatToken(SyntaxKind.NewKeyword);
var openBracket = this.EatToken(SyntaxKind.OpenBracketToken);
var commas = _pool.Allocate();
int lastTokenPosition = -1;
while (IsMakingProgress(ref lastTokenPosition))
{
if (this.IsPossibleExpression())
{
var size = this.AddError(this.ParseExpressionCore(), ErrorCode.ERR_InvalidArray);
if (commas.Count == 0)
{
openBracket = AddTrailingSkippedSyntax(openBracket, size);
}
else
{
AddTrailingSkippedSyntax(commas, size);
}
}
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
commas.Add(this.EatToken());
continue;
}
break;
}
return _syntaxFactory.ImplicitArrayCreationExpression(
@new,
openBracket,
_pool.ToTokenListAndFree(commas),
this.EatToken(SyntaxKind.CloseBracketToken),
this.ParseArrayInitializer());
}
private InitializerExpressionSyntax ParseArrayInitializer()
{
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
var list = this.ParseCommaSeparatedSyntaxList(
ref openBrace,
SyntaxKind.CloseBraceToken,
static @this => @this.IsPossibleVariableInitializer(),
static @this => @this.ParseVariableInitializer(),
skipBadArrayInitializerTokens,
allowTrailingSeparator: true,
requireOneElement: false,
allowSemicolonAsSeparator: false);
return _syntaxFactory.InitializerExpression(
SyntaxKind.ArrayInitializerExpression,
openBrace,
list,
this.EatToken(SyntaxKind.CloseBraceToken));
static PostSkipAction skipBadArrayInitializerTokens(
LanguageParser @this, ref SyntaxToken openBrace, SeparatedSyntaxListBuilder<ExpressionSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openBrace, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleVariableInitializer(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private ExpressionSyntax ParseStackAllocExpression()
{
return this.IsImplicitlyTypedArray()
? ParseImplicitlyTypedStackAllocExpression()
: ParseRegularStackAllocExpression();
}
private ExpressionSyntax ParseImplicitlyTypedStackAllocExpression()
{
var @stackalloc = this.EatToken(SyntaxKind.StackAllocKeyword);
var openBracket = this.EatToken(SyntaxKind.OpenBracketToken);
int lastTokenPosition = -1;
while (IsMakingProgress(ref lastTokenPosition))
{
if (this.IsPossibleExpression())
{
var size = this.AddError(this.ParseExpressionCore(), ErrorCode.ERR_InvalidStackAllocArray);
openBracket = AddTrailingSkippedSyntax(openBracket, size);
}
if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
var comma = this.AddError(this.EatToken(), ErrorCode.ERR_InvalidStackAllocArray);
openBracket = AddTrailingSkippedSyntax(openBracket, comma);
continue;
}
break;
}
return _syntaxFactory.ImplicitStackAllocArrayCreationExpression(
@stackalloc,
openBracket,
this.EatToken(SyntaxKind.CloseBracketToken),
this.ParseArrayInitializer());
}
private ExpressionSyntax ParseRegularStackAllocExpression()
{
return _syntaxFactory.StackAllocArrayCreationExpression(
this.EatToken(SyntaxKind.StackAllocKeyword),
this.ParseType(),
this.CurrentToken.Kind == SyntaxKind.OpenBraceToken ? this.ParseArrayInitializer() : null);
}
private AnonymousMethodExpressionSyntax ParseAnonymousMethodExpression()
{
var parentScopeIsInAsync = this.IsInAsync;
var parentScopeForceConditionalAccess = this.ForceConditionalAccessExpression;
this.ForceConditionalAccessExpression = false;
var result = parseAnonymousMethodExpressionWorker();
this.ForceConditionalAccessExpression = parentScopeForceConditionalAccess;
this.IsInAsync = parentScopeIsInAsync;
return result;
AnonymousMethodExpressionSyntax parseAnonymousMethodExpressionWorker()
{
var modifiers = ParseAnonymousFunctionModifiers();
if (modifiers.Any((int)SyntaxKind.AsyncKeyword))
{
this.IsInAsync = true;
}
var @delegate = this.EatToken(SyntaxKind.DelegateKeyword);
ParameterListSyntax parameterList = null;
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
parameterList = this.ParseParenthesizedParameterList();
}
// In mismatched braces cases (missing a }) it is possible for delegate declarations to be
// parsed as delegate statement expressions. When this situation occurs all subsequent
// delegate declarations will also be parsed as delegate statement expressions. In a file with
// a sufficient number of delegates, common in generated code, it will put considerable
// stack pressure on the parser.
//
// To help avoid this problem we don't recursively descend into a delegate expression unless
// { } are actually present. This keeps the stack pressure lower in bad code scenarios.
if (this.CurrentToken.Kind != SyntaxKind.OpenBraceToken)
{
// There's a special error code for a missing token after an accessor keyword
var openBrace = this.EatToken(SyntaxKind.OpenBraceToken);
return _syntaxFactory.AnonymousMethodExpression(
modifiers,
@delegate,
parameterList,
_syntaxFactory.Block(
attributeLists: default,
openBrace,
statements: default,
SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken)),
expressionBody: null);
}
return _syntaxFactory.AnonymousMethodExpression(
modifiers,
@delegate,
parameterList,
this.ParseBlock(attributes: default),
expressionBody: null);
}
}
private SyntaxList<SyntaxToken> ParseAnonymousFunctionModifiers()
{
var modifiers = _pool.Allocate();
while (true)
{
if (this.CurrentToken.Kind == SyntaxKind.StaticKeyword)
{
modifiers.Add(this.EatToken(SyntaxKind.StaticKeyword));
continue;
}
if (this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword &&
IsAnonymousFunctionAsyncModifier())
{
modifiers.Add(this.EatContextualToken(SyntaxKind.AsyncKeyword));
continue;
}
break;
}
return _pool.ToTokenListAndFree(modifiers);
}
private bool IsAnonymousFunctionAsyncModifier()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.AsyncKeyword);
switch (this.PeekToken(1).Kind)
{
case SyntaxKind.OpenParenToken:
case SyntaxKind.IdentifierToken:
case SyntaxKind.StaticKeyword:
case SyntaxKind.RefKeyword:
case SyntaxKind.DelegateKeyword:
return true;
case var kind:
return IsPredefinedType(kind);
};
}
/// <summary>
/// Parse expected lambda expression but assume `x ? () => y :` is a conditional
/// expression rather than a lambda expression with an explicit return type and
/// return null in that case only.
/// </summary>
private LambdaExpressionSyntax TryParseLambdaExpression()
{
using var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false);
var result = ParseLambdaExpression();
if (this.CurrentToken.Kind == SyntaxKind.ColonToken &&
result is ParenthesizedLambdaExpressionSyntax { ReturnType: NullableTypeSyntax })
{
resetPoint.Reset();
return null;
}
return result;
}
private LambdaExpressionSyntax ParseLambdaExpression()
{
var attributes = ParseAttributeDeclarations(inExpressionContext: true);
var parentScopeIsInAsync = this.IsInAsync;
var parentScopeForceConditionalAccess = this.ForceConditionalAccessExpression;
this.ForceConditionalAccessExpression = false;
var result = parseLambdaExpressionWorker();
this.ForceConditionalAccessExpression = parentScopeForceConditionalAccess;
this.IsInAsync = parentScopeIsInAsync;
return result;
LambdaExpressionSyntax parseLambdaExpressionWorker()
{
var modifiers = ParseAnonymousFunctionModifiers();
if (modifiers.Any((int)SyntaxKind.AsyncKeyword))
{
this.IsInAsync = true;
}
TypeSyntax returnType;
using (var resetPoint = this.GetDisposableResetPoint(resetOnDispose: false))
{
returnType = ParseReturnType();
if (CurrentToken.Kind != SyntaxKind.OpenParenToken)
{
resetPoint.Reset();
returnType = null;
}
}
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
var paramList = this.ParseLambdaParameterList();
var arrow = this.EatToken(SyntaxKind.EqualsGreaterThanToken);
var (block, expression) = ParseLambdaBody();
return _syntaxFactory.ParenthesizedLambdaExpression(
attributes, modifiers, returnType, paramList, arrow, block, expression);
}
else
{
// Unparenthesized lambda case
// x => ...
var identifier = (this.CurrentToken.Kind != SyntaxKind.IdentifierToken && this.PeekToken(1).Kind == SyntaxKind.EqualsGreaterThanToken)
? this.EatTokenAsKind(SyntaxKind.IdentifierToken)
: this.ParseIdentifierToken();
// Case x=>, x =>
var arrow = this.EatToken(SyntaxKind.EqualsGreaterThanToken);
var parameter = _syntaxFactory.Parameter(
attributeLists: default, modifiers: default, type: null, identifier, @default: null);
var (block, expression) = ParseLambdaBody();
return _syntaxFactory.SimpleLambdaExpression(
attributes, modifiers, parameter, arrow, block, expression);
}
}
}
private (BlockSyntax, ExpressionSyntax) ParseLambdaBody()
=> CurrentToken.Kind == SyntaxKind.OpenBraceToken
? (ParseBlock(attributes: default), null)
: (null, ParsePossibleRefExpression());
private ParameterListSyntax ParseLambdaParameterList()
{
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var saveTerm = _termState;
_termState |= TerminatorState.IsEndOfParameterList;
var nodes = ParseCommaSeparatedSyntaxList(
ref openParen,
SyntaxKind.CloseParenToken,
static @this => @this.IsPossibleLambdaParameter(),
static @this => @this.ParseLambdaParameter(),
skipBadLambdaParameterListTokens,
allowTrailingSeparator: false,
requireOneElement: false,
allowSemicolonAsSeparator: false);
_termState = saveTerm;
return _syntaxFactory.ParameterList(
openParen,
nodes,
this.EatToken(SyntaxKind.CloseParenToken));
static PostSkipAction skipBadLambdaParameterListTokens(
LanguageParser @this, ref SyntaxToken openParen, SeparatedSyntaxListBuilder<ParameterSyntax> list, SyntaxKind expectedKind, SyntaxKind closeKind)
{
return @this.SkipBadSeparatedListTokensWithExpectedKind(ref openParen, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleLambdaParameter(),
static (p, closeKind) => p.CurrentToken.Kind == closeKind,
expectedKind, closeKind);
}
}
private bool IsPossibleLambdaParameter()
{
switch (this.CurrentToken.Kind)
{
case SyntaxKind.ParamsKeyword:
case SyntaxKind.ReadOnlyKeyword:
case SyntaxKind.RefKeyword:
case SyntaxKind.OutKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.OpenParenToken: // tuple
case SyntaxKind.OpenBracketToken: // attribute
return true;
case SyntaxKind.IdentifierToken:
return this.IsTrueIdentifier();
case SyntaxKind.DelegateKeyword:
return this.IsFunctionPointerStart();
default:
return IsPredefinedType(this.CurrentToken.Kind);
}
}
private ParameterSyntax ParseLambdaParameter()
{
var attributes = ParseAttributeDeclarations(inExpressionContext: false);
// Params are actually illegal in a lambda, but we'll allow it for error recovery purposes and
// give the "params unexpected" error at semantic analysis time.
SyntaxListBuilder modifiers = _pool.Allocate();
if (IsParameterModifierExcludingScoped(this.CurrentToken) || this.CurrentToken.ContextualKind == SyntaxKind.ScopedKeyword)
{
ParseParameterModifiers(modifiers, isFunctionPointerParameter: false);
}
// If we have "scoped/ref/out/in/params" always try to parse out a type.
var paramType = modifiers.Count != 0 || ShouldParseLambdaParameterType()
? ParseType(ParseTypeMode.Parameter)
: null;
var identifier = this.ParseIdentifierToken();
// Parse default value if any
var equalsToken = TryEatToken(SyntaxKind.EqualsToken);
return _syntaxFactory.Parameter(
attributes,
_pool.ToTokenListAndFree(modifiers),
paramType,
identifier,
equalsToken != null
? _syntaxFactory.EqualsValueClause(equalsToken, this.ParseExpressionCore())
: null);
}
private bool ShouldParseLambdaParameterType()
{
// If we have "int/string/etc." always parse out a type.
if (IsPredefinedType(this.CurrentToken.Kind))
{
return true;
}
// if we have a tuple type in a lambda.
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
{
return true;
}
if (this.IsFunctionPointerStart())
{
return true;
}
if (this.IsTrueIdentifier(this.CurrentToken))
{
// Don't parse out a type if we see:
//
// (a,
// (a)
// (a =>
// (a {
// (a =
//
// In all other cases, parse out a type.
var peek1 = this.PeekToken(1);
if (peek1.Kind != SyntaxKind.CommaToken &&
peek1.Kind != SyntaxKind.CloseParenToken &&
peek1.Kind != SyntaxKind.EqualsGreaterThanToken &&
peek1.Kind != SyntaxKind.OpenBraceToken &&
peek1.Kind != SyntaxKind.EqualsToken)
{
return true;
}
}
return false;
}
private bool IsCurrentTokenQueryContextualKeyword
=> IsTokenQueryContextualKeyword(this.CurrentToken);
private static bool IsTokenQueryContextualKeyword(SyntaxToken token)
{
if (IsTokenStartOfNewQueryClause(token))
{
return true;
}
switch (token.ContextualKind)
{
case SyntaxKind.OnKeyword:
case SyntaxKind.EqualsKeyword:
case SyntaxKind.AscendingKeyword:
case SyntaxKind.DescendingKeyword:
case SyntaxKind.ByKeyword:
return true;
}
return false;
}
private static bool IsTokenStartOfNewQueryClause(SyntaxToken token)
{
switch (token.ContextualKind)
{
case SyntaxKind.FromKeyword:
case SyntaxKind.JoinKeyword:
case SyntaxKind.IntoKeyword:
case SyntaxKind.WhereKeyword:
case SyntaxKind.OrderByKeyword:
case SyntaxKind.GroupKeyword:
case SyntaxKind.SelectKeyword:
case SyntaxKind.LetKeyword:
return true;
default:
return false;
}
}
private bool IsQueryExpression(bool mayBeVariableDeclaration, bool mayBeMemberDeclaration)
{
return this.CurrentToken.ContextualKind == SyntaxKind.FromKeyword &&
this.IsQueryExpressionAfterFrom(mayBeVariableDeclaration, mayBeMemberDeclaration);
}
// from_clause ::= from <type>? <identifier> in expression
private bool IsQueryExpressionAfterFrom(bool mayBeVariableDeclaration, bool mayBeMemberDeclaration)
{
// from x ...
var pk1 = this.PeekToken(1).Kind;
if (IsPredefinedType(pk1))
{
return true;
}
if (pk1 == SyntaxKind.IdentifierToken)
{
var pk2 = this.PeekToken(2).Kind;
if (pk2 == SyntaxKind.InKeyword)
{
return true;
}
if (mayBeVariableDeclaration)
{
if (pk2 is SyntaxKind.SemicolonToken or // from x;
SyntaxKind.CommaToken or // from x, y;
SyntaxKind.EqualsToken) // from x = null;
{
return false;
}
}
if (mayBeMemberDeclaration)
{
// from idf { ... property decl
// from idf(... method decl
if (pk2 is SyntaxKind.OpenParenToken or SyntaxKind.OpenBraceToken)
{
return false;
}
// otherwise we need to scan a type
}
else
{
return true;
}
}
// from T x ...
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
this.EatToken();
return this.ScanType() != ScanTypeFlags.NotType && this.CurrentToken.Kind is SyntaxKind.IdentifierToken or SyntaxKind.InKeyword;
}
private QueryExpressionSyntax ParseQueryExpression(Precedence precedence)
{
var previousIsInQuery = this.IsInQuery;
this.IsInQuery = true;
var fc = this.ParseFromClause();
if (precedence > Precedence.Assignment)
{
fc = this.AddError(fc, ErrorCode.WRN_PrecedenceInversion, SyntaxFacts.GetText(SyntaxKind.FromKeyword));
}
var body = this.ParseQueryBody();
this.IsInQuery = previousIsInQuery;
return _syntaxFactory.QueryExpression(fc, body);
}
private QueryBodySyntax ParseQueryBody()
{
var clauses = _pool.Allocate<QueryClauseSyntax>();
// from, join, let, where and orderby
while (true)
{
switch (this.CurrentToken.ContextualKind)
{
case SyntaxKind.FromKeyword:
var fc = this.ParseFromClause();
clauses.Add(fc);
continue;
case SyntaxKind.JoinKeyword:
clauses.Add(this.ParseJoinClause());
continue;
case SyntaxKind.LetKeyword:
clauses.Add(this.ParseLetClause());
continue;
case SyntaxKind.WhereKeyword:
clauses.Add(this.ParseWhereClause());
continue;
case SyntaxKind.OrderByKeyword:
clauses.Add(this.ParseOrderByClause());
continue;
}
break;
}
// select or group clause
SelectOrGroupClauseSyntax selectOrGroupBy = this.CurrentToken.ContextualKind switch
{
SyntaxKind.SelectKeyword => this.ParseSelectClause(),
SyntaxKind.GroupKeyword => this.ParseGroupClause(),
_ => _syntaxFactory.SelectClause(
this.EatToken(SyntaxKind.SelectKeyword, ErrorCode.ERR_ExpectedSelectOrGroup),
this.CreateMissingIdentifierName()),
};
return _syntaxFactory.QueryBody(
_pool.ToListAndFree(clauses),
selectOrGroupBy,
this.CurrentToken.ContextualKind == SyntaxKind.IntoKeyword
? this.ParseQueryContinuation()
: null);
}
private FromClauseSyntax ParseFromClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.FromKeyword);
var @from = this.EatContextualToken(SyntaxKind.FromKeyword);
var type = this.PeekToken(1).Kind != SyntaxKind.InKeyword
? this.ParseType()
: null;
SyntaxToken name;
if (this.PeekToken(1).ContextualKind == SyntaxKind.InKeyword &&
(this.CurrentToken.Kind != SyntaxKind.IdentifierToken || SyntaxFacts.IsQueryContextualKeyword(this.CurrentToken.ContextualKind)))
{
//if this token is a something other than an identifier (someone accidentally used a contextual
//keyword or a literal, for example), but we can see that the "in" is in the right place, then
//just replace whatever is here with a missing identifier
name = this.EatToken();
name = WithAdditionalDiagnostics(name, this.GetExpectedTokenError(SyntaxKind.IdentifierToken, name.ContextualKind, name.GetLeadingTriviaWidth(), name.Width));
name = this.ConvertToMissingWithTrailingTrivia(name, SyntaxKind.IdentifierToken);
}
else
{
name = this.ParseIdentifierToken();
}
return _syntaxFactory.FromClause(
@from,
type,
name,
this.EatToken(SyntaxKind.InKeyword),
this.ParseExpressionCore());
}
private JoinClauseSyntax ParseJoinClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.JoinKeyword);
return _syntaxFactory.JoinClause(
joinKeyword: this.EatContextualToken(SyntaxKind.JoinKeyword),
type: this.PeekToken(1).Kind != SyntaxKind.InKeyword
? this.ParseType()
: null,
identifier: this.ParseIdentifierToken(),
inKeyword: this.EatToken(SyntaxKind.InKeyword),
inExpression: this.ParseExpressionCore(),
onKeyword: this.EatContextualToken(SyntaxKind.OnKeyword, ErrorCode.ERR_ExpectedContextualKeywordOn),
leftExpression: this.ParseExpressionCore(),
equalsKeyword: this.EatContextualToken(SyntaxKind.EqualsKeyword, ErrorCode.ERR_ExpectedContextualKeywordEquals),
rightExpression: this.ParseExpressionCore(),
into: this.CurrentToken.ContextualKind == SyntaxKind.IntoKeyword
? _syntaxFactory.JoinIntoClause(ConvertToKeyword(this.EatToken()), this.ParseIdentifierToken())
: null);
}
private LetClauseSyntax ParseLetClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.LetKeyword);
return _syntaxFactory.LetClause(
this.EatContextualToken(SyntaxKind.LetKeyword),
this.ParseIdentifierToken(),
this.EatToken(SyntaxKind.EqualsToken),
this.ParseExpressionCore());
}
private WhereClauseSyntax ParseWhereClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword);
return _syntaxFactory.WhereClause(
this.EatContextualToken(SyntaxKind.WhereKeyword),
this.ParseExpressionCore());
}
private OrderByClauseSyntax ParseOrderByClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.OrderByKeyword);
var @orderby = this.EatContextualToken(SyntaxKind.OrderByKeyword);
var list = _pool.AllocateSeparated<OrderingSyntax>();
// first argument
list.Add(this.ParseOrdering());
// additional arguments
while (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
if (this.CurrentToken.Kind is SyntaxKind.CloseParenToken or SyntaxKind.SemicolonToken)
{
break;
}
else if (this.CurrentToken.Kind == SyntaxKind.CommaToken)
{
list.AddSeparator(this.EatToken(SyntaxKind.CommaToken));
list.Add(this.ParseOrdering());
continue;
}
else if (skipBadOrderingListTokens(list, SyntaxKind.CommaToken) == PostSkipAction.Abort)
{
break;
}
}
return _syntaxFactory.OrderByClause(
@orderby,
_pool.ToListAndFree(list));
PostSkipAction skipBadOrderingListTokens(SeparatedSyntaxListBuilder<OrderingSyntax> list, SyntaxKind expected)
{
CSharpSyntaxNode tmp = null;
Debug.Assert(list.Count > 0);
return this.SkipBadSeparatedListTokensWithExpectedKind(ref tmp, list,
static p => p.CurrentToken.Kind != SyntaxKind.CommaToken,
static (p, _) => p.CurrentToken.Kind == SyntaxKind.CloseParenToken
|| p.CurrentToken.Kind == SyntaxKind.SemicolonToken
|| p.IsCurrentTokenQueryContextualKeyword,
expected);
}
}
private OrderingSyntax ParseOrdering()
{
var expression = this.ParseExpressionCore();
SyntaxToken direction = null;
SyntaxKind kind = SyntaxKind.AscendingOrdering;
if (this.CurrentToken.ContextualKind is SyntaxKind.AscendingKeyword or SyntaxKind.DescendingKeyword)
{
direction = ConvertToKeyword(this.EatToken());
if (direction.Kind == SyntaxKind.DescendingKeyword)
{
kind = SyntaxKind.DescendingOrdering;
}
}
return _syntaxFactory.Ordering(kind, expression, direction);
}
private SelectClauseSyntax ParseSelectClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.SelectKeyword);
return _syntaxFactory.SelectClause(
this.EatContextualToken(SyntaxKind.SelectKeyword),
this.ParseExpressionCore());
}
private GroupClauseSyntax ParseGroupClause()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.GroupKeyword);
return _syntaxFactory.GroupClause(
this.EatContextualToken(SyntaxKind.GroupKeyword),
this.ParseExpressionCore(),
this.EatContextualToken(SyntaxKind.ByKeyword, ErrorCode.ERR_ExpectedContextualKeywordBy),
this.ParseExpressionCore());
}
private QueryContinuationSyntax ParseQueryContinuation()
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.IntoKeyword);
return _syntaxFactory.QueryContinuation(
this.EatContextualToken(SyntaxKind.IntoKeyword),
this.ParseIdentifierToken(),
this.ParseQueryBody());
}
[Obsolete("Use IsIncrementalAndFactoryContextMatches")]
#pragma warning disable IDE0051 // Remove unused private members
private new bool IsIncremental
#pragma warning restore IDE0051 // Remove unused private members
{
get { throw new Exception("Use IsIncrementalAndFactoryContextMatches"); }
}
private bool IsIncrementalAndFactoryContextMatches
{
get
{
if (!base.IsIncremental)
{
return false;
}
CSharp.CSharpSyntaxNode current = this.CurrentNode;
return current != null && MatchesFactoryContext(current.Green, _syntaxFactoryContext);
}
}
internal static bool MatchesFactoryContext(GreenNode green, SyntaxFactoryContext context)
{
return context.IsInAsync == green.ParsedInAsync &&
context.IsInQuery == green.ParsedInQuery &&
context.IsInFieldKeywordContext == green.ParsedInFieldKeywordContext;
}
private bool IsInAsync
{
get => _syntaxFactoryContext.IsInAsync;
set => _syntaxFactoryContext.IsInAsync = value;
}
private bool ForceConditionalAccessExpression
{
get => _syntaxFactoryContext.ForceConditionalAccessExpression;
set => _syntaxFactoryContext.ForceConditionalAccessExpression = value;
}
private bool IsInQuery
{
get => _syntaxFactoryContext.IsInQuery;
set => _syntaxFactoryContext.IsInQuery = value;
}
private bool IsInFieldKeywordContext
{
get => _syntaxFactoryContext.IsInFieldKeywordContext;
set => _syntaxFactoryContext.IsInFieldKeywordContext = value;
}
private delegate PostSkipAction SkipBadTokens<TNode>(
LanguageParser parser, ref SyntaxToken openToken, SeparatedSyntaxListBuilder<TNode> builder, SyntaxKind expectedKind, SyntaxKind closeTokenKind) where TNode : GreenNode;
#nullable enable
/// <summary>
/// Parses a comma separated list of nodes.
/// </summary>
/// <typeparam name="TNode">The type of node to return back in the <see cref="SeparatedSyntaxList{TNode}"/>.</typeparam>
/// <param name="openToken">The token preceding the separated elements. Used to attach skipped tokens to if no
/// elements have been parsed out yet, and the error recovery algorithm chooses to continue parsing, versus
/// aborting the list parsing.</param>
/// <param name="closeTokenKind">The token kind to look for that indicates the list is complete</param>
/// <param name="isPossibleElement">Callback to indicate if the parser is at a point in the source that could
/// parse out a <typeparamref name="TNode"/>.</param>
/// <param name="parseElement">Callback to actually parse out an element. May be called even at a location
/// where <paramref name="isPossibleElement"/> returned <see langword="false"/> for.</param>
/// <param name="skipBadTokens">Error recovery callback. Used to determine if the list parsing routine should
/// skip tokens (attaching them to the last thing successfully parsed), and continue looking for more elements.
/// Or if it should abort parsing the list entirely.</param>
/// <param name="allowTrailingSeparator">Whether or not a trailing comma is allowed at the end of the list. For
/// example, an array initializer allows for a trailing comma at the end of it, while a parameter list does
/// not.</param>
/// <param name="requireOneElement">Whether or not at least one element is required in the list. For example, a
/// parameter list does not require any elements, while an attribute list "<c>[...]</c>" does.</param>
/// <param name="allowSemicolonAsSeparator">Whether or not an errant semicolon found in a location where a comma
/// is expected should just be treated as a comma (still with an error reported). Useful for constructs where users
/// often forget which separator is needed and use the wrong one.</param>
/// <remarks>
/// All the callbacks should passed as static lambdas or static methods to prevent unnecessary delegate
/// allocations.
/// </remarks>
private SeparatedSyntaxList<TNode> ParseCommaSeparatedSyntaxList<TNode>(
ref SyntaxToken openToken,
SyntaxKind closeTokenKind,
Func<LanguageParser, bool> isPossibleElement,
Func<LanguageParser, TNode> parseElement,
SkipBadTokens<TNode> skipBadTokens,
bool allowTrailingSeparator,
bool requireOneElement,
bool allowSemicolonAsSeparator) where TNode : GreenNode
{
return ParseCommaSeparatedSyntaxList(
ref openToken,
closeTokenKind,
isPossibleElement,
parseElement,
immediatelyAbort: null,
skipBadTokens,
allowTrailingSeparator,
requireOneElement,
allowSemicolonAsSeparator);
}
private SeparatedSyntaxList<TNode> ParseCommaSeparatedSyntaxList<TNode>(
ref SyntaxToken openToken,
SyntaxKind closeTokenKind,
Func<LanguageParser, bool> isPossibleElement,
Func<LanguageParser, TNode> parseElement,
Func<TNode, bool>? immediatelyAbort,
SkipBadTokens<TNode> skipBadTokens,
bool allowTrailingSeparator,
bool requireOneElement,
bool allowSemicolonAsSeparator) where TNode : GreenNode
{
// If we ever want this function to parse out separated lists with a different separator, we can
// parameterize this method on this value.
var separatorTokenKind = SyntaxKind.CommaToken;
var nodes = _pool.AllocateSeparated<TNode>();
tryAgain:
if (requireOneElement || this.CurrentToken.Kind != closeTokenKind)
{
if (requireOneElement || shouldParseSeparatorOrElement())
{
// first argument
var node = parseElement(this);
nodes.Add(node);
// now that we've gotten one element, we don't require any more.
requireOneElement = false;
// Ensure that if parsing separators/elements doesn't move us forward, that we always bail out from
// parsing this list.
int lastTokenPosition = -1;
while (immediatelyAbort?.Invoke(node) != true && IsMakingProgress(ref lastTokenPosition))
{
if (this.CurrentToken.Kind == closeTokenKind)
break;
if (shouldParseSeparatorOrElement())
{
// If we got a semicolon instead of comma, consume it with error and act as if it were a comma.
nodes.AddSeparator(this.CurrentToken.Kind == SyntaxKind.SemicolonToken
? this.EatTokenWithPrejudice(separatorTokenKind)
: this.EatToken(separatorTokenKind));
if (allowTrailingSeparator)
{
// check for exit case after legal trailing comma
if (this.CurrentToken.Kind == closeTokenKind)
{
break;
}
else if (!isPossibleElement(this))
{
goto tryAgain;
}
}
node = parseElement(this);
nodes.Add(node);
continue;
}
// Something we didn't recognize, try to skip tokens, reporting that we expected a separator here.
if (skipBadTokens(this, ref openToken, nodes, separatorTokenKind, closeTokenKind) == PostSkipAction.Abort)
break;
}
}
else if (skipBadTokens(this, ref openToken, nodes, SyntaxKind.IdentifierToken, closeTokenKind) == PostSkipAction.Continue)
{
// Something we didn't recognize, try to skip tokens, reporting that we expected an identifier here.
// While 'identifier' may not be completely accurate in terms of what the list needs, it's a
// generally good 'catch all' indicating that some name/expr was needed, where something else
// invalid was found.
goto tryAgain;
}
}
return _pool.ToListAndFree(nodes);
bool shouldParseSeparatorOrElement()
{
// if we're on a separator, we def should parse it out as such.
if (this.CurrentToken.Kind == separatorTokenKind)
return true;
// We're not on a valid separator, but we want to be resilient for the user accidentally using the wrong
// one in common cases.
if (allowSemicolonAsSeparator && this.CurrentToken.Kind is SyntaxKind.SemicolonToken)
return true;
if (isPossibleElement(this))
return true;
return false;
}
}
#nullable disable
private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose)
=> new DisposableResetPoint(this, resetOnDispose, GetResetPoint());
private new ResetPoint GetResetPoint()
{
return new ResetPoint(
base.GetResetPoint(),
_termState,
IsInAsync,
IsInQuery,
IsInFieldKeywordContext);
}
private void Reset(ref ResetPoint state)
{
_termState = state.TerminatorState;
IsInAsync = state.IsInAsync;
IsInQuery = state.IsInQuery;
IsInFieldKeywordContext = state.IsInFieldKeywordContext;
base.Reset(ref state.BaseResetPoint);
}
private void Release(ref ResetPoint state)
{
base.Release(ref state.BaseResetPoint);
}
private ref struct DisposableResetPoint
{
private readonly LanguageParser _languageParser;
private readonly bool _resetOnDispose;
private ResetPoint _resetPoint;
public DisposableResetPoint(LanguageParser languageParser, bool resetOnDispose, ResetPoint resetPoint)
{
_languageParser = languageParser;
_resetOnDispose = resetOnDispose;
_resetPoint = resetPoint;
}
public void Reset()
=> _languageParser.Reset(ref _resetPoint);
public void Dispose()
{
if (_resetOnDispose)
this.Reset();
_languageParser.Release(ref _resetPoint);
}
}
private new struct ResetPoint
{
internal SyntaxParser.ResetPoint BaseResetPoint;
internal readonly TerminatorState TerminatorState;
internal readonly bool IsInAsync;
internal readonly bool IsInQuery;
internal readonly bool IsInFieldKeywordContext;
internal ResetPoint(
SyntaxParser.ResetPoint resetPoint,
TerminatorState terminatorState,
bool isInAsync,
bool isInQuery,
bool isInFieldKeywordContext)
{
this.BaseResetPoint = resetPoint;
this.TerminatorState = terminatorState;
this.IsInAsync = isInAsync;
this.IsInQuery = isInQuery;
this.IsInFieldKeywordContext = isInFieldKeywordContext;
}
}
internal TNode ConsumeUnexpectedTokens<TNode>(TNode node) where TNode : CSharpSyntaxNode
{
if (this.CurrentToken.Kind == SyntaxKind.EndOfFileToken) return node;
SyntaxListBuilder<SyntaxToken> b = _pool.Allocate<SyntaxToken>();
while (this.CurrentToken.Kind != SyntaxKind.EndOfFileToken)
{
b.Add(this.EatToken());
}
var trailingTrash = b.ToList();
_pool.Free(b);
node = this.AddError(node, ErrorCode.ERR_UnexpectedToken, trailingTrash[0].ToString());
node = this.AddTrailingSkippedSyntax(node, trailingTrash.Node);
return node;
}
private static bool ContainsErrorDiagnostic(GreenNode node)
{
// ContainsDiagnostics returns true if this node (or any descendants) contain any sort of error. However,
// GetDiagnostics() only returns diagnostics at that node itself. So we have to explicitly walk down the
// tree to find out if the diagnostics are error or not.
// Quick check to avoid any unnecessary work.
if (node.ContainsDiagnostics)
{
var stack = ArrayBuilder<GreenNode>.GetInstance();
try
{
stack.Push(node);
while (stack.Count > 0)
{
var current = stack.Pop();
if (!current.ContainsDiagnostics)
continue;
foreach (var diagnostic in current.GetDiagnostics())
{
if (diagnostic.Severity == DiagnosticSeverity.Error)
return true;
}
foreach (var child in current.ChildNodesAndTokens())
stack.Push(child);
}
}
finally
{
stack.Free();
}
}
return false;
}
}
}
|