Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derived FromDeriveInput silently skips attributes that don't parse as syn::Meta #108

Closed
DusterTheFirst opened this issue Dec 16, 2020 · 2 comments

Comments

@DusterTheFirst
Copy link

When attempting to get this struct

#[derive(Debug, FromDeriveInput)]
#[darling(supports(struct_unit), attributes(table))]
pub struct InterpolatedDataTableArgs {
    pub ident: Ident,
    pub st: Path,
    pub file: LitStr,
}

from this derive:

#[derive(InterpolatedDataTable)]
#[table(file = "motors/Estes_C6.csv", st = RocketEngine)]
pub struct EstesC6;

I get these errors that both st and file are not missing, yet they can be seen right above, and in the following clip of the DeriveInput

error: Missing field `st`
  --> $DIR/pass-c6.rs:10:10
   |
10 | #[derive(InterpolatedDataTable)]
   |          ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: Missing field `file`
  --> $DIR/pass-c6.rs:10:10
   |
10 | #[derive(InterpolatedDataTable)]
   |          ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

DeriveInput

DeriveInput {
    attrs: [
        Attribute {
            pound_token: Pound,
            style: Outer,
            bracket_token: Bracket,
            path: Path {
                leading_colon: None,
                segments: [
                    PathSegment {
                        ident: Ident {
                            ident: "table",
                            span: #0 bytes(229..234),
                        },
                        arguments: None,
                    },
                ],
            },
            tokens: TokenStream [
                Group {
                    delimiter: Parenthesis,
                    stream: TokenStream [
                        Ident {
                            ident: "file",
                            span: #0 bytes(235..239),
                        },
                        Punct {
                            ch: '=',
                            spacing: Alone,
                            span: #0 bytes(240..241),
                        },
                        Literal {
                            kind: Str,
                            symbol: "motors/Estes_C6.csv",
                            suffix: None,
                            span: #0 bytes(242..263),
                        },
                        Punct {
                            ch: ',',
                            spacing: Alone,
                            span: #0 bytes(263..264),
                        },
                        Ident {
                            ident: "st",
                            span: #0 bytes(265..267),
                        },
                        Punct {
                            ch: '=',
                            spacing: Alone,
                            span: #0 bytes(268..269),
                        },
                        Ident {
                            ident: "RocketEngine",
                            span: #0 bytes(270..282),
                        },
                        Punct {
                            ch: ',',
                            spacing: Alone,
                            span: #0 bytes(282..283),
                        },
                        Ident {
                            ident: "j",
                            span: #0 bytes(284..285),
                        },
                        Punct {
                            ch: '=',
                            spacing: Alone,
                            span: #0 bytes(286..287),
                        },
                        Literal {
                            kind: Str,
                            symbol: "s",
                            suffix: None,
                            span: #0 bytes(288..291),
                        },
                    ],
                    span: #0 bytes(234..292),
                },
            ],
        },
    ],
    vis: Public(
        VisPublic {
            pub_token: Pub,
        },
    ),
    ident: Ident {
        ident: "EstesC6",
        span: #0 bytes(305..312),
    },
    generics: Generics {
        lt_token: None,
        params: [],
        gt_token: None,
        where_clause: None,
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Unit,
            semi_token: Some(
                Semi,
            ),
        },
    ),
}
@DusterTheFirst DusterTheFirst changed the title FromDeriveInput wrong missing field error FromDeriveInput invalid missing field error Dec 17, 2020
@TedDriggs
Copy link
Owner

TedDriggs commented Jan 4, 2021

This is "by design" (ish).

The lack of quotation marks around the value passed to st means that syn no longer parses a valid Meta in this method. darling chooses to silently skip attributes that fail to parse as meta, seen in the continue; in the expansion below.

#[darling(supports(struct_unit), attributes(table))]
pub struct InterpolatedDataTableArgs {
    pub ident: Ident,
    pub st: Path,
    pub file: LitStr,
}

impl ::darling::FromDeriveInput for InterpolatedDataTableArgs {
    fn from_derive_input(__di: &::syn::DeriveInput) -> ::darling::Result<Self> {
        let mut __errors = ::darling::export::Vec::new();
        let mut st: (bool, ::darling::export::Option<Path>) = (false, None);
        let mut file: (bool, ::darling::export::Option<LitStr>) = (false, None);
        use ::darling::ToTokens;
        let mut __fwd_attrs: ::darling::export::Vec<::syn::Attribute> = ::alloc::vec::Vec::new();
        for __attr in &__di.attrs {
            match ::darling::export::ToString::to_string(&__attr.path.clone().into_token_stream())
                .as_str()
            {
                "table" => {
                    if let ::darling::export::Ok(::syn::Meta::List(ref __data)) =
                        __attr.parse_meta()
                    {
                        let __items = &__data.nested;
                        for __item in __items {
                            if let ::syn::NestedMeta::Meta(ref __inner) = *__item {
                                let __name = __inner
                                    .path()
                                    .segments
                                    .iter()
                                    .map(|s| s.ident.to_string())
                                    .collect::<Vec<String>>()
                                    .join("::");
                                match __name.as_str() {
                                    "st" => {
                                        if !st.0 {
                                            match ::darling::FromMeta::from_meta(__inner)
                                                .map_err(|e| e.with_span(&__inner).at("st"))
                                            {
                                                ::darling::export::Ok(__val) => {
                                                    st = (true, ::darling::export::Some(__val));
                                                }
                                                ::darling::export::Err(__err) => {
                                                    st = (true, None);
                                                    __errors.push(__err);
                                                }
                                            }
                                        } else {
                                            __errors.push(
                                                ::darling::Error::duplicate_field("st")
                                                    .with_span(&__inner),
                                            );
                                        }
                                    }
                                    "file" => {
                                        if !file.0 {
                                            match ::darling::FromMeta::from_meta(__inner)
                                                .map_err(|e| e.with_span(&__inner).at("file"))
                                            {
                                                ::darling::export::Ok(__val) => {
                                                    file = (true, ::darling::export::Some(__val));
                                                }
                                                ::darling::export::Err(__err) => {
                                                    file = (true, None);
                                                    __errors.push(__err);
                                                }
                                            }
                                        } else {
                                            __errors.push(
                                                ::darling::Error::duplicate_field("file")
                                                    .with_span(&__inner),
                                            );
                                        }
                                    }
                                    __other => {
                                        __errors.push(
                                            ::darling::Error::unknown_field_with_alts(
                                                __other,
                                                &["st", "file"],
                                            )
                                            .with_span(__inner),
                                        );
                                    }
                                }
                            }
                        }
                    } else {
                        continue;
                    }
                }
                _ => continue,
            }
        }
        #[allow(unused_variables)]
        fn __validate_body(__body: &::syn::Data) -> ::darling::Result<()> {
            match *__body {
                ::syn::Data::Enum(ref data) => {
                    fn validate_variant(data: &::syn::Fields) -> ::darling::Result<()> {
                        ::darling::export::Err(::darling::Error::unsupported_shape("enum"))
                    }
                    for variant in &data.variants {
                        validate_variant(&variant.fields)?;
                    }
                    Ok(())
                }
                ::syn::Data::Struct(ref struct_data) => {
                    let data = &struct_data.fields;
                    match *data {
                        ::syn::Fields::Unit => ::darling::export::Ok(()),
                        ::syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
                            ::darling::export::Err(::darling::Error::unsupported_shape("newtype"))
                        }
                        ::syn::Fields::Unnamed(_) => {
                            ::darling::export::Err(::darling::Error::unsupported_shape("tuple"))
                        }
                        ::syn::Fields::Named(_) => {
                            ::darling::export::Err(::darling::Error::unsupported_shape("named"))
                        }
                    }
                }
                ::syn::Data::Union(_) => {
                    ::std::rt::begin_panic("internal error: entered unreachable code")
                }
            }
        }
        __validate_body(&__di.data)?;
        if !st.0 {
            __errors.push(::darling::Error::missing_field("st"));
        }
        if !file.0 {
            __errors.push(::darling::Error::missing_field("file"));
        }
        if !__errors.is_empty() {
            return ::darling::export::Err(::darling::Error::multiple(__errors));
        }
        ::darling::export::Ok(InterpolatedDataTableArgs {
            ident: __di.ident.clone(),
            st: st
                .1
                .expect("Uninitialized fields without defaults were already checked"),
            file: file
                .1
                .expect("Uninitialized fields without defaults were already checked"),
        })
    }
}

The current behavior appears to be the result of the history of converting syn::Attribute to syn::Meta. In the very early days, an attribute had to be a Meta (source), then later that was changed to allow interpreting a Meta (but not providing error data), before finally moving to parse_meta.

It's possible for darling to handle this differently, explicitly returning an error when an attribute has a name darling expects to handle but it doesn't parse as-expected. That change would impact derived impls of FromDeriveInput, FromTypeParam, FromField, and FromVariant. It looks like syn would say "expected literal" with some span. darling would still additionally emit the missing field errors.

I'm supportive of making that change, but 1) it'll require a minor version bump because it could break existing library users and 2) it may require an escape hatch, which I'll need to think about a bit. In the meantime, adding the quotation marks will unblock you.

@TedDriggs TedDriggs changed the title FromDeriveInput invalid missing field error Derived FromDeriveInput silently skips attributes that don't parse as syn::Meta Jan 4, 2021
@TedDriggs
Copy link
Owner

This is actually a duplicate of #96; let's move the discussion there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants