[go: up one dir, main page]

blanket/
lib.rs

1#![doc = include_str!("../README.md")]
2
3extern crate proc_macro;
4extern crate proc_macro2;
5extern crate quote;
6
7use std::collections::HashSet;
8
9use quote::{quote, ToTokens};
10use syn::{parse_macro_input, punctuated::Punctuated, spanned::Spanned};
11
12// ---------------------------------------------------------------------------
13
14mod default;
15mod derive;
16mod types;
17mod utils;
18
19// ---------------------------------------------------------------------------
20
21struct Args {
22    default: Option<syn::Path>,
23    derives: HashSet<types::Type>,
24}
25
26impl Args {
27    fn from_meta(arg: &syn::Meta) -> syn::Result<Self> {
28        let mut default = None;
29        let mut derives = HashSet::new();
30
31        match arg {
32            syn::Meta::List(ref l) if l.path.to_token_stream().to_string() == "derive" => {
33                let types = l.parse_args_with(
34                    Punctuated::<syn::Path, syn::Token![,]>::parse_separated_nonempty,
35                )?;
36                for pair in types.into_pairs() {
37                    if let Some(d) = types::Type::from_path(pair.value()) {
38                        derives.insert(d);
39                    } else {
40                        return Err(syn::Error::new(
41                            pair.span(),
42                            "unknown blanket derive option",
43                        ));
44                    }
45                }
46            }
47            syn::Meta::NameValue(ref n) if n.path.to_token_stream().to_string() == "default" => {
48                match n.value {
49                    syn::Expr::Lit(ref lit) => {
50                        if let syn::Lit::Str(ref s) = lit.lit {
51                            match syn::parse_str(&s.value()) {
52                                Ok(path) if default.is_none() => {
53                                    default = Some(path);
54                                }
55                                Ok(_) => {
56                                    return Err(syn::Error::new(
57                                        s.span(),
58                                        "duplicate default module given",
59                                    ))
60                                }
61                                Err(_) => {
62                                    return Err(syn::Error::new(
63                                        s.span(),
64                                        "expected module identifier",
65                                    ))
66                                }
67                            }
68                        } else {
69                            return Err(syn::Error::new(lit.lit.span(), "expected string literal"));
70                        }
71                    }
72                    syn::Expr::Path(ref expr) => {
73                        if default.replace(expr.path.clone()).is_some() {
74                            return Err(syn::Error::new(
75                                n.span(),
76                                "duplicate default module given",
77                            ));
78                        }
79                    }
80                    _ => {
81                        return Err(syn::Error::new(
82                            n.value.span(),
83                            "expected path or string literal",
84                        ));
85                    }
86                }
87            }
88            _ => return Err(syn::Error::new(arg.span(), "unexpected argument")),
89        }
90
91        Ok(Self { default, derives })
92    }
93}
94
95// ---------------------------------------------------------------------------
96
97/// Generate blanket implementations for a trait.
98///
99/// This procedural macro must be used on a `trait` block. It can be used
100/// to either derive a blanket implementation, or to delegate the method
101/// implementations of a trait to a module.
102///
103#[proc_macro_attribute]
104pub fn blanket(
105    args: proc_macro::TokenStream,
106    input: proc_macro::TokenStream,
107) -> proc_macro::TokenStream {
108    // parse input
109    let trait_ = parse_macro_input!(input as syn::ItemTrait);
110    let args = parse_macro_input!(args as syn::Meta);
111    // parse macro arguments and immediately exit if they are invalid
112    let args = match Args::from_meta(&args) {
113        Ok(args) => args,
114        Err(e) => {
115            let err = e.to_compile_error();
116            return proc_macro::TokenStream::from(quote!(#err #trait_));
117        }
118    };
119    // generate output
120    let mut out = proc_macro2::TokenStream::new();
121    // update trait methods declaration if given a `default = "..."` argument,
122    // otherwise simply keep the output
123    match args.default {
124        None => out.extend(quote!(#trait_)),
125        Some(d) => match default::defer_trait_methods(trait_.clone(), d) {
126            Ok(trait_) => out.extend(quote!(#trait_)),
127            Err(err) => out.extend(err.to_compile_error()),
128        },
129    };
130    // add derived implementations
131    for d in args.derives {
132        match d.defer_trait_methods(&trait_) {
133            Ok(item) => out.extend(quote!(#item)),
134            Err(e) => out.extend(e.to_compile_error()),
135        }
136    }
137    // // return the new `proc-macro2` token stream as a `proc-macro` stream
138    proc_macro::TokenStream::from(out)
139}