Skip to content

Commit e7cd138

Browse files
authored
Parse directives in trailing trivia as skipped tokens trivia (#75724)
* Handle directives in trailing trivia * Revert the change and improve tests * Attach trailing directives as skipped tokens trivia * Update tests * Parse as bad directive trivia * Simplify tests * Remove trailing parameter * Parse as skipped tokens trivia * Carry trivia over * Wrap trivia in a token * Put text into one token * Simplify width logic * Refactor to simplify the code * Add more tests * Pass `false` and explain
1 parent 3bc2a12 commit e7cd138

File tree

9 files changed

+720
-56
lines changed

9 files changed

+720
-56
lines changed

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

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,10 @@ public void ReInitialize(DirectiveStack context)
3131
public CSharpSyntaxNode ParseDirective(
3232
bool isActive,
3333
bool endIsActive,
34-
bool isAfterFirstTokenInFile,
35-
bool isAfterNonWhitespaceOnLine)
34+
bool isFollowingToken)
3635
{
3736
var hashPosition = lexer.TextWindow.Position;
3837
var hash = this.EatToken(SyntaxKind.HashToken, false);
39-
if (isAfterNonWhitespaceOnLine)
40-
{
41-
hash = this.AddError(hash, ErrorCode.ERR_BadDirectivePlacement);
42-
}
4338

4439
// The behavior of these directives when isActive is false is somewhat complicated.
4540
// The key functions in the native compiler are ScanPreprocessorIfSection and
@@ -81,7 +76,7 @@ public CSharpSyntaxNode ParseDirective(
8176

8277
case SyntaxKind.DefineKeyword:
8378
case SyntaxKind.UndefKeyword:
84-
result = this.ParseDefineOrUndefDirective(hash, this.EatContextualToken(contextualKind), isActive, isAfterFirstTokenInFile && !isAfterNonWhitespaceOnLine);
79+
result = this.ParseDefineOrUndefDirective(hash, this.EatContextualToken(contextualKind), isActive, isFollowingToken);
8580
break;
8681

8782
case SyntaxKind.ErrorKeyword:
@@ -101,11 +96,11 @@ public CSharpSyntaxNode ParseDirective(
10196
break;
10297

10398
case SyntaxKind.ReferenceKeyword:
104-
result = this.ParseReferenceDirective(hash, this.EatContextualToken(contextualKind), isActive, isAfterFirstTokenInFile && !isAfterNonWhitespaceOnLine);
99+
result = this.ParseReferenceDirective(hash, this.EatContextualToken(contextualKind), isActive, isFollowingToken);
105100
break;
106101

107102
case SyntaxKind.LoadKeyword:
108-
result = this.ParseLoadDirective(hash, this.EatContextualToken(contextualKind), isActive, isAfterFirstTokenInFile && !isAfterNonWhitespaceOnLine);
103+
result = this.ParseLoadDirective(hash, this.EatContextualToken(contextualKind), isActive, isFollowingToken);
109104
break;
110105

111106
case SyntaxKind.NullableKeyword:
@@ -121,16 +116,13 @@ public CSharpSyntaxNode ParseDirective(
121116
{
122117
var id = this.EatToken(SyntaxKind.IdentifierToken, false);
123118
var end = this.ParseEndOfDirective(ignoreErrors: true);
124-
if (!isAfterNonWhitespaceOnLine)
119+
if (!id.IsMissing)
125120
{
126-
if (!id.IsMissing)
127-
{
128-
id = this.AddError(id, ErrorCode.ERR_PPDirectiveExpected);
129-
}
130-
else
131-
{
132-
hash = this.AddError(hash, ErrorCode.ERR_PPDirectiveExpected);
133-
}
121+
id = this.AddError(id, ErrorCode.ERR_PPDirectiveExpected);
122+
}
123+
else
124+
{
125+
hash = this.AddError(hash, ErrorCode.ERR_PPDirectiveExpected);
134126
}
135127

136128
result = SyntaxFactory.BadDirectiveTrivia(hash, id, end, isActive);

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

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ private static int GetFullWidth(SyntaxListBuilder? builder)
303303
private SyntaxToken LexSyntaxToken()
304304
{
305305
_leadingTriviaCache.Clear();
306-
this.LexSyntaxTrivia(afterFirstToken: TextWindow.Position > 0, isTrailing: false, triviaList: ref _leadingTriviaCache);
306+
this.LexSyntaxTrivia(isFollowingToken: TextWindow.Position > 0, isTrailing: false, triviaList: ref _leadingTriviaCache);
307307
var leading = _leadingTriviaCache;
308308

309309
var tokenInfo = default(TokenInfo);
@@ -313,7 +313,7 @@ private SyntaxToken LexSyntaxToken()
313313
var errors = this.GetErrors(GetFullWidth(leading));
314314

315315
_trailingTriviaCache.Clear();
316-
this.LexSyntaxTrivia(afterFirstToken: true, isTrailing: true, triviaList: ref _trailingTriviaCache);
316+
this.LexSyntaxTrivia(isFollowingToken: true, isTrailing: true, triviaList: ref _trailingTriviaCache);
317317
var trailing = _trailingTriviaCache;
318318

319319
return Create(in tokenInfo, leading, trailing, errors);
@@ -322,15 +322,15 @@ private SyntaxToken LexSyntaxToken()
322322
internal SyntaxTriviaList LexSyntaxLeadingTrivia()
323323
{
324324
_leadingTriviaCache.Clear();
325-
this.LexSyntaxTrivia(afterFirstToken: TextWindow.Position > 0, isTrailing: false, triviaList: ref _leadingTriviaCache);
325+
this.LexSyntaxTrivia(isFollowingToken: TextWindow.Position > 0, isTrailing: false, triviaList: ref _leadingTriviaCache);
326326
return new SyntaxTriviaList(default(Microsoft.CodeAnalysis.SyntaxToken),
327327
_leadingTriviaCache.ToListNode(), position: 0, index: 0);
328328
}
329329

330330
internal SyntaxTriviaList LexSyntaxTrailingTrivia()
331331
{
332332
_trailingTriviaCache.Clear();
333-
this.LexSyntaxTrivia(afterFirstToken: true, isTrailing: true, triviaList: ref _trailingTriviaCache);
333+
this.LexSyntaxTrivia(isFollowingToken: true, isTrailing: true, triviaList: ref _trailingTriviaCache);
334334
return new SyntaxTriviaList(default(Microsoft.CodeAnalysis.SyntaxToken),
335335
_trailingTriviaCache.ToListNode(), position: 0, index: 0);
336336
}
@@ -1889,7 +1889,7 @@ private bool ScanIdentifierOrKeyword(ref TokenInfo info)
18891889
}
18901890
}
18911891

1892-
private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxListBuilder triviaList)
1892+
private void LexSyntaxTrivia(bool isFollowingToken, bool isTrailing, ref SyntaxListBuilder triviaList)
18931893
{
18941894
bool onlyWhitespaceOnLine = !isTrailing;
18951895

@@ -1995,7 +1995,33 @@ private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxLi
19951995
case '#':
19961996
if (_allowPreprocessorDirectives)
19971997
{
1998-
this.LexDirectiveAndExcludedTrivia(afterFirstToken, isTrailing || !onlyWhitespaceOnLine, ref triviaList);
1998+
if (isTrailing || !onlyWhitespaceOnLine)
1999+
{
2000+
// Directives cannot be in trailing trivia.
2001+
// We parse the directive (ignoring its effects like disabled text or defined symbols).
2002+
// We extract the text corresponding to the parsed directive
2003+
// and add it as a BadToken in SkippedTokensTrivia.
2004+
2005+
var savePosition = TextWindow.Position;
2006+
2007+
// All the `false` arguments affect only error reporting and the resulting node, both of which we discard.
2008+
// However, passing `false` can skip some unnecessary checks.
2009+
this.ParseDirective(isActive: false, endIsActive: false, isFollowingToken: false);
2010+
2011+
var text = TextWindow.Text.GetSubText(TextSpan.FromBounds(savePosition, TextWindow.Position));
2012+
2013+
var error = new SyntaxDiagnosticInfo(offset: 0, width: 1, code: ErrorCode.ERR_BadDirectivePlacement);
2014+
2015+
var token = SyntaxFactory.BadToken(null, text.ToString(), null).WithDiagnosticsGreen([error]);
2016+
2017+
this.AddTrivia(SyntaxFactory.SkippedTokensTrivia(token), ref triviaList);
2018+
}
2019+
else
2020+
{
2021+
this.LexDirectiveAndExcludedTrivia(isFollowingToken, ref triviaList);
2022+
}
2023+
2024+
onlyWhitespaceOnLine = true;
19992025
break;
20002026
}
20012027
else
@@ -2332,13 +2358,12 @@ private static SyntaxTrivia CreateWhitespaceTrivia(SlidingTextWindow textWindow)
23322358
}
23332359

23342360
private void LexDirectiveAndExcludedTrivia(
2335-
bool afterFirstToken,
2336-
bool afterNonWhitespaceOnLine,
2361+
bool isFollowingToken,
23372362
ref SyntaxListBuilder triviaList)
23382363
{
2339-
var directive = this.LexSingleDirective(true, true, afterFirstToken, afterNonWhitespaceOnLine, ref triviaList);
2364+
var directive = this.LexSingleDirective(true, true, isFollowingToken, ref triviaList);
23402365

2341-
// also lex excluded stuff
2366+
// also lex excluded stuff
23422367
var branching = directive as BranchingDirectiveTriviaSyntax;
23432368
if (branching != null && !branching.BranchTaken)
23442369
{
@@ -2362,7 +2387,7 @@ private void LexExcludedDirectivesAndTrivia(bool endIsActive, ref SyntaxListBuil
23622387
break;
23632388
}
23642389

2365-
var directive = this.LexSingleDirective(false, endIsActive, false, false, ref triviaList);
2390+
var directive = this.LexSingleDirective(false, endIsActive, false, ref triviaList);
23662391
var branching = directive as BranchingDirectiveTriviaSyntax;
23672392
if (directive.Kind == SyntaxKind.EndIfDirectiveTrivia || (branching != null && branching.BranchTaken))
23682393
{
@@ -2378,8 +2403,7 @@ private void LexExcludedDirectivesAndTrivia(bool endIsActive, ref SyntaxListBuil
23782403
private CSharpSyntaxNode LexSingleDirective(
23792404
bool isActive,
23802405
bool endIsActive,
2381-
bool afterFirstToken,
2382-
bool afterNonWhitespaceOnLine,
2406+
bool isFollowingToken,
23832407
ref SyntaxListBuilder triviaList)
23842408
{
23852409
if (SyntaxFacts.IsWhitespace(TextWindow.PeekChar()))
@@ -2388,16 +2412,25 @@ private CSharpSyntaxNode LexSingleDirective(
23882412
this.AddTrivia(this.ScanWhitespace(), ref triviaList);
23892413
}
23902414

2391-
CSharpSyntaxNode directive;
2415+
CSharpSyntaxNode directive = ParseDirective(isActive, endIsActive, isFollowingToken);
2416+
2417+
this.AddTrivia(directive, ref triviaList);
2418+
_directives = directive.ApplyDirectives(_directives);
2419+
return directive;
2420+
}
2421+
2422+
private CSharpSyntaxNode ParseDirective(
2423+
bool isActive,
2424+
bool endIsActive,
2425+
bool isFollowingToken)
2426+
{
23922427
var saveMode = _mode;
23932428

23942429
_directiveParser ??= new DirectiveParser(this);
23952430
_directiveParser.ReInitialize(_directives);
23962431

2397-
directive = _directiveParser.ParseDirective(isActive, endIsActive, afterFirstToken, afterNonWhitespaceOnLine);
2432+
CSharpSyntaxNode directive = _directiveParser.ParseDirective(isActive, endIsActive, isFollowingToken);
23982433

2399-
this.AddTrivia(directive, ref triviaList);
2400-
_directives = directive.ApplyDirectives(_directives);
24012434
_mode = saveMode;
24022435
return directive;
24032436
}

src/Compilers/CSharp/Test/Syntax/LexicalAndXml/PreprocessorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,7 +2426,7 @@ class A { } #define XXX
24262426
var node = Parse(text);
24272427
TestRoundTripping(node, text, false);
24282428
VerifyErrorCode(node, (int)ErrorCode.ERR_BadDirectivePlacement); // CS1040
2429-
VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.DefineDirectiveTrivia, Status = NodeStatus.IsActive, Text = "XXX" });
2429+
VerifyDirectivesSpecial(node);
24302430
}
24312431

24322432
[Fact]
@@ -2587,7 +2587,7 @@ class A { } #undef XXX
25872587
var node = Parse(text);
25882588
TestRoundTripping(node, text, false);
25892589
VerifyErrorCode(node, (int)ErrorCode.ERR_BadDirectivePlacement);
2590-
VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.UndefDirectiveTrivia, Status = NodeStatus.IsActive, Text = "XXX" });
2590+
VerifyDirectivesSpecial(node);
25912591
}
25922592

25932593
[Fact]

0 commit comments

Comments
 (0)