swiftlang/swift-syntax

MacroSystem expansion detached nodes lose diagnostic context

Opened this issue · 4 comments

Description

Macro expansions create diagnostics that are not relative to the given context (because they're detached), which means that there's no way to collocate diagnostics with the original source.

Steps to Reproduce

You can take the AddBlocker macro demo from swift-macro-examples and expand the given macro:

let x = 1
let y = 2
let z = 3
#addBlocker(x * y + z)

If you expand as is, the context's diagnostic for the "+" will be relative to the #addBlocker expression macro node, not the entire source (starting at let x = 1), so formatting the diagnostics against the original source will render the diagnostic in the wrong place:

let x = 1
let y = 2
//       ╰─ warning: blocked an add; did you mean to subtract?
let z = 3
#addBlocker(x * y + z)

It can be rendered correctly by not detaching the context during expansion here:

-node: node.detach(in: context),
+node: node, //.detach(in: context),
let x = 1
let y = 2
let z = 3
#addBlocker(x * y + z)
//                ╰─ warning: blocked an add; did you mean to subtract?

Diagnostics added during expansion should ideally be adjusted from where they were detached.

Tracked in Apple’s issue tracker as rdar://113788567

@stephencelis Where do you see the error at the wrong location? While expanding the macro in the compiler or when using assertMacroExpansion. Also: Which swift compiler version and which swift-syntax version are you using?

I tried reproducing it by

  • Building swift-macro-examples -> the warnings in main.swift show up at the correct location.
  • Creating a new macro that emits a diagnostic and using it in assertMacroExpansion -> diagnostic shows up at the correct line.

If you have an example project that reproduces the issue, that would be ideal.

@ahoppen When using swift-syntax's macro expansion helpers directly. If I take the expanded source tree and feed it along with the context's diagnostics to DiagnosticsFormatter, it formats the source's diagnostics incorrectly since they are relative to their detached point and not the expanded source.

That behaves as expected (at least for now, I’ve got some ideas of tracking the locations of syntax nodes through modifications of the syntax tree but they are very much still experimental, unfinished and potentially has huge performance problems, so nothing to promise here).

To get the location of the diagnostic in the original source, you need to convert it by calling BasicMacroExpansionConext.location(for:anchoredAt:fileName:). You can see an example of how we do it assertMacroExpansion here:

https://github.com/apple/swift-syntax/blob/a7e833e3c9f4324237de3d649fa41fc18b5e32dd/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift#L191

So: It is not currently possible to feed the diagnostics from a macro expansion to DiagnosticsFormatter.

It can be rendered correctly by not detaching the context during expansion here:

We intentionally detach here because the macro only has access to the macro expansion expression and not the rest of the source file (which you could access by calling .parent if we didn’t detach). This matches what you get when expanding a macro as part of compilation.