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

Rust Analyzer's auto-completion is weakened by the #[component] macro #1781

Closed
pikaju opened this issue Sep 24, 2023 · 5 comments · Fixed by #1782
Closed

Rust Analyzer's auto-completion is weakened by the #[component] macro #1781

pikaju opened this issue Sep 24, 2023 · 5 comments · Fixed by #1782

Comments

@pikaju
Copy link
Contributor

pikaju commented Sep 24, 2023

Describe the bug

The #[component] proc macro makes Rust Analyzer less capable at auto-completing code when a syntax error is present. Consider the following example code:

#[component]
pub fn Example(cx: Scope) -> impl IntoView {
    let // <-- Causes a syntax error

    let (value, set_value) = create_signal(cx, initial_value);

    set_value. // <-- Place your cursor after this dot and try auto-completing with Rust Analyzer.
}

When placing the cursor in the specified location, r-a does not give me auto-complete suggestions. However, if I remove the #[component] attribute, r-a is smart enough to provide suggestions, despite the syntax error in line 3.

Explanation

I believe this issue occurs because of the way the component proc-macro is implemented. It uses the syn crate to parse the whole function body and then serializes it again. If an error occurs during parsing, the proc-macro will exit with only a compile error, and no actual code.

How it could be better (I think)

Rust Analyzer can work with code that is emitted from a proc-macro even if it is prefixed or followed up with a compile error. In the case of this (relatively) simple attribute macro, it would be sufficient to emit the input token stream in the case of a parsing error. Since I've noticed you are using the proc_macro_error crate, this could be done with

proc_macro_error::set_dummy(input)

at the beginning of the component macro. This might even remove the necessity to output the error generated by syn for the function body.

See also:

Rust Analyzer setup

I use the following Neovim configuration for Rust Analyzer. Notably, I have macro expansion enabled:

lspconfig.rust_analyzer.setup {
  cmd = { "rustup", "run", "stable", "rust-analyzer" },
  on_attach = on_attach,
  capabilities = capabilities,
  settings = {
    ["rust-analyzer"] = {
      procMacro = {
        enable = true,
      },
      checkOnSave = {
        command = "clippy",
      },
    },
  },
}

Leptos Dependencies

leptos = "0.4.10"
@gbj
Copy link
Collaborator

gbj commented Sep 24, 2023

Thanks. This is an issue that's been discussed for a long time, but yours is the first suggestion I've heard on how to fix it, which I appreciate.

Playing around I was not able to get a very simple approach to set_dummy to work (just invoking set_dummy(s.clone().into()) at the top of the component function in leptos_macro) in a way that changed the autocomplete behavior. It sounds like you know more about it than I do -- I would definitely welcome a PR that improves this part of the experience for #[component] and #[server].

@kerkmann
Copy link
Contributor

kerkmann commented Oct 22, 2023

Sorry, I hate to reopen this issue but I think the "hacky fix" from the PR #1782 isn't working as expected. As far as I can tell is, that exactly problem still exists. I've tested it with the #[component] and without the #[component] macro and I am getting still error by using that macro.

And Like the issue is pinpointing, i am just getting that error. when there is an error. (for example by typing the dot). The rust-analyzer is yelling with all the errors, but I can't get any ide completion running. :(

Tested with Helix and NeoVIM with leptos 0.5.0, 0.5.1, main branch 3e08486 and latest rust-analyzer running.

@pikaju @blorbb or @gbj , do you have any other idea what we could try out? I would like to help and fix that problem but I am running out of ideas. ^^"

@kerkmann
Copy link
Contributor

kerkmann commented Oct 22, 2023

Okay, I've spent hours trying to figure out how it could be improved, and the short answer is, "you can't."

The idea @pikaju had is completely the right way. This helps to perform operations like renaming or completion. For example, that example is working with that hacky way:

#[component]
pub fn SomeComponent() -> impl IntoView {
    let (flag, set_flag) = RwSignal::new(false);

    // If you create a new line here, you can type "fla" and the code completion will give you the word "flag", that is working

    view! {
        <div>{flag}</div>
    }
}

But now comes the "but" part, which isn't working. Let me first show you the example:

#[component]
pub fn SomeComponent() -> impl IntoView {
    let (flag, set_flag) = RwSignal::new(false);

    // Here you are trying to get completion for the WriteSignal part of the "set_flag" and that part is not working. :(
    set_flag. 

    view! {
        <div>{flag}</div>
    }
}

As far as I understand, this is something that can't be fixed. I've spoofed the packages and TokenStream, which is going to be sent to the rust-analyzer, but the rust-analyzer will NOT give a list of items back when you are using proc macros. That's something that's kind of sad, but maybe some Rust developers will fix that later. :/

As @pikaju found out, this issue was already discovered in Actix-Web, Tracing, Tokio and async-trait. Tracing solved it as @pikaju did, in a more or less larger way. There is also an easier and smaller way to catch that special case; all you need to do is give back the original TokenStream and extend it with the compile_error!. That's how Tokio solved it in this PR.

In short, there is a more extensive article in the rust-analyzer repository /blogpost and Improving autocompletion in your Rust macros.

Sadly, there is no better solution for the proc macro. But! We have one solution so far. Instead of using the component macro, you can use a struct and implement the trait for it, like this:

pub struct App;

impl IntoView for App {
    fn into_view(self) -> View {
        provide_meta_context();
        let [flag, set_flag] = RwSignal::new(false);
        
        // That completion is now working! hurray!
        set_flag.

        view! {
            <div>{flag}</div>
        }
        .into()
    }
}

I'm not sure, but does it make sense to pinpoint that problem somewhere in the docs @gbj? So other developers know that, at the current state, the completion from the rust-analyzer isn't working so well? Please let me know. :)

@gbj
Copy link
Collaborator

gbj commented Oct 22, 2023

@kerkmann the only meaningful functional of the component macro provides is to create the props struct so that the component can be used in the view macro with named props, since named function arguments don't exist in Rust. So I guess another possibility would be to create a derive macro that lets you construct an explicit props struct, and have a component as an unannotated function that takes that props struct as its only argument.

The other option is to disable rust-analyzer expansion on the component macro, and that's documented here

@kerkmann
Copy link
Contributor

kerkmann commented Oct 22, 2023

So I guess another possibility would be to create a derive macro that lets you construct an explicit props struct, and have a component as an unannotated function that takes that props struct as its only argument.

Sounds interesting to me, I think I will tinker around into that approach, thanks. :D

The other option is to disable rust-analyzer expansion on the component macro, and that's documented here
And that is not working for me, for whatever reason. I can search for the basewords, but it stops working after pressing the . ^^"
After that I can not trigger the completion, also not with the lsp command and the commands in Helix and neovim.

That's what I mean with "it's only partly working". ^^"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants