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.