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
26 changes: 12 additions & 14 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6028,36 +6028,34 @@ private bool IsStartOfTypeParameter()
if (this.IsCurrentTokenWhereOfConstraintClause())
return false;

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

Choose a reason for hiding this comment

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

this blocks good error recovery in the Foo<[]T> case. There isn't a good reason for this, as [] isn't a suitable start of some other construct. In all other attribute cases, running into [] is supported, just not for type params.

This simply removes that restriction, and gives consistent support for attributes here like everywhere else.

return true;

// possible attributes.
// Variance.
if (this.CurrentToken.Kind is SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
if (this.CurrentToken.Kind is SyntaxKind.OpenBracketToken or 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)
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.

It's not obvious why "partial" is getting special treatment. Should we similarly care about other modifier keywords?

Copy link
Member Author

Choose a reason for hiding this comment

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

partial/where refer to the special identifiers that IsTrueIdentifier returns false for if it sees that these should not actually be treated as identifiers but as contextual keywords.

They're treated specially here as they are very reasonable contextual keywords that would follow a type param being written.

{
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