illicit_macro/
lib.rs

1//! Procedural macro support crate for the `illicit` crate.
2
3extern crate proc_macro;
4use proc_macro::TokenStream;
5use proc_macro_error::{abort, abort_call_site, proc_macro_error};
6use quote::quote;
7use syn::{
8    parse::Parser, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
9    Attribute, FnArg, ItemFn, Local, PatType, Stmt, Token, Type, TypeReference,
10};
11
12#[proc_macro_attribute]
13#[proc_macro_error]
14pub fn from_env(args: TokenStream, input: TokenStream) -> TokenStream {
15    let mut input_fn: ItemFn = parse_macro_input!(input);
16
17    let args = Punctuated::<FnArg, Token![,]>::parse_terminated.parse(args).unwrap();
18    if args.is_empty() {
19        abort_call_site!("must specify >=1 one argument");
20    }
21
22    let doc_prelude = "
23# Environment Expectations
24
25This function requires the following types to be visible to [`illicit::get`] and will
26panic otherwise:
27";
28
29    for line in doc_prelude.lines() {
30        input_fn.attrs.push(parse_quote!(#[doc = #line]));
31    }
32
33    for arg in args {
34        let arg = match arg {
35            FnArg::Receiver(rec) => abort!(rec.span(), "can't receive self by-environment"),
36            FnArg::Typed(pt) => pt,
37        };
38        let ([first_stmt, second_stmt], doc_attr) = bind_env_reference(&arg);
39        input_fn.block.stmts.insert(0, Stmt::Local(first_stmt));
40        input_fn.block.stmts.insert(1, Stmt::Local(second_stmt));
41        input_fn.attrs.push(doc_attr);
42    }
43
44    quote::quote!(#input_fn).into()
45}
46
47/// Create a pair of local assignment expressions from the `pattern: &type`
48/// pair which is passed.
49fn bind_env_reference(arg: &PatType) -> ([Local; 2], Attribute) {
50    let arg_span = arg.span();
51
52    let ty = match &*arg.ty {
53        Type::Reference(TypeReference { lifetime, mutability, elem, .. }) => {
54            if mutability.is_some() {
55                abort!(mutability.span(), "mutable references cannot be passed by environment");
56            }
57
58            if lifetime.is_some() {
59                abort!(
60                    lifetime.span(),
61                    "cannot bind to concrete lifetimes for environment references"
62                );
63            }
64
65            quote!(#elem)
66        }
67
68        ty => abort!(ty.span(), "arguments must be references"),
69    };
70
71    let name = *arg.pat.clone();
72    let init_expr = parse_quote!(illicit::expect::<#ty>());
73    let deref_expr = parse_quote!(&*#name);
74
75    let shadowed = Local {
76        attrs: vec![],
77        let_token: Token![let](arg_span),
78        pat: name.clone(),
79        init: Some((Token![=](arg_span), Box::new(init_expr))),
80        semi_token: Token![;](arg_span),
81    };
82    let derefd = Local {
83        attrs: vec![],
84        let_token: Token![let](arg_span),
85        pat: name,
86        init: Some((Token![=](arg_span), Box::new(deref_expr))),
87        semi_token: Token![;](arg_span),
88    };
89
90    let ty_bullet = format!("* `{}`", ty);
91    let doc_attr = parse_quote!(#[doc = #ty_bullet]);
92
93    ([shadowed, derefd], doc_attr)
94}