4

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?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204

2 Answers2

5

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());
}

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • It' a bit disappointing if it's necessary to include `()` even if there are no arguments :/ – Peter Hall Jun 24 '21 at 15:36
  • It's hard to define a consistent and obvious spec here. The behavior for bool is a little special, especially if you consider the default value of a bool is false. – Denys Séguret Jun 24 '21 at 16:09
  • 1
    @PeterHall Why not create an issue on darling's GH repo? They may have a workaround – Denys Séguret Jun 24 '21 at 16:23
  • @DenysSéguret there is indeed a workaround, but it's not easy to find right now. See [this answer](https://stackoverflow.com/a/68637124/86381) – ehdv Aug 03 '21 at 13:39
1

There is a way to do this, using darling::util::Override. I've never had a great name for this particular utility, so I'm going to add this exact snippet as an example, and am interested in having a discussion on GH for a name that people are more likely to find.

ehdv
  • 4,483
  • 7
  • 32
  • 47
  • The language in the docs is may have caused some confusion too. I took "inherit" to refer to the struct that you are parsing into, something like struct-level attributes being inherited at the field-level, or that kind of thing. So it didn't seem relevant to my problem. – Peter Hall Aug 03 '21 at 15:21
  • Yeah, the name `inherit` comes from where this type was originally used, in the `#[builder(default)]` attribute. Like serde, you can say `#[builder(default = "...")]`or just `#[builder(default)]`. The latter case means "IF the struct has a default, via the `Default` trait or an expression, inherit from that ELSE use `Default` on the type of this field." – ehdv Dec 08 '21 at 14:41