opendp_tooling/bootstrap/
arguments.rs

1use darling::{Error, FromMeta, Result, ast::NestedMeta};
2use proc_macro2::TokenStream;
3use std::{collections::HashMap, str::FromStr};
4use syn::{Expr, ExprLit, Lit, Meta, MetaNameValue, Token, Type, punctuated::Punctuated};
5
6use crate::{TypeRecipe, Value};
7
8use super::signature::{syn_path_to_string, syn_type_to_type_recipe};
9
10// Arguments to the bootstrap proc-macro
11// The rest of this file is for parsing the arguments to bootstrap(*) into the Bootstrap struct
12#[derive(Debug, FromMeta, Clone)]
13pub struct BootstrapArguments {
14    pub name: Option<String>,
15    pub proof_path: Option<String>,
16    #[darling(default)]
17    pub rust_path: Option<String>,
18    #[darling(default)]
19    pub has_ffi: Option<bool>,
20    #[darling(default)]
21    pub unproven: bool,
22    #[darling(default)]
23    pub features: Features,
24    #[darling(default)]
25    pub generics: BootTypeHashMap,
26    #[darling(default)]
27    pub arguments: BootTypeHashMap,
28    pub derived_types: Option<DerivedTypes>,
29    pub returns: Option<BootType>,
30}
31
32impl BootstrapArguments {
33    pub fn from_attribute_args(items: &[NestedMeta]) -> darling::Result<Self> {
34        Self::from_list(items)
35    }
36}
37
38#[derive(Debug, Clone, Default)]
39pub struct DerivedTypes(pub Vec<(String, TypeRecipe)>);
40
41impl FromMeta for DerivedTypes {
42    fn from_list(items: &[NestedMeta]) -> Result<Self> {
43        // each item should be a metalist consisting of the derived type name and info: T = "X"
44
45        (items.iter())
46            .map(|nested| {
47                let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, value, .. })) = nested
48                else {
49                    return Err(
50                        Error::custom("expected name = value pairs in derived_types")
51                            .with_span(nested),
52                    );
53                };
54                let Expr::Lit(ExprLit { lit, .. }) = value else {
55                    return Err(Error::custom("expected literal").with_span(value));
56                };
57                Ok((syn_path_to_string(path)?, TypeRecipe::from_value(lit)?))
58            })
59            .collect::<Result<Vec<(String, TypeRecipe)>>>()
60            .map(DerivedTypes)
61    }
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct Features(pub Vec<String>);
66
67impl FromMeta for Features {
68    fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
69        items
70            .iter()
71            .map(String::from_nested_meta)
72            .collect::<darling::Result<Vec<String>>>()
73            .map(Features)
74    }
75}
76
77#[derive(Debug, Clone, Default)]
78pub struct BootTypeHashMap(pub HashMap<String, BootType>);
79
80impl FromMeta for BootTypeHashMap {
81    fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
82        (items.iter())
83            .map(|nested| {
84                if let NestedMeta::Meta(Meta::List(list)) = nested {
85                    let type_name = list
86                        .path
87                        .get_ident()
88                        .ok_or_else(|| {
89                            darling::Error::custom("path must consist of a single ident")
90                                .with_span(&list.path)
91                        })?
92                        .to_string();
93
94                    let type_ = BootType::from_list(
95                        &list
96                            .parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)?
97                            .into_iter()
98                            .collect::<Vec<_>>(),
99                    )?;
100                    Ok((type_name, type_))
101                } else {
102                    Err(
103                        darling::Error::custom("expected metalist in BootstrapTypes")
104                            .with_span(nested),
105                    )
106                }
107            })
108            .collect::<darling::Result<HashMap<String, BootType>>>()
109            .map(BootTypeHashMap)
110    }
111}
112
113#[derive(Debug, FromMeta, Clone, Default)]
114pub struct BootType {
115    pub c_type: Option<String>,
116    pub rust_type: Option<TypeRecipe>,
117    pub hint: Option<String>,
118    pub default: Option<Value>,
119    #[darling(default)]
120    pub do_not_convert: bool,
121    pub example: Option<TypeRecipe>,
122    #[darling(default)]
123    pub suppress: bool,
124}
125
126impl FromMeta for Value {
127    fn from_expr(expr: &Expr) -> Result<Self> {
128        match *expr {
129            Expr::Unary(ref unary) => {
130                if !matches!(unary.op, syn::UnOp::Neg(_)) {
131                    return Err(
132                        Error::custom("the only unary operation supported is negation")
133                            .with_span(expr),
134                    );
135                }
136                Ok(match Self::from_expr(&unary.expr)? {
137                    Value::Integer(v) => Value::Integer(-v),
138                    Value::Float(v) => Value::Float(-v),
139                    v => v,
140                })
141            }
142            Expr::Lit(ref lit) => Self::from_value(&lit.lit),
143            Expr::Group(ref group) => {
144                // syn may generate this invisible group delimiter when the input to the darling
145                // proc macro (specifically, the attributes) are generated by a
146                // macro_rules! (e.g. propagating a macro_rules!'s expr)
147                // Since we want to basically ignore these invisible group delimiters,
148                // we just propagate the call to the inner expression.
149                Self::from_expr(&group.expr)
150            }
151            _ => Err(Error::unexpected_expr_type(expr)),
152        }
153        .map_err(|e| e.with_span(expr))
154    }
155
156    fn from_value(value: &syn::Lit) -> darling::Result<Self> {
157        Ok(match value {
158            syn::Lit::Str(str) => Value::String(str.value()),
159            syn::Lit::Int(int) => Value::Integer(int.base10_parse::<i64>()?),
160            syn::Lit::Float(float) => Value::Float(float.base10_parse::<f64>()?),
161            syn::Lit::Bool(bool) => Value::Bool(bool.value),
162            syn::Lit::ByteStr(bstr) => {
163                if bstr.value() == b"null" {
164                    Value::Null
165                } else {
166                    return Err(Error::custom("Byte strings are reserved for expressing nullity. Use b\"null\" to represent a null literal.").with_span(bstr));
167                }
168            }
169            lit => return Err(darling::Error::unexpected_lit_type(lit).with_span(value)),
170        })
171    }
172}
173
174impl FromMeta for TypeRecipe {
175    fn from_value(value: &Lit) -> Result<Self> {
176        match value {
177            Lit::Str(litstr) => {
178                let litstr = litstr.value();
179                if litstr.starts_with('$') {
180                    let mut chars = litstr.chars();
181                    chars.next(); // discard leading $
182                    let stream = TokenStream::from_str(chars.as_str()).map_err(|_| {
183                        Error::custom("error while lexing function").with_span(value)
184                    })?;
185
186                    syn_meta_to_type_recipe_function(syn::parse2::<NestedMeta>(stream)?)
187                } else {
188                    let stream = TokenStream::from_str(&litstr)
189                        .map_err(|_| Error::custom("error while lexing type").with_span(value))?;
190
191                    let ty = syn::parse2::<Type>(stream).map_err(|e| {
192                        Error::custom(format!(
193                            "error while parsing type {}: {}",
194                            litstr,
195                            e.to_string()
196                        ))
197                        .with_span(value)
198                    })?;
199                    syn_type_to_type_recipe(&ty)
200                }
201            }
202            Lit::ByteStr(bstr) => {
203                if bstr.value() == b"null" {
204                    Ok(TypeRecipe::None)
205                } else {
206                    Err(Error::custom("Byte strings are reserved for expressing nullity. Use b\"null\" to represent a null literal.").with_span(bstr))
207                }
208            }
209            _ => Err(Error::custom("expected string").with_span(value)),
210        }
211    }
212}
213
214fn syn_meta_to_type_recipe_function(meta: NestedMeta) -> Result<TypeRecipe> {
215    match meta {
216        NestedMeta::Meta(Meta::Path(path)) => syn_path_to_string(&path).map(TypeRecipe::Name),
217        NestedMeta::Meta(Meta::List(list)) => Ok(TypeRecipe::Function {
218            function: syn_path_to_string(&list.path)?,
219            params: list
220                .parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)?
221                .into_iter()
222                .map(syn_meta_to_type_recipe_function)
223                .collect::<Result<Vec<TypeRecipe>>>()?,
224        }),
225        meta => Err(Error::custom(
226            "rust_type = \"$<expression>\" only supports a limited function grammar",
227        )
228        .with_span(&meta)),
229    }
230}
231
232#[derive(Debug, Default, Clone)]
233pub struct DefaultGenerics(pub Vec<String>);
234
235impl FromMeta for DefaultGenerics {
236    fn from_value(lit: &Lit) -> Result<Self> {
237        if let Lit::Str(litstr) = lit {
238            Ok(DefaultGenerics(
239                litstr
240                    .value()
241                    .split(',')
242                    .map(|v| v.trim().to_string())
243                    .collect(),
244            ))
245        } else {
246            Err(Error::custom("expected string").with_span(lit))
247        }
248    }
249}