Skip to content

Commit 13d6ec2

Browse files
Update logic and add tests
1 parent 78d6908 commit 13d6ec2

File tree

2 files changed

+360
-7
lines changed

2 files changed

+360
-7
lines changed

src/Compilers/CSharp/Portable/Parser/LanguageParser.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5967,21 +5967,29 @@ private bool IsStartOfTypeParameter()
59675967
// We have `[]`. Recover from a partially written attribute.
59685968
//
59695969
// <[] ,
5970-
// <[] Id
59715970
// <[] >
59725971
// <[] in/out
5972+
// <[] { ...
59735973
if (nextKind is SyntaxKind.CommaToken
5974-
or SyntaxKind.IdentifierToken
59755974
or SyntaxKind.GreaterThanToken
59765975
or SyntaxKind.InKeyword
5977-
or SyntaxKind.OutKeyword)
5976+
or SyntaxKind.OutKeyword
5977+
or SyntaxKind.OpenBraceToken)
59785978
{
59795979
return true;
59805980
}
59815981

5982-
// Some other use of `[]` that doesn't look like a type parameter. Bail out so normal error recovery
5983-
// can proceed.
5984-
return false;
5982+
// <[] where
5983+
// <[] partial
5984+
//
5985+
// Parsing a type parameter will see this and treat the 'where' appropriately if it is the start
5986+
// of a where-clause or not. Similarly for 'partial' and if it is a keyword, or just an identifier.
5987+
if (this.PeekToken(1).ContextualKind is SyntaxKind.WhereKeyword or SyntaxKind.PartialKeyword)
5988+
return true;
5989+
5990+
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);
5991+
this.EatToken(); // eat '['
5992+
return IsTrueIdentifier();
59855993
}
59865994

59875995
// Variance.
@@ -6002,7 +6010,8 @@ private TypeParameterSyntax ParseTypeParameter()
60026010
_termState = saveTerm;
60036011
}
60046012

6005-
if (this.IsCurrentTokenWhereOfConstraintClause())
6013+
if (this.IsCurrentTokenWhereOfConstraintClause() ||
6014+
this.IsCurrentTokenPartialKeywordOfPartialMemberOrType())
60066015
{
60076016
return _syntaxFactory.TypeParameter(
60086017
attrs,

src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13229,5 +13229,349 @@ class C<[] where T : class { }
1322913229
}
1323013230
EOF();
1323113231
}
13232+
13233+
[Fact]
13234+
public void ParseEmptyTypeParameterAttributeLists7()
13235+
{
13236+
UsingTree("""
13237+
class C<[] where> { }
13238+
""",
13239+
// (1,10): error CS1001: Identifier expected
13240+
// class C<[] where> { }
13241+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "]").WithLocation(1, 10));
13242+
13243+
N(SyntaxKind.CompilationUnit);
13244+
{
13245+
N(SyntaxKind.ClassDeclaration);
13246+
{
13247+
N(SyntaxKind.ClassKeyword);
13248+
N(SyntaxKind.IdentifierToken, "C");
13249+
N(SyntaxKind.TypeParameterList);
13250+
{
13251+
N(SyntaxKind.LessThanToken);
13252+
N(SyntaxKind.TypeParameter);
13253+
{
13254+
N(SyntaxKind.AttributeList);
13255+
{
13256+
N(SyntaxKind.OpenBracketToken);
13257+
M(SyntaxKind.Attribute);
13258+
{
13259+
M(SyntaxKind.IdentifierName);
13260+
{
13261+
M(SyntaxKind.IdentifierToken);
13262+
}
13263+
}
13264+
N(SyntaxKind.CloseBracketToken);
13265+
}
13266+
N(SyntaxKind.IdentifierToken, "where");
13267+
}
13268+
N(SyntaxKind.GreaterThanToken);
13269+
}
13270+
N(SyntaxKind.OpenBraceToken);
13271+
N(SyntaxKind.CloseBraceToken);
13272+
}
13273+
N(SyntaxKind.EndOfFileToken);
13274+
}
13275+
EOF();
13276+
}
13277+
13278+
[Fact]
13279+
public void ParseEmptyTypeParameterAttributeLists8()
13280+
{
13281+
UsingTree("""
13282+
class C<[] { }
13283+
""",
13284+
// (1,10): error CS1001: Identifier expected
13285+
// class C<[] { }
13286+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "]").WithLocation(1, 10),
13287+
// (1,12): error CS1001: Identifier expected
13288+
// class C<[] { }
13289+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 12),
13290+
// (1,12): error CS1003: Syntax error, '>' expected
13291+
// class C<[] { }
13292+
Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(">").WithLocation(1, 12));
13293+
13294+
N(SyntaxKind.CompilationUnit);
13295+
{
13296+
N(SyntaxKind.ClassDeclaration);
13297+
{
13298+
N(SyntaxKind.ClassKeyword);
13299+
N(SyntaxKind.IdentifierToken, "C");
13300+
N(SyntaxKind.TypeParameterList);
13301+
{
13302+
N(SyntaxKind.LessThanToken);
13303+
N(SyntaxKind.TypeParameter);
13304+
{
13305+
N(SyntaxKind.AttributeList);
13306+
{
13307+
N(SyntaxKind.OpenBracketToken);
13308+
M(SyntaxKind.Attribute);
13309+
{
13310+
M(SyntaxKind.IdentifierName);
13311+
{
13312+
M(SyntaxKind.IdentifierToken);
13313+
}
13314+
}
13315+
N(SyntaxKind.CloseBracketToken);
13316+
}
13317+
M(SyntaxKind.IdentifierToken);
13318+
}
13319+
M(SyntaxKind.GreaterThanToken);
13320+
}
13321+
N(SyntaxKind.OpenBraceToken);
13322+
N(SyntaxKind.CloseBraceToken);
13323+
}
13324+
N(SyntaxKind.EndOfFileToken);
13325+
}
13326+
EOF();
13327+
}
13328+
13329+
[Fact]
13330+
public void ParseEmptyTypeParameterAttributeLists9()
13331+
{
13332+
UsingTree("""
13333+
class C<[] partial class D { }
13334+
""",
13335+
// (1,10): error CS1001: Identifier expected
13336+
// class C<[] partial class D { }
13337+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "]").WithLocation(1, 10),
13338+
// (1,12): error CS1001: Identifier expected
13339+
// class C<[] partial class D { }
13340+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "partial").WithLocation(1, 12),
13341+
// (1,12): error CS1003: Syntax error, '>' expected
13342+
// class C<[] partial class D { }
13343+
Diagnostic(ErrorCode.ERR_SyntaxError, "partial").WithArguments(">").WithLocation(1, 12),
13344+
// (1,12): error CS1514: { expected
13345+
// class C<[] partial class D { }
13346+
Diagnostic(ErrorCode.ERR_LbraceExpected, "partial").WithLocation(1, 12),
13347+
// (1,12): error CS1513: } expected
13348+
// class C<[] partial class D { }
13349+
Diagnostic(ErrorCode.ERR_RbraceExpected, "partial").WithLocation(1, 12));
13350+
13351+
N(SyntaxKind.CompilationUnit);
13352+
{
13353+
N(SyntaxKind.ClassDeclaration);
13354+
{
13355+
N(SyntaxKind.ClassKeyword);
13356+
N(SyntaxKind.IdentifierToken, "C");
13357+
N(SyntaxKind.TypeParameterList);
13358+
{
13359+
N(SyntaxKind.LessThanToken);
13360+
N(SyntaxKind.TypeParameter);
13361+
{
13362+
N(SyntaxKind.AttributeList);
13363+
{
13364+
N(SyntaxKind.OpenBracketToken);
13365+
M(SyntaxKind.Attribute);
13366+
{
13367+
M(SyntaxKind.IdentifierName);
13368+
{
13369+
M(SyntaxKind.IdentifierToken);
13370+
}
13371+
}
13372+
N(SyntaxKind.CloseBracketToken);
13373+
}
13374+
M(SyntaxKind.IdentifierToken);
13375+
}
13376+
M(SyntaxKind.GreaterThanToken);
13377+
}
13378+
M(SyntaxKind.OpenBraceToken);
13379+
M(SyntaxKind.CloseBraceToken);
13380+
}
13381+
N(SyntaxKind.ClassDeclaration);
13382+
{
13383+
N(SyntaxKind.PartialKeyword);
13384+
N(SyntaxKind.ClassKeyword);
13385+
N(SyntaxKind.IdentifierToken, "D");
13386+
N(SyntaxKind.OpenBraceToken);
13387+
N(SyntaxKind.CloseBraceToken);
13388+
}
13389+
N(SyntaxKind.EndOfFileToken);
13390+
}
13391+
EOF();
13392+
}
13393+
13394+
[Fact]
13395+
public void ParseEmptyTypeParameterAttributeLists10()
13396+
{
13397+
UsingTree("""
13398+
class C<[] partial { }
13399+
""",
13400+
// (1,10): error CS1001: Identifier expected
13401+
// class C<[] partial { }
13402+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "]").WithLocation(1, 10),
13403+
// (1,20): error CS1003: Syntax error, '>' expected
13404+
// class C<[] partial { }
13405+
Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(">").WithLocation(1, 20));
13406+
13407+
N(SyntaxKind.CompilationUnit);
13408+
{
13409+
N(SyntaxKind.ClassDeclaration);
13410+
{
13411+
N(SyntaxKind.ClassKeyword);
13412+
N(SyntaxKind.IdentifierToken, "C");
13413+
N(SyntaxKind.TypeParameterList);
13414+
{
13415+
N(SyntaxKind.LessThanToken);
13416+
N(SyntaxKind.TypeParameter);
13417+
{
13418+
N(SyntaxKind.AttributeList);
13419+
{
13420+
N(SyntaxKind.OpenBracketToken);
13421+
M(SyntaxKind.Attribute);
13422+
{
13423+
M(SyntaxKind.IdentifierName);
13424+
{
13425+
M(SyntaxKind.IdentifierToken);
13426+
}
13427+
}
13428+
N(SyntaxKind.CloseBracketToken);
13429+
}
13430+
N(SyntaxKind.IdentifierToken, "partial");
13431+
}
13432+
M(SyntaxKind.GreaterThanToken);
13433+
}
13434+
N(SyntaxKind.OpenBraceToken);
13435+
N(SyntaxKind.CloseBraceToken);
13436+
}
13437+
N(SyntaxKind.EndOfFileToken);
13438+
}
13439+
EOF();
13440+
}
13441+
13442+
[Fact]
13443+
public void ParseEmptyTypeParameterAttributeLists11()
13444+
{
13445+
UsingTree("""
13446+
var v = from x in y
13447+
let z = () =>
13448+
{
13449+
// Inside this query 'from' is a keyword, and is not a legal type
13450+
// parameter name.
13451+
void X<[] from>() { }
13452+
}
13453+
select x;
13454+
""",
13455+
// (6,21): error CS1001: Identifier expected
13456+
// void X<[] from>() { }
13457+
Diagnostic(ErrorCode.ERR_IdentifierExpected, "]").WithLocation(6, 21),
13458+
// (6,23): error CS1525: Invalid expression term 'from'
13459+
// void X<[] from>() { }
13460+
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "from").WithArguments("from").WithLocation(6, 23),
13461+
// (6,23): error CS1003: Syntax error, ',' expected
13462+
// void X<[] from>() { }
13463+
Diagnostic(ErrorCode.ERR_SyntaxError, "from").WithArguments(",").WithLocation(6, 23));
13464+
13465+
N(SyntaxKind.CompilationUnit);
13466+
{
13467+
N(SyntaxKind.GlobalStatement);
13468+
{
13469+
N(SyntaxKind.LocalDeclarationStatement);
13470+
{
13471+
N(SyntaxKind.VariableDeclaration);
13472+
{
13473+
N(SyntaxKind.IdentifierName);
13474+
{
13475+
N(SyntaxKind.IdentifierToken, "var");
13476+
}
13477+
N(SyntaxKind.VariableDeclarator);
13478+
{
13479+
N(SyntaxKind.IdentifierToken, "v");
13480+
N(SyntaxKind.EqualsValueClause);
13481+
{
13482+
N(SyntaxKind.EqualsToken);
13483+
N(SyntaxKind.QueryExpression);
13484+
{
13485+
N(SyntaxKind.FromClause);
13486+
{
13487+
N(SyntaxKind.FromKeyword);
13488+
N(SyntaxKind.IdentifierToken, "x");
13489+
N(SyntaxKind.InKeyword);
13490+
N(SyntaxKind.IdentifierName);
13491+
{
13492+
N(SyntaxKind.IdentifierToken, "y");
13493+
}
13494+
}
13495+
N(SyntaxKind.QueryBody);
13496+
{
13497+
N(SyntaxKind.LetClause);
13498+
{
13499+
N(SyntaxKind.LetKeyword);
13500+
N(SyntaxKind.IdentifierToken, "z");
13501+
N(SyntaxKind.EqualsToken);
13502+
N(SyntaxKind.ParenthesizedLambdaExpression);
13503+
{
13504+
N(SyntaxKind.ParameterList);
13505+
{
13506+
N(SyntaxKind.OpenParenToken);
13507+
N(SyntaxKind.CloseParenToken);
13508+
}
13509+
N(SyntaxKind.EqualsGreaterThanToken);
13510+
N(SyntaxKind.Block);
13511+
{
13512+
N(SyntaxKind.OpenBraceToken);
13513+
N(SyntaxKind.LocalFunctionStatement);
13514+
{
13515+
N(SyntaxKind.PredefinedType);
13516+
{
13517+
N(SyntaxKind.VoidKeyword);
13518+
}
13519+
N(SyntaxKind.IdentifierToken, "X");
13520+
N(SyntaxKind.TypeParameterList);
13521+
{
13522+
N(SyntaxKind.LessThanToken);
13523+
N(SyntaxKind.TypeParameter);
13524+
{
13525+
N(SyntaxKind.AttributeList);
13526+
{
13527+
N(SyntaxKind.OpenBracketToken);
13528+
M(SyntaxKind.Attribute);
13529+
{
13530+
M(SyntaxKind.IdentifierName);
13531+
{
13532+
M(SyntaxKind.IdentifierToken);
13533+
}
13534+
}
13535+
N(SyntaxKind.CloseBracketToken);
13536+
}
13537+
M(SyntaxKind.IdentifierToken);
13538+
}
13539+
N(SyntaxKind.GreaterThanToken);
13540+
}
13541+
N(SyntaxKind.ParameterList);
13542+
{
13543+
N(SyntaxKind.OpenParenToken);
13544+
N(SyntaxKind.CloseParenToken);
13545+
}
13546+
N(SyntaxKind.Block);
13547+
{
13548+
N(SyntaxKind.OpenBraceToken);
13549+
N(SyntaxKind.CloseBraceToken);
13550+
}
13551+
}
13552+
N(SyntaxKind.CloseBraceToken);
13553+
}
13554+
}
13555+
}
13556+
N(SyntaxKind.SelectClause);
13557+
{
13558+
N(SyntaxKind.SelectKeyword);
13559+
N(SyntaxKind.IdentifierName);
13560+
{
13561+
N(SyntaxKind.IdentifierToken, "x");
13562+
}
13563+
}
13564+
}
13565+
}
13566+
}
13567+
}
13568+
}
13569+
N(SyntaxKind.SemicolonToken);
13570+
}
13571+
}
13572+
N(SyntaxKind.EndOfFileToken);
13573+
}
13574+
EOF();
13575+
}
1323213576
}
1323313577
}

0 commit comments

Comments
 (0)