Help with macro_rules error

I'm trying to write a macro that will parse the result of some dbg!() output (so I can easily paste correct results into tests). Here's a simplified version of what I've got so far (there are more fields in the real version):

use std::collections::BTreeMap;

struct CommitInfo {
    parents: Vec<String>,
}


macro_rules! commit_info_graph {
    (
        $(
            $hash:literal : CommitInfo {
                $($field_name:ident : $field_value:tt),*
            }
        ),*
    ) => {{
        let graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
        $(
            graph.insert($hash.to_string(), CommitInfo {
                $(
                    commit_info_graph!(@field $field_name $field_value),
                )*
            });
        )*
        graph
    }};

    (@field parents [ $($hash:expr),* ]) => {
        parents: vec![$($hash, )*]
    };
    
}

fn main() {
    let expected_graph = commit_info_graph!(
        "04417144022049d97bc19e759d1955958e21f339": CommitInfo {
            parents: [
                "a6de41485a5af44adc18b599a63840c367043e39",
            ],
        },
        "a6de41485a5af44adc18b599a63840c367043e39": CommitInfo {
            parents: [
                "d3591307bd5590f14ae24d03ab41121ab94e2a90",
            ],
        },
        "d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitInfo {
            parents: [],
        },
    );
}

(Playground)

Unfortunately it doesn't work! I get this error:

error: no rules expected the token `}`
  --> src/main.rs:39:9
   |
8  | macro_rules! commit_info_graph {
   | ------------------------------ when calling this macro
...
39 |         },
   |         ^ no rules expected this token in macro call

I can't figure it out. Can anyone help? I tried trace_macros but it didn't add any extra information.

You have some extra trailing commas, you can fix it by strategically placing $(,)? like so: Rust Playground

After that, you'll need to deal with macros being unable to expand to fields in struct literals.

1 Like

You can't have a macro expand to the fields of a struct only; so I'd start with:

macro_rules! commit_info_graph {
    (
        $(
            $hash:literal : CommitInfo {
                $($field_name:ident : $field_value:tt),* $(,)? 
            }
        ),* $(,)? 
    ) => {{
        let graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
        $(
-           graph.insert($hash.to_string(), CommitInfo {
-               $(
-                   commit_info_graph!(@field $field_name $field_value),
-               )*
-           });
+           graph.insert($hash.to_string(), commit_info_graph!(@fields[]
+               $(
+                   $field_name $field_value,
+               )*
+           ));
        )*
        graph
    }};

-   (@field parents [ $($hash:expr),* ]) => {
-       parents: vec![$($hash, )*]
-   };

+   (@fields
+       [$($acc:tt)*]
+       parent [ $($hash:expr),* $(,)? ],
+       $($rest:tt)*
+   ) => (commit_info_graph! {
+       @fields[$($acc)*
+           parents: vec![$($hash.to_string(), )*],
+       ]
+       $($rest)*
+   });

+   (@fields [$($fields:tt)*] /* nothing left */) => (
+       CommitInfo { $($fields)* }
+   );
}

Oooo $(foo),* doesn't allow trailing commas? That is surprising given they are allowed everywhere else!

After that, you'll need to deal with macros being unable to expand to fields in struct literals.

Ah damn.... is there a workaround for that?

Ah I found this but I think I'll just change it to:

let tmp = CommitInfo::default();
$(
   tmp.$field = $value;
)*

Lots simpler.

Success! Thanks!

use std::collections::BTreeMap;

#[derive(Default, Debug)]
struct CommitInfo {
    parents: Vec<String>,
}


macro_rules! commit_info_graph {
    (
        $(
            $hash:literal : CommitInfo {
                $($field_name:ident : $field_value:tt),*
                $(,)?
            }
        ),*
        $(,)?
    ) => {{
        let mut graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
        $(
        
            graph.insert($hash.to_string(), {
                let mut info: CommitInfo = Default::default();
                $(
                    commit_info_graph!(@set_field info, $field_name, $field_value);
                )*
                info
            });
        )*
        graph
    }};

    (@set_field $object:ident, parents, [ $($hash:expr),* $(,)? ]) => {
        $object.parents = vec![$($hash.to_string(), )*];
    };
}

fn main() {
    let expected_graph = commit_info_graph!(
        "04417144022049d97bc19e759d1955958e21f339": CommitInfo {
            parents: [
                "a6de41485a5af44adc18b599a63840c367043e39",
            ],
        },
        "a6de41485a5af44adc18b599a63840c367043e39": CommitInfo {
            parents: [
                "d3591307bd5590f14ae24d03ab41121ab94e2a90",
            ],
        },
        "d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitInfo {
            parents: [],
        },
    );
    
    dbg!(expected_graph);
}