Some problems with a proc macro

Hi, I am trying to create a procedural macro, and I’m having some problems.
The complete crate is included on github.

The procedural macro should work as follows:

#[mutual_from(Hello1)]
struct Hello2 {
    a: u32,
    b: String,
}

Will add the following conversion implementations:

impl From<Hello1> for Hello2 {
    fn from(input: Hello1) -> Self {
        Hello2 {
            a: input.a,
            b: input.b,
        }
    }
}
impl From<Hello2> for Hello1 {
    fn from(input: Hello2) -> Self {
        Hello1 {
            a: input.a,
            b: input.b,
        }
    }
}

I wrote some code attempting to do this. This is the relevant part:

#[proc_macro_attribute]
pub fn mutual_from(
    args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // See: https://github.com/dtolnay/syn/issues/86
    // for information about arguments.

    // This is the name of the other struct:
    let remote_name = args.to_string();

    // let item: syn::Item = syn::parse(input).expect("failed to parse input into `syn::Item`");
    let input = parse_macro_input!(input as DeriveInput);

    // Name of local struct:
    let local_name = input.ident.to_string();

    let conversion = match input.data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => {
                // Example:
                // struct Point {
                //     x: u32,
                //     y: u32,
                // }
                let recurse1 = fields.named.iter().map(|f| {
                    let fname = &f.ident;
                    quote_spanned! { f.span() =>
                        #fname: input.#fname
                    }
                });
                // TODO: Is there a more elegant way to do this except cloning?
                let recurse2 = recurse1.clone();
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name {
                                #(#recurse1, )*
                            }
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name {
                                #(#recurse2, )*
                            }
                        }
                    }
                }
            }
            Fields::Unnamed(ref fields) => {
                // Example:
                // struct Pair(i32, f32);

                let recurse1 = fields.unnamed.iter().enumerate().map(|(i, f)| {
                    // TODO: Should we use Index::from(i) here?
                    // What happens if we don't?
                    quote_spanned! { f.span() =>
                        input.#i
                    }
                });
                // TODO: Is there a more elegant way to do this except cloning?
                let recurse2 = recurse1.clone();
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name(#(#recurse1,)*)
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name(#(#recurse2,)*)
                        }
                    }
                }
            }
            Fields::Unit => {
                // Example:
                // struct MyStruct;
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name
                        }
                    }
                }
            }
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    };

    let expanded = quote! {
        // Original structure
        #input
        // Generated mutual From conversion code:
        #conversion
    };

    proc_macro::TokenStream::from(expanded)
}

However, when trying to apply this attribute macro in a basic test:

use offst_mutual_from_derive::mutual_from;

#[derive(PartialEq, Eq, Clone, Debug)]
struct Hello1 {
    a: u32,
    b: String,
}

#[mutual_from(Hello1)]
#[derive(PartialEq, Eq, Clone, Debug)]
struct Hello2 {
    a: u32,
    b: String,
}

#[test]
fn basic() {
    let hello1 = Hello1 {
        a: 0u32,
        b: "some_string".to_owned(),
    };
    let hello2 = Hello2::from(hello1.clone());
    let hello1_new = Hello1::from(hello2.clone());
    let hello2_new = Hello2::from(hello1_new.clone());

    assert_eq!(hello1, hello1_new);
    assert_eq!(hello2, hello2_new);
}

I get the following compilation errors:

$ cargo test -p offst-mutual-from-derive
   Compiling offst-mutual-from-derive v0.1.0 (/home/real/projects/d/offst/components/mutual_from_derive)
error: expected type, found `"Hello1"`
 --> components/mutual_from_derive/tests/basic.rs:9:1
  |
9 | #[mutual_from(Hello1)]
  | ^^^^^^^^^^^^^^^^^^^^^^

error[E0433]: failed to resolve: use of undeclared type or module `Hello2`
  --> components/mutual_from_derive/tests/basic.rs:22:18
   |
22 |     let hello2 = Hello2::from(hello1.clone());
   |                  ^^^^^^ use of undeclared type or module `Hello2`

error[E0433]: failed to resolve: use of undeclared type or module `Hello2`
  --> components/mutual_from_derive/tests/basic.rs:24:22
   |
24 |     let hello2_new = Hello2::from(hello1_new.clone());
   |                      ^^^^^^ use of undeclared type or module `Hello2`

My guess is that I’m doing something incorrect with the attribute argument (“Hello1”), but I’m not really sure what. I tried to get some insight about what is happening using cargo expand, this was the result:

$ cargo expand -p offst-mutual-from-derive --test basic
    Checking offst-mutual-from-derive v0.1.0 (/home/real/projects/d/offst/components/mutual_from_derive)
error: expected type, found `"Hello1"`
 --> components/mutual_from_derive/tests/basic.rs:9:1
  |
9 | #[mutual_from(Hello1)]
  | ^^^^^^^^^^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type or module `Hello2`
  --> components/mutual_from_derive/tests/basic.rs:22:18
   |
22 |     let hello2 = Hello2::from(hello1.clone());
   |                  ^^^^^^ use of undeclared type or module `Hello2`
error[E0433]: failed to resolve: use of undeclared type or module `Hello2`
  --> components/mutual_from_derive/tests/basic.rs:24:22
   |
24 |     let hello2_new = Hello2::from(hello1_new.clone());
   |                      ^^^^^^ use of undeclared type or module `Hello2`
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0433`.
error: Could not compile `offst-mutual-from-derive`.
To learn more, run the command again with --verbose.

#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::v1::*;
#[macro_use]
extern crate std as std;
use offst_mutual_from_derive::mutual_from;
#[structural_match]
struct Hello1 {
    a: u32,
    b: String,
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::std::cmp::PartialEq for Hello1 {
    #[inline]
    fn eq(&self, other: &Hello1) -> bool {
        match *other {
            Hello1 {
                a: ref __self_1_0,
                b: ref __self_1_1,
            } => match *self {
                Hello1 {
                    a: ref __self_0_0,
                    b: ref __self_0_1,
                } => (*__self_0_0) == (*__self_1_0) && (*__self_0_1) == (*__self_1_1),
            },
        }
    }
    #[inline]
    fn ne(&self, other: &Hello1) -> bool {
        match *other {
            Hello1 {
                a: ref __self_1_0,
                b: ref __self_1_1,
            } => match *self {
                Hello1 {
                    a: ref __self_0_0,
                    b: ref __self_0_1,
                } => (*__self_0_0) != (*__self_1_0) || (*__self_0_1) != (*__self_1_1),
            },
        }
    }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::std::cmp::Eq for Hello1 {
    #[inline]
    #[doc(hidden)]
    fn assert_receiver_is_total_eq(&self) -> () {
        {
            let _: ::std::cmp::AssertParamIsEq<u32>;
            let _: ::std::cmp::AssertParamIsEq<String>;
        }
    }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::std::clone::Clone for Hello1 {
    #[inline]
    fn clone(&self) -> Hello1 {
        match *self {
            Hello1 {
                a: ref __self_0_0,
                b: ref __self_0_1,
            } => Hello1 {
                a: ::std::clone::Clone::clone(&(*__self_0_0)),
                b: ::std::clone::Clone::clone(&(*__self_0_1)),
            },
        }
    }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::std::fmt::Debug for Hello1 {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            Hello1 {
                a: ref __self_0_0,
                b: ref __self_0_1,
            } => {
                let mut debug_trait_builder = f.debug_struct("Hello1");
                let _ = debug_trait_builder.field("a", &&(*__self_0_0));
                let _ = debug_trait_builder.field("b", &&(*__self_0_1));
                debug_trait_builder.finish()
            }
        }
    }
}
extern crate test as test;
#[cfg(test)]
#[rustc_test_marker]
pub const basic: test::TestDescAndFn = test::TestDescAndFn {
    desc: test::TestDesc {
        name: test::StaticTestName("basic"),
        ignore: false,
        allow_fail: false,
        should_panic: test::ShouldPanic::No,
    },
    testfn: test::StaticTestFn(|| test::assert_test_result(basic())),
};
fn basic() {
    let hello1 = Hello1 {
        a: 0u32,
        b: "some_string".to_owned(),
    };
    let hello2 = Hello2::from(hello1.clone());
    let hello1_new = Hello1::from(hello2.clone());
    let hello2_new = Hello2::from(hello1_new.clone());
    {
        match (&hello1, &hello1_new) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    {
                        ::std::rt::begin_panic_fmt(
                            &::std::fmt::Arguments::new_v1(
                                &[
                                    "assertion failed: `(left == right)`\n  left: `",
                                    "`,\n right: `",
                                    "`",
                                ],
                                &match (&&*left_val, &&*right_val) {
                                    (arg0, arg1) => [
                                        ::std::fmt::ArgumentV1::new(arg0, ::std::fmt::Debug::fmt),
                                        ::std::fmt::ArgumentV1::new(arg1, ::std::fmt::Debug::fmt),
                                    ],
                                },
                            ),
                            &("components/mutual_from_derive/tests/basic.rs", 26u32, 5u32),
                        )
                    }
                }
            }
        }
    };
    {
        match (&hello2, &hello2_new) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    {
                        ::std::rt::begin_panic_fmt(
                            &::std::fmt::Arguments::new_v1(
                                &[
                                    "assertion failed: `(left == right)`\n  left: `",
                                    "`,\n right: `",
                                    "`",
                                ],
                                &match (&&*left_val, &&*right_val) {
                                    (arg0, arg1) => [
                                        ::std::fmt::ArgumentV1::new(arg0, ::std::fmt::Debug::fmt),
                                        ::std::fmt::ArgumentV1::new(arg1, ::std::fmt::Debug::fmt),
                                    ],
                                },
                            ),
                            &("components/mutual_from_derive/tests/basic.rs", 27u32, 5u32),
                        )
                    }
                }
            }
        }
    };
}
pub mod __test_reexports {
    pub use super::basic;
}
#[main]
pub fn main() -> () {
    extern crate test as test;
    test::test_main_static(&[&__test_reexports::basic])
}

It seems like Hello2 is not present in the generated code.
This is my first attempt at writing a procedural macro. Any help is appreciated!

The first error is the relevant one here: from the proc macro, you’re outputting roughly impl From<"Hello1"> for "Hello2" { ... }.

You need to create identifiers from those strings; use syn::Ident.

2 Likes

@birkenfeld: thanks, it worked!
For future readers, this is the what the new code looks like:

/// Automatically derive bidirectional From traits (Between current struct and other specified
/// struct).
#[proc_macro_attribute]
pub fn mutual_from(
    args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // See: https://github.com/dtolnay/syn/issues/86
    // for information about arguments.

    // This is the name of the other struct:
    // let remote_name = args.to_string();
    let remote_name = Ident::new(&args.to_string(), Span::call_site());

    let input = parse_macro_input!(input as DeriveInput);

    // Name of local struct:
    let local_name = &input.ident;

    let conversion = match input.data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => {
                // Example:
                // struct Point {
                //     x: u32,
                //     y: u32,
                // }
                let recurse1 = fields.named.iter().map(|f| {
                    let fname = &f.ident;
                    quote_spanned! { f.span() =>
                        #fname: input.#fname
                    }
                });
                // TODO: Is there a more elegant way to do this except cloning?
                let recurse2 = recurse1.clone();
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name {
                                #(#recurse1, )*
                            }
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name {
                                #(#recurse2, )*
                            }
                        }
                    }
                }
            }
            Fields::Unnamed(ref fields) => {
                // Example:
                // struct Pair(i32, f32);

                let recurse1 = fields.unnamed.iter().enumerate().map(|(i, f)| {
                    // TODO: Should we use Index::from(i) here?
                    // What happens if we don't?
                    quote_spanned! { f.span() =>
                        input.#i
                    }
                });
                // TODO: Is there a more elegant way to do this except cloning?
                let recurse2 = recurse1.clone();
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name(#(#recurse1,)*)
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name(#(#recurse2,)*)
                        }
                    }
                }
            }
            Fields::Unit => {
                // Example:
                // struct MyStruct;
                quote! {
                    impl From<#local_name> for #remote_name {
                        fn from(input: #local_name) -> Self {
                            #remote_name
                        }
                    }
                    impl From<#remote_name> for #local_name {
                        fn from(input: #remote_name) -> Self {
                            #local_name
                        }
                    }
                }
            }
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    };

    let expanded = quote! {
        // Original structure
        #input
        // Generated mutual From conversion code:
        #conversion
    };

    proc_macro::TokenStream::from(expanded)
}

It would be better to write:

let remote_name = parse_macro_input!(args as Ident);

That way errors like “cannot find type” will point to the right place.

@dtolnay: I just changed the code according to your recommendation.
Thank you for your work on syn and quote, I am number 1 fan!