fix: render # and $ tight against the following token in to_string() and stringify! - #23255
Closed
samvallad33 wants to merge 1 commit into
Closed
fix: render # and $ tight against the following token in to_string() and stringify!#23255samvallad33 wants to merge 1 commit into
# and $ tight against the following token in to_string() and stringify!#23255samvallad33 wants to merge 1 commit into
Conversation
`TokenStream::to_string()` and `stringify!` both put a space after every punct whose spacing is Alone. `#` and `$` are prefix sigils, so the token after them is normally an ident or a delimiter, which makes them Alone, which produces `# ty` where rustc produces `#ty`. That breaks proc macros that do textual work on `input.to_string()` and scan for `#name` interpolation markers. The scan stops matching and the macro emits its template verbatim, which rust-analyzer then reports as bogus syntax and trait errors on code that compiles fine. Expectations checked against rustc 1.95.0 by running the same inputs through a real proc macro built by the real compiler.
Contributor
|
First, I'm pretty sure you used AI to write the top comment and perhaps also the code, which is a violation of our AI policy. If you keep doing that we'll reach to moderation. Second, this "fix" is entirely wrong, and I'm pretty sure the actual reason is invisible groups (which I'm working at). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses the second report in #18571.
Root cause
crates/proc-macro-srv/src/token_stream.rs:484decides whether to emit a space after a punct purely from its jointness:crates/tt/src/lib.rs:738does the same thing fortt::pretty, which backs thestringify!builtin atcrates/hir-expand/src/builtin/fn_macro.rs:195.#and$are prefix sigils, not binary operators. The token after them is normally an ident or a delimiter, not another punct, so jointness isAloneand both renderers insert a space. rustc never does. Jointness cannot express this, because it describes whether two puncts fuse into one compound operator, and#tyis not a compound operator.Why it is a correctness bug and not a formatting preference
The reporter in #18571 (comment) has a proc macro that calls
input.to_string()and does textual replacement of#tyand#inner. Under rustc the string contains#ty, the replacement fires, and real code comes out. Under rust-analyzer the string contains# ty, the replacement never fires, and the macro emits its own template verbatim. rust-analyzer then type checksimpl From<#inner<#ty>>and reportscannot define inherent impl on foreign typeplusunexpected token in inputon code thatcargo checkaccepts. The diagnostics in that report are downstream of this one space.stringify!has the same problem for a simpler reason. Its result is an observable string literal, so any divergence from rustc is directly wrong.Evidence
rustc is the only oracle that matters here and it is trivially available, so I built one. A real proc macro compiled by the real compiler:
I ran a 43 case corpus through that under
rustc 1.95.0and throughTokenStream::from_str(..).to_string()one96ea7a5, then diffed. Only 5 of 43 cases matched. Most of the divergence is harmless whitespace that re-lexes identically. The sigil cases are not:Same for
stringify!, checked directly againstrustc 1.95.0:The fix
Suppress the trailing space after
#and$in both renderers, independently of jointness. Two lines plus comments.Test proof
Three new or corrected assertions fail on the parent commit and pass on this one. Reverting only the two fix hunks and keeping the tests:
Restoring both hunks:
Gates. Full
proc-macro-srvsuite under the CI invocation,cargo test --features in-rust-tree -p proc-macro-srv:cargo test -p tt -p mbe -p hir-def -p syntax-bridge:cargo fmt --checkclean.cargo clippy -p tt -p hir-def -p proc-macro-srv --all-targetsproduces 5 warnings inproc-macro-srv, all pre-existing, none on changed lines.The existing test encoded the bug
doc_comment_from_strasserted# [doc = " foo"]. rustc renders/// fooas#[doc = r" foo"]. I corrected the#[half. The literal kind still differs, we produce a normal string where rustc produces a raw one. That is a separate divergence and I left it alone.What this does not fix, honestly
The original report by @feois is a different bug.
feois/rust-enumerationhas no[dependencies]and noproc-macro = true, so it ismacro_rules!only and never reachesTokenStream::to_string(). Nothing here touches it. #18571 is two unrelated bugs sharing a title, and the first one still has no root cause. It probably deserves its own issue.I have not written an end to end test that loads a real dylib whose macro does string replacement. The proof above is at the renderer, plus the rustc oracle.
The remaining 30-odd whitespace divergences from rustc are untouched.
rustc_parity_known_divergencesrecords eight representative ones so they are visible in the tree and any future change to them shows up as a diff instead of silently. That test asserts both the current output and that it still differs from rustc, so it fails loudly in either direction.Question for a maintainer
Do you want full
space_betweenparity withrustc_ast_pretty, or is this sigil fix the right scope? Full parity is a much larger change and would churn expectations across the macro expansion tests. My read is that the sigil case is the only one that is semantically load bearing, since everything else re-lexes to the same tokens, but you know the downstream consumers better than I do. Happy to do the larger port in a follow-up if you want it.