Skip to main content

miniextendr_macros/
type_inspect.rs

1//! Lightweight type-introspection helpers shared by parsing and codegen.
2
3/// Returns the `n`-th generic type argument from a path segment.
4pub(crate) fn nth_type_argument(seg: &syn::PathSegment, n: usize) -> Option<&syn::Type> {
5    if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
6        let mut count = 0;
7        for arg in ab.args.iter() {
8            if let syn::GenericArgument::Type(ty) = arg {
9                if count == n {
10                    return Some(ty);
11                }
12                count += 1;
13            }
14        }
15    }
16    None
17}
18
19/// Returns the first generic type argument from a path segment.
20pub(crate) fn first_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
21    nth_type_argument(seg, 0)
22}
23
24/// Returns the second generic type argument from a path segment.
25pub(crate) fn second_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
26    nth_type_argument(seg, 1)
27}
28
29/// Returns `true` if `ty` is syntactically `SEXP`.
30#[inline]
31pub(crate) fn is_sexp_type(ty: &syn::Type) -> bool {
32    matches!(ty, syn::Type::Path(p) if p
33        .path
34        .segments
35        .last()
36        .map(|s| s.ident == "SEXP")
37        .unwrap_or(false))
38}
39
40/// Returns `true` if `ty` is an input type bound to the R main thread.
41///
42/// Used only for thread-strategy selection: parameters of these types cannot
43/// move into the worker closure (`run_on_worker` requires `Send`), so the
44/// function stays on the main thread even under `worker-default`. Covers the
45/// raw `SEXP` and framework wrapper types that hold R memory and are `!Send`
46/// by design: `AltrepSexp` and the zero-copy R-backed views (`RDVector`,
47/// `RDMatrix`, `RndVec`, `RndMat`). Arbitrary user `!Send` types can't be
48/// detected syntactically — those need an explicit `no_worker`.
49/// Return-type analysis keeps the narrower [`is_sexp_type`].
50#[inline]
51pub(crate) fn is_main_thread_bound_input(ty: &syn::Type) -> bool {
52    const MAIN_THREAD_BOUND: &[&str] = &[
53        "SEXP",
54        "AltrepSexp",
55        "RDVector",
56        "RDMatrix",
57        "RndVec",
58        "RndMat",
59        "ProtectedStrVec",
60    ];
61    matches!(ty, syn::Type::Path(p) if p
62        .path
63        .segments
64        .last()
65        .map(|s| MAIN_THREAD_BOUND.contains(&s.ident.to_string().as_str()))
66        .unwrap_or(false))
67}
68
69/// Container family for a `several_ok` parameter, returned by
70/// [`classify_several_ok_container`].
71#[derive(Debug, Clone)]
72pub(crate) enum SeveralOkContainer {
73    /// `Vec<T>`
74    Vec,
75    /// `Box<[T]>`
76    BoxedSlice,
77    /// `[T; N]` — the `usize` is the fixed array length N
78    Array(usize),
79    /// `&[T]` or `&mut [T]` — allocate `Vec<T>` then borrow
80    BorrowedSlice,
81}
82
83/// Classify a `several_ok` parameter type into one of the four container
84/// families and extract its inner element type `T`.
85///
86/// Returns `Some((container, inner_ty))` or `None` if the type is not one of
87/// the four accepted container shapes.
88pub(crate) fn classify_several_ok_container(
89    ty: &syn::Type,
90) -> Option<(SeveralOkContainer, &syn::Type)> {
91    match ty {
92        // Vec<T>
93        syn::Type::Path(tp) => {
94            let seg = tp.path.segments.last()?;
95            if seg.ident == "Vec" {
96                let inner = first_type_argument(seg)?;
97                return Some((SeveralOkContainer::Vec, inner));
98            }
99            // Box<[T]>
100            if seg.ident == "Box"
101                && let syn::PathArguments::AngleBracketed(ab) = &seg.arguments
102            {
103                for arg in &ab.args {
104                    if let syn::GenericArgument::Type(syn::Type::Slice(s)) = arg {
105                        return Some((SeveralOkContainer::BoxedSlice, s.elem.as_ref()));
106                    }
107                }
108            }
109            None
110        }
111        // [T; N]
112        syn::Type::Array(arr) => {
113            if let syn::Expr::Lit(syn::ExprLit {
114                lit: syn::Lit::Int(n),
115                ..
116            }) = &arr.len
117            {
118                let n = n.base10_parse::<usize>().ok()?;
119                return Some((SeveralOkContainer::Array(n), arr.elem.as_ref()));
120            }
121            None
122        }
123        // &[T] or &mut [T]
124        syn::Type::Reference(r) => {
125            if let syn::Type::Slice(s) = r.elem.as_ref() {
126                return Some((SeveralOkContainer::BorrowedSlice, s.elem.as_ref()));
127            }
128            None
129        }
130        _ => None,
131    }
132}