1

I have a macro_rules that takes a function_name and calls function_name_x(). Right now I do that by passing function_name as an ident, and creating the new function name via concat_idents!.

The problem with this approach is that my IDE does not lint the function_name part because it takes is as an ident. I'd like to change that to a ty but I can't use it in concat_idents! if I do that.

Is there a solution?

macro_rules! decl_func {
    ($env:expr, $func:ident) => {{
        let f = concat_idents!($func, _to_ocaml);
        let binding = f($env, None);
        println!("{}", binding);
    }};
}
Herohtar
  • 5,347
  • 4
  • 31
  • 41
David 天宇 Wong
  • 3,724
  • 4
  • 35
  • 47
  • *"my IDE does not lint the `function_name` part"* - can you clarify the desired behavior? Its not clear to me what linting you're talking about. – kmdreko Oct 01 '21 at 21:37
  • `concat_idents` is nightly only. Might want to look into [the paste crate](https://lib.rs/crates/paste) – Colonel Thirty Two Oct 01 '21 at 23:12
  • I should remark that if you want an identifier, you shouldn't use `ty` since it'll accept things like paths `some::nested::Struct` and templates `Vec::` – kmdreko Oct 02 '21 at 01:48
  • @kmdreko I want it to tell me if the function/type I wrote doesn't exist, and I want to be able to click to go to the definition (VSCode) – David 天宇 Wong Oct 02 '21 at 05:27
  • @kmdreko I realize that `ty` is probably not suited for a function name. Perhaps `tt` would be better? Right now my IDE doesn't seem to recognize that I'm passing a function name there. – David 天宇 Wong Oct 02 '21 at 05:28

1 Answers1

2

Judging by your usage, I would say ident is the correct choice. While other things like, path, tt, expr may be appropriate for accepting a function in general, since you're only using it to create another identifier, ident makes the most sense.


By way of sophisticated monte-carlo analysis, the way rust-analyzer seems to handle syntax detection in macros by expanding it first, assessing what code is generated, and then back-referencing that info to the original macro parameters.

In the following example, the decl_func1 call correctly links the function_name identifier to the function definition, while decl_func2 call does not.

fn function_name() {}

macro_rules! decl_func1 {
    ($func:ident) => {
        $func();
    };
}

macro_rules! decl_func2 {
    ($func:ident) => {};
}

fn main() {
    decl_func1!(function_name);
    decl_func2!(function_name);
}

So in your code, rust-analyzer won't make the connection to function_name because the generated code only has function_name_x.


A potential workaround would be to introduce a statement into the macro that would syntactically use the identifier as is, but in a way where it ultimately does nothing. This may be a good idea anyway so that the compiler, not just rust-analyzer, can verify the identifier is what it should be. A couple ideas:

// would check that it is some kind of value
let _ = &$func;
// would check that it is a function with a signature that can take these types
let _ = || $func(1, 2);
kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • thanks for the answer! BTW I'm doing this because I have another attribute macro that generates a `func_to_ocaml()` function from a `#[my_macro]func()`. It feels a bit hacky, if it was a type I could generate an implementation on it and it would be cleaner... – David 天宇 Wong Oct 02 '21 at 19:20