Macros must expand to a complete syntax element (e.g. item, expression etc). A list of fields is not a complete anything.
The way to do this is with a recursive macro that builds up partial results by passing them to itself, and then generates the output all at once. This is often called a muncher because it methodically munches through its input.
Your macro, converted to a muncher, would look something like this:
macro_rules! new_struct {
// input is empty: time to output
(@munch () -> {$(#[$attr:meta])* struct $name:ident $(($id:ident: $ty:ty))*}) => {
$(#[$attr])* struct $name { $($id: $ty),* }
};
// branch off to generate an inner struct
(@munch ($id:ident: struct $name:ident {$($inner:tt)*} $($next:tt)*) -> {$(#[$attr:meta])* struct $($output:tt)*}) => {
new_struct!(@munch ($($inner)*) -> {$(#[$attr])* struct $name});
new_struct!(@munch ($($next)*) -> {$(#[$attr])* struct $($output)* ($id: $name)});
};
// throw on the last field
(@munch ($id:ident: $ty:ty) -> {$($output:tt)*}) => {
new_struct!(@munch () -> {$($output)* ($id: $ty)});
};
// throw on another field (not the last one)
(@munch ($id:ident: $ty:ty, $($next:tt)*) -> {$($output:tt)*}) => {
new_struct!(@munch ($($next)*) -> {$($output)* ($id: $ty)});
};
// entry point (this is where a macro call starts)
($(#[$attr:meta])* struct $name:ident { $($input:tt)*} ) => {
new_struct!(@munch ($($input)*) -> {$(#[$attr])* struct $name});
// ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// input output
}
}
new_struct! {
#[derive(Debug)]
struct Foo {
foo: i32,
bar: struct Bar {
bar: i32,
foobar: i64
}
}
}
fn main() {
println!("{:#?}", Foo { foo: 1, bar: Bar { bar: 2, foobar: 3 } });
}
I took some liberties with your syntax because a few things didn't make sense: like where did you plan to get field names or type names for the inner structs?
The general strategy is to transform the input, with its nested structs, into a flat structure that looks like #[$attr]... struct $name { $id: $ty, ...}
. But this is kept in the $($output:tt)*
argument until the macro is ready to output the whole thing. You can put #![feature(trace_macros)] trace_macros!(true);
at the top of the file to see this happening.
I'm happy to add more comments to my code if needed. By the way, I highly recommend reading Daniel Keep's The Little Book of Rust Macros, it explains lots of tricks like this.