Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced #line directives #54489

Merged
merged 9 commits into from
Jul 2, 2021
Merged

Conversation

cston
Copy link
Member

@cston cston commented Jun 30, 2021

#line (startLine, startChar) - (endLine, endChar) charOffset "fileName"

Map lines after the #line directive to the span (startLine, startChar) - (endLine, endChar) in "fileName" where the origin of the unmapped span is the first line after the directive at character charOffset.

Proposal: dotnet/csharplang#4747
Test plan: #54509

@cston cston marked this pull request as ready for review July 1, 2021 00:45
@cston cston requested review from a team as code owners July 1, 2021 00:45
@cston cston requested a review from tmat July 1, 2021 00:45
@cston cston marked this pull request as draft July 1, 2021 00:49
@jcouv
Copy link
Member

jcouv commented Jul 1, 2021

Please add an entry to feature status page with a test plan link. Thanks


In reply to: 871838112

@cston cston marked this pull request as ready for review July 1, 2021 07:01
@cston
Copy link
Member Author

cston commented Jul 1, 2021

cc @captainsafia

@RikkiGibson RikkiGibson self-assigned this Jul 1, 2021
Comment on lines +90 to +92
result = (this.CurrentToken.Kind == SyntaxKind.OpenParenToken) ?
this.ParseLineSpanDirective(hash, lineKeyword, isActive) :
this.ParseLineDirective(hash, lineKeyword, isActive);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: consider placing ?: tokens at the beginning of the line

Suggested change
result = (this.CurrentToken.Kind == SyntaxKind.OpenParenToken) ?
this.ParseLineSpanDirective(hash, lineKeyword, isActive) :
this.ParseLineDirective(hash, lineKeyword, isActive);
result = (this.CurrentToken.Kind == SyntaxKind.OpenParenToken)
? this.ParseLineSpanDirective(hash, lineKeyword, isActive)
: this.ParseLineDirective(hash, lineKeyword, isActive);

src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs Outdated Show resolved Hide resolved
Comment on lines 45 to 47
var mappedLine = (previous.State == PositionState.RemappedSpan) ?
unmappedLine :
previous.MappedLine + directiveLineNumber - previous.UnmappedLine;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var mappedLine = (previous.State == PositionState.RemappedSpan) ?
unmappedLine :
previous.MappedLine + directiveLineNumber - previous.UnmappedLine;
var mappedLine = (previous.State == PositionState.RemappedSpan)
? unmappedLine
: previous.MappedLine + directiveLineNumber - previous.UnmappedLine;

/// <summary>
/// The optional offset in the syntax tree for the line immediately following an enhanced <c>#line</c> directive in C#.
/// </summary>
public readonly int? CharacterOffset { get; }
Copy link
Contributor

Choose a reason for hiding this comment

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

did API review have any conclusion about the choice to make this int? versus int with a special "not specified" or default value?

Copy link
Member Author

@cston cston Jul 1, 2021

Choose a reason for hiding this comment

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

This wasn't resolved at the API review (see comment). We might need to close on this separately.

Copy link
Contributor

@RikkiGibson RikkiGibson Jul 1, 2021

Choose a reason for hiding this comment

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

Let's address after preview2.

@jcouv
Copy link
Member

jcouv commented Jul 1, 2021

Looking

var comma = EatToken(SyntaxKind.CommaToken, reportError);
if (comma.IsMissing) reportError = false;

var character = ParseNumericLiteral(reportError);
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

The error we'll report here will be ERR_InvalidLineNumber (" error CS1576: The line number specified for #line directive is missing or invalid") but this number doesn't refer to a line #Resolved

var openParen = EatToken(SyntaxKind.OpenParenToken, reportError);
if (openParen.IsMissing) reportError = false;

var line = ParseNumericLiteral(reportError);
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

It looks like we have some validation for original #line directives, possibly reporting ERR_InvalidLineNumber or WRN_TooManyLinesForDebugger. Feels like we should keep those #Closed

Copy link
Member

Choose a reason for hiding this comment

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

Another rule from the spec: [if] start line is equal to end line then end character is greater than start character.

var end = ParseLineDirectivePosition(ref reportError);
var characterOffset = (reportError && CurrentToken.Kind == SyntaxKind.NumericLiteralToken) ? this.EatToken() : null;

var file = EatToken(SyntaxKind.StringLiteralToken, ErrorCode.ERR_MissingPPFile, reportError: reportError);
Copy link
Member

Choose a reason for hiding this comment

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

From offline discussion, we probably need something like line 389 above

@@ -78,10 +92,26 @@ public LineMappingEntry(int unmappedLine)
{
this.UnmappedLine = unmappedLine;
this.MappedLine = mappedLine;
this.MappedSpan = default;
this.UnmappedCharacterOffset = null;
this.MappedPathOpt = mappedPathOpt;
this.State = state;
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

Consider asserting that the state isn't PositionState.RemappedSpan #Closed

@@ -21,69 +21,143 @@ public CSharpLineDirectiveMap(SyntaxTree syntaxTree)
// Add all active #line directives under trivia into the list, in source code order.
protected override bool ShouldAddDirective(DirectiveTriviaSyntax directive)
{
return directive.IsActive && directive.Kind() == SyntaxKind.LineDirectiveTrivia;
return directive.IsActive && (directive.Kind() is SyntaxKind.LineDirectiveTrivia or SyntaxKind.LineSpanDirectiveTrivia);
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

There's a couple references to SyntaxKind.LineDirectiveTrivia in the IDE layer (relates to classification, in ClassifyTrivia and ClassifyPreprocessorDirective) and one to LineDirectiveTriviaSyntax in CSharpSyntaxFacts.TryGetExternalSourceInfo which will probably need looking at. #Pending

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, added to the test plan.

case LineDirectiveTriviaSyntax lineDirective:
{
var mappedLine = (previous.State == PositionState.RemappedSpan) ?
unmappedLine :
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

I didn't understand this. It seems strange to use something from "unmapped" domain directly in "mapped" domain without any sort of mapping.
Consider adding a comment: "only used in error cases" #Closed

throw ExceptionUtilities.UnexpectedValue(directive);
}

static bool tryGetOneBasedNumericLiteralValue(in SyntaxToken token, ref int value)
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

Consider making this an out instead #Resolved


static bool tryGetOneBasedNumericLiteralValue(in SyntaxToken token, ref int value)
{
if (!token.IsMissing &&
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

It looks like these two checks (IsMissing and Kind() == NumericLiteralToken) were not in the original source code. Do we need them?
I'm fine if it's for extra safety... #Closed

Copy link
Member Author

@cston cston Jul 2, 2021

Choose a reason for hiding this comment

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

It looks like these two checks (IsMissing and Kind() == NumericLiteralToken) were not in the original source code. Do we need them?

The original checks are still there above the call to the local function, so these are redundant. In fact, the checks might be redundant from each of the callers but it seems useful for safety as you suggested.

if (entry.State == PositionState.Hidden ||
entry.State == PositionState.Unknown && GetUnknownStateVisibility(currentIndex) == LineVisibility.Hidden)
{
return new LineMapping(unmapped, null, default);
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

nit: consider naming last two args #Closed

EOF();
}
}
}
Copy link
Member

@jcouv jcouv Jul 2, 2021

Choose a reason for hiding this comment

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

nit: Consider adding a ParseIncompleteSyntax test (like ParseIncompleteRecordSyntax) #Closed

int value = (int)token.Value;
if (value < minValue || value > maxValue)
{
token = this.AddError(token, ErrorCode.ERR_LineSpanDirectiveInvalidValue);
Copy link
Member

Choose a reason for hiding this comment

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

For old #line directives, one of these scenarios is just a warning: WRN_TooManyLinesForDebugger. Do we want to maintain that?

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 6)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 8)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 9)

@jcouv
Copy link
Member

jcouv commented Jul 2, 2021

Marked as "Approved to merge" in @jaredpar's absence, given compiler feature review and IDE approval (blocking formatting issue was fixed).

@jcouv jcouv self-assigned this Jul 2, 2021
@jcouv jcouv added this to the C# 10 milestone Jul 2, 2021
@cston cston merged commit ec005c8 into dotnet:release/dev17.0 Jul 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants