How to import procedural macros that is not in lib.rs?

@Davidliudonghao you cannot define #[proc_macro…] functions outside the root file of the proc-macro = true crate (usually lib.rs).

You have two options:

  • the most common and cleanest one, is to define the functions at the top-level file (lib.rs) by delegating to a call to the function with the meat of the logic:

    //! `src/lib.rs`
    use ::proc_macro::TokenStream;
    
    #[proc_macro_attribute] pub
    fn foo (attrs: TokenStream, input: TokenStream)
      -> TokenStream
    {
        my_module::foo(attrs.into(), input.into())
            .unwrap_or_else(|err| err.to_compile_error())
            .into()
    }
    
    …
    
    mod my_module;
    
    //! `src/my_module.rs` or `src/my_module/mod.rs`
    
    #![allow(unused_imports)]
    use ::proc_macro2::TokenStream /* as TokenStream2 */;
    use ::quote::{format_ident, quote, quote_spanned, ToTokens};
    use ::syn::{*,
        parse::{self, Parse, ParseStream, Parser},
        punctuated::Punctuated,
        Result, // explicitly shadow Result
    };
    
    pub
    fn foo (attrs: TokenStream, input: TokenStream)
      -> Result<TokenStream>
    {
        let _: parse::Nothing = parse2(attrs)?; // notice how we get to use `?` in the function's body 👌
        let fun: ItemFn = parse2(input)?;
        …
        Ok(quote!(
            …
        ))
    }
    
  • The other option is to use include!("my_module.rs") instead of mod my_module;, although this has the drawback of flattening all your crate namespacing, possibly leading to clashes. This allows you to write #[proc_macro…] functions inside the include!d files.


Regarding the first option, if you have many such proc-macros, you can define a helper macro_rules! to wrap the stuff for you:

macro_rules! reexport_proc_macro_attribute {
  (
    pub use $module:ident :: {    
        $(
            $( #[doc = $doc:expr] )*
            $fname:ident
        ),+ $(,)?
    };
  ) => (
    $(
        $( #[doc = $doc] )*
        #[proc_macro_attribute] pub
        fn $fname (
            attrs: ::proc_macro::TokenStream,
            input: ::proc_macro::TokenStream,
        ) -> ::proc_macro::TokenStream
        {
            $module::$fname(attrs.into(), input.into())
                .unwrap_or_else(|err| err.to_compile_error())
                .into()
        }
    )*
  );

  (
    $( #[doc = $doc:expr] )*
    pub use $module_name:ident :: $fname:ident;
  ) => (
    reexport_proc_macro_attribute! {
        pub use $module_name::{ $( #[doc = $doc] )* $fname };
    }
  );
}
  • (and so on for proc_macro_derive and proc_macro)

That way you can write:

mod my_module;

reexport_proc_macro_attribute! {
    pub use my_module::{
        /// This is a very nice proc-macro that does …
        foo,
    };
}
1 Like