Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,7 @@ private AttributeSyntax ParseAttribute()
SyntaxKind.CloseParenToken,
static @this => @this.IsPossibleAttributeArgument(),
static @this => @this.ParseAttributeArgument(),
immediatelyAbort,
skipBadAttributeArgumentTokens,
allowTrailingSeparator: false,
requireOneElement: false,
Expand All @@ -1058,6 +1059,23 @@ static PostSkipAction skipBadAttributeArgumentTokens(
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()
Expand Down Expand Up @@ -13321,6 +13339,8 @@ private bool IsInQuery
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>
Expand Down Expand Up @@ -13357,6 +13377,29 @@ private SeparatedSyntaxList<TNode> ParseCommaSeparatedSyntaxList<TNode>(
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.
Expand All @@ -13369,15 +13412,17 @@ private SeparatedSyntaxList<TNode> ParseCommaSeparatedSyntaxList<TNode>(
if (requireOneElement || shouldParseSeparatorOrElement())
{
// first argument
nodes.Add(parseElement(this));
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 (IsMakingProgress(ref lastTokenPosition))

while (immediatelyAbort?.Invoke(node) != true && IsMakingProgress(ref lastTokenPosition))
{
if (this.CurrentToken.Kind == closeTokenKind)
break;
Expand All @@ -13402,7 +13447,8 @@ private SeparatedSyntaxList<TNode> ParseCommaSeparatedSyntaxList<TNode>(
}
}

nodes.Add(parseElement(this));
node = parseElement(this);
nodes.Add(node);
continue;
}

Expand Down Expand Up @@ -13441,6 +13487,8 @@ bool shouldParseSeparatorOrElement()
}
}

#nullable disable

private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose)
=> new DisposableResetPoint(this, resetOnDispose, GetResetPoint());

Expand Down
Loading