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;