-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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 TokenMap
with an abstraction that matches reality
#9403
Comments
Hmm, isn't the issue here just that most of our span-related APIs are stubs (for example, the |
Kind of — we can redefine the id to mean the identity of span (a pair of location, hygiene) rather than the identity of some original token. But today TokenId is very much tied to the identity of the token itself. |
So, let me summarize the issue again. At the moment, macro expansion in rust-analyzer works by mapping tokens to tokens. If you have a recursive macro, you can start with the token in the original invocation, than map it to the token in the next invocation, then repeat, etc. The core primitive are
Note that in practice, models are mostly compatible, as, typically, each expansion token has the same span as some invocation/definition token. This doesn't hold universally though, the two exceptions being:
So, to solve the problem, we need to change the primitive operations of |
What does |
Added the link: join takes two spans from the same file and returns the span, covering both of them. |
+1 for use rustc model
And current token mapping mechanism could not handle the followings too :
https://github.com/rust-analyzer/rust-analyzer/blob/e5c1c8cf2fcfae3e15c8bcf5256e84cad3bd3436/crates/proc_macro_srv/src/rustc_server.rs#L666
On Mon, 5 Jul 2021 at 02:59, Aleksey Kladov ***@***.***> wrote:
Added the link: join takes two spans from the same file and returns the
span, covering both of them.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9403 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACUA7Z32P667355GCBOICCLTWCVSTANCNFSM47JLVPRQ>
.
--
Cheers,
Edwin Cheng
|
Thinking about this, I suggest a following model (a bit close to what we have today, a bit different):
This data should support two operations: First, given a token in the syntax tree, find a macro_rules! m { () => { 1 + () } } // we don't want an error here
fn f() { m!() } // we want the error here here, just the FileRange info won't allow the diagnostic to point to Other than that, this should be a straightforward operation. Second, we need the reverse -- given a range in the file, select the (deepest in terms of expansion) syntax tree that contains this range. One way to do this would be to look at all the macro expansions in the universe. This is essentially the approach used by old RLS: it has a giant table with all spans and binary-searches the relevant offset there. In rust-analyzer, we want to avoid that global search. So we make the following modification to the algorithm. We first identify a macro expansion where the range might come from, and then look for relevant span only in that expansion. In practice this means one of the two cases:
Note that this approach is meaningfully less powerful. In theory, a procedural macro can create a token with a span pointing anywhere into your project. With RLS model, we'll find that span. With rust-analyzer model, we won't. A less hypothetical scenario is when a proc macro (eg parser generator) reads input from external file (grammar in specific meta syntax) and uses spans from that. If the user then "find all references" on the production in the grammar, in theory we should be able to do that (and even find references between grammar rules themseves, if the spans are set up properly). In the proposed system, we'll need some extra bit of info which tells us that the grammar file relates to a particular macro expansion. A similar effect can be observed today with a hidden include: macro_rules! m { () => include!("foo.rs") }
fn f() {
m!()
} In this situation, when we invoke "find usages" in the |
Fixed in #15959 |
AKA, @matklad have been misunderstanding how macro expansion works this whole time.
Background: originally, I thought about macro expansion process as transforming a stream of tokens into a different strem of tokens:
Here, I thought that token
foo
gets translated from macro call site to macro expansion site.This motivated the
TokenMap
and related abstractions. The idea is that we assign ids to tokens (=tokens have identity), and track those ids through macro expansion. Yesterday, having looked at https://doc.rust-lang.org/stable/proc_macro/struct.Span.html, I concluded that this is not, in fact, how the world works.Consider these two procedural macros:
I believe their semantics is the same -- from rustc point of view, they produce equivalent outputs. The implementation of
id2
completely erases identity though.So, bad news, we need to rewrite TokenMap-based stuff to use something else (and I don't know what that something else would be). Good news -- I think this should make more weird cases like
include
work in a more out-of-the-box way perhaps?cc @jonas-schievink , @edwin0cheng
The text was updated successfully, but these errors were encountered: