Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
56 changes: 45 additions & 11 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5956,8 +5956,41 @@ private bool IsStartOfTypeParameter()
return false;

// possible attributes
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind != SyntaxKind.CloseBracketToken)
return true;
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. i was actually able to simplify this to just:

            // possible attributes.
            // Variance.
            if (this.CurrentToken.Kind is SyntaxKind.OpenBracketToken or SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
                return true;

and i got no regressions (in the syntax tests at least). i'm going to go with that much simpler approach as i don't really see a reason why we would accept [ <non-close-brace as an attribute, but not [] as well.

{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would also be fine simplifying this so that we always return true when we see [ regardless of what follows.

var nextKind = this.PeekToken(1).Kind;
// [...
// The start of an attribute on a type parameter.
if (nextKind != SyntaxKind.CloseBracketToken)
return true;

// We have `[]`. Recover from a partially written attribute.
//
// <[] ,
// <[] >
// <[] in/out
// <[] { ...
if (nextKind is SyntaxKind.CommaToken
or SyntaxKind.GreaterThanToken
or SyntaxKind.InKeyword
or SyntaxKind.OutKeyword
or SyntaxKind.OpenBraceToken)
{
return true;
}

// <[] where
// <[] partial
//
// Parsing a type parameter will see this and treat the 'where' appropriately if it is the start
// of a where-clause or not. Similarly for 'partial' and if it is a keyword, or just an identifier.
if (this.PeekToken(1).ContextualKind is SyntaxKind.WhereKeyword or SyntaxKind.PartialKeyword)
return true;

using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
this.EatToken(); // eat '['
return IsTrueIdentifier();
}

// Variance.
if (this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
Expand All @@ -5968,23 +6001,24 @@ private bool IsStartOfTypeParameter()

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)
if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken)
{
var saveTerm = _termState;
_termState = TerminatorState.IsEndOfTypeArgumentList;
attrs = this.ParseAttributeDeclarations(inExpressionContext: false);
_termState = saveTerm;
}

if (this.IsCurrentTokenWhereOfConstraintClause() ||
this.IsCurrentTokenPartialKeywordOfPartialMemberOrType())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added for this case.

{
return _syntaxFactory.TypeParameter(
attrs,
varianceKeyword: null,
this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected));
}

return _syntaxFactory.TypeParameter(
attrs,
this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword ? EatToken() : null,
Expand Down
Loading
Loading