Macro Function Calls in Proc Macro Attributes Returns Error

Dependencies: Using Rust 1.64.0-nightly with Rocket 0.5.0-rc.2.

I try to procedural generate some Rocket end points:

#[macro_export]
macro_rules! generate_pageable_getter {
    ( $T:ident ) => {
        use paste::paste;
        paste! {
            #[get = include_str!(concat!("/get_", $T, "?<page>"))]
            pub async fn [<get_ $T:lower>](page: Option<u64>) -> RawJson<String> {
                // impl
            }
        }
    }
}

generate_pageable_getter{ MyCustomStruct }

However I get the following error:

error: key-value macro attributes are not supported

I was under the impression that 1.54 had enabled support for macro calls within attributes? Thanks!

With the aforementioned change:

  • it was syntactically valid (so that it would not error before macros were to get the chance to process it);
  • it was made semantically valid for the stdlib attributes (so that stdlib macros then processing that stuff would accept it (easy for them to do, since they have magic eager-expansion capabilities));

But user-made proc-macro attributes don't have that luxury: all they see is include_str!(concat!(…)), so unless they resorted to reïmplementing all that logic by themselves, they're kind of forced to reject it.

You can and should use a helper proc-macro[1] which has actually reimplemented that stuff, and offers an "eager-expansion friendly" API, precisely to solve this problem:

With it:

pub use ::paste;
pub use ::with_builtin_macros;

#[macro_export]
macro_rules! generate_pageable_getter {( $T:ident ) => (
    $crate::paste::paste! {
        $crate::with_builtin_macros::with_builtin! {
            let $contents = include_str_from_root!(
                concat!("/get_", $T, "?<page>"),
            ) in {
                #[get = $contents]
                pub async fn [<get_ $T:lower>](page: Option<u64>) -> RawJson<String> {
                    // impl
                }
            }
        }
    }
)}

  1. disclaimer: of mine ↩︎

Thank Yandros for your succinct response. That was very helpful in my understanding of the situation. It has been such a difficult journey trying to find documentation on macros.

I was trying to play around with the code to try to get your suggested changes to work. I realized in my original post I mistakenly used include_str! when all I needed was a string.

So I tried to do the following as suggested and attempted to play around a bit:

#[macro_export]
macro_rules! generate_pageable_getter {
    ($T:ident) => {
        $crate::paste::paste! {
            $crate::with_builtin_macros::with_builtin!{
                let $v_path = concat!("/get_", stringify!($T), "?<page>") in {
                    #[get = $v_path]
                    pub async fn [<get_ $T:lower>](page: Option<u64>) -> RawJson<String> {
                        // impl
                    }
                }
            }
        }
    }
}

The usage of this macro being:

struct Address {}
geenerate_pageable_getter!{Address}

Since get requires a string literal but my input is a identifier, I have to convert it to a string inside a concat!.
However I seem to get the following error:

error: key-value macro attributes are not supported
...
16 | /             $crate::with_builtin_macros::with_builtin!{
17 | |                 let $v_path = concat!("/get_", stringify!($T), "?<page>") in {
18 | |                     #[get = $v_path]
19 | |                     pub async fn [<get_ $T:lower>](page: Option<u64>) -> RawJson<String> {
...  |
47 | |                 }
48 | |             }
   | |_____________^

It appears that when I try to embed an additional stringify!() the with_builtin! does not expand the inner macro. I tried to nest a with_builtin! but ended up getting errors and using two let..in... led to errors about using let...in... twice.

Thank you for bearing with me. Appreciate any insights you may have.

Thanks for the info :slightly_smiling_face:

So, it turns out I skimmed a bit to quickly over your situation, and failed to notice something obvious :sweat_smile::

  • your case is #[get = "…"], not #[helper(get = "…")] nor outer! { … #[get = "…"] … }.

It turns out that the last two can be handled, then needing the usage of with_builtin!_, but your #[get = "…"] is simply invalid Rust syntax for the case of custom / procedural macro attributes.

I have looked at how Rocket works, and I think you may have swapped #[get("…")] with #[get = "…"].

So the fix would be:

#[macro_export]
macro_rules! generate_pageable_getter {
    ($T:ident) => {
        $crate::paste::paste! {
            $crate::with_builtin_macros::with_builtin!{
                let $v_path = concat!("/get_", stringify!($T), "?<page>") in {
-                   #[get = $v_path]
+                   #[get($v_path)]
                    pub async fn [<get_ $T:lower>](page: Option<u64>) -> RawJson<String> {
                        // impl
                    }
                }
            }
        }
    }
}
  • your nested usage of stringify! was correct, and something with_builtin does support :slightly_smiling_face:
1 Like

Got it! It works, thank you!

Would you happen to know if with_builtin would support casey::lower! (repo) or a way to be able to have a stringify! lowercase equivalent? I come to realize that exposing a "get_Address" endpoint breaks the naming convention with underscores :sweat_smile:

1 Like

I'll try to see if I find the time to extend that functionality :slightly_smiling_face:

In the meantime, since we have paste in scope, we can let it do the job for us :upside_down_face::

- concat!("/get_", stringify!($T), "?<page>")
+ concat!("/get_", stringify!([<$T:lower>]), "?<page>")
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.