Cannot use import of standard library or 3rd party libraries inside quote! when import within proc macro file

In my proc macro I want to use imports (including standard library, 3rd party library and my own functions etc) inside quote!, but seems like quote! not allow me to do this when I use import within macro file like below:

// here is import I want to use in quote!
use std::io::Cursor;
use proc_macro::{TokenStream};
use proc_macro2::Ident;
use quote::{quote, format_ident};
use paste::paste;
use syn::{parse_macro_input, DeriveInput, ItemStruct, Token, punctuated::Punctuated, NestedMeta};

#[proc_macro_derive(Packet, attributes(dynamic_field))]
pub fn derive(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, .. } = parse_macro_input!(input);

    let field_data = fields.iter().filter_map(|f| {
        let ident = f.ident.clone().unwrap().to_string();
        let field_type = f.ty.clone();
        let ident = format_ident!("{}", f.ident.as_ref().unwrap());

        if f.attrs.iter().any(|attr| attr.path.is_ident("dynamic_field")) {
            Some(quote! { Self::#ident(); })
        } else {
            None
        }
    }).collect::<Vec<_>>();

    let fields_names = fields.iter().map(|f| {
        f.ident.clone()
    }).collect::<Vec<_>>();

    let value = quote!(1);

    let output = quote! {
        impl #ident {
            pub fn from_binary(buffer: &Vec<u8>) -> Self {
                // here is Cursor that I imported before
                let reader = Cursor::new(buffer);

                // ...

                Self {
                    #(#fields_names: Default::default()),*
                }
            }
        }
    };

    TokenStream::from(output)
}

on compile the error is:

error[E0433]: failed to resolve: use of undeclared type `Cursor`
 --> src\main.rs:6:10
  |
6 | #[derive(Packet, Debug)]
  |          ^^^^^^ use of undeclared type `Cursor`
  |
  = note: this error originates in the derive macro `Packet` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider importing one of these items
  |
4 | use std::collections::linked_list::Cursor;
  |
4 | use std::io::Cursor;

but when I do same import in file (in my case main.rs) where I use #[derive(Packet)], all works OK:

// this import helps
use std::io::Cursor;
use my_proc_macros_lib::Packet;

#[derive(Packet, Debug)]
struct Test {
    #[dynamic_field]
    size: u8,
    #[dynamic_field]
    size1: u16,
    #[dynamic_field]
    size2: u32,
    #[dynamic_field]
    field2: u64,
    field3: u8,
}

impl Test {
    // ...
}

fn main() {
    Test::debug();
    let test = Test::from_binary(&vec![]);
    println!("{:?}", test);
}

Could somebody explain how to correctly manage the imports in this case ? I am not sure it's good way to import all outside the macro.

quote! isn't magic; it's simply creating code which is pasted into the crate which uses the proc macro. When the code is expanded into the calling crate, it knows nothing about what names you had defined in your proc macro crate.

Because of this, you need to use names which will work in the crate using the proc macro. Typically, this means using full paths everywhere (e.g. ::std::io::Cursor) instead of short names. You can reduce the clutter a bit by using a local; e.g. if you have let Cursor = quote!(::std::io::Cursor);, you can use #Cursor in latter quote!s instead of ::std::io::Cursor.

And unfortunately, there's currently no way to refer to a library crate other than assuming that the expansion crate has it as a non-renamed dependency and using it by name (e.g. ::my_runtime_lib::Item).

For functionlike! macros specifically (but not derives or attributes), there's a trick you can use: in your runtime crate, instead of just pub useing the proc macro from the proc macro crate, do

#[macro_export]
macro_rules! functionlike {($($tt:tt)*) => {
    $crate::mycrate_macros::functionlike! {
        #![crate = $crate]
        $($tt)*
    }
}}

and your proc macro crate can use the injected $crate token to refer to the runtime crate. Generally this level of paranoia isn't necessary, though.

2 Likes

thank you for detailed explanation !