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