Skip to main content

miniextendr_macros/
c_wrapper_builder.rs

1//! Unified C wrapper generation for standalone functions and impl methods.
2//!
3//! This module provides shared infrastructure for generating C wrappers that:
4//! - Handle worker thread vs main thread execution strategies
5//! - Perform parameter conversion from SEXP to Rust types
6//! - Convert Rust return values back to SEXP
7//! - Properly handle panics and R errors
8//!
9//! The same infrastructure is used by both `#[miniextendr]` on standalone functions
10//! and `#[miniextendr(env|r6|s3|s4|s7)]` on impl blocks.
11
12use proc_macro2::TokenStream;
13use quote::{format_ident, quote};
14
15/// How a `#[miniextendr]` parameter type borrows R's data pointer, if it does.
16///
17/// `&[T]` / `&mut [T]` (and their `Option<_>` wrappers) are the `TryFromSexp`
18/// impls in `miniextendr-api/src/from_r.rs` that return a *view* over R's
19/// `DATAPTR` without copying (`r_slice` / `r_slice_mut`). Two such parameters
20/// bound to the same SEXP alias one buffer; that is undefined behavior whenever
21/// at least one of the two borrows is mutable (#1104).
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23enum SliceBorrow {
24    /// `&mut [T]` / `Option<&mut [T]>` — exclusive borrow via `r_slice_mut`.
25    Mut,
26    /// `&[T]` / `Option<&[T]>` — shared borrow via `r_slice`.
27    Shared,
28}
29
30/// Classify `ty` as a zero-copy slice borrow of R's data pointer, if it is one.
31///
32/// `Vec<T>` / `Box<[T]>` copy the R vector and never alias R's buffer, so they
33/// are not classified. `match_arg` + `several_ok` `&mut [T]` params get their
34/// own owned `Vec<T>` storage and are excluded by the caller, not here.
35fn slice_borrow_kind(ty: &syn::Type) -> Option<SliceBorrow> {
36    match ty {
37        // &[T] / &mut [T]
38        syn::Type::Reference(r) if matches!(r.elem.as_ref(), syn::Type::Slice(_)) => {
39            Some(if r.mutability.is_some() {
40                SliceBorrow::Mut
41            } else {
42                SliceBorrow::Shared
43            })
44        }
45        // Option<&[T]> / Option<&mut [T]>
46        syn::Type::Path(tp) => {
47            let seg = tp.path.segments.last()?;
48            if seg.ident != "Option" {
49                return None;
50            }
51            let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
52                return None;
53            };
54            args.args.iter().find_map(|a| match a {
55                syn::GenericArgument::Type(inner) => slice_borrow_kind(inner),
56                _ => None,
57            })
58        }
59        _ => None,
60    }
61}
62
63/// Thread execution strategy for C wrappers.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ThreadStrategy {
66    /// Execute on main R thread with `with_r_unwind_protect`. **Default.**
67    ///
68    /// All code runs on R's main thread. Errors are returned as tagged SEXP values
69    /// (via `make_rust_condition_value`) and the R wrapper raises structured
70    /// condition objects. Simpler execution model with better R integration.
71    ///
72    /// Also required when:
73    /// - Function takes SEXP inputs (not Send)
74    /// - Function returns raw SEXP
75    /// - Instance method (self_ptr isn't Send)
76    /// - Function uses variadic dots (Dots type isn't Send)
77    /// - `#[miniextendr(check_interrupt)]` used
78    MainThread,
79
80    /// Execute on worker thread with panic catching. **Opt-in via `#[miniextendr(worker)]`.**
81    ///
82    /// Structure:
83    /// 1. Argument conversion on main thread
84    /// 2. Function execution on worker thread via `run_on_worker`
85    /// 3. SEXP conversion on main thread with `with_r_unwind_protect`
86    WorkerThread,
87}
88
89/// Strategy for converting a Rust return value into an R `SEXP`.
90///
91/// Determined automatically by [`detect_return_handling`] from the function's return type,
92/// or set explicitly via [`CWrapperContextBuilder::return_handling`]. Each variant
93/// handles a different return type pattern, controlling how the C wrapper converts
94/// the Rust value back to R and how errors/None values are surfaced.
95#[derive(Debug, Clone)]
96pub enum ReturnHandling {
97    /// Returns unit type `()` -- emits `R_NilValue`.
98    Unit,
99    /// Returns raw `SEXP` -- passes the value through unchanged (no conversion).
100    RawSexp,
101    /// Returns `Self` -- wraps the value in an `ExternalPtr` via `ExternalPtr::new`.
102    ExternalPtr,
103    /// Returns `&Self` / `&mut Self` (an in-place builder) -- evaluates the call
104    /// for its side effect (the mutation already happened through `&mut self`),
105    /// discards the returned borrow, and returns the *same* `self_sexp` handle
106    /// unchanged. This gives R in-place value semantics with no clone: the R
107    /// object the user piped in is returned verbatim, wrapping the now-mutated
108    /// Rust value. Only valid for instance methods (requires `self_sexp`).
109    SelfHandle,
110    /// Returns an arbitrary type `T: IntoR` -- converts via `IntoR::into_sexp`.
111    IntoR,
112    /// Returns `Option<()>` -- raises an error on `None`, otherwise emits `R_NilValue`.
113    OptionUnit,
114    /// Returns `Option<SEXP>` -- raises an error on `None`, otherwise passes through.
115    OptionSexp,
116    /// Returns `Option<T>` where `Option<T>: IntoR` -- calls `IntoR::into_sexp` on the whole
117    /// `Option` value. Suitable when the type has a direct `impl IntoR for Option<T>` (e.g.,
118    /// `Option<&T>`, `Option<Vec<T>>`, `Option<i32>`). `None` maps to whatever the `IntoR`
119    /// impl returns (typically NULL or NA).
120    ///
121    /// Use this variant explicitly via [`CWrapperContextBuilder::return_handling`] when
122    /// the type has a direct `IntoR` impl for the whole `Option`. The auto-detector
123    /// `detect_return_handling` conservatively returns `OptionIntoRUnwrap` instead
124    /// since it cannot resolve trait impls at macro expansion time.
125    #[allow(dead_code)]
126    // Used via explicit return_handling() call; auto-detect uses OptionIntoRUnwrap
127    OptionIntoR,
128    /// Returns `Option<T>` where `T: IntoR` -- unwraps the option first, then converts the
129    /// inner value via `IntoR::into_sexp`. Raises an error on `None`. Suitable when `T: IntoR`
130    /// but `Option<T>` doesn't have a direct `IntoR` impl (e.g., `Option<SomeExternalPtr>`).
131    OptionIntoRUnwrap,
132    /// Returns `Option<Self>` -- a lookup-shaped fallible constructor (e.g. `try_find`).
133    /// Raises an error on `None` (same as [`OptionIntoRUnwrap`](Self::OptionIntoRUnwrap)),
134    /// but wraps `Some(Self)` in an `ExternalPtr` via `ExternalPtr::new` (same as
135    /// [`ExternalPtr`](Self::ExternalPtr)) instead of routing it through `IntoR` (which
136    /// `Self` generally does not implement). The R-side wrapper mirrors this: a successful
137    /// return is treated exactly like a bare `Self` return (wrapped class object via
138    /// `ReturnStrategy::for_method` — see `ParsedMethod::returns_option_self`). Symmetric
139    /// with [`ResultExternalPtr`](Self::ResultExternalPtr).
140    OptionExternalPtr,
141    /// Returns `Result<(), E>` -- raises an error on `Err`, otherwise emits `R_NilValue`.
142    ResultUnit,
143    /// Returns `Result<SEXP, E>` -- raises an error on `Err`, otherwise passes through.
144    ResultSexp,
145    /// Returns `Result<T, E>` -- raises an error on `Err`, otherwise converts via `IntoR::into_sexp`.
146    ResultIntoR,
147    /// Returns `Result<T, ()>` -- maps `Err(())` to `Err(NullOnErr)` then converts via `IntoR`.
148    /// `None`/`Err` maps to R `NULL` (unit error is a deliberate sentinel, not a Rust failure).
149    ResultNullOnErr,
150    /// Returns `Result<Self, E>` -- a fallible constructor-shaped method (e.g. `from_r`,
151    /// `try_new`). Raises an error on `Err` (same as [`ResultIntoR`](Self::ResultIntoR)), but
152    /// wraps `Ok(Self)` in an `ExternalPtr` via `ExternalPtr::new` (same as
153    /// [`ExternalPtr`](Self::ExternalPtr)) instead of routing it through `IntoR` (which `Self`
154    /// generally does not implement). The R-side wrapper mirrors this: a successful return is
155    /// treated exactly like a bare `Self` return (wrapped class object via
156    /// `ReturnStrategy::for_method` — see `ParsedMethod::returns_result_self`).
157    ResultExternalPtr,
158    /// Returns `T` where `T: IntoList` -- wraps in `AsList(result)` then calls `IntoR::into_sexp`.
159    ///
160    /// Produced when `#[miniextendr(prefer = "list")]` is used on a function returning `T: IntoList`.
161    /// Distinct from returning `AsList<T>` explicitly only in that the wrapping is generated
162    /// by the macro rather than written by the user.
163    AsListOf,
164    /// Returns `T` where `T: IntoExternalPtr` -- wraps in `AsExternalPtr(result)` then calls `IntoR::into_sexp`.
165    ///
166    /// Produced when `#[miniextendr(prefer = "externalptr")]` is used on a function returning
167    /// `T: IntoExternalPtr`. Distinct from the existing `ExternalPtr` variant (which boxes `Self`
168    /// via `ExternalPtr::new`): this variant calls `IntoExternalPtr::into_external_ptr()` on `T`.
169    AsExternalPtrOf,
170    /// Returns `T` where `T: RNativeType` -- wraps in `AsRNative(result)` then calls `IntoR::into_sexp`.
171    ///
172    /// Produced when `#[miniextendr(prefer = "native")]` is used on a function returning `T: RNativeType`.
173    AsNativeOf,
174}
175
176/// All information needed to generate a C wrapper function for an R-exported Rust item.
177///
178/// This struct abstracts over the differences between standalone `#[miniextendr]` functions
179/// and `impl` block methods (R6, S3, S4, S7, Env). It is constructed via
180/// [`CWrapperContextBuilder`] and consumed by [`CWrapperContext::generate`], which emits
181/// both the `extern "C-unwind"` wrapper and the corresponding `R_CallMethodDef` constant.
182pub struct CWrapperContext {
183    /// Identifier of the original Rust function or method being wrapped.
184    pub fn_ident: syn::Ident,
185    /// Identifier for the generated C wrapper (e.g., `C_<crate>_foo` or `C_<crate>_Type__method`).
186    pub c_ident: syn::Ident,
187    /// Identifier of the `R_WRAPPER_*` or `R_WRAPPERS_IMPL_*` const that holds the
188    /// generated R wrapper code string. Used for rustdoc cross-references.
189    pub r_wrapper_const: syn::Ident,
190    /// Function parameters (excluding the `self` receiver for methods).
191    /// Each parameter becomes a `SEXP` argument in the C wrapper signature.
192    pub inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
193    /// The original Rust return type. Used by strict-mode to inspect whether the inner
194    /// type is lossy (e.g., `i64`, `u64`) and needs checked conversion.
195    pub output: syn::ReturnType,
196    /// Statements emitted before the call expression. For instance methods, this
197    /// includes extracting `self` from the `ExternalPtr` SEXP.
198    pub pre_call: Vec<TokenStream>,
199    /// The actual Rust call expression (e.g., `my_func(arg0, arg1)` or
200    /// `self_ref.method(arg0)`). Inserted into the wrapper body after conversions.
201    pub call_expr: TokenStream,
202    /// Whether to run on the main R thread or dispatch to the worker thread.
203    pub thread_strategy: ThreadStrategy,
204    /// How to convert the Rust return value into a `SEXP` for R.
205    pub return_handling: ReturnHandling,
206    /// When `true`, all parameters use coercing conversion (`Rf_coerceVector`) instead
207    /// of strict type-matching. Set by `#[miniextendr(coerce)]`.
208    pub coerce_all: bool,
209    /// Names of individual parameters that use coercing conversion.
210    /// Set by `#[miniextendr(coerce = "param_name")]`.
211    pub coerce_params: Vec<String>,
212    /// When `true`, emits `R_CheckUserInterrupt()` before the call expression.
213    /// Set by `#[miniextendr(check_interrupt)]`.
214    pub check_interrupt: bool,
215    /// When `true`, wraps the call in `GetRNGstate()`/`PutRNGstate()` for R's
216    /// random number generator state management. Set by `#[miniextendr(rng)]`.
217    pub rng: bool,
218    /// `#[cfg(...)]` attributes from the original item, propagated to the C wrapper
219    /// and `call_method_def` constant so they are conditionally compiled.
220    pub cfg_attrs: Vec<syn::Attribute>,
221    /// For methods: the type identifier (e.g., `MyStruct`). Used in doc comments
222    /// and default `call_method_def` naming. `None` for standalone functions.
223    pub type_context: Option<syn::Ident>,
224    /// Whether the original method has a `self` receiver. When `true`, the C wrapper
225    /// includes a `self_sexp` parameter before the regular arguments.
226    pub has_self: bool,
227    /// Override for the `call_method_def` constant name. If `None`, defaults to
228    /// `call_method_def_{type}_{method}` (methods) or `call_method_def_{fn}` (standalone).
229    pub call_method_def_ident: Option<syn::Ident>,
230    /// When `true`, uses `checked_into_sexp_*` for lossy return types (`i64`, `u64`,
231    /// `isize`, `usize` and their `Vec` variants) instead of regular `IntoR::into_sexp`.
232    /// Set by `#[miniextendr(strict)]`.
233    pub strict: bool,
234    /// Parameter names with `#[miniextendr(match_arg, several_ok)]` — use
235    /// `match_arg_vec_from_sexp` (instead of `TryFromSexp`) for the `Vec<T>` conversion
236    /// so each element is validated against the enum's `MatchArg::CHOICES`.
237    ///
238    /// Scalar `match_arg` doesn't need entries here because R-side `match.arg()`
239    /// already narrowed the SEXP to a valid choice; the default `TryFromSexp for Enum`
240    /// (generated by `#[derive(MatchArg)]`) decodes it.
241    pub match_arg_several_ok_params: Vec<String>,
242    /// When `true`, preserve original parameter names from `inputs` in the C wrapper
243    /// signature instead of renaming to `arg_0`, `arg_1`, ... The fn path preserves
244    /// user identifiers for rustdoc visibility; impl method path uses `arg_N` for safety.
245    pub preserve_param_names: bool,
246    /// Visibility of the generated `extern "C-unwind"` wrapper function.
247    /// Default: [`syn::Visibility::Inherited`] (no visibility keyword).
248    /// Standalone `#[miniextendr]` fns forward the user's visibility (`pub`, `pub(crate)`, etc.).
249    pub vis: syn::Visibility,
250    /// Generic parameters of the wrapped function, emitted on the C wrapper signature
251    /// as `fn #c_ident #generics(...)`. Default: empty (no generics).
252    pub generics: syn::Generics,
253    /// When `true`, the original Rust fn is already an `extern "C-unwind"` symbol (user-written).
254    /// Skip generating the wrapper body but still emit the `R_CallMethodDef` for registration.
255    /// The `numArgs` count excludes the synthetic `__miniextendr_call` SEXP parameter since
256    /// the user-written fn doesn't have it.
257    pub skip_wrapper: bool,
258}
259
260impl CWrapperContext {
261    /// Creates a new [`CWrapperContextBuilder`] with the given function and C wrapper identifiers.
262    ///
263    /// All other fields start at their defaults (empty/false/None). Use the builder methods
264    /// to configure the context, then call [`CWrapperContextBuilder::build`] to finalize.
265    pub fn builder(fn_ident: syn::Ident, c_ident: syn::Ident) -> CWrapperContextBuilder {
266        CWrapperContextBuilder {
267            fn_ident,
268            c_ident,
269            r_wrapper_const: None,
270            inputs: syn::punctuated::Punctuated::new(),
271            output: syn::ReturnType::Default,
272            pre_call: Vec::new(),
273            call_expr: None,
274            thread_strategy: None,
275            return_handling: None,
276            coerce_all: false,
277            coerce_params: Vec::new(),
278            check_interrupt: false,
279            rng: false,
280            cfg_attrs: Vec::new(),
281            type_context: None,
282            has_self: false,
283            call_method_def_ident: None,
284            strict: false,
285            match_arg_several_ok_params: Vec::new(),
286            preserve_param_names: false,
287            vis: syn::Visibility::Inherited,
288            generics: syn::Generics::default(),
289            skip_wrapper: false,
290        }
291    }
292
293    /// Generates the complete output for this wrapper: an `extern "C-unwind"` function
294    /// and an `R_CallMethodDef` constant, both decorated with `#[cfg(...)]` attributes
295    /// if present.
296    ///
297    /// When `skip_wrapper` is set (for user-written `extern "C-unwind"` fns), only the
298    /// `R_CallMethodDef` is emitted — the fn body itself is already the C symbol.
299    ///
300    /// Dispatches to [`generate_main_thread_wrapper`](Self::generate_main_thread_wrapper) or
301    /// [`generate_worker_thread_wrapper`](Self::generate_worker_thread_wrapper) based on
302    /// [`thread_strategy`](Self::thread_strategy).
303    pub fn generate(&self) -> TokenStream {
304        let call_method_def = self.generate_call_method_def();
305
306        let cfg_attrs = &self.cfg_attrs;
307
308        if self.skip_wrapper {
309            // User-written extern "C-unwind" fn — only emit the registration entry
310            quote! {
311                #(#cfg_attrs)*
312                #call_method_def
313            }
314        } else {
315            let c_wrapper = match self.thread_strategy {
316                ThreadStrategy::MainThread => self.generate_main_thread_wrapper(),
317                ThreadStrategy::WorkerThread => self.generate_worker_thread_wrapper(),
318            };
319
320            quote! {
321                #(#cfg_attrs)*
322                #c_wrapper
323
324                #(#cfg_attrs)*
325                #call_method_def
326            }
327        }
328    }
329
330    /// Builds the C wrapper's parameter list from the Rust function signature.
331    ///
332    /// Returns a tuple of:
333    /// - `c_params`: `SEXP` parameter declarations for the C wrapper signature. Always
334    ///   starts with `__miniextendr_call` (the R call object for error context), followed
335    ///   by `self_sexp` for instance methods, then `arg_0`, `arg_1`, ... for each input.
336    /// - `rust_args`: The original Rust parameter identifiers (used in the call expression).
337    /// - `sexp_idents`: The generated `arg_N` identifiers (used in SEXP-to-Rust conversions).
338    fn build_c_params(&self) -> (Vec<TokenStream>, Vec<syn::Ident>, Vec<syn::Ident>) {
339        let mut c_params: Vec<TokenStream> = Vec::new();
340        let mut rust_args: Vec<syn::Ident> = Vec::new();
341        let mut sexp_idents: Vec<syn::Ident> = Vec::new();
342
343        // First param is always __miniextendr_call for error context
344        c_params.push(quote!(__miniextendr_call: ::miniextendr_api::SEXP));
345
346        // For instance methods, add self_sexp parameter
347        if self.has_self {
348            c_params.push(quote!(self_sexp: ::miniextendr_api::SEXP));
349        }
350
351        // Add regular parameters
352        for (idx, arg) in self.inputs.iter().enumerate() {
353            if let syn::FnArg::Typed(pt) = arg
354                && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
355            {
356                let ident = &pat_ident.ident;
357                // When preserve_param_names is set, use the original parameter name
358                // (visible in rustdoc). Otherwise use arg_N for predictable mangling.
359                let param_ident = if self.preserve_param_names {
360                    ident.clone()
361                } else {
362                    format_ident!("arg_{}", idx)
363                };
364
365                c_params.push(quote!(#param_ident: ::miniextendr_api::SEXP));
366                rust_args.push(ident.clone());
367                sexp_idents.push(param_ident);
368            }
369        }
370
371        (c_params, rust_args, sexp_idents)
372    }
373
374    /// Generates `TryFromSexp` conversion statements for each parameter.
375    ///
376    /// Each statement converts an `arg_N: SEXP` into the corresponding Rust type
377    /// declared in the original function signature. Respects `strict` and `coerce` settings.
378    ///
379    /// Used by the main-thread wrapper where all conversions happen inline.
380    fn build_conversion_stmts(&self, sexp_idents: &[syn::Ident]) -> Vec<TokenStream> {
381        let mut builder = crate::RustConversionBuilder::new();
382        if self.strict {
383            builder = builder.with_strict();
384        }
385        if self.coerce_all {
386            builder = builder.with_coerce_all();
387        }
388        for param in &self.coerce_params {
389            builder = builder.with_coerce_param(param.clone());
390        }
391        for param in &self.match_arg_several_ok_params {
392            builder = builder.with_match_arg_several_ok(param.clone());
393        }
394        builder.build_conversions(&self.inputs, sexp_idents)
395    }
396
397    /// Build conversion statements split for worker thread execution.
398    ///
399    /// Returns (pre_closure, in_closure) statements:
400    /// - pre_closure: Run on main thread, produce owned values to move
401    /// - in_closure: Run inside worker closure, create borrows
402    fn build_conversion_stmts_split(
403        &self,
404        sexp_idents: &[syn::Ident],
405    ) -> (Vec<TokenStream>, Vec<TokenStream>) {
406        let mut builder = crate::RustConversionBuilder::new();
407        if self.strict {
408            builder = builder.with_strict();
409        }
410        if self.coerce_all {
411            builder = builder.with_coerce_all();
412        }
413        for param in &self.coerce_params {
414            builder = builder.with_coerce_param(param.clone());
415        }
416        for param in &self.match_arg_several_ok_params {
417            builder = builder.with_match_arg_several_ok(param.clone());
418        }
419
420        let mut all_pre = Vec::new();
421        let mut all_in = Vec::new();
422
423        for (arg, sexp_ident) in self.inputs.iter().zip(sexp_idents.iter()) {
424            if let syn::FnArg::Typed(pat_type) = arg {
425                let (owned, borrowed) = builder.build_conversion_split(pat_type, sexp_ident);
426                all_pre.extend(owned);
427                all_in.extend(borrowed);
428            }
429        }
430
431        (all_pre, all_in)
432    }
433
434    /// Emit a debug-only guard against aliasing zero-copy slice arguments (#1104).
435    ///
436    /// `impl TryFromSexp for &mut [T]` (and `Option<&mut [T]>`) hands out a
437    /// mutable slice over R's data pointer without copying, and `&[T]` /
438    /// `Option<&[T]>` hand out a shared one. When R binds the same vector to two
439    /// such parameters (`f(x, x)`), the wrapper would produce two aliasing slices
440    /// over one buffer. That is undefined behavior whenever at least one of the
441    /// two borrows is mutable — two mutable slices, or one mutable and one shared
442    /// (`&mut [T]` + `&[T]`). Two shared borrows do not conflict and are allowed.
443    ///
444    /// Since the wrapper knows the parameter shapes, we compare the raw SEXP
445    /// identities pairwise before any conversion and panic (converted to an R
446    /// error by the surrounding unwind guard) if an offending pair shares a SEXP,
447    /// naming both parameters. Comparing SEXP identity (not the data pointer) is
448    /// deliberate: two *distinct* empty vectors share R's `0x1` sentinel data
449    /// pointer but are different SEXPs, so identity avoids a false positive there.
450    ///
451    /// `debug_assert!` compiles to nothing in release builds, so this is a
452    /// zero-cost debugging aid. `match_arg` + `several_ok` `&mut [T]` params are
453    /// excluded: they get their own owned `Vec<T>` storage and never alias R.
454    fn build_alias_guard(&self, sexp_idents: &[syn::Ident]) -> TokenStream {
455        // Collect (sexp_ident, param_name, borrow_kind) for every slice-family
456        // parameter that borrows R's data pointer directly.
457        let mut slice_params: Vec<(&syn::Ident, String, SliceBorrow)> = Vec::new();
458        for (arg, sexp_ident) in self.inputs.iter().zip(sexp_idents.iter()) {
459            if let syn::FnArg::Typed(pt) = arg
460                && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
461            {
462                let param_name = pat_ident.ident.to_string();
463                if self.match_arg_several_ok_params.contains(&param_name) {
464                    continue;
465                }
466                if let Some(kind) = slice_borrow_kind(pt.ty.as_ref()) {
467                    slice_params.push((sexp_ident, param_name, kind));
468                }
469            }
470        }
471
472        let mut checks = Vec::new();
473        for i in 0..slice_params.len() {
474            for j in (i + 1)..slice_params.len() {
475                let (id_a, name_a, kind_a) = &slice_params[i];
476                let (id_b, name_b, kind_b) = &slice_params[j];
477                // Two shared `&[T]` reads over one buffer are sound; only a pair
478                // where at least one borrow is mutable is undefined behavior.
479                if *kind_a != SliceBorrow::Mut && *kind_b != SliceBorrow::Mut {
480                    continue;
481                }
482                let msg = format!(
483                    "aliasing slice arguments: parameters `{name_a}` and `{name_b}` are bound to \
484                     the same R object, and at least one borrows it mutably (`&mut [T]`), so they \
485                     would produce aliasing slices over one vector (undefined behavior). Pass \
486                     distinct vectors."
487                );
488                checks.push(quote! {
489                    ::core::debug_assert!(#id_a != #id_b, #msg);
490                });
491            }
492        }
493
494        quote! { #(#checks)* }
495    }
496
497    /// Generates an `extern "C-unwind"` wrapper that runs entirely on the R main thread.
498    ///
499    /// The wrapper body is enclosed in `with_r_unwind_protect`, which catches both Rust
500    /// panics and R longjmps and returns a tagged-condition SEXP on failure (the R-side
501    /// wrapper raises a structured condition). When `rng` is enabled, the call is
502    /// additionally wrapped in `catch_unwind` so that `PutRNGstate()` runs even on panic.
503    fn generate_main_thread_wrapper(&self) -> TokenStream {
504        let c_ident = &self.c_ident;
505        let vis = &self.vis;
506        let generics = &self.generics;
507        let (c_params, _, sexp_idents) = self.build_c_params();
508        let conversion_stmts = self.build_conversion_stmts(&sexp_idents);
509        let alias_guard = self.build_alias_guard(&sexp_idents);
510        let pre_call = &self.pre_call;
511        let call_expr = &self.call_expr;
512
513        let pre_call_checks = if self.check_interrupt {
514            quote! {
515                unsafe { ::miniextendr_api::sys::R_CheckUserInterrupt(); }
516            }
517        } else {
518            TokenStream::new()
519        };
520
521        let return_handling = self.generate_return_handling(call_expr);
522
523        let doc = self.generate_doc_comment("main thread");
524        let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
525
526        // Unwind protection returns tagged condition SEXP on panic; the R-side wrapper raises.
527        let unwind_protect_fn = quote! { ::miniextendr_api::unwind_protect::with_r_unwind_protect };
528
529        if self.rng {
530            // RNG variant: wrap in catch_unwind so we can call PutRNGstate before error handling.
531            // The wrapper always returns a tagged condition SEXP on panic; the R-side wrapper raises.
532            let rng_panic_handler = quote! {
533                unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
534                    &::miniextendr_api::unwind_protect::panic_payload_to_string(&*payload),
535                    ::miniextendr_api::error_value::kind::PANIC,
536                    ::core::option::Option::None,
537                    Some(__miniextendr_call),
538                ) }
539            };
540            quote! {
541                #[doc = #doc]
542                #[doc = #source_loc_doc]
543                #[doc = concat!("Generated from source file `", file!(), "`.")]
544                #[unsafe(no_mangle)]
545                #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::SEXP {
546                    unsafe { ::miniextendr_api::sys::GetRNGstate(); }
547                    let __result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
548                        #unwind_protect_fn(
549                            || {
550                                #alias_guard
551                                #pre_call_checks
552                                #(#pre_call)*
553                                #(#conversion_stmts)*
554                                #return_handling
555                            },
556                            Some(__miniextendr_call),
557                        )
558                    }));
559                    // PutRNGstate runs after catch_unwind, before error handling
560                    unsafe { ::miniextendr_api::sys::PutRNGstate(); }
561                    match __result {
562                        Ok(sexp) => sexp,
563                        Err(payload) => { #rng_panic_handler },
564                    }
565                }
566            }
567        } else {
568            // Non-RNG variant: direct call to with_r_unwind_protect
569            quote! {
570                #[doc = #doc]
571                #[doc = #source_loc_doc]
572                #[doc = concat!("Generated from source file `", file!(), "`.")]
573                #[unsafe(no_mangle)]
574                #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::SEXP {
575                    #unwind_protect_fn(
576                        || {
577                            #alias_guard
578                            #pre_call_checks
579                            #(#pre_call)*
580                            #(#conversion_stmts)*
581                            #return_handling
582                        },
583                        Some(__miniextendr_call),
584                    )
585                }
586            }
587        }
588    }
589
590    /// Generates an `extern "C-unwind"` wrapper that dispatches to the worker thread.
591    ///
592    /// Structure:
593    /// 1. `GetRNGstate()` (if `rng` enabled)
594    /// 2. `catch_unwind` around the entire body
595    /// 3. Pre-closure conversions on the main thread (produces owned values)
596    /// 4. `run_on_worker` (returns `Result<T, String>`) with a
597    ///    `move` closure containing in-closure conversions and the call expression
598    /// 5. Return conversion back on the main thread via `with_r_unwind_protect`
599    /// 6. `PutRNGstate()` (if `rng` enabled)
600    /// 7. Panic handling: either tagged error value or `Rf_errorcall`
601    fn generate_worker_thread_wrapper(&self) -> TokenStream {
602        let c_ident = &self.c_ident;
603        let vis = &self.vis;
604        let generics = &self.generics;
605        let (c_params, _, sexp_idents) = self.build_c_params();
606        let (pre_closure_stmts, in_closure_stmts) = self.build_conversion_stmts_split(&sexp_idents);
607        let alias_guard = self.build_alias_guard(&sexp_idents);
608        let pre_call = &self.pre_call;
609        let call_expr = &self.call_expr;
610
611        // Compile-time check: worker dispatch requires the `worker-thread` feature.
612        // Check both `worker-thread` (direct) and `worker-default` (implies worker-thread
613        // via miniextendr-api, but the user crate may only have the latter in its features).
614        let fn_name = self.fn_ident.to_string();
615        let feature_msg = format!(
616            "`#[miniextendr(worker)]` on `{fn_name}` requires the `worker-thread` cargo feature. \
617             Add `worker-thread = [\"miniextendr-api/worker-thread\"]` to your [features] in Cargo.toml."
618        );
619        let worker_feature_check = quote! {
620            #[cfg(not(any(feature = "worker-thread", feature = "worker-default")))]
621            compile_error!(#feature_msg);
622        };
623
624        let pre_call_checks = if self.check_interrupt {
625            quote! {
626                unsafe { ::miniextendr_api::sys::R_CheckUserInterrupt(); }
627            }
628        } else {
629            TokenStream::new()
630        };
631
632        let (worker_body, return_conversion) = self.generate_worker_return_handling(call_expr);
633
634        let doc = self.generate_doc_comment("worker thread");
635        let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
636
637        // RNG state management: GetRNGstate at start, PutRNGstate before returning/error handling
638        let (rng_get, rng_put) = if self.rng {
639            (
640                quote! { unsafe { ::miniextendr_api::sys::GetRNGstate(); } },
641                quote! { unsafe { ::miniextendr_api::sys::PutRNGstate(); } },
642            )
643        } else {
644            (TokenStream::new(), TokenStream::new())
645        };
646
647        // Panic error handling: return tagged error value (the only mode).
648        //
649        // #1245 Gap 2 (not fixed here): this block is reused below for the
650        // OUTER `Err(payload) => #panic_error_handling` arm — the defensive
651        // case where the whole worker-dispatch closure panics directly
652        // (rather than `run_on_worker` returning `Err`, which is handled
653        // separately just above and already carries a location-folded
654        // message per #1245 Gap 1). This site stringifies via
655        // `panic_payload_to_string` (no location fold), so a panic reaching
656        // it loses its `(at file:line)` suffix. Near-no-op in practice — the
657        // panics that actually reach it are framework-internal (e.g.
658        // re-entrant `run_on_worker`), not user code. Fixing it properly
659        // would need `panic_message_with_location` made `pub`.
660        let panic_error_handling = quote! {
661            unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
662                &::miniextendr_api::unwind_protect::panic_payload_to_string(&*payload),
663                ::miniextendr_api::error_value::kind::PANIC,
664                ::core::option::Option::None,
665                Some(__miniextendr_call),
666            ) }
667        };
668
669        // run_on_worker returns Result; Err → tagged error value.
670        quote! {
671            #worker_feature_check
672
673            #[doc = #doc]
674            #[doc = #source_loc_doc]
675            #[doc = concat!("Generated from source file `", file!(), "`.")]
676            #[unsafe(no_mangle)]
677            #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::SEXP {
678                #rng_get
679                let __miniextendr_panic_result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
680                    #alias_guard
681                    #pre_call_checks
682                    #(#pre_call)*
683                    #(#pre_closure_stmts)*
684
685                    match ::miniextendr_api::worker::run_on_worker(move || {
686                        #(#in_closure_stmts)*
687                        #worker_body
688                    }) {
689                        Ok(__miniextendr_result) => {
690                            #return_conversion
691                        }
692                        Err(__panic_msg) => {
693                            unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
694                                &__panic_msg, ::miniextendr_api::error_value::kind::PANIC, ::core::option::Option::None, Some(__miniextendr_call),
695                            ) }
696                        }
697                    }
698                }));
699                #rng_put
700                match __miniextendr_panic_result {
701                    Ok(sexp) => sexp,
702                    Err(payload) => {
703                        #panic_error_handling
704                    },
705                }
706            }
707        }
708    }
709
710    /// Generates the inline return-handling code for the main-thread wrapper.
711    ///
712    /// Emits the call expression followed by conversion logic based on [`ReturnHandling`].
713    /// For `Option`/`Result` variants, also emits error-path code that returns a
714    /// tagged condition SEXP (which the R-side wrapper raises).
715    fn generate_return_handling(&self, call_expr: &TokenStream) -> TokenStream {
716        let fn_ident = &self.fn_ident;
717
718        match &self.return_handling {
719            ReturnHandling::Unit => {
720                quote! {
721                    #call_expr;
722                    ::miniextendr_api::SEXP::nil()
723                }
724            }
725            ReturnHandling::RawSexp => {
726                quote! {
727                    #call_expr
728                }
729            }
730            ReturnHandling::ExternalPtr => {
731                quote! {
732                    let __result = #call_expr;
733                    ::miniextendr_api::into_r::IntoR::into_sexp(
734                        ::miniextendr_api::externalptr::ExternalPtr::new(__result)
735                    )
736                }
737            }
738            ReturnHandling::SelfHandle => {
739                // In-place builder: the `&mut self` method mutated the value
740                // pointed to by `self_sexp`. Evaluate the call only for that
741                // side effect, drop the returned `&Self`/`&mut Self` borrow
742                // immediately (the trailing `;` ends its lifetime), and hand
743                // back the SAME ExternalPtr handle. No clone, no rewrap.
744                quote! {
745                    let _ = #call_expr;
746                    self_sexp
747                }
748            }
749            ReturnHandling::IntoR => {
750                let result_ident = format_ident!("__result");
751                let conversion = self.sexp_conversion_expr(&result_ident, true);
752                quote! {
753                    let #result_ident = #call_expr;
754                    #conversion
755                }
756            }
757            ReturnHandling::OptionUnit => {
758                let error_msg = format!("`{}()` returned no value", fn_ident);
759                quote! {
760                    let __result = #call_expr;
761                    if __result.is_none() {
762                        return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
763                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
764                        ) };
765                    }
766                    ::miniextendr_api::SEXP::nil()
767                }
768            }
769            ReturnHandling::OptionSexp => {
770                let error_msg = format!("`{}()` returned no value", fn_ident);
771                quote! {
772                    let __result = #call_expr;
773                    match __result {
774                        Some(v) => v,
775                        None => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
776                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
777                        ) },
778                    }
779                }
780            }
781            ReturnHandling::OptionIntoR => {
782                // For Option<T> where Option<T>: IntoR (e.g. Option<&T>, Option<Vec<T>>).
783                // Call into_sexp on the whole Option — IntoR impl handles None → NULL/NA.
784                // result_ident holds the full Option<T>, so the strict lookup must
785                // see the full type (checked_option_* helpers).
786                let result_ident = format_ident!("__result");
787                let conversion = self.sexp_conversion_expr(&result_ident, false);
788                quote! {
789                    let #result_ident = #call_expr;
790                    #conversion
791                }
792            }
793            ReturnHandling::OptionIntoRUnwrap => {
794                // For Option<T> where T: IntoR but Option<T>: IntoR is not available.
795                // Unwraps first (raises error on None), then converts T via IntoR.
796                let error_msg = format!("`{}()` returned no value", fn_ident);
797                let result_ident = format_ident!("__result");
798                let conversion = self.sexp_conversion_expr(&result_ident, true);
799                quote! {
800                    let __result = #call_expr;
801                    let #result_ident = match __result {
802                        Some(v) => v,
803                        None => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
804                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
805                        ) },
806                    };
807                    #conversion
808                }
809            }
810            // Option<Self>: raise on None like OptionIntoRUnwrap, but wrap Some(Self) in an
811            // ExternalPtr like the bare-Self path rather than routing through IntoR.
812            ReturnHandling::OptionExternalPtr => {
813                let error_msg = format!("`{}()` returned no value", fn_ident);
814                quote! {
815                    let __result = #call_expr;
816                    let __result = match __result {
817                        Some(v) => v,
818                        None => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
819                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
820                        ) },
821                    };
822                    ::miniextendr_api::into_r::IntoR::into_sexp(
823                        ::miniextendr_api::externalptr::ExternalPtr::new(__result)
824                    )
825                }
826            }
827            ReturnHandling::ResultUnit => {
828                quote! {
829                    let __result = #call_expr;
830                    if let Err(e) = __result {
831                        return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
832                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
833                        ) };
834                    }
835                    ::miniextendr_api::SEXP::nil()
836                }
837            }
838            ReturnHandling::ResultSexp => {
839                quote! {
840                    let __result = #call_expr;
841                    match __result {
842                        Ok(v) => v,
843                        Err(e) => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
844                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
845                        ) },
846                    }
847                }
848            }
849            ReturnHandling::ResultIntoR => {
850                let result_ident = format_ident!("__result");
851                let conversion = self.sexp_conversion_expr(&result_ident, true);
852                quote! {
853                    let __result = #call_expr;
854                    let #result_ident = match __result {
855                        Ok(v) => v,
856                        Err(e) => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
857                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
858                        ) },
859                    };
860                    #conversion
861                }
862            }
863            // Result<T, ()>: unit error is a deliberate sentinel — always return NULL on Err.
864            ReturnHandling::ResultNullOnErr => {
865                let result_ident = format_ident!("__result");
866                let conversion = self.sexp_conversion_expr(&result_ident, true);
867                quote! {
868                    let __result = #call_expr;
869                    match __result {
870                        Ok(#result_ident) => #conversion,
871                        Err(()) => ::miniextendr_api::SEXP::nil(),
872                    }
873                }
874            }
875            // Result<Self, E>: raise on Err like ResultIntoR, but wrap Ok(Self) in an
876            // ExternalPtr like the bare-Self path rather than routing through IntoR.
877            ReturnHandling::ResultExternalPtr => {
878                quote! {
879                    let __result = #call_expr;
880                    let __result = match __result {
881                        Ok(v) => v,
882                        Err(e) => return unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
883                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
884                        ) },
885                    };
886                    ::miniextendr_api::into_r::IntoR::into_sexp(
887                        ::miniextendr_api::externalptr::ExternalPtr::new(__result)
888                    )
889                }
890            }
891            ReturnHandling::AsListOf => {
892                quote! {
893                    let __result = #call_expr;
894                    ::miniextendr_api::into_r::IntoR::into_sexp(
895                        ::miniextendr_api::convert::AsList(__result)
896                    )
897                }
898            }
899            ReturnHandling::AsExternalPtrOf => {
900                quote! {
901                    let __result = #call_expr;
902                    ::miniextendr_api::into_r::IntoR::into_sexp(
903                        ::miniextendr_api::convert::AsExternalPtr(__result)
904                    )
905                }
906            }
907            ReturnHandling::AsNativeOf => {
908                quote! {
909                    let __result = #call_expr;
910                    ::miniextendr_api::into_r::IntoR::into_sexp(
911                        ::miniextendr_api::convert::AsRNative(__result)
912                    )
913                }
914            }
915        }
916    }
917
918    /// Generates return-handling code split between worker and main threads.
919    ///
920    /// Returns `(worker_body, return_conversion)`:
921    /// - `worker_body`: Runs inside the `run_on_worker` closure. Contains just the call
922    ///   expression (the worker returns the raw `Option`/`Result` for the main thread
923    ///   to inspect).
924    /// - `return_conversion`: Runs back on the main thread after the worker returns.
925    ///   Converts the Rust value to SEXP (via `with_r_unwind_protect`). For `Option`
926    ///   and `Result` variants, error checking happens here and produces a tagged
927    ///   condition SEXP that the R-side wrapper raises.
928    fn generate_worker_return_handling(
929        &self,
930        call_expr: &TokenStream,
931    ) -> (TokenStream, TokenStream) {
932        let fn_ident = &self.fn_ident;
933
934        match &self.return_handling {
935            ReturnHandling::Unit => {
936                let worker = quote! {
937                    #call_expr;
938                };
939                let convert = quote! {
940                    ::miniextendr_api::SEXP::nil()
941                };
942                (worker, convert)
943            }
944            ReturnHandling::RawSexp => {
945                // Raw SEXP can't use worker thread - this shouldn't happen
946                // but handle it gracefully
947                let worker = quote! {
948                    #call_expr
949                };
950                let convert = quote! {
951                    __miniextendr_result
952                };
953                (worker, convert)
954            }
955            ReturnHandling::ExternalPtr => {
956                let worker = quote! {
957                    #call_expr
958                };
959                let unwind_fn = self.worker_conversion_unwind_fn();
960                let convert = quote! {
961                    #unwind_fn(
962                        || ::miniextendr_api::into_r::IntoR::into_sexp(
963                            ::miniextendr_api::externalptr::ExternalPtr::new(__miniextendr_result)
964                        ),
965                        None,
966                    )
967                };
968                (worker, convert)
969            }
970            ReturnHandling::IntoR => {
971                let worker = quote! {
972                    #call_expr
973                };
974                let result_ident = format_ident!("__miniextendr_result");
975                let conversion = self.sexp_conversion_expr(&result_ident, true);
976                let unwind_fn = self.worker_conversion_unwind_fn();
977                let convert = quote! {
978                    #unwind_fn(
979                        || #conversion,
980                        None,
981                    )
982                };
983                (worker, convert)
984            }
985            ReturnHandling::OptionUnit => {
986                let error_msg = format!("`{}()` returned no value", fn_ident);
987                // Return the Option from worker, check on main thread.
988                let worker = quote! { #call_expr };
989                let convert = quote! {
990                    if __miniextendr_result.is_none() {
991                        unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
992                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
993                        ) }
994                    } else {
995                        ::miniextendr_api::SEXP::nil()
996                    }
997                };
998                (worker, convert)
999            }
1000            ReturnHandling::OptionSexp => {
1001                let error_msg = format!("`{}()` returned no value", fn_ident);
1002                let worker = quote! { #call_expr };
1003                let convert = quote! {
1004                    match __miniextendr_result {
1005                        Some(v) => v,
1006                        None => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1007                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1008                        ) },
1009                    }
1010                };
1011                (worker, convert)
1012            }
1013            ReturnHandling::OptionIntoR => {
1014                // For Option<T> where Option<T>: IntoR, call into_sexp on the whole Option.
1015                // The worker returns the raw Option<T>; the main thread converts via IntoR.
1016                // None maps to whatever IntoR for Option<T> returns (NULL/NA) — not an error.
1017                // result_ident holds the full Option<T>, so the strict lookup must
1018                // see the full type (checked_option_* helpers).
1019                let worker = quote! { #call_expr };
1020                let result_ident = format_ident!("__miniextendr_result");
1021                let conversion = self.sexp_conversion_expr(&result_ident, false);
1022                let unwind_fn = self.worker_conversion_unwind_fn();
1023                let convert = quote! {
1024                    {
1025                        let #result_ident = __miniextendr_result;
1026                        #unwind_fn(|| #conversion, None)
1027                    }
1028                };
1029                (worker, convert)
1030            }
1031            ReturnHandling::OptionIntoRUnwrap => {
1032                // For Option<T> where T: IntoR but Option<T>: IntoR is not available.
1033                // Unwraps first (raises error on None), then converts T via IntoR.
1034                let error_msg = format!("`{}()` returned no value", fn_ident);
1035                // Return the Option from worker, check on main thread.
1036                let worker = quote! { #call_expr };
1037                let result_ident = format_ident!("__miniextendr_result");
1038                let conversion = self.sexp_conversion_expr(&result_ident, true);
1039                let unwind_fn = self.worker_conversion_unwind_fn();
1040                let convert = quote! {
1041                    match __miniextendr_result {
1042                        Some(#result_ident) => #unwind_fn(|| #conversion, None),
1043                        None => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1044                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1045                        ) },
1046                    }
1047                };
1048                (worker, convert)
1049            }
1050            // Option<Self>: raise on None like OptionIntoRUnwrap, but wrap Some(Self) in an
1051            // ExternalPtr like the bare-Self path rather than routing through IntoR.
1052            ReturnHandling::OptionExternalPtr => {
1053                let error_msg = format!("`{}()` returned no value", fn_ident);
1054                let worker = quote! { #call_expr };
1055                let unwind_fn = self.worker_conversion_unwind_fn();
1056                let convert = quote! {
1057                    match __miniextendr_result {
1058                        Some(v) => #unwind_fn(
1059                            || ::miniextendr_api::into_r::IntoR::into_sexp(
1060                                ::miniextendr_api::externalptr::ExternalPtr::new(v)
1061                            ),
1062                            None,
1063                        ),
1064                        None => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1065                            #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1066                        ) },
1067                    }
1068                };
1069                (worker, convert)
1070            }
1071            ReturnHandling::ResultUnit => {
1072                let worker = quote! { #call_expr };
1073                let convert = quote! {
1074                    match __miniextendr_result {
1075                        Ok(()) => ::miniextendr_api::SEXP::nil(),
1076                        Err(e) => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1077                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1078                        ) },
1079                    }
1080                };
1081                (worker, convert)
1082            }
1083            ReturnHandling::ResultSexp => {
1084                let worker = quote! { #call_expr };
1085                let convert = quote! {
1086                    match __miniextendr_result {
1087                        Ok(v) => v,
1088                        Err(e) => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1089                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1090                        ) },
1091                    }
1092                };
1093                (worker, convert)
1094            }
1095            ReturnHandling::ResultIntoR => {
1096                let worker = quote! { #call_expr };
1097                let result_ident = format_ident!("__miniextendr_result");
1098                let conversion = self.sexp_conversion_expr(&result_ident, true);
1099                let unwind_fn = self.worker_conversion_unwind_fn();
1100                let convert = quote! {
1101                    match __miniextendr_result {
1102                        Ok(#result_ident) => #unwind_fn(
1103                            || #conversion,
1104                            None,
1105                        ),
1106                        Err(e) => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1107                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1108                        ) },
1109                    }
1110                };
1111                (worker, convert)
1112            }
1113            // Result<T, ()>: unit error is a deliberate sentinel — always map to NULL.
1114            // Convert via NullOnErr so IntoR returns R NULL on Err.
1115            ReturnHandling::ResultNullOnErr => {
1116                let result_ident = format_ident!("__miniextendr_result");
1117                let unwind_fn = self.worker_conversion_unwind_fn();
1118                let worker = quote! { #call_expr };
1119                let conversion = self.sexp_conversion_expr(&result_ident, true);
1120                let convert = quote! {
1121                    match __miniextendr_result {
1122                        Ok(#result_ident) => #unwind_fn(|| #conversion, None),
1123                        Err(()) => ::miniextendr_api::SEXP::nil(),
1124                    }
1125                };
1126                (worker, convert)
1127            }
1128            // Result<Self, E>: raise on Err like ResultIntoR, but wrap Ok(Self) in an
1129            // ExternalPtr like the bare-Self path rather than routing through IntoR.
1130            ReturnHandling::ResultExternalPtr => {
1131                let worker = quote! { #call_expr };
1132                let unwind_fn = self.worker_conversion_unwind_fn();
1133                let convert = quote! {
1134                    match __miniextendr_result {
1135                        Ok(v) => #unwind_fn(
1136                            || ::miniextendr_api::into_r::IntoR::into_sexp(
1137                                ::miniextendr_api::externalptr::ExternalPtr::new(v)
1138                            ),
1139                            None,
1140                        ),
1141                        Err(e) => unsafe { ::miniextendr_api::error_value::make_rust_condition_value(
1142                            &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
1143                        ) },
1144                    }
1145                };
1146                (worker, convert)
1147            }
1148            ReturnHandling::AsListOf => {
1149                let worker = quote! { #call_expr };
1150                let unwind_fn = self.worker_conversion_unwind_fn();
1151                let convert = quote! {
1152                    #unwind_fn(
1153                        || ::miniextendr_api::into_r::IntoR::into_sexp(
1154                            ::miniextendr_api::convert::AsList(__miniextendr_result)
1155                        ),
1156                        None,
1157                    )
1158                };
1159                (worker, convert)
1160            }
1161            ReturnHandling::AsExternalPtrOf => {
1162                let worker = quote! { #call_expr };
1163                let unwind_fn = self.worker_conversion_unwind_fn();
1164                let convert = quote! {
1165                    #unwind_fn(
1166                        || ::miniextendr_api::into_r::IntoR::into_sexp(
1167                            ::miniextendr_api::convert::AsExternalPtr(__miniextendr_result)
1168                        ),
1169                        None,
1170                    )
1171                };
1172                (worker, convert)
1173            }
1174            ReturnHandling::AsNativeOf => {
1175                let worker = quote! { #call_expr };
1176                let unwind_fn = self.worker_conversion_unwind_fn();
1177                let convert = quote! {
1178                    #unwind_fn(
1179                        || ::miniextendr_api::into_r::IntoR::into_sexp(
1180                            ::miniextendr_api::convert::AsRNative(__miniextendr_result)
1181                        ),
1182                        None,
1183                    )
1184                };
1185                (worker, convert)
1186            }
1187            ReturnHandling::SelfHandle => {
1188                // `SelfHandle` is only assigned to instance methods, which always
1189                // run on the main thread (the `&self`/`&mut self` borrow can't
1190                // cross to the worker). It therefore never reaches the worker
1191                // return-handling path.
1192                unreachable!(
1193                    "ReturnHandling::SelfHandle is instance-only and always uses the main thread"
1194                )
1195            }
1196        }
1197    }
1198
1199    /// Returns the unwind protection function for worker-thread conversion steps.
1200    /// Always returns tagged condition SEXP on conversion panics; the R-side wrapper raises.
1201    fn worker_conversion_unwind_fn(&self) -> TokenStream {
1202        quote! { ::miniextendr_api::unwind_protect::with_r_unwind_protect }
1203    }
1204
1205    /// Returns the SEXP conversion expression for `result_ident`, using strict
1206    /// checked conversion if strict mode is on and the inner return type is lossy,
1207    /// otherwise falling back to `IntoR::into_sexp()`.
1208    ///
1209    /// `result_holds_unwrapped` says what `result_ident` is bound to at runtime:
1210    /// `true` when the arm already unwrapped the declared `Option<T>` /
1211    /// `Result<T, E>` wrapper (so the strict lookup must see the inner `T`),
1212    /// `false` when `result_ident` holds the full declared return type (so
1213    /// `Option<lossy>` must resolve to the `checked_option_*` helpers — passing
1214    /// the stripped inner type there emits a scalar helper call on an `Option`
1215    /// value, which does not compile; caught by the strict-default CI leg).
1216    fn sexp_conversion_expr(
1217        &self,
1218        result_ident: &syn::Ident,
1219        result_holds_unwrapped: bool,
1220    ) -> TokenStream {
1221        if self.strict {
1222            // Extract the type `result_ident` actually holds from the output type
1223            let inner_ty = match &self.output {
1224                syn::ReturnType::Type(_, ty) => {
1225                    let ty = ty.as_ref();
1226                    // Strip an Option<T> / Result<T, E> wrapper only when the
1227                    // calling arm bound the unwrapped value.
1228                    if result_holds_unwrapped
1229                        && let syn::Type::Path(p) = ty
1230                        && let Some(seg) = p.path.segments.last()
1231                    {
1232                        let name = seg.ident.to_string();
1233                        if (name == "Option" || name == "Result")
1234                            && let Some(inner) = first_type_argument(seg)
1235                        {
1236                            Some(inner)
1237                        } else {
1238                            Some(ty)
1239                        }
1240                    } else {
1241                        Some(ty)
1242                    }
1243                }
1244                syn::ReturnType::Default => None,
1245            };
1246
1247            if let Some(inner_ty) = inner_ty.and_then(|ty| {
1248                crate::return_type_analysis::strict_conversion_for_type(ty, result_ident)
1249            }) {
1250                return inner_ty;
1251            }
1252        }
1253
1254        quote! { ::miniextendr_api::into_r::IntoR::into_sexp(#result_ident) }
1255    }
1256
1257    /// Generates the `R_CallMethodDef` constant for R's `.Call` interface registration.
1258    ///
1259    /// The constant contains the C symbol name, a `DL_FUNC` pointer to the wrapper
1260    /// (obtained via `transmute`), and the argument count. R uses this at package load
1261    /// time (via `R_registerRoutines`) to register the native routine.
1262    fn generate_call_method_def(&self) -> TokenStream {
1263        let fn_ident = &self.fn_ident;
1264        let c_ident = &self.c_ident;
1265        // When skip_wrapper is set, the user-written fn doesn't have the synthetic
1266        // __miniextendr_call SEXP param — use the actual input count. Otherwise
1267        // use build_c_params() which includes __miniextendr_call + self_sexp.
1268        let num_args = if self.skip_wrapper {
1269            self.inputs
1270                .iter()
1271                .filter(|arg| matches!(arg, syn::FnArg::Typed(_)))
1272                .count()
1273        } else {
1274            let (c_params, _, _) = self.build_c_params();
1275            c_params.len()
1276        };
1277        let num_args_lit = syn::LitInt::new(&num_args.to_string(), proc_macro2::Span::call_site());
1278
1279        let c_ident_name = syn::LitCStr::new(
1280            std::ffi::CString::new(c_ident.to_string())
1281                .expect("valid C string")
1282                .as_c_str(),
1283            c_ident.span(),
1284        );
1285
1286        // Use custom call_method_def_ident if set, otherwise use default naming
1287        let call_method_def_ident = self.call_method_def_ident.clone().unwrap_or_else(|| {
1288            if let Some(ref type_ident) = self.type_context {
1289                format_ident!("call_method_def_{}_{}", type_ident, fn_ident)
1290            } else {
1291                format_ident!("call_method_def_{}", fn_ident)
1292            }
1293        });
1294
1295        // Build func_ptr_def for transmute
1296        let func_ptr_def: Vec<syn::Type> = (0..num_args)
1297            .map(|_| syn::parse_quote!(::miniextendr_api::SEXP))
1298            .collect();
1299
1300        let item_label = if let Some(ref type_ident) = self.type_context {
1301            format!("`{}::{}`", type_ident, fn_ident)
1302        } else {
1303            format!("`{}`", fn_ident)
1304        };
1305        let doc = format!(
1306            "R call method definition for {} (C wrapper: [`{}`]).",
1307            item_label, c_ident
1308        );
1309        let doc_example = format!(
1310            "Value: `R_CallMethodDef {{ name: \"{}\", numArgs: {}, fun: <DL_FUNC> }}`",
1311            c_ident, num_args
1312        );
1313        let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
1314
1315        quote! {
1316            #[doc = #doc]
1317            #[doc = #doc_example]
1318            #[doc = #source_loc_doc]
1319            #[doc = concat!("Generated from source file `", file!(), "`.")]
1320            #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_CALL_DEFS), linkme(crate = ::miniextendr_api::linkme))]
1321            #[allow(non_upper_case_globals)]
1322            #[allow(non_snake_case)]
1323            static #call_method_def_ident: ::miniextendr_api::sys::R_CallMethodDef = unsafe {
1324                ::miniextendr_api::sys::R_CallMethodDef {
1325                    name: #c_ident_name.as_ptr(),
1326                    fun: Some(std::mem::transmute::<
1327                        unsafe extern "C-unwind" fn(#(#func_ptr_def),*) -> ::miniextendr_api::SEXP,
1328                        unsafe extern "C-unwind" fn() -> *mut ::std::os::raw::c_void
1329                    >(#c_ident)),
1330                    numArgs: #num_args_lit,
1331                }
1332            };
1333        }
1334    }
1335
1336    /// Generates a rustdoc comment string for the C wrapper function.
1337    ///
1338    /// Includes the original function/method name, thread strategy label, and a
1339    /// cross-reference to the R wrapper constant.
1340    fn generate_doc_comment(&self, thread_info: &str) -> String {
1341        if let Some(ref type_ident) = self.type_context {
1342            format!(
1343                "C wrapper for [`{}::{}`] ({}). See [`{}`] for R wrapper.",
1344                type_ident, self.fn_ident, thread_info, self.r_wrapper_const
1345            )
1346        } else {
1347            format!(
1348                "C wrapper for [`{}`] ({}). See [`{}`] for R wrapper.",
1349                self.fn_ident, thread_info, self.r_wrapper_const
1350            )
1351        }
1352    }
1353}
1354
1355/// Builder for [`CWrapperContext`].
1356///
1357/// Created via [`CWrapperContext::builder`]. All fields except `fn_ident` and `c_ident`
1358/// (provided at construction) default to empty/false/None. Required fields (`call_expr`,
1359/// `r_wrapper_const`) must be set before calling [`build`](Self::build) or it will panic.
1360///
1361/// Optional fields like `thread_strategy` and `return_handling` are auto-detected from
1362/// the function signature if not explicitly set.
1363pub struct CWrapperContextBuilder {
1364    /// Rust function/method identifier (set at construction).
1365    fn_ident: syn::Ident,
1366    /// C wrapper function identifier (set at construction).
1367    c_ident: syn::Ident,
1368    /// R wrapper constant identifier for doc cross-references. **Required.**
1369    r_wrapper_const: Option<syn::Ident>,
1370    /// Function parameters (excluding `self`). Defaults to empty.
1371    inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
1372    /// Rust return type. Defaults to `()` (no return type annotation).
1373    output: syn::ReturnType,
1374    /// Pre-call statements emitted before the call expression. Defaults to empty.
1375    pre_call: Vec<TokenStream>,
1376    /// The Rust call expression. **Required.**
1377    call_expr: Option<TokenStream>,
1378    /// Thread strategy override. If `None`, defaults to [`ThreadStrategy::MainThread`].
1379    thread_strategy: Option<ThreadStrategy>,
1380    /// Return handling override. If `None`, auto-detected from `output` via [`detect_return_handling`].
1381    return_handling: Option<ReturnHandling>,
1382    /// Enable coercing conversion for all parameters.
1383    coerce_all: bool,
1384    /// Names of individual parameters with coercing conversion enabled.
1385    coerce_params: Vec<String>,
1386    /// Emit `R_CheckUserInterrupt()` before the call.
1387    check_interrupt: bool,
1388    /// Wrap call in `GetRNGstate()`/`PutRNGstate()`.
1389    rng: bool,
1390    /// `#[cfg(...)]` attributes to propagate to generated items.
1391    cfg_attrs: Vec<syn::Attribute>,
1392    /// Type identifier for method context (e.g., `MyStruct`). `None` for standalone functions.
1393    type_context: Option<syn::Ident>,
1394    /// Whether the original method has a `self` receiver.
1395    has_self: bool,
1396    /// Custom `call_method_def` constant name override.
1397    call_method_def_ident: Option<syn::Ident>,
1398    /// Enable strict checked conversions for lossy return types.
1399    strict: bool,
1400    /// Parameter names with `match_arg + several_ok` — forwarded to
1401    /// `RustConversionBuilder::with_match_arg_several_ok` so each element of the
1402    /// Vec is decoded via `match_arg_vec_from_sexp` (enum's `MatchArg::CHOICES`).
1403    match_arg_several_ok_params: Vec<String>,
1404    /// When `true`, use original parameter names in C wrapper signature (for rustdoc).
1405    preserve_param_names: bool,
1406    /// Visibility of the generated `extern "C-unwind"` wrapper.
1407    vis: syn::Visibility,
1408    /// Generic parameters for the C wrapper signature.
1409    generics: syn::Generics,
1410    /// When `true`, skip wrapper body but still emit `R_CallMethodDef`.
1411    skip_wrapper: bool,
1412}
1413
1414impl CWrapperContextBuilder {
1415    /// Sets the R wrapper constant identifier (e.g., `R_WRAPPER_my_func`).
1416    /// **Required** -- [`build`](Self::build) panics if not set.
1417    pub fn r_wrapper_const(mut self, ident: syn::Ident) -> Self {
1418        self.r_wrapper_const = Some(ident);
1419        self
1420    }
1421
1422    /// Sets the function parameters (excluding `self` receiver).
1423    /// Each input becomes a `SEXP` argument in the C wrapper.
1424    pub fn inputs(
1425        mut self,
1426        inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
1427    ) -> Self {
1428        self.inputs = inputs;
1429        self
1430    }
1431
1432    /// Sets the Rust return type. Used for auto-detecting [`ReturnHandling`]
1433    /// and for strict-mode type inspection.
1434    pub fn output(mut self, output: syn::ReturnType) -> Self {
1435        self.output = output;
1436        self
1437    }
1438
1439    /// Sets pre-call statements emitted before the call expression.
1440    /// Typically used for self-extraction in instance methods.
1441    pub fn pre_call(mut self, stmts: Vec<TokenStream>) -> Self {
1442        self.pre_call = stmts;
1443        self
1444    }
1445
1446    /// Sets the Rust call expression (e.g., `my_func(arg0)` or `self_ref.method(arg0)`).
1447    /// **Required** -- [`build`](Self::build) panics if not set.
1448    pub fn call_expr(mut self, expr: TokenStream) -> Self {
1449        self.call_expr = Some(expr);
1450        self
1451    }
1452
1453    /// Overrides the thread strategy. If not called, defaults to [`ThreadStrategy::MainThread`].
1454    pub fn thread_strategy(mut self, strategy: ThreadStrategy) -> Self {
1455        self.thread_strategy = Some(strategy);
1456        self
1457    }
1458
1459    /// Overrides the return handling strategy. If not called, auto-detected from `output`
1460    /// via [`detect_return_handling`].
1461    pub fn return_handling(mut self, handling: ReturnHandling) -> Self {
1462        self.return_handling = Some(handling);
1463        self
1464    }
1465
1466    /// Enables coercing conversion for all parameters via `Rf_coerceVector`.
1467    pub fn coerce_all(mut self) -> Self {
1468        self.coerce_all = true;
1469        self
1470    }
1471
1472    /// Enables coercing conversion for a specific named parameter.
1473    pub fn with_coerce_param(mut self, param_name: String) -> Self {
1474        self.coerce_params.push(param_name);
1475        self
1476    }
1477
1478    /// Enables `R_CheckUserInterrupt()` before the call expression.
1479    pub fn check_interrupt(mut self) -> Self {
1480        self.check_interrupt = true;
1481        self
1482    }
1483
1484    /// Enable RNG state management (GetRNGstate/PutRNGstate).
1485    pub fn rng(mut self) -> Self {
1486        self.rng = true;
1487        self
1488    }
1489
1490    /// Sets `#[cfg(...)]` attributes to propagate to the C wrapper and `call_method_def`.
1491    pub fn cfg_attrs(mut self, attrs: Vec<syn::Attribute>) -> Self {
1492        self.cfg_attrs = attrs;
1493        self
1494    }
1495
1496    /// Sets the type context for methods (e.g., `MyStruct`). Used in doc comments
1497    /// and default `call_method_def` naming.
1498    pub fn type_context(mut self, type_ident: syn::Ident) -> Self {
1499        self.type_context = Some(type_ident);
1500        self
1501    }
1502
1503    /// Marks this as an instance method with a `self` receiver.
1504    /// Causes the C wrapper to include a `self_sexp` parameter.
1505    pub fn has_self(mut self) -> Self {
1506        self.has_self = true;
1507        self
1508    }
1509
1510    /// Enables strict checked conversions for lossy return types (`i64`, `u64`, `isize`,
1511    /// `usize` and their `Vec` variants).
1512    pub fn strict(mut self) -> Self {
1513        self.strict = true;
1514        self
1515    }
1516
1517    /// Record a parameter as `match_arg + several_ok`.
1518    ///
1519    /// Passed through to `RustConversionBuilder::with_match_arg_several_ok`, which
1520    /// switches that parameter's conversion from `TryFromSexp` to
1521    /// `match_arg_vec_from_sexp::<Inner>` so each STRSXP element is validated against
1522    /// the enum's `MatchArg::CHOICES`.
1523    pub fn match_arg_several_ok(mut self, param_name: String) -> Self {
1524        self.match_arg_several_ok_params.push(param_name);
1525        self
1526    }
1527
1528    /// Set a custom call_method_def identifier.
1529    ///
1530    /// If not set, the default naming is used:
1531    /// - With type_context: `call_method_def_{type}_{method}`
1532    /// - Without: `call_method_def_{method}`
1533    pub fn call_method_def_ident(mut self, ident: syn::Ident) -> Self {
1534        self.call_method_def_ident = Some(ident);
1535        self
1536    }
1537
1538    /// Preserve original parameter names in the C wrapper signature.
1539    ///
1540    /// When `true`, `build_c_params` uses the original identifier from `inputs` instead
1541    /// of renaming to `arg_N`. Enables rustdoc to show descriptive parameter names.
1542    /// Used by the standalone-fn path; impl methods use the default `arg_N` form.
1543    pub fn preserve_param_names(mut self) -> Self {
1544        self.preserve_param_names = true;
1545        self
1546    }
1547
1548    /// Set the visibility of the generated `extern "C-unwind"` wrapper.
1549    ///
1550    /// Defaults to [`syn::Visibility::Inherited`]. Standalone fns forward the user's
1551    /// declared visibility (`pub`, `pub(crate)`, etc.).
1552    pub fn vis(mut self, vis: syn::Visibility) -> Self {
1553        self.vis = vis;
1554        self
1555    }
1556
1557    /// Set the generic parameters for the C wrapper function signature.
1558    ///
1559    /// Defaults to empty generics. Standalone fns with generic parameters
1560    /// must forward them so the generated wrapper is also generic.
1561    pub fn generics(mut self, generics: syn::Generics) -> Self {
1562        self.generics = generics;
1563        self
1564    }
1565
1566    /// Skip generating the wrapper body and only emit the `R_CallMethodDef`.
1567    ///
1568    /// Use this when the Rust fn is already `extern "C-unwind"` with `#[no_mangle]` or
1569    /// `#[unsafe(no_mangle)]` (the user wrote the C symbol directly). The function still
1570    /// needs to be registered with R via `R_CallMethodDef`.
1571    ///
1572    /// When set, `numArgs` is computed from `inputs` directly (no synthetic
1573    /// `__miniextendr_call` param).
1574    pub fn skip_wrapper(mut self) -> Self {
1575        self.skip_wrapper = true;
1576        self
1577    }
1578
1579    /// Consumes the builder and returns a fully configured [`CWrapperContext`].
1580    ///
1581    /// If `thread_strategy` was not set, defaults to [`ThreadStrategy::MainThread`].
1582    /// If `return_handling` was not set, auto-detects from the `output` type via
1583    /// [`detect_return_handling`].
1584    ///
1585    /// # Panics
1586    ///
1587    /// Panics if `call_expr` or `r_wrapper_const` was not set.
1588    pub fn build(self) -> CWrapperContext {
1589        let call_expr = self
1590            .call_expr
1591            .expect("call_expr is required for CWrapperContext");
1592        let r_wrapper_const = self
1593            .r_wrapper_const
1594            .expect("r_wrapper_const is required for CWrapperContext");
1595
1596        // Detect thread strategy if not explicitly set.
1597        // Main thread is the default for all methods (safer, simpler execution model).
1598        let thread_strategy = self.thread_strategy.unwrap_or(ThreadStrategy::MainThread);
1599
1600        // Detect return handling if not explicitly set
1601        let return_handling = self
1602            .return_handling
1603            .unwrap_or_else(|| detect_return_handling(&self.output));
1604
1605        CWrapperContext {
1606            fn_ident: self.fn_ident,
1607            c_ident: self.c_ident,
1608            r_wrapper_const,
1609            inputs: self.inputs,
1610            output: self.output,
1611            pre_call: self.pre_call,
1612            call_expr,
1613            thread_strategy,
1614            return_handling,
1615            coerce_all: self.coerce_all,
1616            coerce_params: self.coerce_params,
1617            check_interrupt: self.check_interrupt,
1618            rng: self.rng,
1619            cfg_attrs: self.cfg_attrs,
1620            type_context: self.type_context,
1621            has_self: self.has_self,
1622            call_method_def_ident: self.call_method_def_ident,
1623            strict: self.strict,
1624            match_arg_several_ok_params: self.match_arg_several_ok_params,
1625            preserve_param_names: self.preserve_param_names,
1626            vis: self.vis,
1627            generics: self.generics,
1628            skip_wrapper: self.skip_wrapper,
1629        }
1630    }
1631}
1632
1633/// Detects the appropriate [`ReturnHandling`] strategy from a function's return type.
1634///
1635/// Inspects the `syn::ReturnType`:
1636/// - No return type annotation (`Default`) maps to [`ReturnHandling::Unit`].
1637/// - An explicit type is analyzed by [`detect_return_handling_from_type`].
1638pub fn detect_return_handling(output: &syn::ReturnType) -> ReturnHandling {
1639    match output {
1640        syn::ReturnType::Default => ReturnHandling::Unit,
1641        syn::ReturnType::Type(_, ty) => detect_return_handling_from_type(ty),
1642    }
1643}
1644
1645/// Detects [`ReturnHandling`] for the standalone-`#[miniextendr]`-fn path.
1646///
1647/// Identical to [`detect_return_handling`] except that general `Option<T>` maps to
1648/// [`ReturnHandling::OptionIntoR`] (call `into_sexp` on the whole Option, matching the
1649/// historical `analyze_return_type` behavior) rather than [`ReturnHandling::OptionIntoRUnwrap`]
1650/// (the default that preserves impl-method behavior). Use this when building a
1651/// [`CWrapperContext`] for a standalone function.
1652pub fn detect_return_handling_standalone_fn(output: &syn::ReturnType) -> ReturnHandling {
1653    let handling = detect_return_handling(output);
1654    // Standalone fns' old path called into_sexp(whole_option), which is OptionIntoR semantics.
1655    match handling {
1656        ReturnHandling::OptionIntoRUnwrap => ReturnHandling::OptionIntoR,
1657        other => other,
1658    }
1659}
1660
1661/// Determines the [`ReturnHandling`] variant for a concrete `syn::Type`.
1662///
1663/// Recognition rules:
1664/// - `()` -> [`Unit`](ReturnHandling::Unit)
1665/// - `Self` -> [`ExternalPtr`](ReturnHandling::ExternalPtr)
1666/// - `SEXP` -> [`RawSexp`](ReturnHandling::RawSexp)
1667/// - `Option<T>` -> recurses into `T` for `OptionUnit`, `OptionSexp`, `OptionExternalPtr`
1668///   (`T = Self`), or `OptionIntoRUnwrap`
1669/// - `Result<T, E>` -> recurses into `T` for `ResultUnit`, `ResultSexp`, `ResultExternalPtr`
1670///   (`T = Self`), or `ResultIntoR`
1671/// - Anything else -> [`IntoR`](ReturnHandling::IntoR)
1672///
1673/// Note: The default for `Option<T>` (non-unit, non-SEXP) is `OptionIntoRUnwrap` (unwrap
1674/// first, error on `None`), which preserves the historical behavior for impl methods.
1675/// Use [`ReturnHandling::OptionIntoR`] explicitly when the type has a direct
1676/// `impl IntoR for Option<T>` (e.g., `Option<&T>`, `Option<Vec<T>>`, `Option<i32>`).
1677fn detect_return_handling_from_type(ty: &syn::Type) -> ReturnHandling {
1678    match ty {
1679        // Unit tuple ()
1680        syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::Unit,
1681
1682        // Self - wrap in ExternalPtr
1683        syn::Type::Path(p)
1684            if p.path
1685                .segments
1686                .last()
1687                .map(|s| s.ident == "Self")
1688                .unwrap_or(false) =>
1689        {
1690            ReturnHandling::ExternalPtr
1691        }
1692
1693        // SEXP - pass through
1694        syn::Type::Path(p)
1695            if p.path
1696                .segments
1697                .last()
1698                .map(|s| s.ident == "SEXP")
1699                .unwrap_or(false) =>
1700        {
1701            ReturnHandling::RawSexp
1702        }
1703
1704        // Option<T>
1705        syn::Type::Path(p)
1706            if p.path
1707                .segments
1708                .last()
1709                .map(|s| s.ident == "Option")
1710                .unwrap_or(false) =>
1711        {
1712            if let Some(inner_ty) = first_type_argument(p.path.segments.last().unwrap()) {
1713                match inner_ty {
1714                    syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::OptionUnit,
1715                    syn::Type::Path(ip)
1716                        if ip
1717                            .path
1718                            .segments
1719                            .last()
1720                            .map(|s| s.ident == "SEXP")
1721                            .unwrap_or(false) =>
1722                    {
1723                        ReturnHandling::OptionSexp
1724                    }
1725                    // Option<Self>: a lookup-shaped fallible constructor. Wrap
1726                    // `Some(Self)` in an ExternalPtr like the bare-`Self` path, rather
1727                    // than routing it through `IntoR` (which `Self` generally lacks).
1728                    // Symmetric with `Result<Self, E>` -> `ResultExternalPtr` below.
1729                    syn::Type::Path(ip)
1730                        if ip
1731                            .path
1732                            .segments
1733                            .last()
1734                            .map(|s| s.ident == "Self")
1735                            .unwrap_or(false) =>
1736                    {
1737                        ReturnHandling::OptionExternalPtr
1738                    }
1739                    _ => ReturnHandling::OptionIntoRUnwrap,
1740                }
1741            } else {
1742                ReturnHandling::OptionIntoRUnwrap
1743            }
1744        }
1745
1746        // Result<T, E>
1747        syn::Type::Path(p)
1748            if p.path
1749                .segments
1750                .last()
1751                .map(|s| s.ident == "Result")
1752                .unwrap_or(false) =>
1753        {
1754            let seg = p.path.segments.last().unwrap();
1755            // Special case: Result<T, ()> — unit error is a deliberate sentinel that maps to
1756            // R NULL, not a failure.
1757            let err_is_unit = crate::second_type_argument(seg)
1758                .is_some_and(|ty| matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty()));
1759            if err_is_unit {
1760                return ReturnHandling::ResultNullOnErr;
1761            }
1762            if let Some(ok_ty) = first_type_argument(seg) {
1763                match ok_ty {
1764                    syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::ResultUnit,
1765                    syn::Type::Path(ip)
1766                        if ip
1767                            .path
1768                            .segments
1769                            .last()
1770                            .map(|s| s.ident == "SEXP")
1771                            .unwrap_or(false) =>
1772                    {
1773                        ReturnHandling::ResultSexp
1774                    }
1775                    // Result<Self, E>: a fallible constructor-shaped method. Wrap
1776                    // `Ok(Self)` in an ExternalPtr like the bare-`Self` path, rather
1777                    // than routing it through `IntoR` (which `Self` generally lacks).
1778                    syn::Type::Path(ip)
1779                        if ip
1780                            .path
1781                            .segments
1782                            .last()
1783                            .map(|s| s.ident == "Self")
1784                            .unwrap_or(false) =>
1785                    {
1786                        ReturnHandling::ResultExternalPtr
1787                    }
1788                    _ => ReturnHandling::ResultIntoR,
1789                }
1790            } else {
1791                ReturnHandling::ResultIntoR
1792            }
1793        }
1794
1795        // Everything else - use IntoR
1796        _ => ReturnHandling::IntoR,
1797    }
1798}
1799
1800/// Extracts the first generic type argument from a path segment's angle-bracketed arguments.
1801///
1802/// For example, given `Option<String>`, returns `Some(&String)`.
1803/// Returns `None` if the segment has no angle-bracketed arguments or no type arguments.
1804fn first_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
1805    if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
1806        for arg in ab.args.iter() {
1807            if let syn::GenericArgument::Type(ty) = arg {
1808                return Some(ty);
1809            }
1810        }
1811    }
1812    None
1813}
1814
1815#[cfg(test)]
1816mod tests;