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