Skip to main content

prebindgen_flat/
types_util.rs

1//! Shared `syn::Type` shape utilities — the Option/Vec/reference peelers and
2//! short-name helpers every pipeline stage needs. One definition here
3//! replaces the per-module copies that used to live in `core::unfold`,
4//! `core::expand`, and the jnigen adapter.
5
6use proc_macro2::Span;
7
8/// If `ty` is `Option<Inner>` (by last path segment), return `Inner`.
9pub fn option_inner_type(ty: &syn::Type) -> Option<syn::Type> {
10    generic_inner(ty, "Option")
11}
12
13/// If `ty` is `Vec<Inner>` (by last path segment), return `Inner`.
14pub fn vec_inner_type(ty: &syn::Type) -> Option<syn::Type> {
15    generic_inner(ty, "Vec")
16}
17
18fn generic_inner(ty: &syn::Type, wrapper: &str) -> Option<syn::Type> {
19    let syn::Type::Path(tp) = ty else { return None };
20    let seg = tp.path.segments.last()?;
21    if seg.ident != wrapper {
22        return None;
23    }
24    let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
25        return None;
26    };
27    match ab.args.first()? {
28        syn::GenericArgument::Type(inner) => Some(inner.clone()),
29        _ => None,
30    }
31}
32
33/// Last path-segment ident of a path type, **generics permitting**
34/// (`Option<T>` → `Option`, `Vec<u8>` → `Vec`). Contrast with
35/// [`bare_path_ident`], which is `None` for any generic/non-path shape.
36pub fn path_tail_ident(ty: &syn::Type) -> Option<syn::Ident> {
37    match ty {
38        syn::Type::Path(tp) => tp.path.segments.last().map(|s| s.ident.clone()),
39        _ => None,
40    }
41}
42
43/// True when `ty`'s last path segment is `name` (`path_tail_is(ty, "Vec")`).
44fn path_tail_is(ty: &syn::Type, name: &str) -> bool {
45    path_tail_ident(ty).is_some_and(|i| i == name)
46}
47
48/// True when `ty` is `Result<…>` (by last path segment).
49pub fn is_result_type(ty: &syn::Type) -> bool {
50    path_tail_is(ty, "Result")
51}
52
53/// If `ty` is `Result<T, E>` (by last path segment), return `(T, E)`.
54pub fn result_parts(ty: &syn::Type) -> Option<(syn::Type, syn::Type)> {
55    let syn::Type::Path(tp) = ty else { return None };
56    let seg = tp.path.segments.last()?;
57    if seg.ident != "Result" {
58        return None;
59    }
60    let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
61        return None;
62    };
63    let mut args = ab.args.iter().filter_map(|a| match a {
64        syn::GenericArgument::Type(t) => Some(t.clone()),
65        _ => None,
66    });
67    let ok = args.next()?;
68    let err = args.next()?;
69    Some((ok, err))
70}
71
72/// If `ty` is `Result<T, E>`, return `T`.
73pub fn result_ok_type(ty: &syn::Type) -> Option<syn::Type> {
74    result_parts(ty).map(|(ok, _)| ok)
75}
76
77// `first_type_arg` lived here — the last generic-argument peel done off a
78// path's angle brackets. `TypeKind` names the argument of every wrapper the
79// language accepts (`Optional`, `Vec`, `Fallible`, `Named { args }`), so a
80// reading answers without one.
81//
82// `is_option_ref` lived here — `option_inner_type(ty)` then a `Type::Reference`
83// match — and decided how a handle parameter locks. Both halves read the
84// spelling, so an optional borrow behind an erased wrapper answered `false`.
85// The reading answers it instead: `TypeRef::optional_inner().borrow_target()`
86// (#273, #275).
87
88/// The bare ident of a plain path type (`ZThing` → `ZThing`); `None` for
89/// references, generics, or multi-shape types.
90pub fn bare_path_ident(ty: &syn::Type) -> Option<syn::Ident> {
91    let syn::Type::Path(tp) = ty else { return None };
92    let seg = tp.path.segments.last()?;
93    if !matches!(seg.arguments, syn::PathArguments::None) {
94        return None;
95    }
96    Some(seg.ident.clone())
97}
98
99/// Build an identifier at call-site span.
100///
101/// `pub`: the registry pipeline that calls this now lives in the separate
102/// `prebindgen-registry` crate.
103pub fn ident(s: &str) -> syn::Ident {
104    syn::Ident::new(s, Span::call_site())
105}
106
107/// Convert a `PascalCase` / `camelCase` identifier to `snake_case`
108/// (`ZKeyExpr` → `z_key_expr`). The single implementation behind the
109/// public `snake_case` re-export in the `prebindgen-c` crate, and behind
110/// cbindgen's type-name mangling.
111pub fn pascal_to_snake(s: &str) -> String {
112    let mut out = String::new();
113    for (i, c) in s.chars().enumerate() {
114        if c.is_uppercase() {
115            if i != 0 {
116                out.push('_');
117            }
118            out.extend(c.to_lowercase());
119        } else {
120            out.push(c);
121        }
122    }
123    out
124}
125
126#[cfg(test)]
127mod tests;