Search code examples
rustrust-proc-macros

How can I parse an attribute with nested arguments with darling?


I'm trying to parse an attribute with darling, and I want to support the following usages:

// att not specified
#[derive(MyTrait)]
struct Foo(u64);

// att specified without an argument
#[derive(MyTrait)]
#[myderive(att)]
struct Foo(u64);

// att specified with an argument
#[derive(MyTrait)]
#[myderive(att(value = "String"))]
struct Foo(u64);

These are my types:

#[derive(FromDeriveInput)]
#[darling(attributes(myderive))]
struct MyDeriveInput {
    #[darling(default)]
    att: Option<MyAttr>,
}

#[derive(FromMeta, Default)]
struct MyAttr {
    #[darling(default)]
    value: Option<Path>,
}

And a test:

#[test]
fn test() {
    let derive_input = syn::parse_str(
        r#"
        #[derive(MyTrait)]
        #[myderive(att)]
        struct Foo(u64);
    "#,
    )
    .unwrap();

    let parsed: MyDeriveInput = FromDeriveInput::from_derive_input(&derive_input).unwrap();
    assert!(parsed.att.is_some());
}

I get this error:

thread 'test' panicked at 'called `Result::unwrap()` on an `Err` value:
Error { kind: UnexpectedFormat("word"), locations: ["att"], span: Some(Span) }'

I get the same error if I specify att, regardless of whether value is specified.

Is this possible? If so, what structure does darling expect to parse this into?


Solution

  • The syntax for the derive doesn't exactly work this way for attributes values which are structs.

    If you want to specify there's an att but a default one, you should set it as att().

    Here's a fixed complete code and test units:

    extern crate proc_macro;
    extern crate syn;
    
    use {
        darling::*,
        std::path::*,
    };
    
    #[derive(FromMeta)]
    struct MyAttr {
        #[darling(default)]
        value: Option<String>, // I dunno what was your "Path" so I've put String
    }
    
    #[derive(FromDeriveInput, Default)]
    #[darling(attributes(myderive))]
    struct MyTrait {
        #[darling(default)]
        att: Option<MyAttr>,
    }
    
    
    #[test]
    fn test() {
    
        // with specified MyAttr:
        let derive_input = syn::parse_str(
            r#"
            #[derive(MyTrait)]
            #[myderive(att(value = "test"))]
            struct Foo(u64);
        "#,
        )
        .unwrap();
        let parsed: MyTrait = FromDeriveInput::from_derive_input(&derive_input).unwrap();
        assert!(parsed.att.is_some());
    
        // with default MyAttr:
        let derive_input = syn::parse_str(
            r#"
            #[derive(MyTrait)]
            #[myderive(att())]
            struct Foo(u64);
        "#,
        )
        .unwrap();
        let parsed: MyTrait = FromDeriveInput::from_derive_input(&derive_input).unwrap();
        assert!(parsed.att.is_some());
    
        // with no MyAttr:
        let derive_input = syn::parse_str(
            r#"
            #[derive(MyTrait)]
            #[myderive()]
            struct Foo(u64);
        "#,
        )
        .unwrap();
        let parsed: MyTrait = FromDeriveInput::from_derive_input(&derive_input).unwrap();
        assert!(parsed.att.is_none());
    
    
        // with no myderive
        let derive_input = syn::parse_str(
            r#"
            #[derive(MyTrait)]
            struct Foo(u64);
        "#,
        )
        .unwrap();
        let parsed: MyTrait = FromDeriveInput::from_derive_input(&derive_input).unwrap();
        assert!(parsed.att.is_none());
    }