opendp_tooling/bootstrap/
signature.rs

1use std::collections::HashSet;
2
3use darling::{Error, Result};
4use syn::{
5    FnArg, GenericArgument, GenericParam, Pat, Path, PathArguments, ReturnType, Signature, Type,
6    TypeParam, TypePath, TypePtr, TypeReference,
7};
8
9use crate::TypeRecipe;
10
11use super::partial::supports_partial;
12
13// try to keep syn parsing insanity contained in this file
14// extract what we need out of the syn signature into parsimonious OpenDP structures.
15
16pub struct BootstrapSignature {
17    pub name: String,
18    pub arguments: Vec<(String, BootSigArgType)>,
19    pub generics: Vec<String>,
20    pub output_c_type: Result<String>,
21    pub supports_partial: bool,
22}
23
24pub struct BootSigArgType {
25    pub c_type: Result<String>,
26    pub rust_type: Result<TypeRecipe>,
27}
28
29impl BootstrapSignature {
30    pub fn from_syn(sig: Signature) -> Result<Self> {
31        let supports_partial = supports_partial(&sig);
32
33        let generics = sig
34            .generics
35            .params
36            .into_iter()
37            .map(|generic| syn_generic_to_syn_type_param(&generic).map(|v| v.ident.to_string()))
38            .collect::<Result<Vec<_>>>()?;
39
40        Ok(BootstrapSignature {
41            name: sig.ident.to_string(),
42            arguments: sig
43                .inputs
44                .into_iter()
45                .map(|fn_arg| {
46                    let (pat, ty) = syn_fnarg_to_syn_pattype(fn_arg)?;
47
48                    Ok((
49                        syn_pat_to_string(&pat)?,
50                        BootSigArgType {
51                            rust_type: syn_type_to_type_recipe(&ty),
52                            c_type: syn_type_to_c_type(ty, &HashSet::from_iter(generics.clone())),
53                        },
54                    ))
55                })
56                .collect::<Result<Vec<_>>>()?,
57            generics: generics.clone(),
58            output_c_type: syn_type_to_c_type(
59                match sig.output {
60                    ReturnType::Default => {
61                        return Err(Error::custom(
62                            "default return types are not supported in bootstrap functions",
63                        )
64                        .with_span(&sig.output))
65                    }
66                    ReturnType::Type(_, ty) => *ty,
67                },
68                &HashSet::from_iter(generics),
69            ),
70            supports_partial,
71        })
72    }
73}
74
75fn syn_generic_to_syn_type_param(generic: &GenericParam) -> Result<&TypeParam> {
76    match generic {
77        GenericParam::Type(v) => Ok(v),
78        GenericParam::Lifetime(l) => {
79            Err(Error::custom("lifetimes are not supported in bootstrap functions").with_span(l))
80        }
81        GenericParam::Const(c) => {
82            Err(Error::custom("consts are not supported in bootstrap functions").with_span(c))
83        }
84    }
85}
86
87pub(super) fn syn_path_to_string(path: &Path) -> Result<String> {
88    match path.get_ident() {
89        Some(ident) => Ok(ident.to_string()),
90        None => Err(Error::custom("path must be consist of a single identifier").with_span(&path)),
91    }
92}
93
94/// extract name from pattern
95fn syn_pat_to_string(pat: &Pat) -> Result<String> {
96    match pat {
97        Pat::Box(b) => syn_pat_to_string(&*b.pat),
98        Pat::Ident(i) => Ok(i.ident.to_string()),
99        Pat::Reference(r) => syn_pat_to_string(&*r.pat),
100        Pat::Type(t) => syn_pat_to_string(&*t.pat),
101        token => Err(Error::custom("unrecognized pattern in argument").with_span(&token)),
102    }
103}
104
105pub(super) fn syn_type_to_type_recipe(ty: &Type) -> Result<TypeRecipe> {
106    Ok(match ty {
107        Type::Path(tpath) => {
108            let segment = (tpath.path.segments.last()).ok_or_else(|| {
109                Error::custom("paths must have at least one segment").with_span(ty)
110            })?;
111
112            let name = segment.ident.to_string();
113            match &segment.arguments {
114                PathArguments::None => TypeRecipe::Name(name),
115                PathArguments::AngleBracketed(ab) => {
116                    let args = (ab.args.iter())
117                        .map(|arg| syn_type_to_type_recipe(syn_generic_arg_to_syn_type(arg)?))
118                        .collect::<Result<Vec<_>>>()?;
119
120                    TypeRecipe::Nest {
121                        origin: name,
122                        args: args,
123                    }
124                }
125                PathArguments::Parenthesized(p) => {
126                    return Err(Error::custom("parenthesized paths are not supported").with_span(p))
127                }
128            }
129            .into()
130        }
131        Type::Reference(refer) => syn_type_to_type_recipe(&*refer.elem)?,
132        Type::Tuple(tuple) => TypeRecipe::Nest {
133            origin: "Tuple".to_string(),
134            args: (tuple.elems.iter())
135                .map(|ty| syn_type_to_type_recipe(ty))
136                .collect::<Result<Vec<_>>>()?,
137        }
138        .into(),
139        Type::Ptr(ptr) => syn_type_to_type_recipe(&*ptr.elem)?,
140        t => return Err(Error::custom("unrecognized type for TypeRecipe").with_span(t)),
141    })
142}
143
144fn syn_generic_arg_to_syn_type(arg: &GenericArgument) -> Result<&Type> {
145    if let GenericArgument::Type(ty) = arg {
146        Ok(ty)
147    } else {
148        Err(Error::custom("generic arguments in this position must be a type").with_span(arg))
149    }
150}
151
152fn syn_type_to_c_type(ty: Type, generics: &HashSet<String>) -> Result<String> {
153    Ok(match ty {
154        Type::Path(TypePath { path, .. }) => {
155            let segment = (path.segments.last())
156                .ok_or_else(|| Error::custom("at least one segment required").with_span(&path))?;
157
158            match segment.ident.to_string() {
159                i if i == "Option" => {
160                    let first_arg = if let PathArguments::AngleBracketed(ab) = &segment.arguments {
161                        ab.args.first().ok_or_else(|| {
162                            Error::custom("Option must have one argument").with_span(&ab)
163                        })?
164                    } else {
165                        return Err(
166                            Error::custom("Option must have angle brackets").with_span(segment)
167                        );
168                    };
169
170                    let inner_c_type = if let GenericArgument::Type(ty) = first_arg {
171                        syn_type_to_c_type(ty.clone(), generics)?
172                    } else {
173                        return Err(
174                            Error::custom("Option's argument must be a Type").with_span(segment)
175                        );
176                    };
177                    match inner_c_type.as_str() {
178                        "AnyObject *" => "AnyObject *".to_string(),
179                        "char *" => "char *".to_string(),
180                        _ => "void *".to_string(),
181                    }
182                }
183                i if i == "String" => "AnyObject *".to_string(),
184                i if i == "str" => "char *".to_string(),
185                i if i == "c_char" => "char *".to_string(),
186                i if i == "AnyObject" => "AnyObject *".to_string(),
187                i if i == "Vec" => "AnyObject *".to_string(),
188                i if i == "HashSet" => "AnyObject *".to_string(),
189                i if i == "bool" || i == "c_bool" => "bool".to_string(),
190                i if i == "i8" => "int8_t".to_string(),
191                i if i == "i16" => "int16_t".to_string(),
192                i if i == "i32" => "int32_t".to_string(),
193                i if i == "i64" => "int64_t".to_string(),
194                i if i == "u8" => "uint8_t".to_string(),
195                i if i == "u16" => "uint16_t".to_string(),
196                i if i == "u32" => "uint32_t".to_string(),
197                i if i == "u64" => "uint64_t".to_string(),
198                i if i == "f32" => "float".to_string(),
199                i if i == "f64" => "double".to_string(),
200                i if i == "usize" => "size_t".to_string(),
201                i if i == "DataFrame" => "AnyObject *".to_string(),
202                i if i == "LazyFrame" => "AnyObject *".to_string(),
203                i if i == "Expr" => "AnyObject *".to_string(),
204                i if i == "LazyFrameDomain" => "AnyDomain *".to_string(),
205                i if i == "FfiSlice" => "FfiSlice *".to_string(),
206                i if i == "Transformation" => "AnyTransformation *".to_string(),
207                i if i == "ExtrinsicObject" => "ExtrinsicObject *".to_string(),
208                i if i == "Measurement" => "AnyMeasurement *".to_string(),
209                i if i == "Function" => "AnyFunction *".to_string(),
210                i if i == "AnyFunction" => "AnyFunction *".to_string(),
211                i if i == "AnyTransformation" => "AnyTransformation *".to_string(),
212                i if i == "AnyMeasurement" => "AnyMeasurement *".to_string(),
213                i if i == "AnyQueryable" => "AnyQueryable *".to_string(),
214                i if i == "AnyDomain" => "AnyDomain *".to_string(),
215                i if i == "AnyMetric" => "AnyMetric *".to_string(),
216                i if i == "AnyMeasure" => "AnyMeasure *".to_string(),
217                i if i == "CallbackFn" => "CallbackFn".to_string(),
218                i if i == "TransitionFn" => "TransitionFn".to_string(),
219                i if i == "Fallible" || i == "FfiResult" => {
220                    let args = match &segment.arguments {
221                        PathArguments::AngleBracketed(ref ab) => &ab.args,
222                        args => {
223                            return Err(Error::custom("Fallible expects one type argument")
224                                .with_span(&args))
225                        }
226                    };
227
228                    if args.len() != 1 {
229                        return Err(Error::custom("Fallible expects one argument"));
230                    }
231                    let rtype = syn_generic_arg_to_syn_type(&args[0])?;
232                    format!(
233                        "FfiResult<{}>",
234                        syn_type_to_c_type(rtype.clone(), generics)?
235                    )
236                }
237                i if generics.contains(&i) => "AnyObject *".to_string(),
238                _ => {
239                    return Err(Error::custom(
240                        "Unrecognized rust type. Failed to convert to C type.",
241                    )
242                    .with_span(segment))
243                }
244            }
245        }
246        Type::Tuple(_) => "AnyObject *".to_string(),
247        Type::Reference(TypeReference { elem, .. }) => syn_type_to_c_type(*elem, generics)?,
248        Type::Ptr(TypePtr { elem, .. }) => syn_type_to_c_type(*elem, generics)?,
249        ty => {
250            return Err(Error::custom(
251                "Unrecognized rust type structure. Failed to convert to C type.",
252            )
253            .with_span(&ty))
254        }
255    })
256}
257
258pub fn syn_fnarg_to_syn_pattype(v: FnArg) -> Result<(Pat, Type)> {
259    match v {
260        FnArg::Receiver(r) => {
261            let msg = "bootstrapped functions don't support receiver (self) args";
262            Err(Error::custom(msg).with_span(&r))
263        }
264        FnArg::Typed(t) => Ok((*t.pat, *t.ty)),
265    }
266}