Is it possible to call a function in the calling crate from within a proc_macro?

Here's the problem I'm trying to solve:

// myproject_codegen/src/lib.rs
[#proc_macro]
pub fn parse(input: TokenStream) -> TokenStream {
  // this obviously doesn't work as written
  let struct_result = dep_crate::parse(#input.to_string(), #settings);
  TokenStream::from(struct_result)
}

The macro is being called from within the same crate as the parser_settings function comes from:

// myproject/src/foo.rs
pub fn parser_settings() -> dep_crate::Settings {}
// myproject/src/other.rs
mod foo;

pub processed() -> Vec<MyStruct> {
  let settings: dep_crate::Settings = foo::parser_settings();
  vec![
    parse!("string"),
    parse!("other string"),
    // this would be fine too
    parse!("third string", settings),
  ]
}

Is this possible? I've tried innumerable ways of accessing the value of settings... what am I missing?

1 Like

When the procedural macro is called the calling crate is not yet compiled because it first needs the result from the macro.

You could try defining the settings as parameters of the procedural macro and using a declarative macro to redefine with your settings

1 Like

Proc-macros only operate on syntax, so you need to feed the settings values "explicitly" in a a syntactical manner; one way to do this would be to:

// myproject/src/foo.rs
macro_rules! with_parser_settings {( $($rules:tt)* ) => ({
    macro_rules! __emit__ { $($rules)* } __emit__!(
        // here comes, *verbatim* what your proc-macro will see
        Settings {
            foo: true,
            bar: false,
        }
    )
})} pub(in crate) use with_parser_settings;

Basic usage:

pub fn parser_settings () -> dep_crate::Settings {
    with_parser_settings! {( $settings:expr ) => (
        $settings
    )}
}

The pattern may look weird (why is it not macro_rules! m {() => (Settings { … })}? –you may ask, and the answer is that this is the general way to get a macro to be expanded before another macro / to emit a macro's output to another macro (in this instance, your parse! proc-macro):

// myproject/src/lib.rs
mod foo;

pub fn processed() -> Vec<MyStruct> {
    foo::with_parser_settings! {( $settings:expr ) => ({
        vec![
            parse!("string", $settings),
            parse!("other string", $settings),
        ]
    })}
}

And then, within your proc-macro crate, you have to parse those settings:

struct Settings {
    foo: bool,
    bar: bool,
}

impl ::syn::Parse for Settings {
    fn parse (input: ::syn::ParseStream<'_>)
      -> ::syn::Result<Settings>
    {
        mod kw {
            ::syn::custom_keyword!(foo);
            ::syn::custom_keyword!(bar);
        }
        let (mut foo, mut bar) = (None, None);
        while input.is_empty().not() {
            let lookahead = input.lookahead1();
            match () {
                _case if lookahead.peek(kw::foo) => {
                    if foo.is_some() { return Err(input.error("Duplicate `foo` found")); }
                    let _: kw::foo = input.parse().unwrap();
                    let _: ::syn::Token![ : ] = input.parse()?;
                    let v: ::syn::LitBool = input.parse()?;
                    foo = Some(v.value());
                },
                _case if lookahead.peek(kw::bar) => {
                    if bar.is_some() { return Err(input.error("Duplicate `bar` found")); }
                    let _: kw::bar = input.parse().unwrap();
                    let _: ::syn::Token![ : ] = input.parse()?;
                    let v: ::syn::LitBool = input.parse()?;
                    bar = Some(v.value());
                },
                _default => return Err(lookahead.error()),
            }
            let _ = input.parse::<Option<::syn::Token![ , ]>>()?;
        }
        Ok(Settings {
            foo: foo.ok_or_else(|| input.error("Missing `foo`"))?,
            bar: bar.ok_or_else(|| input.error("Missing `bar`"))?,
        })
    }
}

struct Input {
    s: String,
    settings: Settings,
}

impl ::syn::Parse for Input {
    fn parse (input: ::syn::ParseStream<'_>)
      -> ::syn::Result<Input>
    {
         let s: ::syn::LitStr = input.parse()?;
         let _: ::syn::Token![ , ] = input.parse()?;
         Ok(Input { s: s.value(), settings: input.parse()? })
    }
}

#[proc_macro] pub
fn parse (input: TokenStream)
  -> TokenStream
{
    let Input { s, settings } = ::syn::parse_macro_input!(input);
    let struct_result = ::dep_crate::parse(s, settings);
    ::quote::ToTokens::into_token_stream(struct_result).into()
}

That being said, this kind of "meta-crossover" between a struct (Settings in this instance), is very uncommon in practice; in your example it looks like foo.rs ought to be moved to myproject_codegen.

If you really need those Settings in myproject as well, then I suggest you factor out the common settings definition out of foo.rs into a file (probably a .toml or .yaml file) that would be loaded by both of these crates at compile-time; one basic such example can be achieved by (ab)using include! or #[path = "../path/to/settings.rs"] mod settings; declarations.

I appreciate the answers, I was able to split my code into separate crates and the way of calling a function and accessing surrounding vars is making more sense now.

One thing that I'm still unclear on about this approach though. As mentioned in my first post I'm wanting to store the output of a dependency's function at compile time. So from this (which I have working today):

static RESULTS = vec![
  parse!("some string"), // calls dep_crate::parse() at runtime
  parse!("another"),
]

dep_crate::use_result(&*RESULTS.get("key"));

to this

static RESULTS = vec![
  parse!("some string"), // instead call dep_crate::parse() at compile time
  parse!("another"),
]
// later, at runtime
dep_crate::use_result(&*RESULTS.get("key"));

I headed into this thinking that a struct could easily be saved at compile time but... it's a complex struct that contains child structs which aren't marked pub in the dependency.

Is it possible to do this when I don't have control of the structs? I have no need to know what's in result and don't want to modify it at all, I simply want to store it as-is in the binary for runtime.

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.