rust-lang / rust-analyzer

A Rust compiler front-end for IDEs
https://rust-analyzer.github.io/
Apache License 2.0
14.09k stars 1.57k forks source link

Show rustdoc of proc macros #14772

Open peter-lyons-kehl opened 1 year ago

peter-lyons-kehl commented 1 year ago

Thank you for rust-analyzer.

Example: Attribute macros aliased on purpose from https://docs.rs/allow_prefixed/latest/allow_prefixed (or from https://docs.rs/allow_prefixed/latest/allow):

Crrent rust-analyzer version v0.3.1506 in current VS Code:

Version: 1.78.1
Commit: 6a995c4f4cc2ced6e3237749973982e751cb0bf9
Date: 2023-05-04T09:46:23.602Z
Electron: 22.5.1
Chromium: 108.0.5359.215
Node.js: 16.17.1
V8: 10.8.168.25-electron.0
OS: Linux x64 6.3.0-1-MANJARO
Sandboxed: No

Thank you in advance.

lowr commented 1 year ago

This is a really unfortunate consequence of #12603. We do show rustdoc of proc macros, but we fail to expand your internal macros to generate the attribute macros in the first place.

The problem is that your internal macro prefixed_lint! takes a path, which rustc would wrap in an opaque AST fragment upon macro expansion (see the Rust reference for details), but rust-analyzer does not (yes, it's a bug). So while your proc macros in allow_internal receive paths wrapped in TokenTree::Group when they're run by rustc, they receive plain paths when they're run by rust-analyzer.

One thing you can do to workaround this is to have prefixed_lint! and other helper macros take paths as tt fragment type, because rustc special-cases it and wouldn't wrap it in the said opaque fragment. This requires changing other internal proc macros like generate_allow_attribute_macro_definition_prefixed but should work properly with both rustc and rust-analyzer.

I hate to suggest a workaround instead of fixing the bug, but we've been failing to figure out how to resolve it properly, so this should be the quickest way to make your crate work in rust-analyzer. Sorry for the inconvenience.

peter-lyons-kehl commented 1 year ago

Hi Ryo,

Thank you so mach for deep insight. Workarounds are fine.

Only for reference, or for anyone curious or with a similar problem: Unfortunately, and to my surprise, paths (or some paths, at least: like clippy::alloc_instead_of_core, and any clippy:: or rustdoc:: valid lint paths) cannot be matched by tt macro variables. (As https://veykril.github.io/tlborm/decl-macros/minutiae/fragment-specifiers.html#tt says, tt "can match nearly anything" - but it doesn't say what correct/paired tokens tt can't match).

That's with 1.71.0-nightly (e9e1bbc7a 2023-05-17), and even if the macro by example takes only one parameter (while in my crate I need two parameters, or more). Example is at https://github.com/peter-kehl/macro_rules_path_as_tt:

❯ cargo check
    Checking macro_rules_path_as_tt v0.1.0 (/share/pkehl/GIT/macro_rules_path_as_tt)
error: no rules expected the token `::`
  --> src/lib.rs:12:41
   |
1  | macro_rules! prefixed_lint_versioned {
   | ------------------------------------ when calling this macro
...
12 |     prefixed_lint_versioned!(1.2, clippy::alloc_instead_of_core);
   |                                         ^^ no rules expected this token in macro call
   |
note: while trying to match meta-variable `$lint_path:tt`
  --> src/lib.rs:2:23
   |
2  |     ($major_minor:tt, $lint_path:tt) => {
   |                       ^^^^^^^^^^^^^

error: no rules expected the token `::`
  --> src/lib.rs:14:30
   |
6  | macro_rules! consume_path_only {
   | ------------------------------ when calling this macro
...
14 |     consume_path_only!(clippy::alloc_instead_of_core);
   |                              ^^ no rules expected this token in macro call
   |
note: while trying to match meta-variable `$lint_path:tt`
  --> src/lib.rs:7:6
   |
7  |     ($lint_path:tt) => {

Update: The above is even more strange, because I do have $lint_path:tt working with macro_rules. It's at

Anyway, I am exploring a different workaround: Since all prefixed lints have only one path level (either rustdoc:: or clippy:: prefix), I'll take the prefix and the lint name as two separate tt parameters, and I'll join them together into a tt (either with paste crate or manually). I'll post here.

peter-lyons-kehl commented 1 year ago

Another surprise: If a macro (by example) accepts a path fragment (like $lint_path:path), then such a macro CAN invoke another macro (by example) passing that meta variable, even if that other macro accepts it as tt (like $lint_path:tt):

macro_rules! consume_path_only_as_path_then_pass_down_as_tt {
    ($lint_path:path) => {
        consume_tt_only!($lint_path);
    };
}

macro_rules! consume_tt_only {
    ($lint_path:tt) => {};
}

pub fn test_macro() {
    // ...
    // the following does compile!
    consume_path_only_as_path_then_pass_down_as_tt!(clippy::alloc_instead_of_core);
}

(Updated https://github.com/peter-kehl/macro_rules_path_as_tt.)

peter-lyons-kehl commented 1 year ago

Thank you. I confirm that using tt all the way from the top level macro_rules! is a good enough workaround. I'm lucky - I have a small number of macro_rules! combinations/"paths" to handle. Otherwise this would not be feasible.

(Then my only problem was that rust-analyzer doesn't show rustdoc coming from stringify! in #[doc = stringify!()] - #8092. I've worked around by generating that from (another) proc macro, from another crate, of course - luckily I already had to use two levels of proc_macro crates.)

Feel free to close this issue (since it can be worked around), or keep open, as you see fit.