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

RFE: Support negation for Flag (or new similar type) #278

Open
loganmzz opened this issue Mar 7, 2024 · 1 comment
Open

RFE: Support negation for Flag (or new similar type) #278

loganmzz opened this issue Mar 7, 2024 · 1 comment

Comments

@loganmzz
Copy link

loganmzz commented Mar 7, 2024

In most configuration systems, a "key" when present let's enable (or enforce) associated feature activation. It is currently supported by Flag type.

However, such systems also support disabling feature.

Example syntax:

#[my_attribute(!my_feature)]

Example (additional) API:

impl Flag {
  fn is_enabled(self) -> bool { /* ... */ }
  fn is_disabled(self) -> bool { /* ... */ }
  fn as_option(self) -> Option<bool> { /* ... */ }
}

impl From<&Flag> for Option<bool> {
    fn from(value: &Flag) -> Self {
        value.as_option()
    }
}

impl From<Option<bool>> for &Option<bool> {
    fn from(value: Option<bool>) -> Self { /* ... */}
}
@TedDriggs
Copy link
Owner

I understand the desire and some of the use-cases for this, but I don't think this is possible under darling's current architecture.

!my_feature isn't a valid syn::Meta. darling recursively transforms each item it encounters into a syn::Meta before calling either the with function or the FromMeta::from_meta of the child, effectively separating the parsing operation into two phases:

  1. Convert TokenStream to syn::Meta
  2. Convert the syn::Meta into whatever output type is expected by the receiver.

Step 2 in that process is very flexible, but step 1 is very inflexible; the only way to change step 1 parsing would be to override the darling impl of the parent element so that it used a custom parser, and then you'd have to rebuild the entire step 2 manually because your output of step 1 wouldn't match the expected input of step 2.

Even some of the deeper discussions about changing darling's traits to allow for more bespoke syntaxes don't seem likely to support this use-case generically, as those still draw a distinction between the left-hand side of the meta item and the right-hand side.

Possible Approach for Specific Scenarios

If you're trying to specifically parse a cfg-like attribute, you might be able to do the following.

use darling::{FromDeriveInput, FromMeta};
use proc_macro2::TokenStream;
use syn::{parse_quote, Meta};

#[derive(Debug)]
struct NegatableIdentList {
    tokens: TokenStream,
}

impl FromMeta for NegatableIdentList {
    fn from_meta(item: &Meta) -> darling::Result<Self> {
        match item {
            Meta::Path(_) => Err(darling::Error::unsupported_format("word").with_span(item)),
            Meta::List(list) => Ok(Self {
                // A real implementation of this parse the token stream now to create the desired
                // output data type. This involves a bunch of use-case-specific decisions about what
                // features are needed, such as and/or/etc.
                tokens: list.tokens.clone(),
            }),
            Meta::NameValue(_) => {
                Err(darling::Error::unsupported_format("name-value").with_span(item))
            }
        }
    }
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(hello))]
struct Demo {
    inner: NegatableIdentList,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let di: syn::DeriveInput = parse_quote! {
        // We need the extra `inner` here so that we don't have to reimplement the outer
        // behavior of `FromDeriveInput` by hand. An alternate approach would be to forward
        // the attribute that will contain this features list and then handle it using `with = ...`
        // on the `attrs` magic field.
        #[hello(inner(!example::flag, my_feature))]
        struct Example;
    };

    let demo = Demo::from_derive_input(&di)?;
    dbg!(&demo.inner.tokens);
    Ok(())
}

In this example, you take over all parsing of the meta contents before the parsing to NestedMetatakes place in the default impl of FromMeta. You would then need to manually implement parsing of this item, likely using syn::parse::Parse (though you don't have to if you didn't want to).

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

No branches or pull requests

2 participants