TedDriggs / darling

A Rust proc-macro attribute parser
MIT License
983 stars 66 forks source link
proc-macro rust

Darling

Build Status Latest Version Rustc Version 1.56+

darling is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by serde both in its internals and in its API.

Benefits

Usage

darling provides a set of traits which can be derived or manually implemented.

  1. FromMeta is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like FromStr or serde::Deserialize. Trait implementations are provided for primitives, some std types, and some syn types.
  2. FromDeriveInput is implemented or derived by each proc-macro crate which depends on darling. This is the root for input parsing; it gets access to the identity, generics, and visibility of the target type, and can specify which attribute names should be parsed or forwarded from the input AST.
  3. FromField is implemented or derived by each proc-macro crate which depends on darling. Structs deriving this trait will get access to the identity (if it exists), type, and visibility of the field.
  4. FromVariant is implemented or derived by each proc-macro crate which depends on darling. Structs deriving this trait will get access to the identity and contents of the variant, which can be transformed the same as any other darling input.
  5. FromAttributes is a lower-level version of the more-specific FromDeriveInput, FromField, and FromVariant traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros.

Additional Modules

Example

use darling::{FromDeriveInput, FromMeta};

#[derive(Default, FromMeta)]
#[darling(default)]
pub struct Lorem {
    #[darling(rename = "sit")]
    ipsum: bool,
    dolor: Option<String>,
}

#[derive(FromDeriveInput)]
#[darling(attributes(my_crate), forward_attrs(allow, doc, cfg))]
pub struct MyTraitOpts {
    ident: syn::Ident,
    attrs: Vec<syn::Attribute>,
    lorem: Lorem,
}

The above code will then be able to parse this input:

/// A doc comment which will be available in `MyTraitOpts::attrs`.
#[derive(MyTrait)]
#[my_crate(lorem(dolor = "Hello", sit))]
pub struct ConsumingType;

Attribute Macros

Non-derive attribute macros are supported. To parse arguments for attribute macros, derive FromMeta on the argument receiver type, then use darling::ast::NestedMeta::parse_meta_list to convert the arguments TokenStream to a Vec<NestedMeta>, then pass that to the derived from_list method on your argument receiver type. This will produce a normal darling::Result<T> that can be used the same as a result from parsing a DeriveInput.

Macro Code

use darling::{Error, FromMeta};
use darling::ast::NestedMeta;
use syn::ItemFn;
use proc_macro::TokenStream;

#[derive(Debug, FromMeta)]
struct MacroArgs {
    #[darling(default)]
    timeout_ms: Option<u16>,
    path: String,
}

#[proc_macro_attribute]
pub fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream {
    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
        Ok(v) => v,
        Err(e) => { return TokenStream::from(Error::from(e).write_errors()); }
    };
    let _input = syn::parse_macro_input!(input as ItemFn);

    let _args = match MacroArgs::from_list(&attr_args) {
        Ok(v) => v,
        Err(e) => { return TokenStream::from(e.write_errors()); }
    };

    // do things with `args`
    unimplemented!()
}

Consuming Code

use your_crate::your_attr;

#[your_attr(path = "hello", timeout_ms = 15)]
fn do_stuff() {
    println!("Hello");
}

Features

Darling's features are built to work well for real-world projects.

Shape Validation

Some proc-macros only work on structs, while others need enums whose variants are either unit or newtype variants. Darling makes this sort of validation extremely simple. On the receiver that derives FromDeriveInput, add #[darling(supports(...))] and then list the shapes that your macro should accept.

Name Description
any Accept anything
struct_any Accept any struct
struct_named Accept structs with named fields, e.g. struct Example { field: String }
struct_newtype Accept newtype structs, e.g. struct Example(String)
struct_tuple Accept tuple structs, e.g. struct Example(String, String)
struct_unit Accept unit structs, e.g. struct Example;
enum_any Accept any enum
enum_named Accept enum variants with named fields
enum_newtype Accept newtype enum variants
enum_tuple Accept tuple enum variants
enum_unit Accept unit enum variants

Each one is additive, so listing #[darling(supports(struct_any, enum_newtype))] would accept all structs and any enum where every variant is a newtype variant.

This can also be used when deriving FromVariant, without the enum_ prefix.