Skip to content

Prepend SyntaxStringInterpolationError errors in macro expansion with Internal macro error: #2176

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

Merged

Conversation

natikgadzhi
Copy link
Contributor

Summary

This pull request prepends SyntaxStringInterpolationError message with Internal macro error:. Closes #2039.

Changes

  • In MacroExpansionContext.addDiagnostics, if the passed error as? SyntaxStringInterpolationError worked, we prepend it with the Internal macro error and grab it's description. Otherwise, we just use String(describing:error) as the diagnostic message.
  • Adds a basic test for addDiagnostics for that behavior.

Next steps

  • @DougGregor, does this look like the right direction to you? I'm new to Macros, and I'm here to learn — no hard feelings if my attempt is naive / plain wrong.
  • @Matejkob, sounds like your area of interest! Could you give me some hints to how I could test this end to end, and construct a test macro that would throw syntax string interpolation error?

// Used in `MacroExpansionContext` to make a nicer error message when a
// macro expansion fails with SwiftSyntaxBuilder making a syntax node
// with string interpolation.
@_spi(Diagnostics) public enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't know if it's okay to @_spi this, but that seemed like a better option than to make it public outright.

Copy link
Member

Choose a reason for hiding this comment

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

I just checked and SyntaxStringInterpolationError currently serves two distinct use cases:

  1. The .diagnostics case in SyntaxProtocol(validating:)
  2. The .producedInvalidNodeType case in SyntaxNodeWithBody and DeclSyntaxProtocol.init

I think what we should do, is split the error into two distinct structs and make the one that represents the producedInvalidNodeType case public. That way, we minimize the public surface are we are introducing while also allowing other clients to catch that case.

// Used in `MacroExpansionContext` to make a nicer error message when a
// macro expansion fails with SwiftSyntaxBuilder making a syntax node
// with string interpolation.
@_spi(Diagnostics) public enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
Copy link
Member

Choose a reason for hiding this comment

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

I just checked and SyntaxStringInterpolationError currently serves two distinct use cases:

  1. The .diagnostics case in SyntaxProtocol(validating:)
  2. The .producedInvalidNodeType case in SyntaxNodeWithBody and DeclSyntaxProtocol.init

I think what we should do, is split the error into two distinct structs and make the one that represents the producedInvalidNodeType case public. That way, we minimize the public surface are we are introducing while also allowing other clients to catch that case.

Comment on lines 56 to 52
let context = BasicMacroExpansionContext()
let error = SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: DeclSyntax.self, actualType: ExprSyntax.self)
// Since we only care about the error switch inside of addDagnostics, we don't care about the particular node we're passing in.
context.addDiagnostics(from: error, node: ExprSyntax("1"))

XCTAssertEqual(context.diagnostics.count, 1)
XCTAssertTrue(context.diagnostics.first!.message.starts(with: "Internal macro error:"))
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 you should be able to test the real-case scenario when this happens by defining an extension macro that returns

try ExtensionDeclSyntax("var x: Int")

And then use assertMacroExpansion to check that the emitted error contains Internal macro error:

@natikgadzhi natikgadzhi force-pushed the macro-expansion/syntax-interpolation-errors branch from 716ed0f to 34a634a Compare September 11, 2023 22:01
- This commit prepends `SyntaxStringInterpolationError` message with `Internal macro
  error:` when adding it as a diagnostic in
  `MacroExpansionContext.addDiagnostics`, so that macro expansion
  clarifies the error as internal to the macro, not to how it was
  applied.
- Any unrecognized errors raised and sent to `addDiagnostics` will be
  added as is, without prepending them with a message. That's consistent
  with how it works now.
- Adds a basic test for `addDiagnostics` for that behavior.
- Adds end to end test with `assertMacroExpansion`, thanks @ahoppen for
  the hint!
@natikgadzhi natikgadzhi force-pushed the macro-expansion/syntax-interpolation-errors branch from 34a634a to 0df36e7 Compare September 11, 2023 22:03
@natikgadzhi
Copy link
Contributor Author

@ahoppen, thank you for the review!

I've cleaned up the code and added an end-to-end test for now. The next part is splitting the error enum into two pieces. That'll be a separate commit.

@natikgadzhi
Copy link
Contributor Author

@ahoppen ready for review!

@natikgadzhi natikgadzhi requested a review from ahoppen September 11, 2023 22:30
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.

A couple of minor comments, otherwise LGTM.

Comment on lines -177 to -178
// Start the diagnostic on a new line so it isn't prefixed with the file, which messes up the
// column-aligned message from ``DiagnosticsFormatter``.
Copy link
Member

Choose a reason for hiding this comment

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

Could we keep this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, added that comment back <3

@@ -12,6 +12,7 @@

import SwiftDiagnostics
import SwiftSyntax
@_spi(Diagnostics) import SwiftSyntaxBuilder
Copy link
Member

Choose a reason for hiding this comment

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

I don’t think we need @_spi(Diagnostics) anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Sorry I missed it.

}
}

final class MacroExpansionContextTests: XCTestCase {
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 a better name for this test would now be something like StringInterpolationErrorTests.

@natikgadzhi
Copy link
Contributor Author

natikgadzhi commented Sep 12, 2023 via email

@natikgadzhi natikgadzhi force-pushed the macro-expansion/syntax-interpolation-errors branch from 21e2acc to 02517bc Compare September 14, 2023 22:55
@natikgadzhi
Copy link
Contributor Author

@ahoppen done!

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.

One minor comment, otherwise LGTM.

// Since we only care about the error switch inside of addDagnostics, we don't care about the particular node we're passing in
context.addDiagnostics(from: error, node: ExprSyntax("1"))
XCTAssertEqual(context.diagnostics.count, 1)
XCTAssertTrue(context.diagnostics.first!.message.starts(with: "Internal macro error:"))
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 do the following? That way the test doesn’t crash if there is no diagnostic.

Suggested change
XCTAssertTrue(context.diagnostics.first!.message.starts(with: "Internal macro error:"))
let diagnostic = try XCTUnwrap(context.diagnostics.first)
XCTAssertTrue(diagnostic.message.starts(with: "Internal macro error:"))

Same for the test below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, good call. Fixed.

This commit splits `SyntaxStringInterpolationError` into two error
types:
- `SyntaxStringInterpolationInvalidTokenTypeError` for situations
  when interpolation resulted in a wrong token type, and
- `SyntaxStringInterpolationDiagnosticError` when another error occured,
  and the user provides additional diagnostics with the error.

Additionally:
- The `SyntaxStringInterpolationInvalidTokenTypeError` is now `public` so
other clients can catch it.
- Both error types are structs instead of enums.
- All invocation spots are updated to match the API.
@natikgadzhi natikgadzhi force-pushed the macro-expansion/syntax-interpolation-errors branch from 02517bc to ec8265e Compare September 15, 2023 00:51
@natikgadzhi natikgadzhi requested a review from ahoppen September 15, 2023 00:51
@ahoppen
Copy link
Member

ahoppen commented Sep 15, 2023

@swift-ci Please test

@ahoppen ahoppen enabled auto-merge September 15, 2023 21:30
@kimdv
Copy link
Contributor

kimdv commented Sep 16, 2023

@swift-ci please test windows

1 similar comment
@kimdv
Copy link
Contributor

kimdv commented Sep 17, 2023

@swift-ci please test windows

@kimdv
Copy link
Contributor

kimdv commented Sep 17, 2023

@swift-ci please test Windows

1 similar comment
@kimdv
Copy link
Contributor

kimdv commented Sep 17, 2023

@swift-ci please test Windows

@ahoppen ahoppen merged commit 636a911 into swiftlang:main Sep 17, 2023
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.

SyntaxStringInterpolationError should prepend Internal macro error: if hit by a macro expansion
3 participants