Skip to main content

miniextendr_macros/
r_wrapper_builder.rs

1//! Shared utilities for building R wrapper code.
2//!
3//! This module provides builders for constructing R function signatures and call arguments
4//! consistently across both standalone functions and impl methods.
5//!
6//! ## Key Components
7//!
8//! - [`RArgumentBuilder`]: Builds R formals and `.Call()` arguments from Rust signatures
9//! - [`DotCallBuilder`]: Formats `.Call()` invocations with proper argument handling
10//! - [`RoxygenBuilder`]: Generates roxygen2 documentation tags
11//!
12//! ## Usage
13//!
14//! ```ignore
15//! // Build R function signature
16//! let formals = build_r_formals_from_sig(&method.sig, &r_formals, &defaults);
17//! let call_args = build_r_call_args_from_sig(&method.sig, &r_formals, absent_formals);
18//!
19//! // Build .Call() invocation
20//! let call = DotCallBuilder::new("C_MyType__method")
21//!     .with_self("self")
22//!     .with_args(&["x", "y"])
23//!     .build();
24//!
25//! // Build roxygen tags
26//! let tags = RoxygenBuilder::new("MyType")
27//!     .name("method")
28//!     .rdname("MyType")
29//!     .export()
30//!     .build();
31//! ```
32
33/// Normalizes a Rust argument name (its [`crate::naming::ident_name`], no
34/// `r#`) for R.
35///
36/// - Leading `_` → stripped (Rust convention for unused params)
37/// - Leading `__` → stripped
38/// - Otherwise → unchanged
39///
40/// # Examples
41/// - `_x` → `x`
42/// - `_to` → `to`
43/// - `__field` → `field`
44/// - `value` → `value`
45///
46/// Note: We strip underscores rather than prefixing "unused" because R callers
47/// (like vctrs) may use named arguments that must match the original name.
48///
49/// This is the R formal of a parameter without a declared `formal = ".."`
50/// only: R-facing names come from [`Formals::of`], which applies it.
51pub fn normalize_r_arg_string(name: &str) -> String {
52    let normalized = name.trim_start_matches('_');
53    if normalized.is_empty() {
54        "arg".to_string()
55    } else {
56        normalized.to_string()
57    }
58}
59
60/// The R formal of each parameter of one signature (#1842).
61///
62/// A parameter declared with `formal = "conf.int"` (a standalone function's
63/// per-parameter option, a method's `formal(conf_int = "conf.int")`) has that
64/// formal; every other one has [`normalize_r_arg_string`] of its Rust name.
65/// [`Formals::of`] is the one place that rule lives: every site that writes a
66/// parameter's name into R text (the formals, the `.Call()` arguments, the
67/// argument checks and their errors, `e$param` of a conversion error, the
68/// `@param` lines) asks it. Maps keyed by Rust name (the per-parameter
69/// options) stay keyed by Rust name, and so do the `match_arg` placeholders,
70/// which the wrapper writer replaces before R reads them.
71///
72/// A declared formal may hold a `.`, which no Rust identifier can, so it is
73/// only ever a string: never an identifier, and never part of one.
74#[derive(#[automatically_derived]
impl ::core::clone::Clone for Formals {
    #[inline]
    fn clone(&self) -> Self {
        Formals { declared: ::core::clone::Clone::clone(&self.declared) }
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Formals {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "Formals",
            "declared", &&self.declared)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for Formals {
    #[inline]
    fn default() -> Self {
        Formals { declared: ::core::default::Default::default() }
    }
}Default)]
75pub(crate) struct Formals {
76    /// The declared formals, keyed by Rust parameter name
77    /// ([`crate::naming::ident_name`]), each with the literal that declared
78    /// it (where its errors point).
79    declared: std::collections::HashMap<String, syn::LitStr>,
80}
81
82impl Formals {
83    /// The formals the per-parameter options of one signature declare.
84    pub(crate) fn from_param_attrs(
85        per_param: &std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs>,
86    ) -> Self {
87        Self {
88            declared: per_param
89                .iter()
90                .filter_map(|(name, attrs)| Some((name.clone(), attrs.formal.clone()?)))
91                .collect(),
92        }
93    }
94
95    /// The R formal of the parameter whose Rust name is `rust_name`: its
96    /// declared formal, else the name without leading underscores.
97    pub(crate) fn of(&self, rust_name: &str) -> String {
98        match self.declared.get(rust_name) {
99            Some(formal) => formal.value(),
100            None => normalize_r_arg_string(rust_name),
101        }
102    }
103
104    /// [`Self::of`] the parameter `ident` (`r#` dropped).
105    pub(crate) fn of_ident(&self, ident: &syn::Ident) -> String {
106        self.of(&crate::naming::ident_name(ident))
107    }
108
109    /// The literal that declared the formal of `rust_name`, if one did.
110    pub(crate) fn declared(&self, rust_name: &str) -> Option<&syn::LitStr> {
111        self.declared.get(rust_name)
112    }
113
114    /// `map` with each key, a Rust parameter name, replaced by that
115    /// parameter's R formal: for maps keyed by Rust name (a function's
116    /// `default = ".."`, a method's `defaults(p = "..")`) that feed a builder
117    /// keyed by R formal.
118    pub(crate) fn rekey<V>(
119        &self,
120        map: impl IntoIterator<Item = (String, V)>,
121    ) -> std::collections::HashMap<String, V> {
122        map.into_iter()
123            .map(|(rust_name, value)| (self.of(&rust_name), value))
124            .collect()
125    }
126}
127
128/// The R names a declared formal must not take, beyond R's own syntax rules:
129/// the names the generated wrapper binds or reads in the function's frame
130/// (#1842). Another dotted name is the framework's only when it starts with
131/// one of [`FRAMEWORK_FORMAL_PREFIXES`].
132const FRAMEWORK_FORMALS: &[(&str, &str)] = &[
133    (
134        ".call",
135        "is the call the generated wrapper passes to `.Call()`",
136    ),
137    (
138        ".val",
139        "is where the generated wrapper keeps the result of `.Call()`",
140    ),
141    (
142        ".ptr",
143        "is the pointer argument of a generated class constructor",
144    ),
145    (
146        ".Generic",
147        "is bound by R's S3 dispatch in a method's frame",
148    ),
149    (".Class", "is bound by R's S3 dispatch in a method's frame"),
150    (".Method", "is bound by R's S3 dispatch in a method's frame"),
151    (".Group", "is bound by R's S3 dispatch in a method's frame"),
152    (
153        ".GenericCallEnv",
154        "is bound by R's S3 dispatch in a method's frame",
155    ),
156    (
157        ".GenericDefEnv",
158        "is bound by R's S3 dispatch in a method's frame",
159    ),
160];
161
162/// Prefixes of the R names the framework generates (locals such as
163/// `.mx_call` and `.mx_self`, the wrappers preamble's `.miniextendr_*`
164/// helpers, the `.rdata_*` helpers `#[derive(ExternalPtr)]` writes for a
165/// sidecar, the write-time `.__MX_*` placeholders): a declared formal starting
166/// with one is refused.
167const FRAMEWORK_FORMAL_PREFIXES: &[&str] = &[".mx_", ".miniextendr_", ".rdata_", ".__MX_"];
168
169/// Why `formal` can't be a declared formal, or `None` when it can: it must be
170/// a syntactic R name ([`crate::naming::is_syntactic_r_name`]: letters,
171/// digits, `.` and `_`, not starting with a digit, `_` or `.` and a digit, and
172/// not a reserved word), not `...` or `..1` (R's dots), and none of the names
173/// the generated wrapper binds itself ([`FRAMEWORK_FORMALS`],
174/// [`FRAMEWORK_FORMAL_PREFIXES`]).
175pub(crate) fn declared_formal_problem(formal: &str) -> Option<String> {
176    if formal.is_empty() {
177        return Some("is empty".to_string());
178    }
179    if formal.starts_with("..") {
180        return Some(
181            "is R's `...` or one of its elements (`..1`, `..2`, ...), not an argument name"
182                .to_string(),
183        );
184    }
185    if crate::naming::is_r_reserved_word(formal) {
186        return Some("is an R reserved word".to_string());
187    }
188    if !crate::naming::is_syntactic_r_name(formal) {
189        return Some(
190            "is not a syntactic R name: it must start with a letter, or with a `.` that is not \
191             followed by a digit, and hold only ASCII letters, digits, `.` and `_`"
192                .to_string(),
193        );
194    }
195    if let Some((_, role)) = FRAMEWORK_FORMALS.iter().find(|(name, _)| *name == formal) {
196        return Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}, so the generated wrapper can\'t take it",
                role))
    })format!("{role}, so the generated wrapper can't take it"));
197    }
198    FRAMEWORK_FORMAL_PREFIXES
199        .iter()
200        .find(|prefix| formal.starts_with(*prefix))
201        .map(|prefix| {
202            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("starts with `{0}`, a prefix of the names the framework generates, so the generated wrapper can\'t take it",
                prefix))
    })format!(
203                "starts with `{prefix}`, a prefix of the names the framework generates, so \
204                 the generated wrapper can't take it"
205            )
206        })
207}
208
209/// Check the R formals a signature produces, before any wrapper is generated.
210///
211/// Each parameter becomes its formal ([`Formals::of`]: the declared
212/// `formal = ".."`, else [`normalize_r_arg_string`] of its name); the `&Dots`
213/// parameter becomes `...`, wherever it sits, and is skipped. A formal must be
214/// an R name (not a reserved word such as `if` or `in`, not starting with a
215/// digit; a declared one must pass [`declared_formal_problem`]), distinct
216/// from the others (`x` and `_x` both become `x`), and not one of the
217/// `reserved` names the generated wrapper binds itself (a method's receiver,
218/// R6's `self` and `private`), each paired with what it is. Any of these would
219/// break the wrapper: a parse error in the wrappers file, or a parameter that
220/// hides the object the method works on.
221///
222/// Every case is a compile error rather than a rename: callers pass arguments
223/// by name, so the R formal has to stay the parameter's own name unless the
224/// author declares another. An error points at the declaring literal for a
225/// declared formal, else at the parameter. Every problem of the signature is
226/// reported, in one combined error.
227pub(crate) fn check_r_formals(
228    inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
229    formals: &Formals,
230    reserved: &[(String, String)],
231) -> syn::Result<()> {
232    let mut errors: Option<syn::Error> = None;
233    let mut push = |error: syn::Error| match errors.as_mut() {
234        Some(all) => all.combine(error),
235        None => errors = Some(error),
236    };
237    let mut seen: std::collections::HashMap<String, &syn::Ident> = std::collections::HashMap::new();
238    for (formal, ident) in r_formal_names(inputs, formals) {
239        let declared = formals.declared(&crate::naming::ident_name(ident));
240        let problem = if let Some(lit) = declared {
241            declared_formal_problem(&formal).map(|problem| (lit.span(), problem))
242        } else if crate::naming::is_r_reserved_word(&formal) {
243            Some((
244                ident.span(),
245                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("is an R reserved word, so the generated R wrapper would not parse. Rename the parameter (for example `{0}_`)",
                formal))
    })format!(
246                    "is an R reserved word, so the generated R wrapper would not parse. \
247                     Rename the parameter (for example `{formal}_`)"
248                ),
249            ))
250        } else if formal.starts_with(|c: char| c.is_ascii_digit()) {
251            Some((
252                ident.span(),
253                "starts with a digit, so the generated R wrapper would not parse. \
254                 Rename the parameter so that, without its leading underscores, it \
255                 starts with a letter"
256                    .to_string(),
257            ))
258        } else {
259            None
260        };
261        let problem = problem.or_else(|| {
262            reserved
263                .iter()
264                .find(|(name, _)| *name == formal)
265                .map(|(_, role)| {
266                    let fix = if declared.is_some() {
267                        "Declare another formal"
268                    } else {
269                        "Rename the parameter"
270                    };
271                    (
272                        declared.map_or(ident.span(), syn::LitStr::span),
273                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}. {1}", role, fix))
    })format!("{role}. {fix}"),
274                    )
275                })
276        });
277        if let Some((span, problem)) = problem {
278            let becomes = if declared.is_some() {
279                "is declared as"
280            } else {
281                "becomes"
282            };
283            push(syn::Error::new(
284                span,
285                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("parameter `{0}` {1} the R argument `{2}`, which {3}.",
                ident, becomes, formal, problem))
    })format!(
286                    "parameter `{ident}` {becomes} the R argument `{formal}`, which {problem}."
287                ),
288            ));
289            continue;
290        }
291        if let Some(first) = seen.insert(formal.clone(), ident) {
292            let span = declared.map_or(ident.span(), syn::LitStr::span);
293            let how = if declared.is_some()
294                || formals
295                    .declared(&crate::naming::ident_name(first))
296                    .is_some()
297            {
298                "a declared `formal` names the R argument"
299            } else {
300                "the R name drops leading underscores"
301            };
302            push(syn::Error::new(
303                span,
304                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("parameters `{0}` and `{1}` both become the R argument `{2}` ({3}), and R rejects a repeated argument name. Rename one of them.",
                first, ident, formal, how))
    })format!(
305                    "parameters `{first}` and `{ident}` both become the R argument `{formal}` \
306                     ({how}), and R rejects a repeated argument name. Rename one of them."
307                ),
308            ));
309        }
310    }
311    errors.map_or(Ok(()), Err)
312}
313
314/// The R formal each parameter of a signature becomes, with the parameter's
315/// ident: [`Formals::of`] its name. The `&Dots` / `LazyDots` parameter becomes
316/// `...` wherever it sits and is skipped, as are receivers, non-ident patterns
317/// and an `NArgs` parameter, which is no formal (#1860).
318///
319/// [`check_r_formals`] checks these names, and the shadowing pass
320/// ([`formal_names`](crate::r_shadowing::formal_names)) qualifies the calls
321/// they would shadow, so the two agree on what each formal is named.
322pub(crate) fn r_formal_names<'a>(
323    inputs: &'a syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
324    formals: &'a Formals,
325) -> impl Iterator<Item = (String, &'a syn::Ident)> {
326    let dots_index = crate::miniextendr_fn::dots_index(inputs);
327    inputs.iter().enumerate().filter_map(move |(idx, input)| {
328        let syn::FnArg::Typed(pat_type) = input else {
329            return None;
330        };
331        if Some(idx) == dots_index || crate::type_inspect::is_nargs_marker(&pat_type.ty) {
332            return None;
333        }
334        let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() else {
335            return None;
336        };
337        let ident = &pat_ident.ident;
338        Some((formals.of_ident(ident), ident))
339    })
340}
341
342/// Split a comma-separated choices list (as given to `choices(param = "a, b, c")`)
343/// into individual trimmed entries. Surrounding double-quotes are tolerated so
344/// users can spell the list either way: `"a, b"` or `"\"a\", \"b\""`.
345///
346/// Shared by the inherent-impl (`miniextendr_impl.rs`) and trait-impl
347/// (`miniextendr_impl_trait/vtable.rs`) `choices(...)` attribute parsers so the
348/// two independently-maintained parsers can't drift on quoting/whitespace rules.
349pub(crate) fn split_choice_list(raw: &str) -> Vec<String> {
350    raw.split(',')
351        .map(|s| s.trim().trim_matches('"').to_string())
352        .filter(|s| !s.is_empty())
353        .collect()
354}
355
356/// Builder for R function formal parameters and call arguments.
357///
358/// Handles:
359/// - R formals: the declared `formal = ".."` (#1842), else the Rust name
360///   without leading underscores (`_x` → `x`)
361/// - Unit type defaults (`()` → `= NULL`)
362/// - Dots: the `&Dots` parameter is `...` in the formals and `list(...)` in
363///   the call arguments, at its own position in the signature; a `LazyDots`
364///   parameter is `...` too, and `environment()` in the call arguments, which
365///   passes the dots unforced (#1892)
366/// - The argument count: an `NArgs` parameter is no formal and `nargs()` in
367///   the call arguments, at its own position (#1860)
368/// - Consistent formatting across function and method wrappers
369pub struct RArgumentBuilder<'a> {
370    /// The function's input parameters from the parsed Rust signature.
371    inputs: &'a syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
372    /// Position of the `&Dots` / `LazyDots` parameter in `inputs` (receiver
373    /// included, the way both build loops count). Its Rust name never reaches
374    /// R: the formal is always plain `...`.
375    dots_index: Option<usize>,
376    /// Which `...` type it is: what the call arguments pass at its position.
377    dots_kind: Option<crate::miniextendr_fn::DotsKind>,
378    /// If true, skip the first parameter (used for `self`/`&self` in method wrappers,
379    /// since the self argument is handled separately by [`DotCallBuilder::with_self`]).
380    skip_first: bool,
381    /// Parameter default values from `#[miniextendr(default = "...")]` attributes.
382    /// Keys are R formals ([`Formals::of`]), values are R expressions emitted
383    /// verbatim (e.g., `"1L"`, `"c(1, 2, 3)"`, `"NULL"`).
384    defaults: std::collections::HashMap<String, String>,
385    /// The single-choice `Missing<..>` parameters that read a value
386    /// `identical()` to their formal as omitted (#2152), R formal → the
387    /// formal's default (see
388    /// [`crate::miniextendr_fn::ParamAttrs::absent_formal`]).
389    absent_formals: std::collections::HashMap<String, String>,
390    /// The R formal of each parameter (#1842).
391    formals: Formals,
392}
393
394impl<'a> RArgumentBuilder<'a> {
395    /// Create a new builder for the given function inputs.
396    pub fn new(inputs: &'a syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>) -> Self {
397        let dots_index = crate::miniextendr_fn::dots_index(inputs);
398        let dots_kind = dots_index.and_then(|idx| match &inputs[idx] {
399            syn::FnArg::Typed(pt) => crate::miniextendr_fn::dots_kind(pt.ty.as_ref()),
400            syn::FnArg::Receiver(_) => None,
401        });
402        Self {
403            inputs,
404            dots_index,
405            dots_kind,
406            skip_first: false,
407            defaults: std::collections::HashMap::new(),
408            absent_formals: std::collections::HashMap::new(),
409            formals: Formals::default(),
410        }
411    }
412
413    /// Name each parameter by its R formal (#1842): the declared
414    /// `formal = ".."`, else the normalized Rust name (the default). The
415    /// keys of [`Self::with_defaults`] and [`Self::with_absent_formals`] are
416    /// these formals.
417    pub fn with_formals(mut self, formals: Formals) -> Self {
418        self.formals = formals;
419        self
420    }
421
422    /// Add the `Missing<..>` choice parameters that read a value `identical()`
423    /// to their formal as omitted (#2152), keyed by R formal, valued by that
424    /// formal's default (see
425    /// [`crate::miniextendr_fn::absent_formals_by_r_name`]).
426    pub fn with_absent_formals(
427        mut self,
428        absent_formals: std::collections::HashMap<String, String>,
429    ) -> Self {
430        self.absent_formals = absent_formals;
431        self
432    }
433
434    /// Add parameter defaults from `#[miniextendr(default = "...")]` attributes.
435    ///
436    /// Keys are R formals (see [`Self::with_formals`]; without a declared
437    /// formal, the Rust name without leading underscores), values are R
438    /// expression strings emitted verbatim into formals.
439    pub fn with_defaults(mut self, defaults: std::collections::HashMap<String, String>) -> Self {
440        self.defaults = defaults;
441        self
442    }
443
444    /// Skip the first parameter (for instance methods with `self`).
445    pub fn skip_first(mut self) -> Self {
446        self.skip_first = true;
447        self
448    }
449
450    /// Build R formal parameters string (for function signature).
451    ///
452    /// # Returns
453    /// Comma-separated parameter list, e.g., `"x, y = NULL, ..."`
454    ///
455    /// This method handles R-style defaults (like `1L`, `c(1,2,3)`) that aren't
456    /// valid Rust syntax by outputting them directly as strings.
457    pub fn build_formals(&self) -> String {
458        let mut formals = Vec::new();
459
460        for (idx, input) in self.inputs.iter().enumerate() {
461            // Skip first if requested (for self in methods)
462            if self.skip_first && idx == 0 {
463                continue;
464            }
465
466            let pat_type = match input {
467                syn::FnArg::Typed(pt) => pt,
468                syn::FnArg::Receiver(_) => continue, // Skip self receivers
469            };
470
471            // The dots, at their own position. In R, `...` takes no name or
472            // default, so the Rust binding never shows: the formal is `...`.
473            if Some(idx) == self.dots_index {
474                formals.push("...".to_string());
475                continue;
476            }
477
478            // The argument count (#1860) is no formal: the call arguments pass
479            // `nargs()` in its place.
480            if crate::type_inspect::is_nargs_marker(&pat_type.ty) {
481                continue;
482            }
483
484            // The parameter's R formal (#1842)
485            let formal = match pat_type.pat.as_ref() {
486                syn::Pat::Ident(pat_ident) => self.formals.of_ident(&pat_ident.ident),
487                _ => continue,
488            };
489
490            // Check for user-specified default value
491            if let Some(default_val) = self.defaults.get(&formal) {
492                // User provided default via #[miniextendr(default = "...")]
493                // Output directly as string - supports R-style defaults like "1L", "c(1,2,3)"
494                formals.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} = {1}", formal, default_val))
    })format!("{} = {}", formal, default_val));
495                continue;
496            }
497
498            // Add default for unit types
499            match pat_type.ty.as_ref() {
500                syn::Type::Tuple(t) if t.elems.is_empty() => {
501                    formals.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} = NULL", formal))
    })format!("{} = NULL", formal));
502                }
503                _ => {
504                    formals.push(formal);
505                }
506            }
507        }
508
509        formals.join(", ")
510    }
511
512    /// Build R call arguments string (for `.Call()` invocation).
513    ///
514    /// # Returns
515    /// Comma-separated argument list, e.g., `"x, y, list(...)"` (or
516    /// `"x, y, environment()"` for a `LazyDots` parameter)
517    pub fn build_call_args(&self) -> String {
518        self.build_call_args_vec().join(", ")
519    }
520
521    /// Build R call arguments as a `Vec<String>`.
522    ///
523    /// Each element is a single argument expression. A `&Dots` parameter
524    /// becomes `"list(...)"`, which captures the variadic args as an R list for
525    /// the `.Call()` interface; a `LazyDots` parameter becomes
526    /// `"environment()"`, the wrapper's own frame, whose `...` binding holds
527    /// the dots unforced (#1892).
528    pub fn build_call_args_vec(&self) -> Vec<String> {
529        let mut call_args = Vec::new();
530
531        for (idx, input) in self.inputs.iter().enumerate() {
532            // Skip first if requested (for self in methods)
533            if self.skip_first && idx == 0 {
534                continue;
535            }
536
537            let syn::FnArg::Typed(pat_type) = input else {
538                continue;
539            };
540
541            // The dots, at their own position: the formal is plain `...`, so
542            // the argument is `list(...)` for `&Dots` and `environment()` for
543            // `LazyDots` (#1892). `.Call()` evaluates its arguments in the
544            // wrapper's frame, so `environment()` is that frame, and nothing in
545            // `...` is forced on the way in.
546            if Some(idx) == self.dots_index {
547                let kind = self
548                    .dots_kind
549                    .unwrap_or(crate::miniextendr_fn::DotsKind::Forced);
550                call_args.push(kind.r_call_arg().to_string());
551                continue;
552            }
553
554            // The argument count (#1860): the wrapper's own `nargs()`, which
555            // the C wrapper converts like any argument. `.Call()` evaluates
556            // its arguments in the wrapper's frame, so `nargs()` counts the
557            // wrapper's call as typed.
558            if crate::type_inspect::is_nargs_marker(&pat_type.ty) {
559                call_args.push("nargs()".to_string());
560                continue;
561            }
562
563            // The parameter's R formal (#1842)
564            let formal = match pat_type.pat.as_ref() {
565                syn::Pat::Ident(pat_ident) => self.formals.of_ident(&pat_ident.ident),
566                _ => continue,
567            };
568
569            // `Quoted` / `Quosure` (also under `Missing<..>`, #1835): the
570            // argument unevaluated, behind the same missing-argument sentinel
571            // as `Missing<T>`. Neither branch forces it. A `default` on
572            // `Missing<..>` only writes the formal: `missing()` is `TRUE` for
573            // an omitted argument with a default, so the default is never
574            // evaluated.
575            if let Some(param) = crate::type_inspect::unevaluated_param(pat_type.ty.as_ref()) {
576                call_args.push(param.kind.r_call_arg(&formal));
577                continue;
578            }
579
580            // `Missing<T>`: forward true missingness as the `R_MissingArg`
581            // sentinel, produced *at the argument position*. A binding holding
582            // the sentinel errors on symbol lookup ("argument is missing, with
583            // no default"), so the former `if (missing(x)) x <- quote(expr=)`
584            // prelude broke every truly-missing call. A `default` on it
585            // (#2161) only writes the formal: `missing()` is `TRUE` for an
586            // omitted argument with a default, so the default is never
587            // evaluated, and a supplied value, the default's own included, is
588            // `Present`. A single-choice parameter also forwards the
589            // sentinel for a value `identical()` to its formal (#2152), which
590            // its prelude check skipped: the default shown in the usage line
591            // means the same as leaving it out.
592            if is_missing_type(pat_type.ty.as_ref()) {
593                let p = formal;
594                call_args.push(match self.absent_formals.get(&p) {
595                    Some(absent) => {
596                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (missing({0}) || identical({0}, {1})) quote(expr=) else {0}",
                p, absent))
    })format!(
597                            "if (missing({p}) || identical({p}, {absent})) quote(expr=) else {p}"
598                        )
599                    }
600                    None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (missing({0})) quote(expr=) else {0}",
                p))
    })format!("if (missing({p})) quote(expr=) else {p}"),
601                });
602                continue;
603            }
604
605            call_args.push(formal);
606        }
607
608        call_args
609    }
610}
611
612/// Build R formal parameters from a Rust function signature, with optional defaults.
613///
614/// Automatically skips `self`/`&self` receivers. `Missing<T>` parameters without
615/// user-provided defaults appear as bare formals (no default value); the
616/// `R_MissingArg` sentinel forwarding is emitted inline in the `.Call()` args
617/// (see [`RArgumentBuilder::build_call_args_vec`]).
618///
619/// Returns a comma-separated string of R formals, e.g., `"x, y = NULL, ..."`.
620/// `formals` names each parameter (see [`RArgumentBuilder::with_formals`]),
621/// and `defaults` is keyed by those formals.
622pub(crate) fn build_r_formals_from_sig(
623    sig: &syn::Signature,
624    formals: &Formals,
625    defaults: &std::collections::HashMap<String, String>,
626) -> String {
627    let mut builder = RArgumentBuilder::new(&sig.inputs).with_formals(formals.clone());
628    if #[allow(non_exhaustive_omitted_patterns)] match sig.inputs.first() {
    Some(syn::FnArg::Receiver(_)) => true,
    _ => false,
}matches!(sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
629        builder = builder.skip_first();
630    }
631    builder = builder.with_defaults(defaults.clone());
632    builder.build_formals()
633}
634
635/// Build R `.Call()` arguments from a Rust function signature.
636///
637/// Automatically skips `self`/`&self` receivers (those are passed separately
638/// via [`DotCallBuilder::with_self`]). `&Dots` becomes `list(...)`, `LazyDots`
639/// `environment()`. `absent_formals` are the choice parameters that read a
640/// value `identical()` to their formal as omitted (see
641/// [`RArgumentBuilder::with_absent_formals`]).
642///
643/// Returns a comma-separated string of R call arguments, e.g., `"x, y, list(...)"`.
644/// `formals` names each parameter, as in [`build_r_formals_from_sig`].
645pub(crate) fn build_r_call_args_from_sig(
646    sig: &syn::Signature,
647    formals: &Formals,
648    absent_formals: std::collections::HashMap<String, String>,
649) -> String {
650    let mut builder = RArgumentBuilder::new(&sig.inputs)
651        .with_formals(formals.clone())
652        .with_absent_formals(absent_formals);
653    if #[allow(non_exhaustive_omitted_patterns)] match sig.inputs.first() {
    Some(syn::FnArg::Receiver(_)) => true,
    _ => false,
}matches!(sig.inputs.first(), Some(syn::FnArg::Receiver(_))) {
654        builder = builder.skip_first();
655    }
656    builder.build_call_args()
657}
658
659// region: Missing<T> detection for automatic defaults
660
661/// Check if a type is `Missing<T>` by examining the last path segment.
662///
663/// `Missing<T>` is the miniextendr wrapper for R's "missing argument" concept,
664/// allowing Rust functions to accept optional arguments that R callers can omit.
665pub(crate) fn is_missing_type(ty: &syn::Type) -> bool {
666    match ty {
667        syn::Type::Path(tp) => tp
668            .path
669            .segments
670            .last()
671            .map(|s| s.ident == "Missing")
672            .unwrap_or(false),
673        _ => false,
674    }
675}
676
677// endregion
678
679// region: DotCallBuilder - .Call() invocation formatting
680
681/// Which frame a generated wrapper hands to `.Call(.., .call = ..)` and uses as
682/// the raise fallback (`.miniextendr_raise_condition(.val, <default>)`).
683#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CallAttribution {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                CallAttribution::Wrapper => "Wrapper",
                CallAttribution::Caller => "Caller",
                CallAttribution::Argument => "Argument",
                CallAttribution::NoCall => "NoCall",
                CallAttribution::Generic => "Generic",
                CallAttribution::GenericEager => "GenericEager",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for CallAttribution { }
#[automatically_derived]
impl ::core::clone::Clone for CallAttribution {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for CallAttribution { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for CallAttribution { }
#[automatically_derived]
impl ::core::cmp::PartialEq for CallAttribution {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CallAttribution { }Eq, #[automatically_derived]
impl ::core::default::Default for CallAttribution {
    #[inline]
    fn default() -> Self { Self::Wrapper }
}Default)]
684pub enum CallAttribution {
685    /// `.call = sys.call()`: the wrapper's own call, as written (default).
686    #[default]
687    Wrapper,
688    /// `.call = .mx_call`, where the wrapper body first binds
689    /// `.mx_call <- .miniextendr_caller_call()`: the caller's call as written,
690    /// resolved by the preamble helper in `miniextendr-api/src/registry.rs`
691    /// (one line per wrapper since #1552, four before). The helper looks two
692    /// frames up the calling chain for the wrapper's caller. It falls back to
693    /// the wrapper's own call when there is no parent frame (top level) or the
694    /// parent frame is not a closure: `eval()`'d code such as a testthat block
695    /// or `source()` has the `eval` primitive as its frame function, whose call
696    /// names `eval`. For a `noexport` entry point behind a hand-written R
697    /// function (`#[miniextendr(noexport, call = caller)]`).
698    ///
699    /// A standalone wrapper with this attribution also takes a trailing
700    /// `.call = NULL` formal ([`CallAttribution::formal`], #1613), which the
701    /// prelude hands to the helper: a hand-written helper sitting between the
702    /// public function and the entry point passes its own caller's frame
703    /// (`call = parent.frame()` in the helper's formals, `.call = call` in its
704    /// body), and the condition names the public function. `.call` also takes a
705    /// call object, used as is. S3 methods keep the zero-argument prelude: a
706    /// trailing formal would break generic/method consistency.
707    Caller,
708    /// `.call = .mx_call`, where the wrapper body first binds
709    /// `.mx_call <- if (is.null(.call)) sys.call() else
710    /// .miniextendr_caller_call(.call, own = TRUE)`: the wrapper's own call,
711    /// as for [`CallAttribution::Wrapper`], unless the caller passes another
712    /// as the trailing `.call = NULL` formal (#1834). Selected by
713    /// `#[miniextendr(call_arg)]` on a standalone function, exported or not:
714    /// an R function composing exported functions (applying them through
715    /// `do.call()`, say) passes `.call = environment()` and every condition
716    /// names its own call. The helper resolves a frame or a call as under
717    /// [`CallAttribution::Caller`]; a frame no closure owns gives the
718    /// wrapper's own call. The success path without `.call` costs one
719    /// `is.null()`. Never parsed from `call = ...`: it is `wrapper`
720    /// attribution plus the formal, so a `Call` marker reads the resolved
721    /// call.
722    Argument,
723    /// `.call = FALSE`: no call at all (#1851). Selected by
724    /// `#[miniextendr(call = none)]` on a standalone function (an S3 method
725    /// included) or by the crate default `call_attribution = "none"`, which
726    /// applies to every standalone function. Every condition the wrapper
727    /// raises then has a `NULL` `conditionCall()`, like R's `call. = FALSE`:
728    /// the R-side checks and the `match_arg` helpers are passed `NULL`, the
729    /// slot carries `FALSE`, the marker the raise helper already reads as "no
730    /// call" (what `call = none` on a condition macro writes, see
731    /// `ConditionCall::None`), and the raise fallback is `NULL`. There is no
732    /// marker spelling: a `Call` / `CallerCall` parameter receives the call,
733    /// and this attribution has none, so either one is a compile error with
734    /// it, as is `call_arg` (whose `.call` would have nothing to replace).
735    NoCall,
736    /// `.call = environment()`: the wrapper's own frame, turned into a call
737    /// only when a condition is raised (#1851). The attribution of every
738    /// generated S3 method that would otherwise be `wrapper`: a standalone
739    /// `s3(...)` function, an impl-block or trait `s3` / `vctrs` method
740    /// ([`CallAttribution::for_s3_method`]). The raise helper and
741    /// `.miniextendr_arg_error` resolve the frame with
742    /// `.miniextendr_frame_call` (`miniextendr-api/src/registry.rs`), which
743    /// names the generic the user called when R dispatched the method
744    /// (`.Generic` is bound in the frame: `summary(x, a = 1)` for
745    /// `summary.cls(x, a = 1)`, `x[i]` for `` `[.cls`(x, i) ``) and writes a
746    /// replacement generic's call as the assignment, `x$name <- value`,
747    /// without the value R passed. A direct call of the method keeps the
748    /// method's name. The success path evaluates `environment()` instead of
749    /// `sys.call()`, the same cost; the rewrite runs only on a raise.
750    Generic,
751    /// `.call = .mx_call`, where the wrapper body first binds
752    /// `.mx_call <- .miniextendr_frame_call(environment())`: the
753    /// [`CallAttribution::Generic`] call resolved before the `.Call()`, for an
754    /// S3 method with a `Call` parameter, whose C wrapper binds it from the
755    /// slot and so needs a call object there (#1851). Costs the frame scan on
756    /// every call, so only the marker selects it.
757    GenericEager,
758}
759
760impl CallAttribution {
761    /// The spelling shared by the attribute (`call = wrapper | caller | none`)
762    /// and the crate default (`call_attribution = "wrapper" | "caller" |
763    /// "none"`).
764    pub fn parse_name(name: &str) -> Option<Self> {
765        match name {
766            "wrapper" => Some(CallAttribution::Wrapper),
767            "caller" => Some(CallAttribution::Caller),
768            "none" => Some(CallAttribution::NoCall),
769            _ => None,
770        }
771    }
772
773    /// The attribute spelling of this attribution. [`CallAttribution::Argument`]
774    /// is `wrapper` attribution with the `call_arg` option, and the two S3
775    /// forms are `wrapper` attribution on a dispatched method, so all three
776    /// share `wrapper`'s spelling.
777    pub fn name(self) -> &'static str {
778        match self {
779            CallAttribution::Wrapper
780            | CallAttribution::Argument
781            | CallAttribution::Generic
782            | CallAttribution::GenericEager => "wrapper",
783            CallAttribution::Caller => "caller",
784            CallAttribution::NoCall => "none",
785        }
786    }
787
788    /// The parameter marker type that selects this attribution (#1566): `Call`
789    /// for `wrapper`, `CallerCall` for `caller`. `none` has no marker: a
790    /// marker receives the call, and `none` passes none.
791    pub fn marker_name(self) -> Option<&'static str> {
792        match self {
793            CallAttribution::Wrapper
794            | CallAttribution::Argument
795            | CallAttribution::Generic
796            | CallAttribution::GenericEager => Some("Call"),
797            CallAttribution::Caller => Some("CallerCall"),
798            CallAttribution::NoCall => None,
799        }
800    }
801
802    /// The attribution of a generated S3 method (#1851): `wrapper` becomes
803    /// [`CallAttribution::Generic`], or [`CallAttribution::GenericEager`] when
804    /// the method takes a `Call` parameter (`has_call_marker`), which needs
805    /// the call resolved before the `.Call()`. `caller` and `none` are
806    /// explicit choices and stay; `Argument` never reaches an S3 method
807    /// (`call_arg` is refused on them).
808    pub fn for_s3_method(self, has_call_marker: bool) -> Self {
809        match self {
810            CallAttribution::Wrapper if has_call_marker => CallAttribution::GenericEager,
811            CallAttribution::Wrapper => CallAttribution::Generic,
812            other => other,
813        }
814    }
815
816    /// Resolve a standalone function's attribution from its three spellings
817    /// (#1566), most specific first: the `Call` / `CallerCall` parameter
818    /// marker, the `call = wrapper | caller | none` attribute, the crate's
819    /// `[package.metadata.miniextendr] call_attribution` default and finally
820    /// the framework default, `wrapper`. A crate default of `caller` applies
821    /// to internal entry points (`noexport` / `internal`) only; an exported
822    /// function's caller is arbitrary user code, so it keeps `wrapper`. A
823    /// crate default of `none` applies to every standalone function, like
824    /// `wrapper` (#1851). The explicit spellings are validated before this
825    /// runs (a `caller` marker or attribute on an exported function is a
826    /// compile error, not a fallback). Class and trait methods always use
827    /// `wrapper`, except the R6 / S7 lambda frames
828    /// ([`DotCallBuilder::null_call_attribution`]) and the S3 methods
829    /// ([`CallAttribution::for_s3_method`]).
830    pub fn resolve(
831        marker: Option<Self>,
832        attribute: Option<Self>,
833        crate_default: Option<Self>,
834        internal_entry: bool,
835    ) -> Self {
836        marker.or(attribute).unwrap_or(match crate_default {
837            Some(CallAttribution::Caller) if !internal_entry => CallAttribution::Wrapper,
838            Some(default) => default,
839            None => CallAttribution::Wrapper,
840        })
841    }
842
843    /// Apply the `call_arg` option (#1834) to a resolved attribution:
844    /// `wrapper` becomes [`CallAttribution::Argument`]. The option is
845    /// validated before this runs: it is a compile error with `call = caller`
846    /// or a `CallerCall` parameter (whose wrapper already takes `.call`), on
847    /// an S3 method and on an `extern "C-unwind"` function, and the caller
848    /// leaves the crate default out of [`CallAttribution::resolve`] when it
849    /// is set, so `caller` never reaches here with it.
850    pub fn with_call_arg(self, call_arg: bool) -> Self {
851        match self {
852            CallAttribution::Wrapper if call_arg => CallAttribution::Argument,
853            other => other,
854        }
855    }
856
857    /// The expression the `.Call()` line passes as `.call`: the call as
858    /// written, the frame it is read from on a raise, or `FALSE` for none.
859    pub fn dot_call_expr(self) -> &'static str {
860        match self {
861            CallAttribution::Wrapper => "sys.call()",
862            CallAttribution::Caller | CallAttribution::Argument | CallAttribution::GenericEager => {
863                ".mx_call"
864            }
865            CallAttribution::NoCall => "FALSE",
866            CallAttribution::Generic => "environment()",
867        }
868    }
869
870    /// The `.call = ...` argument for the `.Call()` line.
871    pub fn dot_call_arg(self) -> &'static str {
872        match self {
873            CallAttribution::Wrapper => ".call = sys.call()",
874            CallAttribution::Caller | CallAttribution::Argument | CallAttribution::GenericEager => {
875                ".call = .mx_call"
876            }
877            CallAttribution::NoCall => ".call = FALSE",
878            CallAttribution::Generic => ".call = environment()",
879        }
880    }
881
882    /// The fallback call handed to `.miniextendr_raise_condition`: the same
883    /// value as the slot, except that `none` passes `NULL` (the helper reads
884    /// `FALSE` in the slot as "no call" and would read it here too; `NULL` is
885    /// the plainer spelling).
886    pub fn raise_default(self) -> &'static str {
887        match self {
888            CallAttribution::Caller | CallAttribution::Argument | CallAttribution::GenericEager => {
889                ".mx_call"
890            }
891            CallAttribution::Wrapper => "sys.call()",
892            CallAttribution::NoCall => "NULL",
893            CallAttribution::Generic => "environment()",
894        }
895    }
896
897    /// The statement the wrapper body needs before anything else: empty for
898    /// [`CallAttribution::Wrapper`], [`CallAttribution::NoCall`] and
899    /// [`CallAttribution::Generic`]; the others bind `.mx_call`. It is the
900    /// first part of the wrapper prelude, ahead of the R-side checks
901    /// (preconditions, `match.arg`), so that those checks can attribute their
902    /// failures to the caller too (#1548). `call_formal` says whether the
903    /// wrapper has the `.call` formal ([`CallAttribution::formal`]); the
904    /// helper then resolves whatever the caller passed there (#1613). Under
905    /// [`CallAttribution::Argument`] a `NULL` `.call` is the wrapper's own
906    /// call, read inline so that a call without `.call` costs one `is.null()`
907    /// (#1834); the wrapper always has the formal there, since the option is
908    /// refused on S3 methods. [`CallAttribution::GenericEager`] resolves the
909    /// method frame's call up front (#1851).
910    pub fn prelude(self, call_formal: bool) -> String {
911        match self {
912            CallAttribution::Caller if call_formal => {
913                ".mx_call <- .miniextendr_caller_call(.call)".to_string()
914            }
915            CallAttribution::Caller => ".mx_call <- .miniextendr_caller_call()".to_string(),
916            CallAttribution::Argument => ".mx_call <- if (is.null(.call)) sys.call() else \
917                 .miniextendr_caller_call(.call, own = TRUE)"
918                .to_string(),
919            CallAttribution::GenericEager => {
920                ".mx_call <- .miniextendr_frame_call(environment())".to_string()
921            }
922            CallAttribution::Wrapper | CallAttribution::NoCall | CallAttribution::Generic => {
923                String::new()
924            }
925        }
926    }
927
928    /// The trailing formal a standalone wrapper with this attribution takes:
929    /// `.call = NULL` for [`CallAttribution::Caller`] (#1613), so a
930    /// hand-written helper between the public function and the entry point
931    /// can pass on the call to report, and for [`CallAttribution::Argument`]
932    /// (#1834), so an R function composing exported functions can. `NULL`
933    /// keeps the wrapper's default (the caller's call, or its own); an
934    /// environment names the closure owning that frame (`parent.frame()` from
935    /// a helper, `environment()` from the composing function); a call object
936    /// is used as is. Rust identifiers cannot start with `.`, so the name
937    /// never collides with a parameter. The caller leaves S3 methods out.
938    pub fn formal(self) -> Option<&'static str> {
939        match self {
940            CallAttribution::Caller | CallAttribution::Argument => Some(".call = NULL"),
941            CallAttribution::Wrapper
942            | CallAttribution::NoCall
943            | CallAttribution::Generic
944            | CallAttribution::GenericEager => None,
945        }
946    }
947
948    /// The generated `@param` text for the [`CallAttribution::formal`]. It is
949    /// written as the author's own line would be, not as a filler, so a block
950    /// on a page or an inheritance source no author block documents `.call`
951    /// for still documents it (see `crate::roxygen::push_fn_param_tags`).
952    pub fn param_doc(self) -> Option<&'static str> {
953        match self {
954            CallAttribution::Caller => Some(
955                "The call conditions from this function report: NULL (the default) for \
956                 the calling function's call, a frame such as parent.frame() for the call \
957                 of the function owning that frame, or a call object. Pass it by name.",
958            ),
959            CallAttribution::Argument => Some(
960                "The call conditions from this function report: NULL (the default) for \
961                 this call, a frame such as environment() for the call of the function \
962                 owning that frame, or a call object. Pass it by name.",
963            ),
964            CallAttribution::Wrapper
965            | CallAttribution::NoCall
966            | CallAttribution::Generic
967            | CallAttribution::GenericEager => None,
968        }
969    }
970
971    /// The call an R-side check raised in the wrapper body should carry:
972    /// `.mx_call` when the prelude binds it, `NULL` for `none`, the frame
973    /// (`environment()`, which `.miniextendr_arg_error` resolves) for an S3
974    /// method, otherwise `None` (the check keeps its own attribution, which
975    /// is the wrapper's frame).
976    pub fn r_check_call(self) -> Option<&'static str> {
977        match self {
978            CallAttribution::Caller | CallAttribution::Argument | CallAttribution::GenericEager => {
979                Some(".mx_call")
980            }
981            CallAttribution::NoCall => Some("NULL"),
982            CallAttribution::Generic => Some("environment()"),
983            CallAttribution::Wrapper => None,
984        }
985    }
986
987    /// The R statement validating a choice parameter (`match_arg` / `choices`).
988    /// `choices` is the R expression for the choice list: a literal
989    /// `c("a", "b")`, or the write-time placeholder for an enum. `attrs` are
990    /// the parameter's own attributes, which carry `several_ok` and the
991    /// type's layers.
992    ///
993    /// Every form is one call to a preamble helper (`.miniextendr_match_arg`
994    /// for a scalar, `.miniextendr_match_arg_several` for `several_ok`,
995    /// #1472), guarded by the layers of the parameter type: `!missing(..)`
996    /// for `Missing<..>` (#1551), with `!identical(.., <choices>)` for a
997    /// single choice, whose formal reads as omitted (#2152; the `.Call()`
998    /// argument forwards the sentinel for it, see
999    /// [`RArgumentBuilder::with_absent_formals`]), then `!is.null(..)` for `Option<..>`
1000    /// (#1473), or `is.character(..) || is.factor(..)` for `Either<T, R>` and
1001    /// `Either<Vec<T>, R>` (#1612), which leaves `NULL` out too: Rust reads it
1002    /// as `None` under `Option`, and otherwise converts it to the `R` arm, even
1003    /// for `several_ok`, whose plain form reads `NULL` as every choice. The
1004    /// helpers name the argument in their messages, read a factor as its
1005    /// labels, and attribute the error to the wrapper's own call by default;
1006    /// under [`CallAttribution::Caller`] the statement passes `.mx_call` so the
1007    /// caller is named instead (#1548), and under
1008    /// [`CallAttribution::Argument`] so that the call passed as `.call` is.
1009    /// The list is spelled out because the helpers, unlike
1010    /// `base::match.arg(param)`, do not read it off the formal.
1011    ///
1012    /// `aliases` is the `match_arg` parameter's write-time
1013    /// `match_arg_keys::aliases_placeholder`, written last as `, <placeholder>`:
1014    /// the wrapper writer turns it into the type's `aliases =` argument, or
1015    /// removes it for a type without aliases (#1843). `None` for a literal
1016    /// `choices(...)` list, which has no aliases.
1017    pub fn match_arg_statement(
1018        self,
1019        param: &str,
1020        choices: &str,
1021        aliases: Option<&str>,
1022        attrs: &crate::miniextendr_fn::ParamAttrs,
1023    ) -> String {
1024        let helper = if attrs.several_ok {
1025            ".miniextendr_match_arg_several"
1026        } else {
1027            ".miniextendr_match_arg"
1028        };
1029        let call = match self.r_check_call() {
1030            Some(call) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", {0}", call))
    })format!(", {call}"),
1031            None => String::new(),
1032        };
1033        let aliases = match aliases {
1034            Some(placeholder) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", {0}", placeholder))
    })format!(", {placeholder}"),
1035            None => String::new(),
1036        };
1037        let statement =
1038            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} <- {1}({0}, {2}, \"{0}\"{3}{4})",
                param, helper, choices, call, aliases))
    })format!("{param} <- {helper}({param}, {choices}, \"{param}\"{call}{aliases})");
1039        let mut guards = Vec::new();
1040        if attrs.omittable {
1041            guards.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("!missing({0})", param))
    })format!("!missing({param})"));
1042            // The formal itself reads as omitted too (#2152): the `.Call()`
1043            // argument forwards the sentinel for it.
1044            if let Some(formal) = attrs.absent_formal(choices) {
1045                guards.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("!identical({0}, {1})", param,
                formal))
    })format!("!identical({param}, {formal})"));
1046            }
1047        }
1048        if attrs.either_noun.is_some() {
1049            // Only the forms `match.arg()` reads are choices; everything else
1050            // (NULL included) goes to the `Either`'s `R` arm unchanged.
1051            let reads = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("is.character({0}) || is.factor({0})",
                param))
    })format!("is.character({param}) || is.factor({param})");
1052            guards.push(if guards.is_empty() {
1053                reads
1054            } else {
1055                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("({0})", reads))
    })format!("({reads})")
1056            });
1057        } else if attrs.optional {
1058            guards.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("!is.null({0})", param))
    })format!("!is.null({param})"));
1059        }
1060        if guards.is_empty() {
1061            statement
1062        } else {
1063            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if ({0}) {1}", guards.join(" && "),
                statement))
    })format!("if ({}) {statement}", guards.join(" && "))
1064        }
1065    }
1066}
1067
1068/// Append a standalone wrapper's [`CallAttribution::formal`] to its joined
1069/// `formals` (#1613). The formal goes last: after `...` and after any formal
1070/// that follows the dots, so positional extras land in the dots and `.call`
1071/// is matched by name only; a wrapper without other formals takes it alone.
1072pub(crate) fn with_call_formal(formals: &str, call_formal: Option<&str>) -> String {
1073    match call_formal {
1074        Some(formal) if formals.is_empty() => formal.to_string(),
1075        Some(formal) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}, {1}", formals, formal))
    })format!("{formals}, {formal}"),
1076        None => formals.to_string(),
1077    }
1078}
1079
1080/// Builder for formatting `.Call()` invocations in R wrapper code.
1081///
1082/// Handles the common pattern of `.Call(C_ident, .call = sys.call(), args...)`.
1083///
1084/// # Example
1085///
1086/// ```ignore
1087/// let call = DotCallBuilder::new("C_Counter__increment")
1088///     .with_self("self")
1089///     .build();
1090/// // => ".Call(C_Counter__increment, .call = sys.call(), self)"
1091///
1092/// let call = DotCallBuilder::new("C_Counter__add")
1093///     .with_self("x")
1094///     .with_args(&["n"])
1095///     .build();
1096/// // => ".Call(C_Counter__add, .call = sys.call(), x, n)"
1097/// ```
1098pub struct DotCallBuilder {
1099    /// The C entry point symbol name (e.g., `"C_Counter__increment"`).
1100    /// This is the first argument to `.Call()`.
1101    c_ident: String,
1102    /// Optional self/receiver variable name (e.g., `"self"`, `"x"`).
1103    /// When present, prepended before other arguments in the `.Call()` invocation.
1104    self_var: Option<String>,
1105    /// Additional argument names passed after self (if any) in the `.Call()` invocation.
1106    args: Vec<String>,
1107    /// Expression for the `.call` named argument. `None` means `sys.call()` (the default).
1108    /// Set via [`DotCallBuilder::null_call_attribution`] to emit `.call = NULL` instead.
1109    call_expr: Option<String>,
1110}
1111
1112impl DotCallBuilder {
1113    /// Create a new builder with the C function identifier.
1114    pub fn new(c_ident: impl Into<String>) -> Self {
1115        Self {
1116            c_ident: c_ident.into(),
1117            self_var: None,
1118            args: Vec::new(),
1119            call_expr: None,
1120        }
1121    }
1122
1123    /// Add a self/x parameter (prepended to args).
1124    pub fn with_self(mut self, var: impl Into<String>) -> Self {
1125        self.self_var = Some(var.into());
1126        self
1127    }
1128
1129    /// Add arguments after self (if any).
1130    pub fn with_args(mut self, args: &[impl AsRef<str>]) -> Self {
1131        self.args = args.iter().map(|s| s.as_ref().to_string()).collect();
1132        self
1133    }
1134
1135    /// Add a pre-joined argument string (e.g., `"x, y"`) as a single emit unit.
1136    ///
1137    /// Empty strings are ignored, so callers can pass the result of
1138    /// `build_r_call_args_from_sig` directly without a length check.
1139    pub fn with_args_str(mut self, args: &str) -> Self {
1140        if !args.is_empty() {
1141            self.args.push(args.to_string());
1142        }
1143        self
1144    }
1145
1146    /// Pass `.call = NULL` instead of `.call = sys.call()`: for the R6 / S7
1147    /// lambda frames, which R6 / S7 dispatch calls (R6 finalizer / `deep_clone`, S7
1148    /// property getter / setter / validator), where `sys.call()` names an
1149    /// internal dispatch frame instead of the user's call. Not reachable from
1150    /// any attribute. With `NULL`, the raise helper's fallback (a `NULL`
1151    /// `.val$call` takes `.call_default`, the `sys.call()` in
1152    /// `condition_check_lines`; only a call-less condition's `FALSE` marker
1153    /// gives `NULL`) surfaces the nearest meaningful frame instead.
1154    pub fn null_call_attribution(mut self) -> Self {
1155        self.call_expr = Some("NULL".to_string());
1156        self
1157    }
1158
1159    /// Pass the `.call` of `attribution` ([`CallAttribution::dot_call_expr`]):
1160    /// `environment()` for a generated S3 method
1161    /// ([`CallAttribution::Generic`], #1851), which the raise helper turns into
1162    /// the generic's call when a condition is raised.
1163    pub fn with_call_attribution(mut self, attribution: CallAttribution) -> Self {
1164        self.call_expr = Some(attribution.dot_call_expr().to_string());
1165        self
1166    }
1167
1168    /// Build the `.Call()` string.
1169    pub fn build(&self) -> String {
1170        let call_arg = self.call_expr.as_deref().unwrap_or("sys.call()");
1171
1172        let mut all_args = Vec::new();
1173
1174        if let Some(ref self_var) = self.self_var {
1175            all_args.push(self_var.clone());
1176        }
1177        all_args.extend(self.args.clone());
1178
1179        if all_args.is_empty() {
1180            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, .call = {1})",
                self.c_ident, call_arg))
    })format!(".Call({}, .call = {})", self.c_ident, call_arg)
1181        } else {
1182            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, .call = {1}, {2})",
                self.c_ident, call_arg, all_args.join(", ")))
    })format!(
1183                ".Call({}, .call = {}, {})",
1184                self.c_ident,
1185                call_arg,
1186                all_args.join(", ")
1187            )
1188        }
1189    }
1190}
1191// endregion
1192
1193// region: RoxygenBuilder - roxygen2 documentation tag generation
1194
1195/// Builder for generating roxygen2 documentation tags.
1196///
1197/// Provides a fluent API for building common roxygen tag patterns used
1198/// across all class systems.
1199///
1200/// # Example
1201///
1202/// ```ignore
1203/// let tags = RoxygenBuilder::new()
1204///     .name("Counter$increment")
1205///     .rdname("Counter")
1206///     .export()
1207///     .build();
1208/// // => vec!["#' @name Counter$increment", "#' @rdname Counter", "#' @export"]
1209/// ```
1210pub struct RoxygenBuilder {
1211    /// Value for `@name` tag. Identifies the documented topic (e.g., `"Counter$increment"`).
1212    name: Option<String>,
1213    /// Value for `@rdname` tag. Groups multiple entries onto a single help page
1214    /// (e.g., all methods of `"Counter"` share one Rd file).
1215    rdname: Option<String>,
1216    /// Value for `@title` tag. The one-line title shown in help page headers.
1217    title: Option<String>,
1218    /// Value for `@description` tag. Longer description text below the title.
1219    description: Option<String>,
1220    /// Value for `@source` tag. Typically `"Generated by miniextendr"` provenance info.
1221    source: Option<String>,
1222    /// Whether to emit `@export`. When true, the item is exported from the package NAMESPACE.
1223    export: bool,
1224    /// Value for `@exportMethod` tag. Used for S4 method exports (e.g., `"show"`).
1225    export_method: Option<String>,
1226    /// Values for `@method` tag as `(generic, class)`. Used for S3 method dispatch
1227    /// (e.g., `("print", "Counter")` emits `@method print Counter`).
1228    method: Option<(String, String)>,
1229    /// Additional custom tag lines emitted verbatim (without the `#' ` prefix,
1230    /// which is added during [`build`](Self::build)). Used for tags like
1231    /// `@keywords internal` or `@param` entries.
1232    custom_tags: Vec<String>,
1233}
1234
1235impl RoxygenBuilder {
1236    /// Create a new empty builder.
1237    pub fn new() -> Self {
1238        Self {
1239            name: None,
1240            rdname: None,
1241            title: None,
1242            description: None,
1243            source: None,
1244            export: false,
1245            export_method: None,
1246            method: None,
1247            custom_tags: Vec::new(),
1248        }
1249    }
1250
1251    /// Set the `@name` tag.
1252    pub fn name(mut self, name: impl Into<String>) -> Self {
1253        self.name = Some(name.into());
1254        self
1255    }
1256
1257    /// Set the `@rdname` tag (groups docs into one page).
1258    pub fn rdname(mut self, rdname: impl Into<String>) -> Self {
1259        self.rdname = Some(rdname.into());
1260        self
1261    }
1262
1263    /// Set the `@title` tag.
1264    pub fn title(mut self, title: impl Into<String>) -> Self {
1265        self.title = Some(title.into());
1266        self
1267    }
1268
1269    /// Set the `@description` tag.
1270    #[allow(dead_code)] // Exercised by tests
1271    pub fn description(mut self, desc: impl Into<String>) -> Self {
1272        self.description = Some(desc.into());
1273        self
1274    }
1275
1276    /// Set the `@source` tag (typically "Generated by miniextendr...").
1277    pub fn source(mut self, source: impl Into<String>) -> Self {
1278        self.source = Some(source.into());
1279        self
1280    }
1281
1282    /// Add `@export` tag.
1283    pub fn export(mut self) -> Self {
1284        self.export = true;
1285        self
1286    }
1287
1288    /// Add `@exportMethod` tag (for S4).
1289    #[allow(dead_code)] // Exercised by tests
1290    pub fn export_method(mut self, method: impl Into<String>) -> Self {
1291        self.export_method = Some(method.into());
1292        self
1293    }
1294
1295    /// Add `@method` tag (for S3).
1296    pub fn method(mut self, generic: impl Into<String>, class: impl Into<String>) -> Self {
1297        self.method = Some((generic.into(), class.into()));
1298        self
1299    }
1300
1301    /// Add a custom tag line (without the `#' ` prefix).
1302    pub fn custom(mut self, tag: impl Into<String>) -> Self {
1303        self.custom_tags.push(tag.into());
1304        self
1305    }
1306
1307    /// Build the roxygen tag lines (each prefixed with `#' `).
1308    pub fn build(&self) -> Vec<String> {
1309        let mut lines = Vec::new();
1310
1311        if let Some(ref title) = self.title {
1312            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @title {0}", title))
    })format!("#' @title {}", title));
1313        }
1314        if let Some(ref desc) = self.description {
1315            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @description {0}", desc))
    })format!("#' @description {}", desc));
1316        }
1317        if let Some(ref name) = self.name {
1318            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @name {0}", name))
    })format!("#' @name {}", name));
1319        }
1320        if let Some(ref rdname) = self.rdname {
1321            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @rdname {0}", rdname))
    })format!("#' @rdname {}", rdname));
1322        }
1323        if let Some(ref source) = self.source {
1324            lines.extend(crate::roxygen::source_tag(source));
1325        }
1326        if let Some((ref generic, ref class)) = self.method {
1327            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @method {0} {1}", generic,
                class))
    })format!("#' @method {} {}", generic, class));
1328        }
1329        // A tag the author wrapped (`@param`, `@describeIn`, ...) keeps its
1330        // continuation lines, each with its own `#' ` lead.
1331        crate::roxygen::push_roxygen_tags(&mut lines, &self.custom_tags);
1332        if self.export {
1333            lines.push("#' @export".to_string());
1334        }
1335        if let Some(ref method) = self.export_method {
1336            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @exportMethod {0}", method))
    })format!("#' @exportMethod {}", method));
1337        }
1338
1339        lines
1340    }
1341}
1342
1343/// Creates an empty builder with no tags set.
1344impl Default for RoxygenBuilder {
1345    fn default() -> Self {
1346        Self::new()
1347    }
1348}
1349// endregion
1350
1351// region: RngSeedContract - the stats::simulate() seed contract
1352
1353/// The seed contract of `stats::simulate()`, which
1354/// `#[miniextendr(rng(seed = <param>))]` writes into a standalone wrapper in
1355/// the order `stats:::simulate.lm` uses (#2198).
1356///
1357/// The [`prelude`](Self::prelude) comes after the argument checks, so a
1358/// refused argument changes no RNG state. It creates `.Random.seed` when it is
1359/// missing. With a `NULL` seed it then remembers the state; otherwise it
1360/// registers the restore of the session's state with `on.exit()` and calls
1361/// `set.seed()` with the value as passed, which validates it. The C wrapper's
1362/// `GetRNGstate()` then loads the seeded state, and its `PutRNGstate()` saves
1363/// the state inside `.Call()`, before `on.exit()` puts the session's state
1364/// back, also when the body returns an error or panics.
1365/// [`ATTRIBUTE`](Self::ATTRIBUTE) records the seed in the result, after the
1366/// result's condition check.
1367pub(crate) struct RngSeedContract<'a> {
1368    /// The seed's R formal.
1369    pub formal: &'a str,
1370}
1371
1372impl RngSeedContract<'_> {
1373    /// The statements before `.Call()`, each line after the first prefixed by
1374    /// `indent`.
1375    pub(crate) fn prelude(&self, indent: &str) -> String {
1376        let seed = self.formal;
1377        let lines = [
1378            "if (!exists(\".Random.seed\", envir = globalenv(), inherits = FALSE)) stats::runif(1)"
1379                .to_owned(),
1380            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (is.null({0})) {{", seed))
    })format!("if (is.null({seed})) {{"),
1381            "  .mx_rng_state <- get(\".Random.seed\", envir = globalenv())".to_owned(),
1382            "} else {".to_owned(),
1383            "  .mx_r_seed <- get(\".Random.seed\", envir = globalenv())".to_owned(),
1384            "  on.exit(assign(\".Random.seed\", .mx_r_seed, envir = globalenv()), add = TRUE)"
1385                .to_owned(),
1386            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("  set.seed({0})", seed))
    })format!("  set.seed({seed})"),
1387            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("  .mx_rng_state <- structure({0}, kind = as.list(RNGkind()))",
                seed))
    })format!("  .mx_rng_state <- structure({seed}, kind = as.list(RNGkind()))"),
1388            "}".to_owned(),
1389        ];
1390        lines.join(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n{0}", indent))
    })format!("\n{indent}"))
1391    }
1392
1393    /// The statement that gives the result `.val` its `seed` attribute. A
1394    /// value that can't carry attributes (`NULL`, an environment, an external
1395    /// pointer, a function) is left as it is; `NULL` is tested on its own,
1396    /// since `is.atomic(NULL)` is `TRUE` before R 4.4.
1397    pub(crate) const ATTRIBUTE: &'static str = "if (!is.null(.val) && (is.atomic(.val) || \
1398         is.list(.val))) attr(.val, \"seed\") <- .mx_rng_state";
1399}
1400// endregion
1401
1402// region: Tests
1403
1404#[cfg(test)]
1405mod tests;
1406// endregion