1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#![cfg_attr(feature = "_doc", feature(doc_cfg, external_doc))]
#![cfg_attr(feature = "_doc", doc(include = "../README.md"))]

extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;

use std::collections::HashSet;

use quote::quote;
use quote::ToTokens;
use syn::parse_macro_input;
use syn::spanned::Spanned;

// ---------------------------------------------------------------------------

mod default;
mod derive;
mod utils;

// ---------------------------------------------------------------------------

struct Args {
    default: Option<syn::Path>,
    derives: HashSet<derive::Derive>,
}

impl Args {
    fn from_args(args: &syn::AttributeArgs) -> syn::Result<Self> {
        let mut default = None;
        let mut derives = HashSet::new();

        let meta = args
            .iter()
            .map(|arg| match arg {
                syn::NestedMeta::Lit(lit) => Err(syn::Error::new(lit.span(), "unexpected literal")),
                syn::NestedMeta::Meta(meta) => Ok(meta),
            })
            .collect::<syn::Result<Vec<&syn::Meta>>>()?;

        for arg in meta {
            // argument paths are compared against their token stream serialization
            // to avoid to compile `syn` with the `extra-traits` feature
            match arg {
                syn::Meta::List(ref l) if l.path.to_token_stream().to_string() == "derive" => {
                    for elem in l.nested.iter() {
                        if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = elem {
                            if let Some(d) = derive::Derive::from_path(&path) {
                                derives.insert(d);
                            } else {
                                return Err(syn::Error::new(
                                    path.span(),
                                    "unknown blanket derive option",
                                ));
                            }
                        } else {
                            return Err(syn::Error::new(elem.span(), "expected identifier"));
                        }
                    }
                }
                syn::Meta::NameValue(ref n)
                    if n.path.to_token_stream().to_string() == "default" =>
                {
                    if let syn::Lit::Str(ref s) = n.lit {
                        match syn::parse_str(&s.value()) {
                            Ok(path) if default.is_none() => {
                                default = Some(path);
                            }
                            Ok(_) => {
                                return Err(syn::Error::new(
                                    s.span(),
                                    "duplicate default module given",
                                ))
                            }
                            Err(_) => {
                                return Err(syn::Error::new(s.span(), "expected module identifier"))
                            }
                        }
                    } else {
                        return Err(syn::Error::new(n.lit.span(), "expected string literal"));
                    }
                }
                _ => return Err(syn::Error::new(arg.span(), "unexpected argument")),
            }
        }

        Ok(Self { default, derives })
    }
}

// ---------------------------------------------------------------------------

#[proc_macro_attribute]
pub fn blanket(
    args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // parse input
    let trait_ = parse_macro_input!(input as syn::ItemTrait);
    let attribute_args = parse_macro_input!(args as syn::AttributeArgs);
    // parse macro arguments and immediately exit if they are invalid
    let args = match Args::from_args(&attribute_args) {
        Ok(args) => args,
        Err(e) => {
            let err = e.to_compile_error();
            return proc_macro::TokenStream::from(quote!(#err #trait_));
        }
    };
    // generate output
    let mut out = proc_macro2::TokenStream::new();
    // update trait methods declaration if given a `default = "..."` argument,
    // otherwise simply keep the output
    match args.default {
        None => out.extend(quote!(#trait_)),
        Some(d) => match default::defer_trait_methods(trait_.clone(), d) {
            Ok(trait_) => out.extend(quote!(#trait_)),
            Err(err) => out.extend(err.to_compile_error()),
        },
    };
    // add derived implementations
    for d in args.derives {
        match d.defer_trait_methods(&trait_) {
            Ok(item) => out.extend(quote!(#item)),
            Err(e) => out.extend(e.to_compile_error()),
        }
    }
    // return the new `proc-macro2` token stream as a `proc-macro` stream
    proc_macro::TokenStream::from(out)
}