Skip to content
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

Go-to-definition and auto-completion inside a macro_rules invocation #14999

Open
LukeMathWalker opened this issue Jun 7, 2023 · 12 comments
Open
Labels
C-feature Category: feature request

Comments

@LukeMathWalker
Copy link

In Pavex, there is a f! macro. It desugars to

macro_rules! f {
    ($($p:tt)*) => {{
        $crate::reflection::RawCallable {
            import_path: stringify!($($p)*),
            registered_at: ::std::env!("CARGO_PKG_NAME")
        }
    }};
}

The expectation is that the argument passed to f! is a path to a Rust type (either a method or a function).

In RA, as soon as you are writing inside the f! context (e.g. f!(crate::...)), you don't get any autocompletion.
Once the path is full spelled out (e.g. f!(crate::get_articles)), you also don't get the "go to definition" assist.

Is there a way to improve the experience here? Is there something that I can do, as the macro author, to help RA in dealing with this case?

@LukeMathWalker LukeMathWalker added the C-feature Category: feature request label Jun 7, 2023
@Veykril
Copy link
Member

Veykril commented Jun 7, 2023

The problem here is that stringify "erases" the input tokens, so there are no semantics attached to them. The trick to make this work better for r-a would be to also somehow use the path in a different way that fits the context you expect it to be used. This only really works well for type paths at the moment though, as any other dummy use usually has some form of annoying semantics that could either cause compilation errors or other problems.

In your case it looks like the input is effectively an import path, in which case you might have look with just doing the following

macro_rules! f {
    ($($p:tt)*) => {{
+        use $($p)* as _;
        $crate::reflection::RawCallable {
            import_path: stringify!($($p)*),
            registered_at: ::std::env!("CARGO_PKG_NAME")
        }
    }};
}

(Note the order will be important, if the use comes after stringify some features will bail out after seeing the use in stringify)

@Veykril
Copy link
Member

Veykril commented Jun 7, 2023

Ideally we'd offer a tool attribute that allows macro authors to instruct r-a to handle specific macro inputs in a special way, but that hasn't happened yet and probably needs to wait for some other architecture work first.

@LukeMathWalker
Copy link
Author

I see, thanks for the explanation!

Unfortunately the path here is an expression path rather than an import path.
These are all valid usage examples:

f!(crate::my_function)
f!(crate::MyType::method)
f!(crate::my_g_function::<Type>)
f!(<crate:: MyType as MyTrait>::trait_method)

I don't think macro rules are necessarily powerful enough for me to craft the "right" usage example in each case. I could probably do it with a procedural macro, but I don't know if RA would be able to see through that.

@HKalbasi
Copy link
Member

HKalbasi commented Jun 7, 2023

I think let _ = $($p)*; would work if they are always methods (or side effect free expressions).

@LukeMathWalker
Copy link
Author

That's what I used to have at the very beginning, but it breaks down (i.e. inference errors) if the function/method has any generic parameter without a default value 😔

@LukeMathWalker
Copy link
Author

LukeMathWalker commented Jun 7, 2023

A thing that I could do, without any negative side-effect, is changing the matcher from

macro_rules! f {
    ($($p:tt)*) => {{ /* */ }};
}

to

macro_rules! f {
    ($p:expr) => {{ /* */ }};
}

but I'm not seeing any difference in the behaviour of rust-analyzer. I'm assuming that it doesn't use the matcher type as hint 🤔

@Veykril
Copy link
Member

Veykril commented Jun 7, 2023

It does not currently, that's something on our wishlist as well still for some other things, but it wouldn't be a goal for this either (#11183). What you really want here is the tool attribute approach I was talking about earlier. #11556
That would allow something odd like:

macro_rules! f {
    ($($p:tt)*) => {{
+        #[rust_analyzer::treat_path(expr, $($p:)*)]
+        use crate as _;
        $crate::reflection::RawCallable {
            import_path: stringify!($($p)*),
            registered_at: ::std::env!("CARGO_PKG_NAME")
        }
    }};
}

to instruct r-a. But for that we'd first need either stable tool attributes or special case rust-analyzer like rustfmt and clippy are as implicit tools already (technically possible now that r-a is an official rust lang tool). And then obviously figure out nice naming conventions for these attributes as we kind of need to promise a stable api for them...

@LukeMathWalker
Copy link
Author

I see. Is there any movement on special-casing r-a or is the feature blocked on a "general" tool annotation feature in the language?

@Veykril
Copy link
Member

Veykril commented Jun 7, 2023

I don't think anyone really tried asking whether we can make r-a a special cased tool as well, the general tool attribute feature is not really being worked on to my knowledge.

@LukeMathWalker
Copy link
Author

vlad on the IntellijRust repository suggested a fix that ended up working for r-a but not for IntellijRust: add a perma-disabled let binding in the macro-generated code, i.e.

#[cfg(always_disabled)]
let _ = $p;

(see intellij-rust/intellij-rust#10543 (comment))

This circumvents the inference issues but it seems to be enough to tip r-a into doing the right thing 🎉

@Veykril
Copy link
Member

Veykril commented Jun 8, 2023

That will only partially work in r-a as well though. As that is effectively disabled, most analysis will not work with it and we fall back to some heuristics or nothing depending on the feature. And in the future I personally want to get rid of those heuristics because they re-derive facts that were already done in our semantics layer for paths 😅, but that is still some time in the future so until then this trick is better than nothing I guess :)

@LukeMathWalker
Copy link
Author

I can totally see this not being a long-term solution, but it seems to enable the two analyses I care about (completion and go-to-definition) so it's a decent stopgap solution :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature Category: feature request
Projects
None yet
Development

No branches or pull requests

3 participants