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/// Peel a return-visibility marker (`Invisible<T>` / `Visible<T>`, matched
30/// on the last path segment like `Dots` / `Missing`).
31///
32/// Returns `(Some(true), &T)` for `Invisible<T>`, `(Some(false), &T)` for
33/// `Visible<T>`, and `(None, ty)` for anything else, including a bare
34/// `Invisible` with no type argument (left alone; the fail-safe direction is
35/// "not a marker", and `IntoR` on the marker types keeps the conversion
36/// correct). Nested markers are the caller's error to report; see
37/// [`visibility_marker_error`].
38pub(crate) fn peel_visibility_marker(ty: &syn::Type) -> (Option<bool>, &syn::Type) {
39    let syn::Type::Path(p) = ty else {
40        return (None, ty);
41    };
42    let Some(seg) = p.path.segments.last() else {
43        return (None, ty);
44    };
45    let invisible = match seg.ident.to_string().as_str() {
46        "Invisible" => true,
47        "Visible" => false,
48        _ => return (None, ty),
49    };
50    match first_type_argument(seg) {
51        Some(inner) => (Some(invisible), inner),
52        None => (None, ty),
53    }
54}
55
56/// [`peel_visibility_marker`] over a whole `syn::ReturnType`: returns the
57/// marker's decision and an owned return type with the marker removed, so the
58/// rest of the analysis (`Option`/`Result` shapes, `Self`, thread strategy)
59/// sees exactly what it would without the marker.
60pub(crate) fn peel_return_visibility(output: &syn::ReturnType) -> (Option<bool>, syn::ReturnType) {
61    match output {
62        syn::ReturnType::Type(arrow, ty) => {
63            let (marker, inner) = peel_visibility_marker(ty);
64            match marker {
65                Some(_) => (
66                    marker,
67                    syn::ReturnType::Type(*arrow, Box::new(inner.clone())),
68                ),
69                None => (None, output.clone()),
70            }
71        }
72        syn::ReturnType::Default => (None, output.clone()),
73    }
74}
75
76/// Model `#[miniextendr(serialize)]` exactly like a declared `AsSerialize<T>`
77/// return, after peeling an outer visibility marker. In particular, `Option`
78/// and `Result` inside it are serde data, not boundary control flow.
79pub(crate) fn serialize_return_type(output: &syn::ReturnType, serialize: bool) -> syn::ReturnType {
80    if !serialize {
81        return output.clone();
82    }
83    let ty: syn::Type = match output {
84        syn::ReturnType::Default => ::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Parenthesis,
            ::quote::__private::TokenStream::new());
        _s
    })syn::parse_quote!(()),
85        syn::ReturnType::Type(_, ty) => (**ty).clone(),
86    };
87    ::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_rarrow(&mut _s);
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "miniextendr_api");
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "serde");
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "AsSerialize");
        ::quote::__private::push_lt(&mut _s);
        ::quote::ToTokens::to_tokens(&ty, &mut _s);
        ::quote::__private::push_gt(&mut _s);
        _s
    })syn::parse_quote!(-> ::miniextendr_api::serde::AsSerialize<#ty>)
88}
89
90/// Prepare the value to match the type used by return analysis. Both functions
91/// and method/shim paths unwrap visibility before applying serde transport.
92pub(crate) fn prepare_return_value(
93    call: proc_macro2::TokenStream,
94    visibility_marker: Option<bool>,
95    serialize: bool,
96) -> proc_macro2::TokenStream {
97    let value = if visibility_marker.is_some() {
98        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            _s
        });
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::parse(&mut _s, "0");
    _s
}quote::quote! { (#call).0 }
99    } else {
100        call
101    };
102    if serialize {
103        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "serde");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "AsSerialize");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&value, &mut _s);
            _s
        });
    _s
}quote::quote! { ::miniextendr_api::serde::AsSerialize(#value) }
104    } else {
105        value
106    }
107}
108
109/// Resolve the wrapper's visibility from the three sources, most explicit
110/// first: a return-type marker, then the `invisible` / `visible` attribute,
111/// then the shape default. A marker and an attribute that disagree are an
112/// error at `span`.
113pub(crate) fn resolve_visibility(
114    marker: Option<bool>,
115    attr: Option<bool>,
116    shape_default: bool,
117    span: proc_macro2::Span,
118) -> syn::Result<bool> {
119    match (marker, attr) {
120        (Some(m), Some(a)) if m != a => Err(syn::Error::new(
121            span,
122            "the return type's visibility marker and the `invisible` / `visible` attribute disagree; keep one of them (or make them agree)",
123        )),
124        (Some(m), _) => Ok(m),
125        (None, Some(a)) => Ok(a),
126        (None, None) => Ok(shape_default),
127    }
128}
129
130/// The error for a marker that cannot be honoured: a nested marker
131/// (`Invisible<Visible<T>>`), a marker in argument position, or, the mirror
132/// case, a parameter marker (`Checked<T>` / `Unchecked<T>`) in return
133/// position (also inside a visibility marker).
134pub(crate) fn visibility_marker_error(ty: &syn::Type, what: &str) -> Option<syn::Error> {
135    let (marker, inner) = peel_visibility_marker(ty);
136    if what == "return"
137        && let Some((param_marker, _)) = outer_param_marker(inner)
138    {
139        return Some(syn::Error::new_spanned(
140            ty,
141            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}<T>` marks a parameter only; it cannot be used as a return type (return the inner `T`)",
                param_marker.name()))
    })format!(
142                "`{}<T>` marks a parameter only; it cannot be used as a return type (return the \
143                 inner `T`)",
144                param_marker.name()
145            ),
146        ));
147    }
148    marker?;
149    if what == "argument" {
150        return Some(syn::Error::new_spanned(
151            ty,
152            "`Invisible<T>` / `Visible<T>` mark the return type only; they cannot be used as a parameter type",
153        ));
154    }
155    if peel_visibility_marker(inner).0.is_some() {
156        return Some(syn::Error::new_spanned(
157            ty,
158            "visibility markers cannot be nested; use a single `Invisible<T>` or `Visible<T>` around the return type",
159        ));
160    }
161    None
162}
163
164/// Returns `true` if `ty` is syntactically `SEXP`.
165#[inline]
166pub(crate) fn is_sexp_type(ty: &syn::Type) -> bool {
167    #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Path(p) if
        p.path.segments.last().map(|s| s.ident == "SEXP").unwrap_or(false) =>
        true,
    _ => false,
}matches!(ty, syn::Type::Path(p) if p
168        .path
169        .segments
170        .last()
171        .map(|s| s.ident == "SEXP")
172        .unwrap_or(false))
173}
174
175/// Framework type names that hold R memory and are `!Send` by design.
176///
177/// Used only for thread-strategy selection: values of these types can neither
178/// move into the worker closure nor cross back out of it (`run_on_worker`
179/// requires `Send`), so any function touching one stays on the main thread
180/// even under `worker-default`. Covers the raw `SEXP`, `AltrepSexp`, the
181/// zero-copy R-backed views (`RDVector`, `RDMatrix`, `RndVec`, `RndMat`,
182/// `ProtectedStrVec`), and the owned GC-rooted handles (`BuiltDataFrame`,
183/// `DataFrameShape`, `NamedListBuilder`, `BuiltList`, `Preserved`,
184/// `REvalError`, `RTxtProgressBar`). Arbitrary user `!Send` types can't be
185/// detected syntactically — those need an explicit `no_worker`.
186const MAIN_THREAD_BOUND: &[&str] = &[
187    "SEXP",
188    // Condition-call markers wrap the `.call` slot's SEXP (#1566).
189    "Call",
190    "CallerCall",
191    // Unevaluated-argument markers hold the argument's expression and
192    // environment (#1835); `Missing<..>` around them is checked by the caller.
193    "Quoted",
194    "Quosure",
195    // The unforced dots hold the wrapper's frame and evaluate R code (#1892).
196    "LazyDots",
197    "AltrepSexp",
198    "RDVector",
199    "RDMatrix",
200    "RndVec",
201    "RndMat",
202    "ProtectedStrVec",
203    "StrVec",
204    "RCow",
205    "BuiltDataFrame",
206    "NamedListBuilder",
207    "BuiltList",
208    "DataFrameShape",
209    // Precious-list roots: a `Preserved` handle, the `REvalError` an R
210    // evaluation fails with, which keeps its condition in one, and an
211    // `RTxtProgressBar`.
212    "Preserved",
213    "REvalError",
214    "RTxtProgressBar",
215];
216
217/// Returns `true` if `ty` is an input type bound to the R main thread.
218///
219/// Checks rooted borrowing conversions (including character-vector Cow
220/// containers), then the outermost path segment for other framework handles.
221/// Return-type analysis keeps the narrower [`is_sexp_type`] and uses the
222/// recursive [`is_main_thread_bound_return`] for thread selection.
223#[inline]
224pub(crate) fn is_main_thread_bound_input(ty: &syn::Type) -> bool {
225    is_rooted_view(ty)
226        // A borrowed external argument holds its value's `RefCell` borrow on
227        // the main thread, like a `&self` receiver (#2106).
228        || crate::externalptr_args::external_arg(ty).is_some()
229        || #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Path(p) if
        p.path.segments.last().map(|s|
                    MAIN_THREAD_BOUND.contains(&s.ident.to_string().as_str())).unwrap_or(false)
        => true,
    _ => false,
}matches!(ty, syn::Type::Path(p) if p
230        .path
231        .segments
232        .last()
233        .map(|s| MAIN_THREAD_BOUND.contains(&s.ident.to_string().as_str()))
234        .unwrap_or(false))
235}
236
237/// Returns `true` if `ty` is (or contains) a main-thread-bound type anywhere
238/// in a return position — e.g. `BuiltDataFrame`, `Result<BuiltDataFrame,
239/// String>`, `Option<DataFrameShape>`, `Vec<BuiltDataFrame>`.
240///
241/// Unlike inputs, main-thread-bound returns routinely nest inside `Result` /
242/// `Option` / containers, so this walks the whole type tree. Under
243/// `worker-default` a function whose return type matches is forced onto the
244/// main thread: the value owns R memory (`!Send`) and cannot cross back from
245/// the worker (`run_on_worker` requires `T: Send`).
246pub(crate) fn is_main_thread_bound_return(ty: &syn::Type) -> bool {
247    match ty {
248        syn::Type::Path(p) => p.path.segments.last().is_some_and(|seg| {
249            if MAIN_THREAD_BOUND.contains(&seg.ident.to_string().as_str()) {
250                return true;
251            }
252            if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
253                return ab.args.iter().any(|arg| {
254                    #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Type(t) if is_main_thread_bound_return(t) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Type(t) if is_main_thread_bound_return(t))
255                });
256            }
257            false
258        }),
259        syn::Type::Reference(r) => is_main_thread_bound_return(&r.elem),
260        syn::Type::Paren(p) => is_main_thread_bound_return(&p.elem),
261        syn::Type::Group(g) => is_main_thread_bound_return(&g.elem),
262        syn::Type::Tuple(t) => t.elems.iter().any(is_main_thread_bound_return),
263        syn::Type::Array(a) => is_main_thread_bound_return(&a.elem),
264        syn::Type::Slice(s) => is_main_thread_bound_return(&s.elem),
265        _ => false,
266    }
267}
268
269/// Returns `true` if a method signature passes an R value across the call: a
270/// main-thread-bound input (an unevaluated `Quoted` / `Quosure` under
271/// `Missing<..>` too) or a main-thread-bound return. Such a static method stays
272/// on the main thread even when `worker` or `worker-default` selects the
273/// worker, the rule standalone functions follow.
274pub(crate) fn signature_holds_r_values(sig: &syn::Signature) -> bool {
275    sig.inputs.iter().any(|arg| {
276        #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::FnArg::Typed(pt) if
        is_main_thread_bound_input(&pt.ty) ||
            unevaluated_param(&pt.ty).is_some() => true,
    _ => false,
}matches!(arg, syn::FnArg::Typed(pt)
277            if is_main_thread_bound_input(&pt.ty) || unevaluated_param(&pt.ty).is_some())
278    }) || #[allow(non_exhaustive_omitted_patterns)] match &sig.output {
    syn::ReturnType::Type(_, ty) if is_main_thread_bound_return(ty) => true,
    _ => false,
}matches!(&sig.output, syn::ReturnType::Type(_, ty) if is_main_thread_bound_return(ty))
279}
280
281// region: condition-call markers (#1566)
282
283/// Detect a condition-call marker parameter type: `Call` selects `wrapper`
284/// attribution, `CallerCall` selects `caller`. Matched on the last path
285/// segment like the visibility markers; a segment carrying generic arguments
286/// (`Call<T>`) is some other type. References and wrappers (`&Call`,
287/// `Option<Call>`) are not markers: the C wrapper binds the marker by value
288/// from its hidden call slot.
289pub(crate) fn call_marker(ty: &syn::Type) -> Option<crate::r_wrapper_builder::CallAttribution> {
290    let syn::Type::Path(p) = ty else {
291        return None;
292    };
293    let seg = p.path.segments.last()?;
294    if !#[allow(non_exhaustive_omitted_patterns)] match seg.arguments {
    syn::PathArguments::None => true,
    _ => false,
}matches!(seg.arguments, syn::PathArguments::None) {
295        return None;
296    }
297    match seg.ident.to_string().as_str() {
298        "Call" => Some(crate::r_wrapper_builder::CallAttribution::Wrapper),
299        "CallerCall" => Some(crate::r_wrapper_builder::CallAttribution::Caller),
300        _ => None,
301    }
302}
303
304// endregion
305
306// region: argument-count marker (#1860)
307
308/// Detect the argument-count marker parameter type, `NArgs`: matched on the
309/// last path segment like [`call_marker`], with no generic arguments.
310/// References and wrappers (`&NArgs`, `Option<NArgs>`) are not the marker.
311///
312/// The parameter is no R formal: the R wrapper passes `nargs()` at its
313/// position in the `.Call()` (`RArgumentBuilder::build_call_args_vec`), and the
314/// C wrapper converts it like any parameter (`TryFromSexp for NArgs`).
315pub(crate) fn is_nargs_marker(ty: &syn::Type) -> bool {
316    let syn::Type::Path(p) = ty else {
317        return false;
318    };
319    p.path
320        .segments
321        .last()
322        .is_some_and(|seg| seg.ident == "NArgs" && seg.arguments.is_none())
323}
324
325// endregion
326
327// region: RNG guard parameter (#2002)
328
329/// Detect the RNG guard parameter type, `&RngGuard` (a shared reference to a
330/// path whose last segment is `RngGuard`, with no generic arguments, any
331/// lifetime). Under `#[miniextendr(rng)]` the parameter is no R formal: the
332/// wrapper has loaded R's RNG state and passes its guard at that position.
333pub(crate) fn is_rng_guard_param(ty: &syn::Type) -> bool {
334    let syn::Type::Reference(reference) = ty else {
335        return false;
336    };
337    reference.mutability.is_none()
338        && #[allow(non_exhaustive_omitted_patterns)] match reference.elem.as_ref() {
    syn::Type::Path(p) if
        p.qself.is_none() &&
            p.path.segments.last().is_some_and(|seg|
                    seg.ident == "RngGuard" && seg.arguments.is_none()) => true,
    _ => false,
}matches!(reference.elem.as_ref(), syn::Type::Path(p) if p.qself.is_none() && p
339            .path
340            .segments
341            .last()
342            .is_some_and(|seg| seg.ident == "RngGuard" && seg.arguments.is_none()))
343}
344
345/// Whether `ty` names `RngGuard` anywhere: a parameter that does without
346/// being [`is_rng_guard_param`] (`&mut RngGuard`, `RngGuard` by value,
347/// `Option<&RngGuard>`) is a shape the wrapper cannot fill.
348pub(crate) fn mentions_rng_guard(ty: &syn::Type) -> bool {
349    match ty {
350        syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
351            seg.ident == "RngGuard"
352                || match &seg.arguments {
353                    syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(
354                        |arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Type(t) if mentions_rng_guard(t) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_rng_guard(t)),
355                    ),
356                    _ => false,
357                }
358        }),
359        syn::Type::Reference(r) => mentions_rng_guard(&r.elem),
360        syn::Type::Paren(p) => mentions_rng_guard(&p.elem),
361        syn::Type::Group(g) => mentions_rng_guard(&g.elem),
362        syn::Type::Tuple(t) => t.elems.iter().any(mentions_rng_guard),
363        syn::Type::Array(a) => mentions_rng_guard(&a.elem),
364        syn::Type::Slice(s) => mentions_rng_guard(&s.elem),
365        _ => false,
366    }
367}
368
369/// The argument expression for an `&RngGuard` parameter: a borrow of a guard
370/// made at the call, which the wrapper has loaded the state for and saves
371/// itself (the guard saves nothing when it drops). No binding, so no user
372/// parameter name can capture it.
373pub(crate) fn rng_guard_arg() -> proc_macro2::TokenStream {
374    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "rng");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "RngGuard");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "__mx_loaded_by_wrapper");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            _s
        });
    _s
}quote::quote! {
375        &unsafe { ::miniextendr_api::rng::RngGuard::__mx_loaded_by_wrapper() }
376    }
377}
378
379// endregion
380
381// region: borrowed vector views (#1427)
382
383/// Conversions selected from the rooted call argument rather than a raw SEXP.
384/// The Cow vector cases are character vectors, not arbitrary lists of views.
385pub(crate) fn is_rooted_view(ty: &syn::Type) -> bool {
386    let syn::Type::Path(path) = ty else {
387        return false;
388    };
389    if path.qself.is_some() {
390        return false;
391    }
392    let Some(seg) = path.path.segments.last() else {
393        return false;
394    };
395    if seg.ident == "StrVec" || seg.ident == "RCow" || seg.ident == "Cow" {
396        return true;
397    }
398    seg.ident == "Vec"
399        && first_type_argument(seg).is_some_and(|elem| {
400            let elem = option_inner_type(elem).unwrap_or(elem);
401            #[allow(non_exhaustive_omitted_patterns)] match elem {
    syn::Type::Path(p) if
        p.qself.is_none() &&
            p.path.segments.last().is_some_and(|s| s.ident == "Cow") => true,
    _ => false,
}matches!(elem, syn::Type::Path(p) if p.qself.is_none() && p.path.segments.last()
402            .is_some_and(|s| s.ident == "Cow"))
403        })
404}
405
406/// Explicit shared reference forms whose main-thread conversions borrow a root:
407/// the references, their `Option`s, and `Vec`s of either (nested `Vec`s too).
408/// Keep worker selection separate: `&str` uses owned storage on the worker,
409/// while mutable/custom references still need their existing conversion path.
410pub(crate) fn is_rooted_reference(ty: &syn::Type) -> bool {
411    let ty = option_inner_type(ty).unwrap_or(ty);
412    if let syn::Type::Path(path) = ty
413        && path.qself.is_none()
414        && let Some(segment) = path.path.segments.last()
415        && segment.ident == "Vec"
416    {
417        return first_type_argument(segment).is_some_and(is_rooted_reference);
418    }
419    let syn::Type::Reference(reference) = ty else {
420        return false;
421    };
422    if reference.mutability.is_some() {
423        return false;
424    }
425    match reference.elem.as_ref() {
426        syn::Type::Slice(_) => true,
427        syn::Type::Path(path) if path.qself.is_none() => {
428            path.path.segments.last().is_some_and(|seg| {
429                seg.arguments.is_none()
430                    && #[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
    {
    "i32" | "f64" | "u8" | "RLogical" | "Rcomplex" | "str" => true,
    _ => false,
}matches!(
431                        seg.ident.to_string().as_str(),
432                        "i32" | "f64" | "u8" | "RLogical" | "Rcomplex" | "str"
433                    )
434            })
435        }
436        _ => false,
437    }
438}
439
440/// A `'static` borrowed parameter (`StrVec<'static>`, `Cow<'static, str>`,
441/// `&'static [T]`, `Option<&'static str>`) can never be bound: its root is the
442/// call's argument slot, or on the worker a copy that ends with the call. Returns the statement that binds `ident` with a
443/// `compile_error!` naming the fix, spanned at the lifetime, in place of a
444/// borrow-checker error about a generated temporary. The binding keeps the code
445/// that uses `ident` free of follow-on errors; it does not diverge, so that
446/// code draws no unreachable warning.
447pub(crate) fn static_rooted_view_binding(
448    ident: &syn::Ident,
449    ty: &syn::Type,
450) -> Option<proc_macro2::TokenStream> {
451    let lifetime = static_lifetime(ty)?;
452    Some({
    let mut _s = ::quote::__private::TokenStream::new();
    let _span: ::quote::__private::Span =
        ::quote::__private::get_span(lifetime.span()).__into_span();
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "core");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "compile_error");
    ::quote::__private::push_bang_spanned(&mut _s, _span);
    ::quote::__private::push_group_spanned(&mut _s, _span,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            let _span: ::quote::__private::Span =
                ::quote::__private::get_span(_span).__into_span();
            ::quote::__private::parse_spanned(&mut _s, _span,
                "\"this parameter borrows the call\'s argument and cannot be `\'static`; write `\'_` or \\\n             elide the lifetime, or take an owned type (`ProtectedStrVec`, `Vec`, `String`) to \\\n             keep the data after the call\"");
            _s
        });
    ::quote::__private::push_semi_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "let");
    ::quote::ToTokens::to_tokens(&ident, &mut _s);
    ::quote::__private::push_colon_spanned(&mut _s, _span);
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    ::quote::__private::push_eq_spanned(&mut _s, _span);
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "core");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "option");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "Option");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_lt_spanned(&mut _s, _span);
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    ::quote::__private::push_gt_spanned(&mut _s, _span);
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "None");
    ::quote::__private::push_dot_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "unwrap");
    ::quote::__private::push_group_spanned(&mut _s, _span,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let _: ::quote::__private::Span =
                ::quote::__private::get_span(_span).__into_span();
            ::quote::__private::TokenStream::new()
        });
    ::quote::__private::push_semi_spanned(&mut _s, _span);
    _s
}quote::quote_spanned! {lifetime.span()=>
453        ::core::compile_error!(
454            "this parameter borrows the call's argument and cannot be `'static`; write `'_` or \
455             elide the lifetime, or take an owned type (`ProtectedStrVec`, `Vec`, `String`) to \
456             keep the data after the call"
457        );
458        let #ident: #ty = ::core::option::Option::<#ty>::None.unwrap();
459    })
460}
461
462/// The first `'static` lifetime written anywhere in `ty`.
463pub(crate) fn static_lifetime(ty: &syn::Type) -> Option<&syn::Lifetime> {
464    let is_static = |lt: &&syn::Lifetime| lt.ident == "static";
465    match ty {
466        syn::Type::Reference(r) => r
467            .lifetime
468            .as_ref()
469            .filter(is_static)
470            .or_else(|| static_lifetime(&r.elem)),
471        syn::Type::Path(p) => p.path.segments.iter().find_map(|seg| {
472            let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
473                return None;
474            };
475            args.args.iter().find_map(|arg| match arg {
476                syn::GenericArgument::Lifetime(lt) => Some(lt).filter(is_static),
477                syn::GenericArgument::Type(ty) => static_lifetime(ty),
478                _ => None,
479            })
480        }),
481        syn::Type::Slice(s) => static_lifetime(&s.elem),
482        syn::Type::Array(a) => static_lifetime(&a.elem),
483        syn::Type::Paren(p) => static_lifetime(&p.elem),
484        syn::Type::Group(g) => static_lifetime(&g.elem),
485        syn::Type::Tuple(t) => t.elems.iter().find_map(static_lifetime),
486        _ => None,
487    }
488}
489
490// endregion
491
492// region: unforced dots (#1892)
493
494/// Detect the unforced-dots parameter type, `LazyDots`: matched on the last
495/// path segment like the other markers, bare or with lifetime arguments only
496/// (`LazyDots<'_>`). Only the whole type by value is the parameter; any other
497/// shape (`&LazyDots`, `Option<LazyDots>`, `Missing<LazyDots>`) is refused by
498/// the callers through [`mentions_lazy_dots`].
499///
500/// The parameter is R's `...` at its position: the R wrapper passes
501/// `environment()` there (`RArgumentBuilder::build_call_args_vec`), and the C
502/// wrapper binds it with `LazyDots::from_wrapper_arg`.
503pub(crate) fn is_lazy_dots_marker(ty: &syn::Type) -> bool {
504    let syn::Type::Path(p) = ty else {
505        return false;
506    };
507    p.path.segments.last().is_some_and(|seg| {
508        seg.ident == "LazyDots"
509            && match &seg.arguments {
510                syn::PathArguments::None => true,
511                syn::PathArguments::AngleBracketed(ab) => ab
512                    .args
513                    .iter()
514                    .all(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Lifetime(_) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Lifetime(_))),
515                syn::PathArguments::Parenthesized(_) => false,
516            }
517    })
518}
519
520/// Whether `ty` names `LazyDots` anywhere, at any depth.
521pub(crate) fn mentions_lazy_dots(ty: &syn::Type) -> bool {
522    match ty {
523        syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
524            seg.ident == "LazyDots"
525                || match &seg.arguments {
526                    syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(
527                        |arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Type(t) if mentions_lazy_dots(t) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_lazy_dots(t)),
528                    ),
529                    _ => false,
530                }
531        }),
532        syn::Type::Reference(r) => mentions_lazy_dots(&r.elem),
533        syn::Type::Paren(p) => mentions_lazy_dots(&p.elem),
534        syn::Type::Group(g) => mentions_lazy_dots(&g.elem),
535        syn::Type::Tuple(t) => t.elems.iter().any(mentions_lazy_dots),
536        syn::Type::Array(a) => mentions_lazy_dots(&a.elem),
537        syn::Type::Slice(s) => mentions_lazy_dots(&s.elem),
538        _ => false,
539    }
540}
541
542// endregion
543
544// region: unevaluated-argument markers (#1835)
545
546/// Which unevaluated-argument marker a parameter takes.
547#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnevaluatedKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                UnevaluatedKind::Quoted => "Quoted",
                UnevaluatedKind::Quosure => "Quosure",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UnevaluatedKind { }
#[automatically_derived]
impl ::core::clone::Clone for UnevaluatedKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnevaluatedKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for UnevaluatedKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for UnevaluatedKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnevaluatedKind { }Eq)]
548pub(crate) enum UnevaluatedKind {
549    /// `Quoted`: base R's `substitute(x)` and `parent.frame()`.
550    Quoted,
551    /// `Quosure`: `rlang::enquo(x)`.
552    Quosure,
553}
554
555impl UnevaluatedKind {
556    /// The marker's type name, for diagnostics.
557    pub(crate) fn name(self) -> &'static str {
558        match self {
559            Self::Quoted => "Quoted",
560            Self::Quosure => "Quosure",
561        }
562    }
563
564    /// The `.Call()` argument the R wrapper passes for parameter `p`: the
565    /// missing-argument sentinel, as for `Missing<T>`, else the capture. Never
566    /// forces `p`.
567    pub(crate) fn r_call_arg(self, p: &str) -> String {
568        let capture = match self {
569            Self::Quoted => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("list(substitute({0}), parent.frame())",
                p))
    })format!("list(substitute({p}), parent.frame())"),
570            Self::Quosure => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("rlang::enquo({0})", p))
    })format!("rlang::enquo({p})"),
571        };
572        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (missing({0})) quote(expr=) else {1}",
                p, capture))
    })format!("if (missing({p})) quote(expr=) else {capture}")
573    }
574
575    /// The marker's path in `miniextendr_api`.
576    pub(crate) fn api_path(self) -> proc_macro2::TokenStream {
577        match self {
578            Self::Quoted => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Quoted");
    _s
}quote::quote!(::miniextendr_api::Quoted),
579            Self::Quosure => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Quosure");
    _s
}quote::quote!(::miniextendr_api::Quosure),
580        }
581    }
582
583    /// The marker a path segment names: `Quoted` / `Quosure`, bare or with
584    /// lifetime arguments only (`Quoted<'_>`).
585    fn of_segment(seg: &syn::PathSegment) -> Option<Self> {
586        let lifetimes_only = match &seg.arguments {
587            syn::PathArguments::None => true,
588            syn::PathArguments::AngleBracketed(ab) => ab
589                .args
590                .iter()
591                .all(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Lifetime(_) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Lifetime(_))),
592            syn::PathArguments::Parenthesized(_) => false,
593        };
594        if !lifetimes_only {
595            return None;
596        }
597        match seg.ident.to_string().as_str() {
598            "Quoted" => Some(Self::Quoted),
599            "Quosure" => Some(Self::Quosure),
600            _ => None,
601        }
602    }
603}
604
605/// An unevaluated-argument parameter: `Quoted` / `Quosure`, or either inside
606/// `Missing<..>` (`optional`).
607#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnevaluatedParam {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "UnevaluatedParam", "kind", &self.kind, "optional",
            &&self.optional)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UnevaluatedParam { }
#[automatically_derived]
impl ::core::clone::Clone for UnevaluatedParam {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<UnevaluatedKind>;
        let _: ::core::clone::AssertParamIsClone<bool>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnevaluatedParam { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for UnevaluatedParam { }
#[automatically_derived]
impl ::core::cmp::PartialEq for UnevaluatedParam {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.optional == other.optional && self.kind == other.kind
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnevaluatedParam {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<UnevaluatedKind>;
        let _: ::core::cmp::AssertParamIsEq<bool>;
    }
}Eq)]
608pub(crate) struct UnevaluatedParam {
609    pub(crate) kind: UnevaluatedKind,
610    /// `Missing<Quoted>` / `Missing<Quosure>`: an omitted argument is
611    /// `Missing::Absent` instead of an error.
612    pub(crate) optional: bool,
613}
614
615/// Detect an unevaluated-argument parameter type, matched on the last path
616/// segment like the other markers: `Quoted`, `Quosure`, or one of them as the
617/// type argument of `Missing<..>`. Any other position (`Option<Quoted>`,
618/// `&Quoted`) is not one; [`mentions_unevaluated_marker`] lets the caller
619/// refuse it.
620pub(crate) fn unevaluated_param(ty: &syn::Type) -> Option<UnevaluatedParam> {
621    let marker = |ty: &syn::Type| match ty {
622        syn::Type::Path(p) => p.path.segments.last().and_then(UnevaluatedKind::of_segment),
623        _ => None,
624    };
625    if let Some(kind) = marker(ty) {
626        return Some(UnevaluatedParam {
627            kind,
628            optional: false,
629        });
630    }
631    let inner = crate::miniextendr_fn::get_missing_inner_type(ty)?;
632    marker(inner).map(|kind| UnevaluatedParam {
633        kind,
634        optional: true,
635    })
636}
637
638/// Whether `ty` names `Quoted` / `Quosure` anywhere, at any depth.
639pub(crate) fn mentions_unevaluated_marker(ty: &syn::Type) -> bool {
640    match ty {
641        syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
642            #[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
    {
    "Quoted" | "Quosure" => true,
    _ => false,
}matches!(seg.ident.to_string().as_str(), "Quoted" | "Quosure")
643                || match &seg.arguments {
644                    syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(|arg| {
645                        #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Type(t) if mentions_unevaluated_marker(t) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_unevaluated_marker(t))
646                    }),
647                    _ => false,
648                }
649        }),
650        syn::Type::Reference(r) => mentions_unevaluated_marker(&r.elem),
651        syn::Type::Paren(p) => mentions_unevaluated_marker(&p.elem),
652        syn::Type::Group(g) => mentions_unevaluated_marker(&g.elem),
653        syn::Type::Tuple(t) => t.elems.iter().any(mentions_unevaluated_marker),
654        syn::Type::Array(a) => mentions_unevaluated_marker(&a.elem),
655        syn::Type::Slice(s) => mentions_unevaluated_marker(&s.elem),
656        _ => false,
657    }
658}
659
660// endregion
661
662// region: parameter markers (#1566 §2)
663
664/// A by-value parameter marker the macro peels from a parameter type before
665/// any R-side analysis: the parameter converts as the inner type, and the C
666/// wrapper wraps the value (`<ctor>::from_inner(x)`) before the call.
667#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParamMarker {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ParamMarker::Checked => "Checked",
                ParamMarker::Unchecked => "Unchecked",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ParamMarker { }
#[automatically_derived]
impl ::core::clone::Clone for ParamMarker {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ParamMarker { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ParamMarker { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ParamMarker {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ParamMarker { }Eq)]
668pub(crate) enum ParamMarker {
669    /// `Checked<T>`: keep the parameter's type-derived R-side checks.
670    Checked,
671    /// `Unchecked<T>`: drop them.
672    Unchecked,
673}
674
675/// The decision family a [`ParamMarker`] belongs to: a parameter carries at
676/// most one marker per family.
677#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParamMarkerFamily {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "Preconditions")
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ParamMarkerFamily { }
#[automatically_derived]
impl ::core::clone::Clone for ParamMarkerFamily {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ParamMarkerFamily { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ParamMarkerFamily { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ParamMarkerFamily {
    #[inline]
    fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ParamMarkerFamily { }Eq)]
678pub(crate) enum ParamMarkerFamily {
679    /// `Checked` / `Unchecked`: the type-derived R-side checks.
680    Preconditions,
681}
682
683impl ParamMarker {
684    /// The marker for a path segment's name, if it is one.
685    fn from_ident(ident: &syn::Ident) -> Option<Self> {
686        match ident.to_string().as_str() {
687            "Checked" => Some(Self::Checked),
688            "Unchecked" => Some(Self::Unchecked),
689            _ => None,
690        }
691    }
692
693    /// The type's name as written in source.
694    pub(crate) fn name(self) -> &'static str {
695        match self {
696            Self::Checked => "Checked",
697            Self::Unchecked => "Unchecked",
698        }
699    }
700
701    /// Which decision the marker makes.
702    pub(crate) fn family(self) -> ParamMarkerFamily {
703        match self {
704            Self::Checked | Self::Unchecked => ParamMarkerFamily::Preconditions,
705        }
706    }
707
708    /// The runtime type, for the rebinding the C wrapper emits.
709    pub(crate) fn ctor_path(self) -> proc_macro2::TokenStream {
710        match self {
711            Self::Checked => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Checked");
    _s
}quote::quote!(::miniextendr_api::Checked),
712            Self::Unchecked => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Unchecked");
    _s
}quote::quote!(::miniextendr_api::Unchecked),
713        }
714    }
715
716    /// The precondition decision: `Some(true)` keeps the type-derived
717    /// checks, `Some(false)` drops them.
718    pub(crate) fn preconditions(self) -> Option<bool> {
719        match self {
720            Self::Checked => Some(true),
721            Self::Unchecked => Some(false),
722        }
723    }
724}
725
726/// The marker a type's outermost segment names, with its single type
727/// argument. Matched on the last path segment like [`peel_visibility_marker`];
728/// a bare `Checked` (no type argument) or one with two is not a marker (the
729/// fail-safe direction: the type then fails to convert).
730fn outer_param_marker(ty: &syn::Type) -> Option<(ParamMarker, &syn::Type)> {
731    let syn::Type::Path(p) = ty else {
732        return None;
733    };
734    let seg = p.path.segments.last()?;
735    let marker = ParamMarker::from_ident(&seg.ident)?;
736    let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
737        return None;
738    };
739    let mut types = ab.args.iter().filter_map(|arg| match arg {
740        syn::GenericArgument::Type(t) => Some(t),
741        _ => None,
742    });
743    let inner = types.next()?;
744    if types.next().is_some() {
745        return None;
746    }
747    Some((marker, inner))
748}
749
750/// Peel the parameter markers off a parameter type, outermost first: returns
751/// the markers in the order written and the inner type the parameter
752/// converts as. `Checked<Strict<T>>`-style stacks of different families peel
753/// in one pass; two markers of one family (`Checked<Unchecked<T>>`) are an
754/// error, as is a marker nested inside `Option` / `Missing` / `Vec` / `&`
755/// (at any depth of those), where the conversion has no marker to unwrap.
756pub(crate) fn peel_param_markers(ty: &syn::Type) -> syn::Result<(Vec<ParamMarker>, &syn::Type)> {
757    let mut markers: Vec<ParamMarker> = Vec::new();
758    let mut inner = ty;
759    while let Some((marker, next)) = outer_param_marker(inner) {
760        if let Some(first) = markers.iter().find(|m| m.family() == marker.family()) {
761            return Err(syn::Error::new_spanned(
762                ty,
763                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("at most one precondition marker per parameter: `{0}` already decides this parameter\'s R-side type checks, so drop `{1}`",
                first.name(), marker.name()))
    })format!(
764                    "at most one precondition marker per parameter: `{}` already decides this \
765                     parameter's R-side type checks, so drop `{}`",
766                    first.name(),
767                    marker.name()
768                ),
769            ));
770        }
771        markers.push(marker);
772        inner = next;
773    }
774    if let Some(nested) = nested_param_marker(inner) {
775        return Err(syn::Error::new_spanned(
776            ty,
777            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("put `{0}` outermost: `{0}<Option<T>>`, not `Option<{0}<T>>`; the marker applies to the whole parameter",
                nested.name()))
    })format!(
778                "put `{0}` outermost: `{0}<Option<T>>`, not `Option<{0}<T>>`; the marker \
779                 applies to the whole parameter",
780                nested.name()
781            ),
782        ));
783    }
784    Ok((markers, inner))
785}
786
787/// A marker inside `Option<..>` / `Missing<..>` / `Vec<..>` / `&..`, at any
788/// depth of those wrappers.
789fn nested_param_marker(ty: &syn::Type) -> Option<ParamMarker> {
790    let arg = match ty {
791        syn::Type::Reference(r) => r.elem.as_ref(),
792        syn::Type::Paren(p) => p.elem.as_ref(),
793        syn::Type::Path(p) => {
794            let seg = p.path.segments.last()?;
795            if !#[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
    {
    "Option" | "Missing" | "Vec" => true,
    _ => false,
}matches!(seg.ident.to_string().as_str(), "Option" | "Missing" | "Vec") {
796                return None;
797            }
798            first_type_argument(seg)?
799        }
800        _ => return None,
801    };
802    match outer_param_marker(arg) {
803        Some((marker, _)) => Some(marker),
804        None => nested_param_marker(arg),
805    }
806}
807
808/// Whether any of `markers` is in `family`, and which.
809pub(crate) fn marker_in_family(
810    markers: &[ParamMarker],
811    family: ParamMarkerFamily,
812) -> Option<ParamMarker> {
813    markers.iter().copied().find(|m| m.family() == family)
814}
815
816// endregion
817
818// region: type rendering for messages
819
820/// Render a type the way it is written in Rust source, for user-facing
821/// messages: `AsFromStrVec<i32>`, `Either<i32, String>`, `&[f64]`,
822/// `&'a str`, `Option<Vec<String>>`.
823///
824/// `quote!(#ty).to_string()` puts a space between every token
825/// (`AsFromStrVec < i32 >`); this walks the tokens and applies the usual
826/// spacing instead: none around `<`, `>`, `::`, after `&` / `*` / `'`, or
827/// inside brackets; a space after `,` / `;`, around `+` / `=` / `->`, between
828/// two words, and between `mut` / `const` / `dyn` / `impl` or a lifetime and
829/// a following bracket group (`&mut [T]`, `&'a [T]`).
830pub(crate) fn type_display(ty: &syn::Type) -> String {
831    let mut out = String::new();
832    write_type_tokens({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    _s
}quote::quote!(#ty), &mut out);
833    out
834}
835
836fn write_type_tokens(tokens: proc_macro2::TokenStream, out: &mut String) {
837    use proc_macro2::{Delimiter, Spacing, TokenTree};
838
839    /// What the previous token leaves for the next one to attach to.
840    #[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Prev { }
#[automatically_derived]
impl ::core::clone::Clone for Prev {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Prev { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Prev { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Prev {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq)]
841    enum Prev {
842        /// Start of the stream, or a token after which nothing is spaced
843        /// (`<`, `&`, `*`, `::`, a trailing-space punct like `, `).
844        Glue,
845        /// A lifetime's `'`: the next ident attaches.
846        Tick,
847        /// An identifier, literal or closing `>`: a following word is spaced.
848        Word,
849        /// `mut` / `const` / `dyn` / `impl` or a lifetime: a following word
850        /// or group is spaced.
851        Keyword,
852    }
853
854    let mut prev = Prev::Glue;
855    // `-` of a pending `->` and the first `:` of a `::`.
856    let mut pending_arrow = false;
857    let mut pending_path_sep = false;
858    for tt in tokens {
859        match tt {
860            TokenTree::Ident(ident) => {
861                let name = ident.to_string();
862                if #[allow(non_exhaustive_omitted_patterns)] match prev {
    Prev::Word | Prev::Keyword => true,
    _ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
863                    out.push(' ');
864                }
865                out.push_str(&name);
866                prev = if prev == Prev::Tick
867                    || #[allow(non_exhaustive_omitted_patterns)] match name.as_str() {
    "mut" | "const" | "dyn" | "impl" => true,
    _ => false,
}matches!(name.as_str(), "mut" | "const" | "dyn" | "impl")
868                {
869                    Prev::Keyword
870                } else {
871                    Prev::Word
872                };
873            }
874            TokenTree::Literal(lit) => {
875                if #[allow(non_exhaustive_omitted_patterns)] match prev {
    Prev::Word | Prev::Keyword => true,
    _ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
876                    out.push(' ');
877                }
878                out.push_str(&lit.to_string());
879                prev = Prev::Word;
880            }
881            TokenTree::Group(group) => {
882                if prev == Prev::Keyword
883                    || (prev == Prev::Word && group.delimiter() == Delimiter::Bracket)
884                {
885                    out.push(' ');
886                }
887                let (open, close) = match group.delimiter() {
888                    Delimiter::Parenthesis => ("(", ")"),
889                    Delimiter::Bracket => ("[", "]"),
890                    Delimiter::Brace => ("{", "}"),
891                    Delimiter::None => ("", ""),
892                };
893                out.push_str(open);
894                write_type_tokens(group.stream(), out);
895                out.push_str(close);
896                prev = Prev::Word;
897            }
898            TokenTree::Punct(punct) => {
899                let c = punct.as_char();
900                match c {
901                    ',' | ';' => {
902                        out.push(c);
903                        out.push(' ');
904                        prev = Prev::Glue;
905                    }
906                    '+' | '=' => {
907                        out.push(' ');
908                        out.push(c);
909                        out.push(' ');
910                        prev = Prev::Glue;
911                    }
912                    '-' if punct.spacing() == Spacing::Joint => {
913                        out.push_str(" -");
914                        pending_arrow = true;
915                    }
916                    '>' if pending_arrow => {
917                        out.push_str("> ");
918                        pending_arrow = false;
919                        prev = Prev::Glue;
920                    }
921                    '>' => {
922                        out.push('>');
923                        prev = Prev::Word;
924                    }
925                    ':' if punct.spacing() == Spacing::Joint => {
926                        out.push(':');
927                        pending_path_sep = true;
928                        prev = Prev::Glue;
929                    }
930                    ':' if pending_path_sep => {
931                        out.push(':');
932                        pending_path_sep = false;
933                        prev = Prev::Glue;
934                    }
935                    ':' => {
936                        // Associated-type bound: `Item: Clone`.
937                        out.push_str(": ");
938                        prev = Prev::Glue;
939                    }
940                    '\'' => {
941                        if #[allow(non_exhaustive_omitted_patterns)] match prev {
    Prev::Word | Prev::Keyword => true,
    _ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
942                            out.push(' ');
943                        }
944                        out.push('\'');
945                        prev = Prev::Tick;
946                    }
947                    _ => {
948                        out.push(c);
949                        prev = Prev::Glue;
950                    }
951                }
952            }
953        }
954    }
955}
956
957/// `ty` with every lifetime replaced by `'_`, for a `let` binding annotation
958/// in generated code: `&'a [f64]` → `&'_ [f64]`. The binding then names the
959/// target type (so the error type of its conversion is known where the
960/// wrapper inspects it) without depending on the user's lifetime parameters
961/// being in scope.
962pub(crate) fn erase_lifetimes(ty: &syn::Type) -> syn::Type {
963    use proc_macro2::{Group, Ident, TokenStream, TokenTree};
964
965    fn erase(tokens: TokenStream) -> TokenStream {
966        let mut out = Vec::new();
967        let mut iter = tokens.into_iter();
968        while let Some(tt) = iter.next() {
969            match tt {
970                TokenTree::Punct(p) if p.as_char() == '\'' => {
971                    out.push(TokenTree::Punct(p));
972                    if let Some(TokenTree::Ident(lifetime)) = iter.next() {
973                        out.push(TokenTree::Ident(Ident::new("_", lifetime.span())));
974                    }
975                }
976                TokenTree::Group(g) => {
977                    let mut erased = Group::new(g.delimiter(), erase(g.stream()));
978                    erased.set_span(g.span());
979                    out.push(TokenTree::Group(erased));
980                }
981                other => out.push(other),
982            }
983        }
984        out.into_iter().collect()
985    }
986
987    syn::parse2(erase({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    _s
}quote::quote!(#ty))).unwrap_or_else(|_| ty.clone())
988}
989
990// endregion
991
992#[cfg(test)]
993mod tests {
994    use super::{
995        ParamMarker, UnevaluatedKind, UnevaluatedParam, call_marker, choice_layer_name,
996        choice_layers, erase_lifetimes, is_lazy_dots_marker, is_main_thread_bound_input,
997        is_main_thread_bound_return, is_nargs_marker, is_rng_guard_param, match_arg_choices_ty,
998        mentions_lazy_dots, mentions_rng_guard, mentions_unevaluated_marker, peel_param_markers,
999        r_value_noun, type_display, unevaluated_param, visibility_marker_error,
1000    };
1001    use crate::r_wrapper_builder::CallAttribution;
1002
1003    fn ty(s: &str) -> syn::Type {
1004        syn::parse_str(s).unwrap()
1005    }
1006
1007    #[test]
1008    fn main_thread_bound_return_detects_nested_positions() {
1009        // Bare and fully-qualified paths
1010        assert!(is_main_thread_bound_return(&ty("BuiltDataFrame")));
1011        assert!(is_main_thread_bound_return(&ty(
1012            "miniextendr_api::dataframe::BuiltDataFrame"
1013        )));
1014        assert!(is_main_thread_bound_return(&ty("DataFrameShape")));
1015        assert!(is_main_thread_bound_input(&ty("NamedListBuilder")));
1016        assert!(is_main_thread_bound_return(&ty("NamedListBuilder")));
1017        assert!(is_main_thread_bound_return(&ty(
1018            "Result<miniextendr_api::NamedListBuilder, String>"
1019        )));
1020        assert!(is_main_thread_bound_return(&ty("SEXP")));
1021        // Nested inside Result / Option / containers / tuples
1022        assert!(is_main_thread_bound_return(&ty(
1023            "Result<BuiltDataFrame, String>"
1024        )));
1025        assert!(is_main_thread_bound_return(&ty(
1026            "Result<DataFrameShape, std::string::String>"
1027        )));
1028        assert!(is_main_thread_bound_return(&ty("Option<BuiltDataFrame>")));
1029        assert!(is_main_thread_bound_return(&ty("Vec<BuiltDataFrame>")));
1030        assert!(is_main_thread_bound_return(&ty("(i32, BuiltDataFrame)")));
1031        // Precious-list roots, in the error position too
1032        assert!(is_main_thread_bound_input(&ty("Preserved")));
1033        assert!(is_main_thread_bound_return(&ty("Result<bool, REvalError>")));
1034        assert!(is_main_thread_bound_input(&ty(
1035            "miniextendr_api::txt_progress_bar::RTxtProgressBar"
1036        )));
1037        // Send-safe returns stay worker-eligible
1038        assert!(!is_main_thread_bound_return(&ty("i32")));
1039        assert!(!is_main_thread_bound_return(&ty(
1040            "Result<Vec<f64>, String>"
1041        )));
1042        assert!(!is_main_thread_bound_return(&ty("ExternalPtr<MyType>")));
1043        assert!(!is_main_thread_bound_return(&ty("DataFrame")));
1044    }
1045
1046    #[test]
1047    fn unevaluated_param_matches_bare_and_missing_markers_only() {
1048        let required = |kind| {
1049            Some(UnevaluatedParam {
1050                kind,
1051                optional: false,
1052            })
1053        };
1054        let optional = |kind| {
1055            Some(UnevaluatedParam {
1056                kind,
1057                optional: true,
1058            })
1059        };
1060        assert_eq!(
1061            unevaluated_param(&ty("Quoted")),
1062            required(UnevaluatedKind::Quoted)
1063        );
1064        assert_eq!(
1065            unevaluated_param(&ty("Quoted<'_>")),
1066            required(UnevaluatedKind::Quoted)
1067        );
1068        assert_eq!(
1069            unevaluated_param(&ty("::miniextendr_api::Quosure<'a>")),
1070            required(UnevaluatedKind::Quosure)
1071        );
1072        assert_eq!(
1073            unevaluated_param(&ty("Missing<Quoted>")),
1074            optional(UnevaluatedKind::Quoted)
1075        );
1076        assert_eq!(
1077            unevaluated_param(&ty("miniextendr_api::Missing<Quosure<'_>>")),
1078            optional(UnevaluatedKind::Quosure)
1079        );
1080        for other in [
1081            "Option<Quoted>",
1082            "&Quoted",
1083            "Vec<Quosure>",
1084            "Quoted<i32>",
1085            "Missing<Option<Quoted>>",
1086            "SEXP",
1087        ] {
1088            assert_eq!(unevaluated_param(&ty(other)), None, "{other}");
1089        }
1090        // The misplaced shapes are still recognised as mentioning a marker, so
1091        // the macro can refuse them by name.
1092        for misplaced in [
1093            "Option<Quoted>",
1094            "&Quoted",
1095            "Vec<Quosure<'_>>",
1096            "(i32, Quoted)",
1097        ] {
1098            assert!(mentions_unevaluated_marker(&ty(misplaced)), "{misplaced}");
1099        }
1100        assert!(!mentions_unevaluated_marker(&ty("Vec<i32>")));
1101        // Bare markers are main-thread-bound inputs; `Missing<..>` is checked
1102        // by the caller.
1103        assert!(is_main_thread_bound_input(&ty("Quoted")));
1104        assert!(is_main_thread_bound_input(&ty("Quosure<'_>")));
1105    }
1106
1107    #[test]
1108    fn unevaluated_kind_r_call_args_never_force_the_argument() {
1109        assert_eq!(
1110            UnevaluatedKind::Quoted.r_call_arg("cond"),
1111            "if (missing(cond)) quote(expr=) else list(substitute(cond), parent.frame())"
1112        );
1113        assert_eq!(
1114            UnevaluatedKind::Quosure.r_call_arg("cols"),
1115            "if (missing(cols)) quote(expr=) else rlang::enquo(cols)"
1116        );
1117    }
1118
1119    #[test]
1120    fn call_marker_matches_bare_marker_types_only() {
1121        assert_eq!(call_marker(&ty("Call")), Some(CallAttribution::Wrapper));
1122        assert_eq!(
1123            call_marker(&ty("miniextendr_api::CallerCall")),
1124            Some(CallAttribution::Caller)
1125        );
1126        assert_eq!(
1127            call_marker(&ty("::miniextendr_api::call_marker::Call")),
1128            Some(CallAttribution::Wrapper)
1129        );
1130        // Not markers: generics, references, wrappers, other types.
1131        assert_eq!(call_marker(&ty("Call<i32>")), None);
1132        assert_eq!(call_marker(&ty("&Call")), None);
1133        assert_eq!(call_marker(&ty("Option<Call>")), None);
1134        assert_eq!(call_marker(&ty("SEXP")), None);
1135        assert_eq!(call_marker(&ty("Caller")), None);
1136        // A marker holds the `.call` slot's SEXP, so it pins the main thread.
1137        assert!(is_main_thread_bound_input(&ty("Call")));
1138        assert!(is_main_thread_bound_input(&ty(
1139            "miniextendr_api::CallerCall"
1140        )));
1141    }
1142
1143    #[test]
1144    fn nargs_marker_matches_the_bare_type_only() {
1145        assert!(is_nargs_marker(&ty("NArgs")));
1146        assert!(is_nargs_marker(&ty("miniextendr_api::NArgs")));
1147        assert!(is_nargs_marker(&ty("::miniextendr_api::nargs::NArgs")));
1148        // Not the marker: generics, references, wrappers, other types.
1149        assert!(!is_nargs_marker(&ty("NArgs<i32>")));
1150        assert!(!is_nargs_marker(&ty("&NArgs")));
1151        assert!(!is_nargs_marker(&ty("Option<NArgs>")));
1152        assert!(!is_nargs_marker(&ty("Missing<NArgs>")));
1153        assert!(!is_nargs_marker(&ty("Nargs")));
1154        // A plain count: it does not pin the main thread.
1155        assert!(!is_main_thread_bound_input(&ty("NArgs")));
1156    }
1157
1158    #[test]
1159    fn rng_guard_param_is_a_shared_borrow_of_the_bare_type() {
1160        for guard in [
1161            "&RngGuard",
1162            "&'a RngGuard",
1163            "&miniextendr_api::RngGuard",
1164            "&::miniextendr_api::rng::RngGuard",
1165        ] {
1166            assert!(is_rng_guard_param(&ty(guard)), "{guard}");
1167            assert!(mentions_rng_guard(&ty(guard)), "{guard}");
1168        }
1169        // Shapes the wrapper cannot fill: named, so the macro refuses them.
1170        for misplaced in [
1171            "&mut RngGuard",
1172            "RngGuard",
1173            "Option<&RngGuard>",
1174            "(i32, &RngGuard)",
1175        ] {
1176            assert!(!is_rng_guard_param(&ty(misplaced)), "{misplaced}");
1177            assert!(mentions_rng_guard(&ty(misplaced)), "{misplaced}");
1178        }
1179        for other in ["&RngGuard<i32>", "&Guard", "&str", "SEXP"] {
1180            assert!(!is_rng_guard_param(&ty(other)), "{other}");
1181        }
1182        assert!(!mentions_rng_guard(&ty("Vec<f64>")));
1183    }
1184
1185    #[test]
1186    fn lazy_dots_marker_matches_the_whole_type_by_value_only() {
1187        assert!(is_lazy_dots_marker(&ty("LazyDots")));
1188        assert!(is_lazy_dots_marker(&ty("LazyDots<'_>")));
1189        assert!(is_lazy_dots_marker(&ty("miniextendr_api::LazyDots<'a>")));
1190        assert!(is_lazy_dots_marker(&ty(
1191            "::miniextendr_api::lazy_dots::LazyDots"
1192        )));
1193        // Not the parameter: generics, references, wrappers, other types.
1194        for other in [
1195            "LazyDots<i32>",
1196            "&LazyDots",
1197            "Option<LazyDots>",
1198            "Missing<LazyDots>",
1199            "Dots",
1200            "&Dots",
1201        ] {
1202            assert!(!is_lazy_dots_marker(&ty(other)), "{other}");
1203        }
1204        // The misplaced shapes are recognised, so the macro can refuse them.
1205        for misplaced in [
1206            "&LazyDots",
1207            "Option<LazyDots<'_>>",
1208            "Vec<LazyDots>",
1209            "Missing<LazyDots>",
1210        ] {
1211            assert!(mentions_lazy_dots(&ty(misplaced)), "{misplaced}");
1212        }
1213        assert!(!mentions_lazy_dots(&ty("&Dots")));
1214        // It holds the wrapper's frame, so it pins the main thread.
1215        assert!(is_main_thread_bound_input(&ty("LazyDots<'_>")));
1216    }
1217
1218    /// The inner type a parameter converts as, and the markers peeled off it.
1219    fn peeled(s: &str) -> (Vec<ParamMarker>, String) {
1220        let t = ty(s);
1221        let (markers, inner) = peel_param_markers(&t).expect("peels");
1222        (markers, type_display(inner))
1223    }
1224
1225    fn peel_error(s: &str) -> String {
1226        peel_param_markers(&ty(s))
1227            .expect_err("rejected")
1228            .to_string()
1229    }
1230
1231    #[test]
1232    fn param_markers_peel_the_outermost_marker() {
1233        assert_eq!(
1234            peeled("Checked<i32>"),
1235            (vec![ParamMarker::Checked], "i32".into())
1236        );
1237        assert_eq!(
1238            peeled("miniextendr_api::Unchecked<Vec<f64>>"),
1239            (vec![ParamMarker::Unchecked], "Vec<f64>".into())
1240        );
1241        assert_eq!(
1242            peeled("::miniextendr_api::precondition_marker::Checked<Option<i32>>"),
1243            (vec![ParamMarker::Checked], "Option<i32>".into())
1244        );
1245        assert_eq!(
1246            peeled("Checked<&'a [f64]>"),
1247            (vec![ParamMarker::Checked], "&'a [f64]".into())
1248        );
1249        // Not markers: no type argument, two of them, or another type.
1250        assert_eq!(peeled("Checked"), (vec![], "Checked".into()));
1251        assert_eq!(
1252            peeled("Checked<i32, f64>"),
1253            (vec![], "Checked<i32, f64>".into())
1254        );
1255        assert_eq!(peeled("Vec<i32>"), (vec![], "Vec<i32>".into()));
1256        assert_eq!(peeled("Missing<i32>"), (vec![], "Missing<i32>".into()));
1257    }
1258
1259    #[test]
1260    fn param_markers_reject_stacks_and_nesting() {
1261        for stacked in ["Checked<Unchecked<i32>>", "Checked<Checked<i32>>"] {
1262            let err = peel_error(stacked);
1263            assert!(
1264                err.contains("at most one precondition marker per parameter"),
1265                "{stacked}: {err}"
1266            );
1267        }
1268        for nested in [
1269            "Option<Checked<i32>>",
1270            "Missing<Unchecked<i32>>",
1271            "Vec<Checked<i32>>",
1272            "&Checked<i32>",
1273            "Option<Vec<Unchecked<i32>>>",
1274            "Checked<Option<Unchecked<i32>>>",
1275        ] {
1276            let err = peel_error(nested);
1277            assert!(err.contains("outermost"), "{nested}: {err}");
1278        }
1279        assert!(peel_error("Option<Checked<i32>>").contains("put `Checked` outermost"));
1280    }
1281
1282    /// A parameter marker in return position is an error, also inside a
1283    /// visibility marker; in argument position it is the parameter's to peel.
1284    #[test]
1285    fn param_markers_are_rejected_in_return_position() {
1286        for ret in [
1287            "Checked<i32>",
1288            "miniextendr_api::Unchecked<Vec<f64>>",
1289            "Invisible<Checked<i32>>",
1290        ] {
1291            let err = visibility_marker_error(&ty(ret), "return")
1292                .expect("rejected")
1293                .to_string();
1294            assert!(err.contains("marks a parameter only"), "{ret}: {err}");
1295        }
1296        assert!(visibility_marker_error(&ty("Checked<i32>"), "argument").is_none());
1297    }
1298
1299    #[test]
1300    fn type_display_uses_source_spacing() {
1301        let cases = [
1302            ("i32", "i32"),
1303            ("AsFromStrVec<i32>", "AsFromStrVec<i32>"),
1304            ("Either<i32, String>", "Either<i32, String>"),
1305            ("&[f64]", "&[f64]"),
1306            ("&mut [f64]", "&mut [f64]"),
1307            ("&str", "&str"),
1308            ("&'a str", "&'a str"),
1309            ("&'static [u8]", "&'static [u8]"),
1310            ("Option<Vec<String>>", "Option<Vec<String>>"),
1311            (
1312                "HashMap<String, Vec<(i32, f64)>>",
1313                "HashMap<String, Vec<(i32, f64)>>",
1314            ),
1315            ("::std::vec::Vec<i32>", "::std::vec::Vec<i32>"),
1316            ("[u8; 4]", "[u8; 4]"),
1317            ("Box<[Mode]>", "Box<[Mode]>"),
1318            ("()", "()"),
1319            ("*const T", "*const T"),
1320            ("RCow<'a, i32>", "RCow<'a, i32>"),
1321            (
1322                "Box<dyn Fn(i32) -> i32 + Send + 'static>",
1323                "Box<dyn Fn(i32) -> i32 + Send + 'static>",
1324            ),
1325            ("impl Iterator<Item = i32>", "impl Iterator<Item = i32>"),
1326            ("<T as Trait>::Assoc", "<T as Trait>::Assoc"),
1327            ("for<'a> fn(&'a str) -> bool", "for<'a> fn(&'a str) -> bool"),
1328        ];
1329        for (src, want) in cases {
1330            assert_eq!(type_display(&ty(src)), want, "rendering `{src}`");
1331        }
1332    }
1333
1334    #[test]
1335    fn r_value_noun_names_the_r_side() {
1336        let cases = [
1337            ("DataFrame", "a data frame"),
1338            ("miniextendr_api::DataFrame", "a data frame"),
1339            ("List", "a list"),
1340            ("HashMap<String, i32>", "a list"),
1341            ("String", "a string"),
1342            ("&str", "a string"),
1343            ("f64", "a number"),
1344            ("i32", "an integer"),
1345            ("bool", "TRUE or FALSE"),
1346            ("Vec<f64>", "a numeric vector"),
1347            ("&[i32]", "an integer vector"),
1348            ("Vec<Option<String>>", "a character vector"),
1349            ("SEXP", "any other R value"),
1350            ("ExternalPtr<Model>", "an external pointer"),
1351            ("Model", "a `Model`"),
1352            ("Vec<Model>", "a `Vec<Model>`"),
1353        ];
1354        for (src, want) in cases {
1355            assert_eq!(r_value_noun(&ty(src)), want, "noun for `{src}`");
1356        }
1357    }
1358
1359    #[test]
1360    fn choice_layers_peel_missing_option_either() {
1361        let layers = |src: &str| {
1362            let t = ty(src);
1363            let l = choice_layers(&t);
1364            (
1365                l.missing,
1366                l.nullable,
1367                l.either_right.map(type_display),
1368                type_display(l.value),
1369            )
1370        };
1371        assert_eq!(layers("Mode"), (false, false, None, "Mode".to_string()));
1372        assert_eq!(
1373            layers("Missing<Option<Either<Mode, DataFrame>>>"),
1374            (
1375                true,
1376                true,
1377                Some("DataFrame".to_string()),
1378                "Mode".to_string()
1379            )
1380        );
1381        assert_eq!(
1382            layers("Either<Mode, Option<f64>>"),
1383            (
1384                false,
1385                false,
1386                Some("Option<f64>".to_string()),
1387                "Mode".to_string()
1388            )
1389        );
1390        // A layer out of order stays in the value, where the classifier
1391        // rejects it.
1392        assert_eq!(
1393            layers("Option<Missing<Mode>>"),
1394            (false, true, None, "Missing<Mode>".to_string())
1395        );
1396        assert_eq!(choice_layer_name(&ty("Missing<Mode>")), Some("Missing"));
1397        assert_eq!(choice_layer_name(&ty("Either<Mode, f64>")), Some("Either"));
1398        assert_eq!(choice_layer_name(&ty("Either<Mode>")), None);
1399        assert_eq!(
1400            type_display(match_arg_choices_ty(&ty("Missing<Vec<Mode>>"), true)),
1401            "Mode"
1402        );
1403        assert_eq!(
1404            type_display(match_arg_choices_ty(
1405                &ty("Option<Either<Mode, List>>"),
1406                false
1407            )),
1408            "Mode"
1409        );
1410        // A `several_ok` list with another kind of value (#1612).
1411        assert_eq!(
1412            layers("Either<Vec<Mode>, DataFrame>"),
1413            (
1414                false,
1415                false,
1416                Some("DataFrame".to_string()),
1417                "Vec<Mode>".to_string()
1418            )
1419        );
1420        assert_eq!(
1421            type_display(match_arg_choices_ty(
1422                &ty("Missing<Either<Vec<Mode>, DataFrame>>"),
1423                true
1424            )),
1425            "Mode"
1426        );
1427    }
1428
1429    #[test]
1430    fn erase_lifetimes_replaces_every_lifetime() {
1431        let erased = |s: &str| type_display(&erase_lifetimes(&ty(s)));
1432        assert_eq!(erased("&'a [f64]"), "&'_ [f64]");
1433        assert_eq!(erased("&'static str"), "&'_ str");
1434        assert_eq!(erased("&mut [i32]"), "&mut [i32]");
1435        assert_eq!(erased("RCow<'a, Vec<&'b str>>"), "RCow<'_, Vec<&'_ str>>");
1436        assert_eq!(erased("&str"), "&str");
1437    }
1438}
1439
1440/// `true` when the last path segment of `ty` is `Option<...>`.
1441pub(crate) fn is_option_type(ty: &syn::Type) -> bool {
1442    option_inner_type(ty).is_some()
1443}
1444
1445/// Return `T` for an `Option<T>` type, `None` for anything else.
1446pub(crate) fn option_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
1447    let syn::Type::Path(tp) = ty else {
1448        return None;
1449    };
1450    let seg = tp.path.segments.last()?;
1451    if seg.ident != "Option" {
1452        return None;
1453    }
1454    first_type_argument(seg)
1455}
1456
1457/// Return `T` for an `ExternalPtr<T>` class handle, `ty` itself otherwise.
1458///
1459/// `IntoR for ExternalPtr<T>` produces the same bare `EXTPTRSXP` as a bare
1460/// `T`, so both return-wrapping paths (inferred and explicit `wrap` /
1461/// `WrapAs*`) name the class through the handle (#1375).
1462pub(crate) fn peel_external_ptr(ty: &syn::Type) -> &syn::Type {
1463    let syn::Type::Path(tp) = ty else {
1464        return ty;
1465    };
1466    match tp.path.segments.last() {
1467        Some(seg) if seg.ident == "ExternalPtr" => first_type_argument(seg).unwrap_or(ty),
1468        _ => ty,
1469    }
1470}
1471
1472// region: choice-parameter layers (`match_arg` / `choices`)
1473
1474/// The wrappers around the value of a `match_arg` / `choices` parameter,
1475/// peeled outermost first: `Missing<..>`, then `Option<..>`, then
1476/// `Either<T, R>` with the choice on the left (a scalar choice, or a
1477/// `several_ok` list: `Either<Vec<T>, R>`, #1612). A plain `T` (or a plain
1478/// `several_ok` container) has none.
1479///
1480/// Each layer changes one contract and leaves the rest to the layer below:
1481/// `Missing` reports an omitted argument as `Missing::Absent` and keeps the
1482/// choice vector as the R formal (#1551), `Option` turns `NULL` into `None`
1483/// (#1473), and `Either` sends every argument that is not character or
1484/// factor to its `R` arm instead of the choice check.
1485#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for ChoiceLayers<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field4_finish(f, "ChoiceLayers",
            "missing", &self.missing, "nullable", &self.nullable,
            "either_right", &self.either_right, "value", &&self.value)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl<'a> ::core::clone::TrivialClone for ChoiceLayers<'a> { }
#[automatically_derived]
impl<'a> ::core::clone::Clone for ChoiceLayers<'a> {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<bool>;
        let _: ::core::clone::AssertParamIsClone<Option<&'a syn::Type>>;
        let _: ::core::clone::AssertParamIsClone<&'a syn::Type>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for ChoiceLayers<'a> { }Copy)]
1486pub(crate) struct ChoiceLayers<'a> {
1487    /// `Missing<..>` is the outermost wrapper.
1488    pub missing: bool,
1489    /// `Option<..>` wraps the value (inside `Missing`, if present).
1490    pub nullable: bool,
1491    /// `Either<T, R>` wraps the value (inside the others): its `R` arm.
1492    pub either_right: Option<&'a syn::Type>,
1493    /// The type under the layers: the `MatchArg` type (or the string type of a
1494    /// `choices` parameter) for a scalar, the container for `several_ok`.
1495    pub value: &'a syn::Type,
1496}
1497
1498/// Peel the [`ChoiceLayers`] of a choice parameter's type.
1499pub(crate) fn choice_layers(ty: &syn::Type) -> ChoiceLayers<'_> {
1500    let (missing, ty) = match crate::miniextendr_fn::get_missing_inner_type(ty) {
1501        Some(inner) => (true, inner),
1502        None => (false, ty),
1503    };
1504    let (nullable, ty) = match option_inner_type(ty) {
1505        Some(inner) => (true, inner),
1506        None => (false, ty),
1507    };
1508    let (either_right, value) = match either_arms(ty) {
1509        Some((left, right)) => (Some(right), left),
1510        None => (None, ty),
1511    };
1512    ChoiceLayers {
1513        missing,
1514        nullable,
1515        either_right,
1516        value,
1517    }
1518}
1519
1520/// The layer name (`"Missing"` / `"Option"` / `"Either"`) when `ty` is itself
1521/// a layer type, i.e. when it sits where the choice value should be. Used to
1522/// reject layers in an unsupported order, such as `Option<Missing<T>>` or
1523/// `Either<Option<T>, R>`.
1524pub(crate) fn choice_layer_name(ty: &syn::Type) -> Option<&'static str> {
1525    if crate::miniextendr_fn::get_missing_inner_type(ty).is_some() {
1526        Some("Missing")
1527    } else if option_inner_type(ty).is_some() {
1528        Some("Option")
1529    } else if either_arms(ty).is_some() {
1530        Some("Either")
1531    } else {
1532        None
1533    }
1534}
1535
1536/// `(L, R)` for an `Either<L, R>` type (last path segment `Either` with two
1537/// type arguments), `None` for anything else.
1538pub(crate) fn either_arms(ty: &syn::Type) -> Option<(&syn::Type, &syn::Type)> {
1539    let syn::Type::Path(tp) = ty else {
1540        return None;
1541    };
1542    let seg = tp.path.segments.last()?;
1543    if seg.ident != "Either" {
1544        return None;
1545    }
1546    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
1547        return None;
1548    };
1549    let mut types = args.args.iter().filter_map(|arg| match arg {
1550        syn::GenericArgument::Type(ty) => Some(ty),
1551        _ => None,
1552    });
1553    match (types.next(), types.next(), types.next()) {
1554        (Some(left), Some(right), None) => Some((left, right)),
1555        _ => None,
1556    }
1557}
1558
1559/// How R documentation names a value of type `ty`, with its article: `"a data
1560/// frame"`, `"a list"`, `"a number"`, `"a character vector"`, ... Used for the
1561/// `R` arm of an `Either<T, R>` choice parameter in its auto-generated
1562/// `@param` line (`One of "a", "b", or a data frame.`). A type without an R
1563/// name falls back to its Rust spelling in code format.
1564pub(crate) fn r_value_noun(ty: &syn::Type) -> String {
1565    let named = match ty {
1566        syn::Type::Reference(r) => match r.elem.as_ref() {
1567            syn::Type::Slice(s) => vector_noun(&s.elem),
1568            elem => return r_value_noun(elem),
1569        },
1570        syn::Type::Slice(s) => vector_noun(&s.elem),
1571        syn::Type::Path(tp) => {
1572            tp.path
1573                .segments
1574                .last()
1575                .and_then(|seg| match seg.ident.to_string().as_str() {
1576                    "DataFrame" | "BuiltDataFrame" => Some("a data frame"),
1577                    "List" | "ListMut" | "NamedList" | "HashMap" | "BTreeMap" => Some("a list"),
1578                    "String" | "str" | "char" | "PathBuf" | "Path" => Some("a string"),
1579                    "f64" | "f32" => Some("a number"),
1580                    "i8" | "i16" | "i32" | "i64" | "isize" | "u16" | "u32" | "u64" | "usize" => {
1581                        Some("an integer")
1582                    }
1583                    "bool" | "Rbool" | "Rboolean" => Some("TRUE or FALSE"),
1584                    "Rcomplex" => Some("a complex number"),
1585                    "ExternalPtr" => Some("an external pointer"),
1586                    "SEXP" => Some("any other R value"),
1587                    "Vec" => first_type_argument(seg).and_then(vector_noun),
1588                    _ => None,
1589                })
1590        }
1591        _ => None,
1592    };
1593    named.map_or_else(|| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}`", type_display(ty)))
    })format!("a `{}`", type_display(ty)), str::to_string)
1594}
1595
1596/// The R name of a vector with elements of type `elem`, if it has one.
1597fn vector_noun(elem: &syn::Type) -> Option<&'static str> {
1598    let syn::Type::Path(tp) = elem else {
1599        return None;
1600    };
1601    let seg = tp.path.segments.last()?;
1602    match seg.ident.to_string().as_str() {
1603        "f64" | "f32" => Some("a numeric vector"),
1604        "i8" | "i16" | "i32" | "i64" | "isize" | "u16" | "u32" | "u64" | "usize" => {
1605            Some("an integer vector")
1606        }
1607        "String" | "str" => Some("a character vector"),
1608        "bool" | "Rbool" | "Rboolean" => Some("a logical vector"),
1609        "u8" => Some("a raw vector"),
1610        "Option" => first_type_argument(seg).and_then(vector_noun),
1611        _ => None,
1612    }
1613}
1614
1615/// Resolve the `MatchArg`-bound type behind a `match_arg` parameter.
1616///
1617/// The [`ChoiceLayers`] are peeled first. A `several_ok` parameter is then a
1618/// container (`Vec<T>`, `Box<[T]>`, `[T; N]`, `&[T]`), so the element type is
1619/// the one carrying `CHOICES`; a scalar parameter's value is that type itself.
1620/// Anything else is returned unchanged, and the `MatchArg` bound on the
1621/// generated code reports the mistake.
1622///
1623/// Shared by the standalone-fn path (`lib.rs`) and the impl-method path
1624/// (`miniextendr_impl.rs`) so the two cannot resolve the type differently.
1625pub(crate) fn match_arg_choices_ty(param_ty: &syn::Type, several_ok: bool) -> &syn::Type {
1626    let value = choice_layers(param_ty).value;
1627    if several_ok {
1628        classify_several_ok_container(value)
1629            .map(|(_, inner)| inner)
1630            .unwrap_or(value)
1631    } else {
1632        value
1633    }
1634}
1635
1636// endregion
1637
1638/// Container family for a `several_ok` parameter, returned by
1639/// [`classify_several_ok_container`].
1640#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SeveralOkContainer {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Vec => ::core::fmt::Formatter::write_str(f, "Vec"),
            Self::BoxedSlice =>
                ::core::fmt::Formatter::write_str(f, "BoxedSlice"),
            Self::Array(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Array",
                    &__self_0),
            Self::BorrowedSlice =>
                ::core::fmt::Formatter::write_str(f, "BorrowedSlice"),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SeveralOkContainer {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::Vec => SeveralOkContainer::Vec,
            Self::BoxedSlice => SeveralOkContainer::BoxedSlice,
            Self::Array(__self_0) =>
                SeveralOkContainer::Array(::core::clone::Clone::clone(__self_0)),
            Self::BorrowedSlice => SeveralOkContainer::BorrowedSlice,
        }
    }
}Clone)]
1641pub(crate) enum SeveralOkContainer {
1642    /// `Vec<T>`
1643    Vec,
1644    /// `Box<[T]>`
1645    BoxedSlice,
1646    /// `[T; N]` — the `usize` is the fixed array length N
1647    Array(usize),
1648    /// `&[T]` or `&mut [T]` — allocate `Vec<T>` then borrow
1649    BorrowedSlice,
1650}
1651
1652/// Classify a `several_ok` parameter type into one of the four container
1653/// families and extract its inner element type `T`.
1654///
1655/// Returns `Some((container, inner_ty))` or `None` if the type is not one of
1656/// the four accepted container shapes.
1657pub(crate) fn classify_several_ok_container(
1658    ty: &syn::Type,
1659) -> Option<(SeveralOkContainer, &syn::Type)> {
1660    match ty {
1661        // Vec<T>
1662        syn::Type::Path(tp) => {
1663            let seg = tp.path.segments.last()?;
1664            if seg.ident == "Vec" {
1665                let inner = first_type_argument(seg)?;
1666                return Some((SeveralOkContainer::Vec, inner));
1667            }
1668            // Box<[T]>
1669            if seg.ident == "Box"
1670                && let syn::PathArguments::AngleBracketed(ab) = &seg.arguments
1671            {
1672                for arg in &ab.args {
1673                    if let syn::GenericArgument::Type(syn::Type::Slice(s)) = arg {
1674                        return Some((SeveralOkContainer::BoxedSlice, s.elem.as_ref()));
1675                    }
1676                }
1677            }
1678            None
1679        }
1680        // [T; N]
1681        syn::Type::Array(arr) => {
1682            if let syn::Expr::Lit(syn::ExprLit {
1683                lit: syn::Lit::Int(n),
1684                ..
1685            }) = &arr.len
1686            {
1687                let n = n.base10_parse::<usize>().ok()?;
1688                return Some((SeveralOkContainer::Array(n), arr.elem.as_ref()));
1689            }
1690            None
1691        }
1692        // &[T] or &mut [T]
1693        syn::Type::Reference(r) => {
1694            if let syn::Type::Slice(s) = r.elem.as_ref() {
1695                return Some((SeveralOkContainer::BorrowedSlice, s.elem.as_ref()));
1696            }
1697            None
1698        }
1699        _ => None,
1700    }
1701}
1702
1703/// Whether a bare `&Self` / `&mut Self` parameter is mutable.
1704/// `None` means this is not a Self-reference parameter.
1705pub(crate) fn self_ref_mutability(ty: &syn::Type) -> Option<bool> {
1706    if let syn::Type::Reference(reference) = ty
1707        && let syn::Type::Path(path) = reference.elem.as_ref()
1708        && path.path.is_ident("Self")
1709    {
1710        Some(reference.mutability.is_some())
1711    } else {
1712        None
1713    }
1714}