Skip to content

[Parser] Emit diagnostics for @ AttributeName and @\nAttributeName #1658

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

Conversation

TiagoMaiaL
Copy link
Contributor

Goal

To fix the second part of issue #1395. The first one (for unexpected whitespace in between the macro declaration), is being fixed by #1395.

Notes

There are some TODOs I've placed to seek assistance from reviewers. Specifically, I don't know how I would synthesize a RawTypeSyntax, as I didn't find an easy way to access its text (without the leading trivia). Any help is appreciated. Thank you.

@TiagoMaiaL TiagoMaiaL requested a review from ahoppen as a code owner May 13, 2023 02:38
@TiagoMaiaL TiagoMaiaL force-pushed the emit-diagnostic-for-at-space-attribute-name branch from 8619d09 to b808267 Compare May 13, 2023 19:54
combining: unexpectedBeforeAtSign, attributeName.as(RawTokenSyntax.self),
arena: self.arena
)
// TODO: Synthesize fixed attributeName (RawTypeSyntax)
Copy link
Member

Choose a reason for hiding this comment

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

I think the following should do the job

// `withLeadingTrivia` only returns `nil` if there is no token in `attributeName`. 
// But since `attributeName` has leadingTriviaLength != 0 there must be trivia and thus a token.
// So we can safely force-unwrap here.
attributeName.raw.withLeadingTrivia([], arena: self.arena)!.cast(RawTypeSyntax.self)

For this to work you need to mark the withLeadingTrivia (and for consistency also withTrailingTrivia) functions in RawSyntax as @_spi(RawSyntax) public.

I also added the following to RawSyntaxProtocol. I think I’ve wanted to add it a couple of times already but somehow never did.

  /// Cast to the specified raw syntax type and trap if this node is not of that type.
  func cast<Node: RawSyntaxNodeProtocol>(_ syntaxType: Node.Type) -> Node {
    return self.as(Node.self)!
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ahoppen, do you know why this round-trip failure is happening? Maybe the fixed attribute name should be marked as missing? I didn't find a way to do it.

testInvalidNewlineBetweenAtSignAndIdenfifierIsDiagnosed(): failed - Actual output (+) differed from expected output (-):
–@
–MyAttribute
+@MyAttribute
 func foo() {}

Source failed to round-trip.

Actual syntax tree:
SourceFileSyntax
├─statements: CodeBlockItemListSyntax
│ ╰─[0]: CodeBlockItemSyntax
│   ╰─item: FunctionDeclSyntax
│     ├─attributes: AttributeListSyntax
│     │ ╰─[0]: AttributeSyntax
│     │   ├─atSignToken: atSign
│     │   ╰─attributeName: SimpleTypeIdentifierSyntax
│     │     ╰─name: identifier("MyAttribute")
│     ├─funcKeyword: keyword(SwiftSyntax.Keyword.func)
│     ├─identifier: identifier("foo")
│     ├─signature: FunctionSignatureSyntax
│     │ ╰─input: ParameterClauseSyntax
│     │   ├─leftParen: leftParen
│     │   ├─parameterList: FunctionParameterListSyntax
│     │   ╰─rightParen: rightParen
│     ╰─body: CodeBlockSyntax
│       ├─leftBrace: leftBrace
│       ├─statements: CodeBlockItemListSyntax
│       ╰─rightBrace: rightBrace
╰─eofToken: eof

testInvalidNewlineBetweenAtSignAndIdenfifierIsDiagnosed(): failed - Expected 1 diagnostics but received 0:

@ahoppen
Copy link
Member

ahoppen commented May 15, 2023

@TiagoMaiaL Just wanted to let you know that I’m making a similar change in #1665 and you might be able to also use handleExtraneousWhitespaceError from it for your PR.

@TiagoMaiaL TiagoMaiaL force-pushed the emit-diagnostic-for-at-space-attribute-name branch from b808267 to a5fa949 Compare May 21, 2023 02:13
@TiagoMaiaL TiagoMaiaL requested a review from ahoppen May 21, 2023 02:18
@TiagoMaiaL TiagoMaiaL force-pushed the emit-diagnostic-for-at-space-attribute-name branch from a5fa949 to e00526b Compare May 23, 2023 23:46
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

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

Two more minor comments I saw while reading it again, otherwise LGTM.

@TiagoMaiaL TiagoMaiaL requested a review from ahoppen May 24, 2023 14:26
@TiagoMaiaL TiagoMaiaL force-pushed the emit-diagnostic-for-at-space-attribute-name branch from e00526b to 263bbfd Compare May 24, 2023 21:22
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

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

Thank you

@ahoppen
Copy link
Member

ahoppen commented May 24, 2023

@swift-ci Please test

Comment on lines +191 to +194
attributeName = attributeName
.raw
.withLeadingTrivia([], arena: self.arena)!
.as(RawTypeSyntax.self)!
Copy link
Member

Choose a reason for hiding this comment

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

I suspect this is where the round-trip failures is coming from. You have already stored attributeName in unexpectedBetweenAtSignAndAttributeName and now you’re adding it again with SourcePresence.present. Although it doesn’t quite match the failure message, we have the node twice in the tree now, once with trivia and once without trivia.

The best solution here is a little bit ugly but it’s the best I can come up with: We want to shove the first token of the attributeName (which contains the leading trivia) into unexpectedBetweenAtSignAndAttributeName and instead synthesize a new missing token that doesn’t have leading trivia for the first token in attributeName.

Getting the first token to put into unexpectedBetweenAtSignAndAttributeName should be as easy as doing attributeName.firstToken(viewMode: .sourceAccurate).

To replace the first token in attributeName you need to write a SyntaxRewriter. On the first token that it encounters, it should generate a corresponding missing token without leading trivia. After that, it doesn’t rewrite any further tokens. SyntaxRewriter operates on the Syntax, not RawSyntax level, so you will need to convert attributeName to a Syntax and extract the RawSyntax from the rewritten node. Also, you need to make sure that the rewritten first token is allocated in the same SyntaxArena as all the other tokens. #1383 contains a change to SyntaxRewriter that allows that.

Copy link
Contributor Author

@TiagoMaiaL TiagoMaiaL May 26, 2023

Choose a reason for hiding this comment

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

Getting the first token to put into unexpectedBetweenAtSignAndAttributeName should be as easy as doing attributeName.firstToken(viewMode: .sourceAccurate).

The only way I managed to do this was to first declare RawSyntax.firstToken as @_spi(RawSyntax) public and then do the following:

    } else if attributeName.raw.leadingTriviaByteLength != 0,
      let firstAttributeNameToken = attributeName.raw.firstToken(viewMode: .sourceAccurate)
    {
      unexpectedBetweenAtSignAndAttributeName = RawUnexpectedNodesSyntax(
        firstAttributeNameToken.withPresence(.present, arena: self.arena)
      )
      ...

Copy link
Member

Choose a reason for hiding this comment

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

Can you push your changes and then I’ll take a look at them.

@ahoppen
Copy link
Member

ahoppen commented Jul 11, 2023

@TiagoMaiaL Are you still working on this? If so, could you rebase on top of main and look at the round-trip failure?

@TiagoMaiaL
Copy link
Contributor Author

@TiagoMaiaL Are you still working on this? If so, could you rebase on top of main and look at the round-trip failure?

Yes, I'll do it soon. I need to solve some conflicts first, as it's been a while since I don't touch this PR.

@adammcarter
Copy link
Contributor

Hey all, is this still open?

I'm just looking for a nice first issue and looks like this is done but not merged and it's been a while so don't want to step on any toes! 🦶🏻

@ahoppen
Copy link
Member

ahoppen commented Mar 28, 2024

This should have been fixed by #2466

@ahoppen ahoppen closed this Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants