-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
Replace the pretty-print/retokenize hack with a direct comparsion of AST nodes #75820
Comments
@petrochenkov: If you're supportive of the general concept, I plan to start working on implementing this. |
I need to think. |
TLDR: I don't think relying on AST more is the right direction. We don't really need AST until expansion is done, before that some simplified mostly token-based represenation like #43081 (comment) could be used. So it could make sense to rely on it less. The plans to get rid of nonterminal tokens and always treat them as simply delimited groups with token streams inside (and not only in proc macros) also fit into the picture. People also expressed desire to delay parsing inside any delimited groups until after expansion (#65860) to be able to use unstable syntax inside items marked with (We'll have to force pre-expansion parsing into AST at least in some cases though to maintain the current behavior of cc @matklad as an interested party |
With regard to the Without const generics, we could probably get away with determining the attribute target via a simple rule like 'stop at the first comma or delimited stream at the same depth' (which would handle |
The hack was removed entirely in #79472. |
Currently, proc-macro expansion pretty-prints the
Nonterminal
being passed to the proc macro:rust/src/librustc_parse/lib.rs
Lines 283 to 293 in 5528caf
This ensures that the
TokenStream
we pass to a proc-macro always agrees with the AST struct it is attached to. This is necessary because the compiler may directly modify the AST structure after it is parsed but before it is passed to a proc-macro. Currently, it looks like#[cfg]
attributes are the only way for a proc-macro to observe this: aderive
macro will have cfg-stripping applied to the fields/variants of the target struct/enum before it is invoked.Unfortunately, while this check has no false negatives (keeping an invalid
TokenStream
), it has had a large number of false positives (throwing away theTokenStream
when it could have been kept):where
clause when pretty-printing #73157When a false positive occurs, it results in an instance of #43081. Originally, I thought that these false positives could only result in unspanned/mis-spanned compiler errors - while obnoxious to deal with, they can be fixed as they come up.
Unfortunately, using the re-created
TokenStream
means that in addition to losing location information, we also lose hygiene information (since both are accessed via aSpan
). This is much more serious - it results in code compiling that should not. In two cases, users actually wrote macros that explicitly relied on hygiene information being lost, likely because they did not realize that this was a bug. Fixing pretty-print/retokenize false positives can therefore incur unavoidable breakage in certain crates. If one of these buggy macros was exposed in a public API, the problem would be much worse. See #73084 for more details.I propose that we replace the pretty-print/reparse hack with the following strategy:
Item
) just before theTokenStream
is attached. We then attach astruct AstTokens(P(T), TokenStream)
to the struct. The struct now stores a copy of itself (behind aP
) as it was just after parsing.#[derive(PartialEq)]
for the entire AST - while a few structs/enum do this, most don't.Nonterminal
, we compare the current AST struct to the original one that it stores. If they are equal, we use the storedTokenStream
- otherwise, we pretty-print and retokenize.There are some details that would need to be worked out:
Nonterminal
) has an attribute macro removed before it is expanded.AstEq
trait, which we derive instead ofPartialEq
. It's conceivable that we might want to implementPartialEq
for a 'looser' form of equality on some AST structs in the future, which would be incompatible with the 'strict' equality (e.g. checking thatNodeId
s match exactly) required by this check. However, doing so might be overkill.PartialEQ
could conceivable be higher than pretty-printing and re-tokenizing - we would need to benchmark it.However, adopting this scheme would allow us to bypass all issues resulting from inconsistencies in the pretty-printer. While we would still require the pretty-printer to produce correct code (when we detect a mismatch), the proc-macro infrastructure would no longer require it to exactly preserve certain information (empty
where
clauses, extra parentheses, etc.)Alternatives:
MutVisitor
, create some kind of smart pointer that invalidates theTokenStream
when mutable access is required. I attempted several variants of this approach, but all proved to be extremely invasive, requiring changes across the entire compiler.#[cfg]
s and inner attributes correctly). While this approach might work, it seems dangerous - any modifications to the AST done by the compiler risk getting lost whenever proc-macros are involved. While I'm pretty sure that#[cfg]
s are the only place where we intentionally modify the AST before a proc-macro sees it, we might inadvertently introduce additional modifications in the future.The text was updated successfully, but these errors were encountered: