Skip to main content

miniextendr_macros/
miniextendr_fn.rs

1//! Function signature parsing for `#[miniextendr]`.
2//!
3//! This module handles parsing and normalizing Rust function signatures for the
4//! `#[miniextendr]` attribute macro. It provides:
5//!
6//! - [`MiniextendrFunctionParsed`]: Parsed function with normalization and codegen helpers
7//! - [`MiniextendrFnAttrs`]: Parsed `#[miniextendr(...)]` attribute options
8//! - [`CoercionMapping`]: Type coercion analysis for automatic R→Rust conversion
9
10use crate::r_wrapper_const_ident_for;
11
12// region: Coercion analysis
13
14/// Conversion selected by `coerce`, shared by Rust conversion and R checks.
15pub(crate) enum CoercionMapping {
16    /// Numeric scalars and vectors already have checked, multi-source converters.
17    Numeric,
18    /// Keep logical scalars and additionally accept integer zero or one.
19    Bool,
20    /// Keep logical vectors and additionally accept integer zeros and ones.
21    BoolVec,
22    /// Native `i32`: keep `INTSXP`, additionally accept whole-valued doubles,
23    /// logicals, and raws.
24    NativeInt,
25    /// Native `Vec<i32>`: element-wise form of [`Self::NativeInt`].
26    NativeIntVec,
27    /// Native `f64`: keep `REALSXP`, additionally accept integers, logicals, and raws.
28    NativeReal,
29    /// Native `Vec<f64>`: element-wise form of [`Self::NativeReal`].
30    NativeRealVec,
31}
32
33impl CoercionMapping {
34    /// Recognize the scalar and vector types supported by `coerce`.
35    ///
36    /// Numeric conversion preserves all sources accepted by `TryFromSexp`:
37    /// integer, double, logical, and raw. Booleans extend their logical-only
38    /// converter with integer zero/one input. The native `i32` / `f64` scalars
39    /// and vectors, whose bare conversion accepts one `SEXPTYPE`, widen to the
40    /// same four sources (whole-valued doubles only for `i32`). Borrowed slices
41    /// and other types keep `TryFromSexp`.
42    pub(crate) fn from_type(ty: &syn::Type) -> Option<Self> {
43        let syn::Type::Path(type_path) = ty else {
44            return None;
45        };
46        let seg = type_path.path.segments.last()?;
47        let is_vec = seg.ident == "Vec";
48        let ident = if is_vec {
49            let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
50                return None;
51            };
52            let syn::GenericArgument::Type(syn::Type::Path(inner)) = args.args.first()? else {
53                return None;
54            };
55            &inner.path.segments.last()?.ident
56        } else {
57            &seg.ident
58        };
59        match ident.to_string().as_str() {
60            "i8" | "i16" | "u16" | "u32" | "i64" | "u64" | "isize" | "usize" | "f32" => {
61                Some(Self::Numeric)
62            }
63            "bool" if is_vec => Some(Self::BoolVec),
64            "bool" => Some(Self::Bool),
65            "i32" if is_vec => Some(Self::NativeIntVec),
66            "i32" => Some(Self::NativeInt),
67            "f64" if is_vec => Some(Self::NativeRealVec),
68            "f64" => Some(Self::NativeReal),
69            _ => None,
70        }
71    }
72}
73
74// endregion
75
76// region: Type inspection helpers
77
78/// Check if a type path ends with the given identifier (e.g., "Dots", "Missing").
79///
80/// Handles fully-qualified paths like `miniextendr_api::dots::Dots` as well as
81/// bare `Dots`.
82fn type_ends_with(ty: &syn::Type, name: &str) -> bool {
83    match ty {
84        syn::Type::Path(tp) => tp
85            .path
86            .segments
87            .last()
88            .map(|s| s.ident == name)
89            .unwrap_or(false),
90        syn::Type::Reference(r) => type_ends_with(&r.elem, name),
91        _ => false,
92    }
93}
94
95/// Which `...` parameter type a parameter takes.
96#[derive(#[automatically_derived]
impl ::core::fmt::Debug for DotsKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                DotsKind::Forced => "Forced",
                DotsKind::Lazy => "Lazy",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for DotsKind { }
#[automatically_derived]
impl ::core::clone::Clone for DotsKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for DotsKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for DotsKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for DotsKind {
    #[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 DotsKind { }Eq)]
97pub(crate) enum DotsKind {
98    /// `&Dots`: the wrapper passes `list(...)`, every element forced.
99    Forced,
100    /// `LazyDots`: the wrapper passes its own frame, `environment()`, and
101    /// nothing is forced (#1892).
102    Lazy,
103}
104
105impl DotsKind {
106    /// The parameter type as written, for diagnostics.
107    pub(crate) fn type_name(self) -> &'static str {
108        match self {
109            Self::Forced => "&Dots",
110            Self::Lazy => "LazyDots",
111        }
112    }
113
114    /// The `.Call()` argument the R wrapper passes for the dots.
115    pub(crate) fn r_call_arg(self) -> &'static str {
116        match self {
117            Self::Forced => "list(...)",
118            Self::Lazy => "environment()",
119        }
120    }
121}
122
123/// The `...` parameter type `ty` is, if any: `Dots` / `&Dots`
124/// ([`DotsKind::Forced`]), or `LazyDots` taken by value ([`DotsKind::Lazy`],
125/// [`crate::type_inspect::is_lazy_dots_marker`]). `&LazyDots` and other
126/// shapes are none; the callers refuse them by name.
127pub(crate) fn dots_kind(ty: &syn::Type) -> Option<DotsKind> {
128    if crate::type_inspect::is_lazy_dots_marker(ty) {
129        Some(DotsKind::Lazy)
130    } else if type_ends_with(ty, "Dots") {
131        Some(DotsKind::Forced)
132    } else {
133        None
134    }
135}
136
137/// Check if a type is a `...` parameter type: `Dots` / `&Dots` or `LazyDots`
138/// (see [`dots_kind`]).
139pub(crate) fn is_dots_type(ty: &syn::Type) -> bool {
140    dots_kind(ty).is_some()
141}
142
143/// Replace Rust variadic syntax (`name: ...` / `_: ...`) with a trailing
144/// `&miniextendr_api::dots::Dots` parameter, so downstream codegen never
145/// emits a non-extern variadic Rust fn. Named dots keep the user's
146/// identifier; `_: ...` binds `__miniextendr_dots`.
147///
148/// A bare `...` binds `__miniextendr_dots` too, but rustc rejects it before
149/// the macro runs (the deny-by-default `varargs_without_pattern` lint,
150/// rust-lang/rust#145544) unless the crate allows that lint, and its fix is
151/// `_: ...` (#1743).
152///
153/// syn parses the variadic only in last position, so a formal after the dots
154/// is spelled with an explicit `rest: &Dots` parameter instead. A signature
155/// with both is an error: a function takes at most one `...`.
156pub(crate) fn rewrite_variadic_dots(sig: &mut syn::Signature) -> syn::Result<()> {
157    use syn::spanned::Spanned;
158
159    let Some(variadic) = sig.variadic.take() else {
160        return Ok(());
161    };
162    if let Some(syn::FnArg::Typed(existing)) = dots_index(&sig.inputs).map(|idx| &sig.inputs[idx]) {
163        let kind = dots_kind(existing.ty.as_ref()).map_or("&Dots", DotsKind::type_name);
164        let existing = match existing.pat.as_ref() {
165            syn::Pat::Ident(pat_ident) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}: {1}`", pat_ident.ident,
                kind))
    })format!("`{}: {kind}`", pat_ident.ident),
166            _ => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}` parameter", kind))
    })format!("a `{kind}` parameter"),
167        };
168        return Err(syn::Error::new(
169            variadic.span(),
170            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this function already takes `...` as {0}; remove one of them",
                existing))
    })format!("this function already takes `...` as {existing}; remove one of them"),
171        ));
172    }
173    let ident = match variadic.pat.map(|(pat, _)| *pat) {
174        Some(syn::Pat::Ident(pat_ident)) => pat_ident.ident,
175        // `_: ...` (rustc's fix for a bare `...`) and a bare `...`.
176        Some(syn::Pat::Wild(_)) | None => {
177            // Cannot use `_` as a variable name, so unnamed dots need a
178            // stable synthetic binding that does not collide with user args.
179            for arg in &sig.inputs {
180                let syn::FnArg::Typed(pat_type) = arg else {
181                    continue;
182                };
183                if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
184                    && pat_ident.ident == "__miniextendr_dots"
185                {
186                    return Err(syn::Error::new(
187                        pat_ident.ident.span(),
188                        "parameter named `__miniextendr_dots` conflicts with the binding of \
189                         unnamed dots (`_: ...`); name the dots instead, e.g. `args: ...`",
190                    ));
191                }
192            }
193            syn::Ident::new("__miniextendr_dots", proc_macro2::Span::call_site())
194        }
195        Some(pat) => {
196            return Err(syn::Error::new(
197                pat.span(),
198                "variadic pattern must be a simple identifier (`args: ...`) or `_` (`_: ...`)",
199            ));
200        }
201    };
202    sig.inputs
203        .push(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::ToTokens::to_tokens(&ident, &mut _s);
        ::quote::__private::push_colon(&mut _s);
        ::quote::__private::push_and(&mut _s);
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "miniextendr_api");
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "dots");
        ::quote::__private::push_colon2(&mut _s);
        ::quote::__private::push_ident(&mut _s, "Dots");
        _s
    })syn::parse_quote!(#ident: &::miniextendr_api::dots::Dots));
204    Ok(())
205}
206
207/// Position of the first `...` parameter (`&Dots` or `LazyDots`) in `inputs`
208/// (receiver included, as
209/// [`RArgumentBuilder`](crate::r_wrapper_builder::RArgumentBuilder) counts
210/// them), without validation: for code that runs after [`find_dots_param`]
211/// has accepted the signature.
212pub(crate) fn dots_index(
213    inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
214) -> Option<usize> {
215    inputs
216        .iter()
217        .position(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::FnArg::Typed(pt) if is_dots_type(pt.ty.as_ref()) => true,
    _ => false,
}matches!(arg, syn::FnArg::Typed(pt) if is_dots_type(pt.ty.as_ref())))
218}
219
220/// Find the dots parameter of a signature by type, at any position, and
221/// return its Rust binding.
222///
223/// The parameter of type `&Dots` or `LazyDots` is R's `...` at its own
224/// position: the R formals and the `.Call()` arguments follow the Rust
225/// signature order, with `...` where it sits (`list(...)` or `environment()`
226/// in the call, [`DotsKind::r_call_arg`]), and every formal after it is
227/// matched by exact name only, as R does for any formal after `...`.
228///
229/// A function takes at most one `...`, so a second `&Dots` / `LazyDots`
230/// parameter is an error spanned on it. The parameter needs a plain name: the
231/// body reads the dots through it.
232pub(crate) fn find_dots_param(
233    inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
234) -> syn::Result<Option<syn::Ident>> {
235    use syn::spanned::Spanned;
236
237    let mut found: Option<(syn::Ident, DotsKind)> = None;
238    for arg in inputs {
239        let syn::FnArg::Typed(pat_type) = arg else {
240            continue;
241        };
242        let Some(kind) = dots_kind(pat_type.ty.as_ref()) else {
243            continue;
244        };
245        let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() else {
246            return Err(syn::Error::new(
247                pat_type.pat.span(),
248                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the `...` parameter needs a plain name, for example `rest: {0}`",
                kind.type_name()))
    })format!(
249                    "the `...` parameter needs a plain name, for example `rest: {}`",
250                    kind.type_name()
251                ),
252            ));
253        };
254        let ident = &pat_ident.ident;
255        if let Some((first, first_kind)) = &found {
256            let msg = if (*first_kind, kind) == (DotsKind::Forced, DotsKind::Forced) {
257                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a function takes at most one `...`: `{0}` and `{1}` both have type `&Dots`; keep one",
                first, ident))
    })format!(
258                    "a function takes at most one `...`: `{first}` and `{ident}` both have type \
259                     `&Dots`; keep one"
260                )
261            } else {
262                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a function takes at most one `...`: `{2}` (`{0}`) and `{3}` (`{1}`) are both R\'s `...`; keep one",
                first_kind.type_name(), kind.type_name(), first, ident))
    })format!(
263                    "a function takes at most one `...`: `{first}` (`{}`) and `{ident}` (`{}`) \
264                     are both R's `...`; keep one",
265                    first_kind.type_name(),
266                    kind.type_name()
267                )
268            };
269            return Err(syn::Error::new(ident.span(), msg));
270        }
271        found = Some((ident.clone(), kind));
272    }
273    Ok(found.map(|(ident, _)| ident))
274}
275
276/// Check if a type is `Missing<T>`.
277pub(crate) fn is_missing_type(ty: &syn::Type) -> bool {
278    type_ends_with(ty, "Missing")
279}
280
281/// Check if a type is a vector-like type that `several_ok` can populate.
282///
283/// Accepts `Vec<T>`, `Box<[T]>`, `&[T]` / `&mut [T]`, and `[T; N]`. Rejects
284/// scalar types (like `Mode`, `String`, `&str`) so `several_ok` — which
285/// produces a multi-element R character vector via
286/// `match.arg(..., several.ok = TRUE)` — fails at compile time instead of
287/// deserialization time.
288pub(crate) fn is_vector_like_type(ty: &syn::Type) -> bool {
289    match ty {
290        syn::Type::Path(tp) => {
291            let Some(seg) = tp.path.segments.last() else {
292                return false;
293            };
294            if seg.ident == "Vec" {
295                return true;
296            }
297            if seg.ident == "Box" {
298                let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
299                    return false;
300                };
301                return #[allow(non_exhaustive_omitted_patterns)] match args.args.first() {
    Some(syn::GenericArgument::Type(syn::Type::Slice(_))) => true,
    _ => false,
}matches!(
302                    args.args.first(),
303                    Some(syn::GenericArgument::Type(syn::Type::Slice(_)))
304                );
305            }
306            false
307        }
308        syn::Type::Reference(r) => #[allow(non_exhaustive_omitted_patterns)] match &*r.elem {
    syn::Type::Slice(_) => true,
    _ => false,
}matches!(&*r.elem, syn::Type::Slice(_)),
309        syn::Type::Slice(_) => true,
310        syn::Type::Array(_) => true,
311        _ => false,
312    }
313}
314
315/// Extract the inner type `T` from `Missing<T>`, if the type is `Missing<T>`.
316///
317/// Returns `None` if the type is not `Missing<T>` or has no generic argument.
318pub(crate) fn get_missing_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
319    let syn::Type::Path(tp) = ty else {
320        return None;
321    };
322    let seg = tp.path.segments.last()?;
323    if seg.ident != "Missing" {
324        return None;
325    }
326    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
327        return None;
328    };
329    if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
330        Some(inner)
331    } else {
332        None
333    }
334}
335
336/// Validate a parameter's type for `Missing` and `Dots` conflicts.
337///
338/// Returns `Err` if:
339/// - `Missing<Missing<T>>` (nested Missing)
340/// - `Missing<Dots>` or `Missing<&Dots>`
341pub(crate) fn validate_param_type(ty: &syn::Type, span: proc_macro2::Span) -> syn::Result<()> {
342    if crate::return_wrap::contains_marker(ty) {
343        return Err(syn::Error::new_spanned(
344            ty,
345            "class return markers are return-position only",
346        ));
347    }
348    if let Some(err) = crate::type_inspect::visibility_marker_error(ty, "argument") {
349        return Err(err);
350    }
351    if let Some(inner) = get_missing_inner_type(ty) {
352        if is_missing_type(inner) {
353            return Err(syn::Error::new(
354                span,
355                "Missing<T> cannot be nested; use Missing<T> with the inner type directly",
356            ));
357        }
358        if crate::type_inspect::is_lazy_dots_marker(inner) {
359            return Err(syn::Error::new(
360                span,
361                "`Missing<LazyDots>` is not a parameter type: the dots are always present \
362                 (`len()` is 0 when the call passes none); take `LazyDots`",
363            ));
364        }
365        if is_dots_type(inner) {
366            return Err(syn::Error::new(
367                span,
368                "Missing<T> cannot wrap Dots; variadic parameters (...) are always present when called",
369            ));
370        }
371    }
372    Ok(())
373}
374
375/// Validate per-parameter attribute conflicts.
376///
377/// Returns `Err` if:
378/// - `coerce` + `match_arg` on the same parameter
379/// - `coerce` + `choices(...)` on the same parameter
380/// - `choices(...)` + explicit `default` on the same parameter
381/// - `match_arg` + `choices(...)` on the same parameter (two sources for one
382///   choice list; the `match_arg` placeholder would shadow the literal list
383///   and, with no `MatchArg` entry to resolve it, dangle in the R formals)
384/// - `no_default` on a parameter that is neither `match_arg` nor `choices`,
385///   or together with `default` (#1828)
386/// - `default` or `formal` on a `&Dots` parameter
387///
388/// The type-dependent checks of a choice parameter live in
389/// [`classify_choice_param`], shared with the method paths.
390pub(crate) fn validate_per_param_attr_conflicts(
391    attr: &PerParamMiniextendrAttr,
392    param_name: &str,
393    is_dots: bool,
394    span: proc_macro2::Span,
395) -> syn::Result<()> {
396    if attr.has_no_default
397        && let Some(msg) = no_default_conflict(
398            param_name,
399            attr.has_match_arg || attr.choices.is_some(),
400            attr.default_value.is_some(),
401            false,
402        )
403    {
404        return Err(syn::Error::new(span, msg));
405    }
406    if attr.has_coerce && attr.has_match_arg {
407        return Err(syn::Error::new(
408            span,
409            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot combine coerce and match_arg on parameter `{0}`; coerce converts the R type while match_arg validates string values",
                param_name))
    })format!(
410                "cannot combine coerce and match_arg on parameter `{}`; \
411                 coerce converts the R type while match_arg validates string values",
412                param_name
413            ),
414        ));
415    }
416    if attr.has_coerce && attr.choices.is_some() {
417        return Err(syn::Error::new(
418            span,
419            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot combine coerce and choices on parameter `{0}`; coerce converts the R type while choices validates string values",
                param_name))
    })format!(
420                "cannot combine coerce and choices on parameter `{}`; \
421                 coerce converts the R type while choices validates string values",
422                param_name
423            ),
424        ));
425    }
426    if attr.choices.is_some() && attr.default_value.is_some() {
427        return Err(syn::Error::new(
428            span,
429            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot combine choices() and default on parameter `{0}`; choices auto-generates its default from the first choice value",
                param_name))
    })format!(
430                "cannot combine choices() and default on parameter `{}`; \
431                 choices auto-generates its default from the first choice value",
432                param_name
433            ),
434        ));
435    }
436    if attr.has_match_arg && attr.choices.is_some() {
437        return Err(syn::Error::new(
438            span,
439            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot combine match_arg and choices() on parameter `{0}`; match_arg takes the choice list from the parameter type\'s `MatchArg` impl, choices() supplies a literal list for a string parameter; use one of them",
                param_name))
    })format!(
440                "cannot combine match_arg and choices() on parameter `{}`; \
441                 match_arg takes the choice list from the parameter type's `MatchArg` impl, \
442                 choices() supplies a literal list for a string parameter; use one of them",
443                param_name
444            ),
445        ));
446    }
447    if attr.has_several_ok && attr.choices.is_none() && !attr.has_match_arg {
448        return Err(syn::Error::new(
449            span,
450            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("several_ok requires choices() or match_arg on parameter `{0}`; several_ok enables multi-value match.arg which needs a choice list",
                param_name))
    })format!(
451                "several_ok requires choices() or match_arg on parameter `{}`; \
452                 several_ok enables multi-value match.arg which needs a choice list",
453                param_name
454            ),
455        ));
456    }
457    if is_dots && !attr.checks.is_empty() {
458        return Err(syn::Error::new(
459            span,
460            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`inherits` / `not_inherits` / `no_na` cannot apply to the variadic (...) parameter `{0}`; use `dots = typed_list!(...)` to check the entries",
                param_name))
    })format!(
461                "`inherits` / `not_inherits` / `no_na` cannot apply to the variadic (...) \
462                 parameter `{}`; use `dots = typed_list!(...)` to check the entries",
463                param_name
464            ),
465        ));
466    }
467    for check in [ClassCheck::Inherits, ClassCheck::NotInherits] {
468        if let Some(classes) = check.classes(&attr.checks)
469            && (classes.is_empty() || classes.iter().any(String::is_empty))
470        {
471            let key = check.keyword();
472            return Err(syn::Error::new(
473                span,
474                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` on parameter `{1}` needs one or more non-empty class names, e.g. `{0} = \"pkg_obj\"` or `{0}(\"pkg_a\", \"pkg_b\")`",
                key, param_name))
    })format!(
475                    "`{key}` on parameter `{param_name}` needs one or more non-empty class \
476                     names, e.g. `{key} = \"pkg_obj\"` or `{key}(\"pkg_a\", \"pkg_b\")`"
477                ),
478            ));
479        }
480    }
481    if let Some(msg) = attr.checks.class_conflict(param_name) {
482        return Err(syn::Error::new(span, msg));
483    }
484    if is_dots && attr.default_value.is_some() {
485        return Err(syn::Error::new(
486            span,
487            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("variadic (...) parameter `{0}` cannot have a default value",
                param_name))
    })format!(
488                "variadic (...) parameter `{}` cannot have a default value",
489                param_name
490            ),
491        ));
492    }
493    if is_dots && let Some(formal) = &attr.formal {
494        return Err(dots_formal_error(formal, param_name));
495    }
496    Ok(())
497}
498
499/// The error of a `no_default` that cannot apply (#1828), or `None`: on a
500/// parameter that is neither `match_arg` nor `choices` (only a choice
501/// parameter gets a generated default to drop), or together with a default.
502/// `method_list` picks the spellings named: the method-level
503/// `no_default(p)` / `defaults(p = ...)`, else the parameter's own
504/// `no_default` / `default = "..."`.
505pub(crate) fn no_default_conflict(
506    param: &str,
507    is_choice: bool,
508    has_default: bool,
509    method_list: bool,
510) -> Option<String> {
511    let (no_default, default) = if method_list {
512        (
513            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`no_default({0})`", param))
    })format!("`no_default({param})`"),
514            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`defaults({0} = ...)`", param))
    })format!("`defaults({param} = ...)`"),
515        )
516    } else {
517        (
518            "`no_default`".to_string(),
519            "`default = \"...\"`".to_string(),
520        )
521    };
522    if !is_choice {
523        return Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} on parameter `{1}`, which is neither `match_arg` nor `choices`; only a choice parameter gets a generated default to drop, and any other parameter without a default already has a bare formal",
                no_default, param))
    })format!(
524            "{no_default} on parameter `{param}`, which is neither `match_arg` nor `choices`; \
525             only a choice parameter gets a generated default to drop, and any other \
526             parameter without a default already has a bare formal"
527        ));
528    }
529    if has_default {
530        return Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot combine {0} and {1} on parameter `{2}`; `no_default` leaves the R formal without a default, so drop one of them",
                no_default, default, param))
    })format!(
531            "cannot combine {no_default} and {default} on parameter `{param}`; \
532             `no_default` leaves the R formal without a default, so drop one of them"
533        ));
534    }
535    None
536}
537
538/// The error of a `formal` on the dots parameter (#1842), at the literal.
539pub(crate) fn dots_formal_error(formal: &syn::LitStr, param_name: &str) -> syn::Error {
540    syn::Error::new(
541        formal.span(),
542        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`formal` on the variadic (...) parameter `{0}`: its R formal is always `...`. Declare a named parameter for an argument with a dotted name",
                param_name))
    })format!(
543            "`formal` on the variadic (...) parameter `{param_name}`: its R formal is always \
544             `...`. Declare a named parameter for an argument with a dotted name"
545        ),
546    )
547}
548
549// endregion
550
551// region: Per-parameter attribute parsing
552
553/// The error of a `formal(...)` on a trait method (#1842), declared or
554/// implemented. A trait method's R formals are its declaration's parameter
555/// names, shared by every implementing type and the trait ABI's metadata;
556/// declared formals cover standalone functions and inherent impl methods.
557pub(crate) const TRAIT_FORMAL_UNSUPPORTED: &str = "`formal(...)` is not supported on trait \
558     methods: a trait method's R formals are the trait declaration's parameter names, which \
559     every implementing type shares. Declare the dotted name on a standalone function or an \
560     inherent impl method instead";
561
562/// The error of a `formal` whose value is no string literal (#1842).
563const FORMAL_NOT_STRING: &str =
564    "`formal` takes the R argument name as a string, e.g. `formal = \"conf.int\"`";
565
566/// The error of a second `formal` on one parameter (#1842).
567const FORMAL_TWICE: &str = "`formal` is given twice on this parameter; a parameter has one R \
568                            formal, so keep one of them";
569
570/// Parsed per-parameter `#[miniextendr(...)]` attribute content.
571///
572/// A single attribute can contain multiple items, e.g.
573/// `#[miniextendr(match_arg, default = "Safe")]`; a parameter carrying
574/// several attributes gets them merged ([`PerParamMiniextendrAttr::merge`]).
575#[derive(#[automatically_derived]
impl ::core::default::Default for PerParamMiniextendrAttr {
    #[inline]
    fn default() -> Self {
        PerParamMiniextendrAttr {
            has_coerce: ::core::default::Default::default(),
            has_match_arg: ::core::default::Default::default(),
            default_value: ::core::default::Default::default(),
            choices: ::core::default::Default::default(),
            has_several_ok: ::core::default::Default::default(),
            has_no_default: ::core::default::Default::default(),
            checks: ::core::default::Default::default(),
            preconditions: ::core::default::Default::default(),
            formal: ::core::default::Default::default(),
        }
    }
}Default)]
576pub(crate) struct PerParamMiniextendrAttr {
577    /// Whether `coerce` was present, enabling automatic type coercion for this parameter
578    /// (e.g., `i32` to `u16`, `f64` to `f32`).
579    pub has_coerce: bool,
580    /// Whether `match_arg` was present, generating R `match.arg()` validation for
581    /// string parameters against a set of allowed values.
582    pub has_match_arg: bool,
583    /// Default value from `default = "..."`, if present. The tuple contains the default
584    /// value string and the attribute span (for error reporting).
585    pub default_value: Option<(String, proc_macro2::Span)>,
586    /// Choices for string parameters: `#[miniextendr(choices("a", "b", "c"))]`.
587    pub choices: Option<Vec<String>>,
588    /// Whether `several_ok` was present, enabling multi-value `match.arg(several.ok = TRUE)`.
589    /// Only valid with `choices(...)` or `match_arg`.
590    pub has_several_ok: bool,
591    /// Whether `no_default` was present: the choice parameter's R formal has
592    /// no default (#1828). Only valid with `choices(...)` or `match_arg`, and
593    /// not together with `default`.
594    pub has_no_default: bool,
595    /// `inherits = "cls"` / `inherits("a", "b")`, `not_inherits` (the same
596    /// spellings) and `no_na`, each with an optional `message = "..."`: R-side
597    /// checks named by the author (see
598    /// [`crate::r_preconditions::ExplicitChecks`]).
599    pub checks: crate::r_preconditions::ExplicitChecks,
600    /// `preconditions` / `no_preconditions` on the parameter (#1566):
601    /// `Some(true)` keeps its type-derived R-side checks, `Some(false)` drops
602    /// them. The last one written wins.
603    pub preconditions: Option<bool>,
604    /// `formal = "conf.int"`: the parameter's R formal (#1842), checked by
605    /// [`crate::r_wrapper_builder::check_r_formals`].
606    pub formal: Option<syn::LitStr>,
607}
608
609impl PerParamMiniextendrAttr {
610    /// Merge the options of another attribute on the same parameter: flags
611    /// add up, the first `default` / `choices(...)` wins, the last
612    /// `preconditions` / `no_preconditions` wins, and the checks merge (one
613    /// `message` per check; the error says which). A second `formal` is an
614    /// error.
615    pub(crate) fn merge(&mut self, other: PerParamMiniextendrAttr) -> Result<(), String> {
616        if let Some(formal) = other.formal {
617            if self.formal.is_some() {
618                return Err(FORMAL_TWICE.to_string());
619            }
620            self.formal = Some(formal);
621        }
622        if other.preconditions.is_some() {
623            self.preconditions = other.preconditions;
624        }
625        self.has_coerce |= other.has_coerce;
626        self.has_match_arg |= other.has_match_arg;
627        self.has_several_ok |= other.has_several_ok;
628        self.has_no_default |= other.has_no_default;
629        if self.default_value.is_none() {
630            self.default_value = other.default_value;
631        }
632        if self.choices.is_none() {
633            self.choices = other.choices;
634        }
635        self.checks.merge(other.checks)
636    }
637}
638
639/// Parse all per-parameter options from a `#[miniextendr(...)]` attribute.
640///
641/// Handles mixed content like `#[miniextendr(match_arg, default = "\"Safe\"")]`
642/// and `#[miniextendr(choices("a", "b", "c"))]`.
643///
644/// Returns `Ok(None)` if `attr` is not a `#[miniextendr(...)]` attribute, if its
645/// content is not a list of options, or if it contains only function-level
646/// options (like `strict`) with no per-parameter options. A malformed
647/// `inherits(...)` / `not_inherits(...)` / `no_na(...)` is an error, as is
648/// `preconditions = bool` / `no_preconditions = bool`, a function-level form
649/// the parameter spells bare, and a `formal` that is not a string or is given
650/// twice.
651///
652/// # Arguments
653///
654/// * `attr` - A `syn::Attribute` to inspect. Only attributes with path `miniextendr`
655///   are considered.
656pub(crate) fn parse_per_param_attr(
657    attr: &syn::Attribute,
658) -> syn::Result<Option<PerParamMiniextendrAttr>> {
659    use syn::spanned::Spanned;
660    if !attr.path().is_ident("miniextendr") {
661        return Ok(None);
662    }
663
664    let syn::Meta::List(meta_list) = &attr.meta else {
665        return Ok(None);
666    };
667
668    let mut result = PerParamMiniextendrAttr::default();
669    let mut is_per_param = false;
670
671    let Ok(metas) = meta_list.parse_args_with(
672        syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated,
673    ) else {
674        return Ok(None);
675    };
676
677    for meta in &metas {
678        match meta {
679            syn::Meta::Path(path) => {
680                if path.is_ident("coerce") {
681                    result.has_coerce = true;
682                    is_per_param = true;
683                } else if path.is_ident("match_arg") {
684                    result.has_match_arg = true;
685                    is_per_param = true;
686                } else if path.is_ident("several_ok") {
687                    result.has_several_ok = true;
688                    is_per_param = true;
689                } else if path.is_ident("no_default") {
690                    result.has_no_default = true;
691                    is_per_param = true;
692                } else if path.is_ident("no_na") {
693                    result.checks.no_na = true;
694                    is_per_param = true;
695                } else if path.is_ident("preconditions") {
696                    result.preconditions = Some(true);
697                    is_per_param = true;
698                } else if path.is_ident("no_preconditions") {
699                    result.preconditions = Some(false);
700                    is_per_param = true;
701                }
702                // Other paths (like `strict`) are function-level, ignore here
703            }
704            syn::Meta::NameValue(nv) => {
705                if nv.path.is_ident("default")
706                    && let syn::Expr::Lit(syn::ExprLit {
707                        lit: syn::Lit::Str(lit_str),
708                        ..
709                    }) = &nv.value
710                {
711                    result.default_value = Some((lit_str.value(), attr.span()));
712                    is_per_param = true;
713                } else if nv.path.is_ident("formal") {
714                    // formal = "conf.int" (#1842): the parameter's R formal.
715                    let syn::Expr::Lit(syn::ExprLit {
716                        lit: syn::Lit::Str(lit_str),
717                        ..
718                    }) = &nv.value
719                    else {
720                        return Err(syn::Error::new_spanned(&nv.value, FORMAL_NOT_STRING));
721                    };
722                    if result.formal.replace(lit_str.clone()).is_some() {
723                        return Err(syn::Error::new_spanned(nv, FORMAL_TWICE));
724                    }
725                    is_per_param = true;
726                } else if let Some(check) = ClassCheck::of(&nv.path)
727                    && let syn::Expr::Lit(syn::ExprLit {
728                        lit: syn::Lit::Str(lit_str),
729                        ..
730                    }) = &nv.value
731                {
732                    check
733                        .classes_mut(&mut result.checks)
734                        .get_or_insert_with(Vec::new)
735                        .push(lit_str.value());
736                    is_per_param = true;
737                } else if let Some(key) = ["preconditions", "no_preconditions"]
738                    .into_iter()
739                    .find(|key| nv.path.is_ident(key))
740                {
741                    return Err(syn::Error::new_spanned(
742                        nv,
743                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0} = ...` on a parameter: write the bare `preconditions` or `no_preconditions` (or the `Checked<T>` / `Unchecked<T>` marker); the `= true | false` form is the function attribute\'s",
                key))
    })format!(
744                            "`{key} = ...` on a parameter: write the bare `preconditions` or \
745                             `no_preconditions` (or the `Checked<T>` / `Unchecked<T>` marker); \
746                             the `= true | false` form is the function attribute's"
747                        ),
748                    ));
749                }
750                // Other name-value pairs are function-level, ignore here
751            }
752            syn::Meta::List(list) => {
753                if list.path.is_ident("choices") {
754                    // Parse choices("a", "b", "c") — a comma-separated list of string literals
755                    let choice_lits = match list.parse_args_with(
756                        syn::punctuated::Punctuated::<syn::LitStr, ::syn::token::Commasyn::Token![,]>::parse_terminated,
757                    ) {
758                        Ok(lits) => lits,
759                        Err(_) => continue,
760                    };
761                    let choices: Vec<String> = choice_lits.iter().map(|l| l.value()).collect();
762                    result.choices = Some(choices);
763                    is_per_param = true;
764                } else if let Some(check) = ClassCheck::of(&list.path) {
765                    // inherits("a", "b"[, message = "..."]) / inherits(class = "a", ...),
766                    // and the same for not_inherits.
767                    let checks = parse_class_check_list(list, check)?;
768                    result
769                        .checks
770                        .merge(checks)
771                        .map_err(|msg| syn::Error::new_spanned(list, msg))?;
772                    is_per_param = true;
773                } else if list.path.is_ident("no_na") {
774                    // no_na(message = "...")
775                    let checks = parse_no_na_list(list)?;
776                    result
777                        .checks
778                        .merge(checks)
779                        .map_err(|msg| syn::Error::new_spanned(list, msg))?;
780                    is_per_param = true;
781                }
782                // Other list forms are function-level, ignore here
783            }
784        }
785    }
786
787    if !is_per_param {
788        return Ok(None);
789    }
790    Ok(Some(result))
791}
792
793/// The two class checks a parameter can name: `inherits` (the argument must
794/// inherit from one of the classes) and `not_inherits` (from none of them,
795/// #1815). Both take the same spellings at parameter and method level; this
796/// says which one a spelling is, and words its errors.
797#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ClassCheck { }
#[automatically_derived]
impl ::core::clone::Clone for ClassCheck {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ClassCheck { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for ClassCheck {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ClassCheck::Inherits => "Inherits",
                ClassCheck::NotInherits => "NotInherits",
            })
    }
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ClassCheck { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ClassCheck {
    #[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 ClassCheck { }Eq)]
798pub(crate) enum ClassCheck {
799    Inherits,
800    NotInherits,
801}
802
803impl ClassCheck {
804    /// The check `path` names, if it is `inherits` or `not_inherits`.
805    pub(crate) fn of(path: &syn::Path) -> Option<Self> {
806        if path.is_ident("inherits") {
807            Some(Self::Inherits)
808        } else if path.is_ident("not_inherits") {
809            Some(Self::NotInherits)
810        } else {
811            None
812        }
813    }
814
815    /// The attribute keyword, for error messages.
816    fn keyword(self) -> &'static str {
817        match self {
818            Self::Inherits => "inherits",
819            Self::NotInherits => "not_inherits",
820        }
821    }
822
823    /// The `when(...)` hints in this check's list of options, for errors:
824    /// only `inherits` takes them.
825    fn hints_option(self) -> &'static str {
826        match self {
827            Self::Inherits => ", `when(...)` hints",
828            Self::NotInherits => "",
829        }
830    }
831
832    /// The error for an unknown option `key` in a parameter-level
833    /// `inherits(...)` / `not_inherits(...)`.
834    fn unknown_option(self, key: &syn::Ident) -> String {
835        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `{0}` option `{2}`; expected class names (`\"cls\"` or `class = \"cls\"`){1} and an optional `message = \"...\"`",
                self.keyword(), self.hints_option(), key))
    })format!(
836            "unknown `{}` option `{key}`; expected class names \
837             (`\"cls\"` or `class = \"cls\"`){} and an optional `message = \"...\"`",
838            self.keyword(),
839            self.hints_option()
840        )
841    }
842
843    /// The error for an unknown option in a method-level
844    /// `inherits(p(...))` / `not_inherits(p(...))`.
845    fn unknown_method_option(self) -> String {
846        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `{0}` option; expected `class = \"...\"`{1} and an optional `message = \"...\"`",
                self.keyword(), self.hints_option()))
    })format!(
847            "unknown `{}` option; expected `class = \"...\"`{} and an optional \
848             `message = \"...\"`",
849            self.keyword(),
850            self.hints_option()
851        )
852    }
853
854    /// This check's classes in `checks`, if it is requested.
855    fn classes(self, checks: &crate::r_preconditions::ExplicitChecks) -> Option<&[String]> {
856        match self {
857            Self::Inherits => checks.inherits.as_deref(),
858            Self::NotInherits => checks.not_inherits.as_deref(),
859        }
860    }
861
862    /// This check's classes in `checks`, to add to.
863    fn classes_mut(
864        self,
865        checks: &mut crate::r_preconditions::ExplicitChecks,
866    ) -> &mut Option<Vec<String>> {
867        match self {
868            Self::Inherits => &mut checks.inherits,
869            Self::NotInherits => &mut checks.not_inherits,
870        }
871    }
872
873    /// The checks holding this check with `classes` and `message`.
874    fn checks(
875        self,
876        classes: Vec<String>,
877        message: Option<String>,
878    ) -> crate::r_preconditions::ExplicitChecks {
879        let mut checks = crate::r_preconditions::ExplicitChecks::default();
880        *self.classes_mut(&mut checks) = Some(classes);
881        match self {
882            Self::Inherits => checks.inherits_message = message,
883            Self::NotInherits => checks.not_inherits_message = message,
884        }
885        checks
886    }
887}
888
889/// `inherits(...)` / `not_inherits(...)` on a parameter (`check` says
890/// which): each string literal, and each `class = "..."`, names one class
891/// (the argument must inherit from one of them, or from none of them);
892/// `message = "..."` gives the condition message of a failure, for all of
893/// them. No string is split on commas, as with `inherits = "cls"` (the
894/// method-level `class = "a, b"` is split, see [`parse_method_class_check`]).
895///
896/// `inherits(...)` also takes `when(...)` hints (#1824), each with its
897/// classes spelled the same way and a required `message = "..."`
898/// ([`parse_when_list`]).
899fn parse_class_check_list(
900    list: &syn::MetaList,
901    check: ClassCheck,
902) -> syn::Result<crate::r_preconditions::ExplicitChecks> {
903    use syn::parse::Parser as _;
904    let key_word = check.keyword();
905    let mut classes = Vec::new();
906    let mut message = None;
907    let mut hints = Vec::new();
908    let parser = |input: syn::parse::ParseStream| -> syn::Result<()> {
909        while !input.is_empty() {
910            if input.peek(syn::LitStr) {
911                let class: syn::LitStr = input.parse()?;
912                classes.push(class.value());
913            } else {
914                let key: syn::Ident = input.parse()?;
915                if key == "when" && input.peek(syn::token::Paren) {
916                    if check != ClassCheck::Inherits {
917                        return Err(syn::Error::new_spanned(&key, WHEN_ON_NOT_INHERITS));
918                    }
919                    let content;
920                    match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
921                    hints.push(parse_when_list(&key, &content)?);
922                } else {
923                    input.parse::<::syn::token::Eqsyn::Token![=]>()?;
924                    let value: syn::LitStr = input.parse()?;
925                    if key == "class" {
926                        classes.push(value.value());
927                    } else if key == "message" {
928                        set_check_message(&mut message, &value, key_word)?;
929                    } else {
930                        return Err(syn::Error::new_spanned(&key, check.unknown_option(&key)));
931                    }
932                }
933            }
934            if input.is_empty() {
935                break;
936            }
937            input.parse::<::syn::token::Commasyn::Token![,]>()?;
938        }
939        Ok(())
940    };
941    parser.parse2(list.tokens.clone())?;
942    if message.is_some() && classes.is_empty() {
943        return Err(syn::Error::new_spanned(
944            list,
945            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`message` in `{0}(...)` needs a class to check, e.g. `{0}(class = \"pkg_obj\", message = \"...\")`",
                key_word))
    })format!(
946                "`message` in `{key_word}(...)` needs a class to check, \
947                 e.g. `{key_word}(class = \"pkg_obj\", message = \"...\")`"
948            ),
949        ));
950    }
951    if !hints.is_empty() && classes.is_empty() {
952        return Err(syn::Error::new_spanned(
953            list,
954            "`when(...)` in `inherits(...)` needs a class to check, e.g. \
955             `inherits(class = \"pkg_obj\", when(class = \"data.frame\", message = \"...\"))`",
956        ));
957    }
958    let mut checks = check.checks(classes, message);
959    checks.inherits_hints = hints;
960    Ok(checks)
961}
962
963/// The error for `when(...)` in `not_inherits(...)`.
964const WHEN_ON_NOT_INHERITS: &str = "`when(...)` is an `inherits` option: it gives a value that \
965     fails `inherits` a message for its class; `not_inherits` has one `message` for every \
966     class it refuses";
967
968/// The content of a parameter-level `when(...)` hint in `inherits(...)`
969/// (`key` is the `when` keyword, for error spans): its classes, spelled as in
970/// `inherits(...)` (each string literal and each `class = "..."` names one
971/// class, none split on commas), and its required `message = "..."`.
972fn parse_when_list(
973    key: &syn::Ident,
974    content: syn::parse::ParseStream,
975) -> syn::Result<crate::r_preconditions::InheritsHint> {
976    let mut classes = Vec::new();
977    let mut message = None;
978    while !content.is_empty() {
979        if content.peek(syn::LitStr) {
980            let class: syn::LitStr = content.parse()?;
981            classes.push(hint_class(&class)?);
982        } else {
983            let opt: syn::Ident = content.parse()?;
984            content.parse::<::syn::token::Eqsyn::Token![=]>()?;
985            let value: syn::LitStr = content.parse()?;
986            if opt == "class" {
987                classes.push(hint_class(&value)?);
988            } else if opt == "message" {
989                set_hint_message(&mut message, &value)?;
990            } else {
991                return Err(syn::Error::new_spanned(
992                    &opt,
993                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `when` option `{0}`; expected class names (`\"cls\"` or `class = \"cls\"`) and `message = \"...\"`",
                opt))
    })format!(
994                        "unknown `when` option `{opt}`; expected class names (`\"cls\"` or \
995                         `class = \"cls\"`) and `message = \"...\"`"
996                    ),
997                ));
998            }
999        }
1000        if content.is_empty() {
1001            break;
1002        }
1003        content.parse::<::syn::token::Commasyn::Token![,]>()?;
1004    }
1005    inherits_hint(classes, message).map_err(|msg| syn::Error::new_spanned(key, msg))
1006}
1007
1008/// One class name of a parameter-level `when(...)` hint: not empty.
1009fn hint_class(value: &syn::LitStr) -> syn::Result<String> {
1010    let class = value.value();
1011    if class.is_empty() {
1012        return Err(syn::Error::new(
1013            value.span(),
1014            "a class name in `when(...)` must not be empty",
1015        ));
1016    }
1017    Ok(class)
1018}
1019
1020/// A parsed `when(...)` hint, which needs both its classes and its message.
1021fn inherits_hint(
1022    classes: Vec<String>,
1023    message: Option<String>,
1024) -> Result<crate::r_preconditions::InheritsHint, &'static str> {
1025    if classes.is_empty() {
1026        return Err("`when(...)` needs the classes it is for, e.g. \
1027             `when(class = \"data.frame\", message = \"...\")`");
1028    }
1029    let Some(message) = message else {
1030        return Err(
1031            "`when(...)` needs a `message = \"...\"`: the hint a value of its classes gets, \
1032             e.g. `when(class = \"data.frame\", message = \"...\")`",
1033        );
1034    };
1035    Ok(crate::r_preconditions::InheritsHint { classes, message })
1036}
1037
1038/// `no_na(message = "...")` on a parameter: `no_na` with the condition
1039/// message of a failure.
1040fn parse_no_na_list(list: &syn::MetaList) -> syn::Result<crate::r_preconditions::ExplicitChecks> {
1041    let mut message = None;
1042    list.parse_nested_meta(|opt| parse_no_na_option(&opt, &mut message))?;
1043    Ok(crate::r_preconditions::ExplicitChecks {
1044        no_na: true,
1045        no_na_message: message,
1046        ..Default::default()
1047    })
1048}
1049
1050/// One option inside `no_na(...)` (parameter level) or `no_na(p(...))`
1051/// (method level): only `message = "..."`.
1052fn parse_no_na_option(
1053    opt: &syn::meta::ParseNestedMeta,
1054    message: &mut Option<String>,
1055) -> syn::Result<()> {
1056    if opt.path.is_ident("message") {
1057        let value: syn::LitStr = opt.value()?.parse()?;
1058        set_check_message(message, &value, "no_na")
1059    } else {
1060        Err(opt.error("unknown `no_na` option; expected `message = \"...\"`"))
1061    }
1062}
1063
1064/// Record the `message = "..."` of an `inherits` / `not_inherits` / `no_na`
1065/// check: the condition message of a failure, used verbatim. It is given
1066/// once, is not empty, and holds no NUL (an R string cannot).
1067fn set_check_message(
1068    slot: &mut Option<String>,
1069    value: &syn::LitStr,
1070    check: &str,
1071) -> syn::Result<()> {
1072    set_message(
1073        slot,
1074        value,
1075        check,
1076        "leave it out to get the generated message",
1077    )
1078}
1079
1080/// Record the `message = "..."` of a `when(...)` hint in `inherits(...)`,
1081/// under the rules of [`set_check_message`]; unlike a check's message, the
1082/// hint's is required.
1083fn set_hint_message(slot: &mut Option<String>, value: &syn::LitStr) -> syn::Result<()> {
1084    set_message(
1085        slot,
1086        value,
1087        "when",
1088        "it is the message a value of those classes gets",
1089    )
1090}
1091
1092/// Record `value` as the message in `slot` of `check(...)`; `if_empty`
1093/// ends the error for an empty message.
1094fn set_message(
1095    slot: &mut Option<String>,
1096    value: &syn::LitStr,
1097    check: &str,
1098    if_empty: &str,
1099) -> syn::Result<()> {
1100    let text = value.value();
1101    if slot.is_some() {
1102        return Err(syn::Error::new(
1103            value.span(),
1104            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`message` is given more than once in `{0}(...)`",
                check))
    })format!("`message` is given more than once in `{check}(...)`"),
1105        ));
1106    }
1107    if text.trim().is_empty() {
1108        return Err(syn::Error::new(
1109            value.span(),
1110            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`message` in `{0}(...)` must not be empty; {1}",
                check, if_empty))
    })format!("`message` in `{check}(...)` must not be empty; {if_empty}"),
1111        ));
1112    }
1113    if text.contains('\0') {
1114        return Err(syn::Error::new(
1115            value.span(),
1116            "`message` must not contain a NUL character: an R string cannot hold one",
1117        ));
1118    }
1119    *slot = Some(text);
1120    Ok(())
1121}
1122
1123/// The parameter an entry of a method-level `match_arg(...)` / `choices(...)`
1124/// / `no_na(...)` / `inherits(...)` / `not_inherits(...)` names, spelled as
1125/// the signature's [`crate::naming::ident_name`] (`r#type` names `type`), so
1126/// [`finalize_method_param_attrs`] finds it.
1127pub(crate) fn method_check_param(entry: &syn::meta::ParseNestedMeta) -> syn::Result<String> {
1128    Ok(crate::naming::ident_name(
1129        entry
1130            .path
1131            .get_ident()
1132            .ok_or_else(|| entry.error("expected parameter name"))?,
1133    ))
1134}
1135
1136/// Method-level `no_na(p, q(message = "..."))` on an impl or trait method,
1137/// whose parameters cannot carry attributes: each entry names a parameter,
1138/// optionally with the condition message of its NA check. Shared by the
1139/// inherent-impl and trait-impl method parsers.
1140pub(crate) fn parse_method_no_na(
1141    meta: &syn::meta::ParseNestedMeta,
1142    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
1143) -> syn::Result<()> {
1144    meta.parse_nested_meta(|entry| {
1145        let name = method_check_param(&entry)?;
1146        let mut message = None;
1147        if entry.input.peek(syn::token::Paren) {
1148            entry.parse_nested_meta(|opt| parse_no_na_option(&opt, &mut message))?;
1149        }
1150        let checks = crate::r_preconditions::ExplicitChecks {
1151            no_na: true,
1152            no_na_message: message,
1153            ..Default::default()
1154        };
1155        per_param
1156            .entry(name)
1157            .or_default()
1158            .checks
1159            .merge(checks)
1160            .map_err(|msg| entry.error(msg))
1161    })
1162}
1163
1164/// Method-level `inherits(p = "a, b", q(class = "a, b", message = "..."))` /
1165/// `not_inherits(...)` (the same shape, `check` says which) on an impl or
1166/// trait method: each entry names a parameter and the classes it must
1167/// inherit from (one of) or must not inherit from (any of), optionally with
1168/// the condition message of the class check. A nested value cannot be a bare
1169/// list of literals, so the classes are one comma-separated string, as in
1170/// `choices(p = "a, b")`. `inherits(q(class = "a", when(class = "b, c",
1171/// message = "...")))` adds a hint (#1824; [`parse_method_when`]). Shared by
1172/// the inherent-impl and trait-impl method parsers.
1173pub(crate) fn parse_method_class_check(
1174    meta: &syn::meta::ParseNestedMeta,
1175    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
1176    check: ClassCheck,
1177) -> syn::Result<()> {
1178    let key_word = check.keyword();
1179    meta.parse_nested_meta(|entry| {
1180        let name = method_check_param(&entry)?;
1181        let mut classes = Vec::new();
1182        let mut message = None;
1183        let mut hints = Vec::new();
1184        if entry.input.peek(::syn::token::Eqsyn::Token![=]) {
1185            let value: syn::LitStr = entry.value()?.parse()?;
1186            classes = method_class_list(&value, check)?;
1187        } else if entry.input.peek(syn::token::Paren) {
1188            entry.parse_nested_meta(|opt| {
1189                if opt.path.is_ident("class") {
1190                    let value: syn::LitStr = opt.value()?.parse()?;
1191                    classes.extend(method_class_list(&value, check)?);
1192                    Ok(())
1193                } else if opt.path.is_ident("message") {
1194                    let value: syn::LitStr = opt.value()?.parse()?;
1195                    set_check_message(&mut message, &value, key_word)
1196                } else if opt.path.is_ident("when") {
1197                    if check != ClassCheck::Inherits {
1198                        return Err(opt.error(WHEN_ON_NOT_INHERITS));
1199                    }
1200                    hints.push(parse_method_when(&opt)?);
1201                    Ok(())
1202                } else {
1203                    Err(opt.error(check.unknown_method_option()))
1204                }
1205            })?;
1206            if classes.is_empty() {
1207                return Err(entry.error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}({1}(...))` needs `class = \"...\"`, e.g. `{0}({1}(class = \"pkg_obj\", message = \"...\"))`",
                key_word, name))
    })format!(
1208                    "`{key_word}({name}(...))` needs `class = \"...\"`, \
1209                     e.g. `{key_word}({name}(class = \"pkg_obj\", message = \"...\"))`"
1210                )));
1211            }
1212        } else {
1213            return Err(entry.error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected `{0} = \"cls\"` or `{0}(class = \"cls\", message = \"...\")`",
                name))
    })format!(
1214                "expected `{name} = \"cls\"` or `{name}(class = \"cls\", message = \"...\")`"
1215            )));
1216        }
1217        let mut checks = check.checks(classes, message);
1218        checks.inherits_hints = hints;
1219        per_param
1220            .entry(name)
1221            .or_default()
1222            .checks
1223            .merge(checks)
1224            .map_err(|msg| entry.error(msg))
1225    })
1226}
1227
1228/// A method-level `when(class = "a, b", message = "...")` hint in
1229/// `inherits(p(...))`: its classes are one comma-separated string, as the
1230/// entry's own `class`, and its message is required.
1231fn parse_method_when(
1232    opt: &syn::meta::ParseNestedMeta,
1233) -> syn::Result<crate::r_preconditions::InheritsHint> {
1234    let mut classes = Vec::new();
1235    let mut message = None;
1236    opt.parse_nested_meta(|hint| {
1237        if hint.path.is_ident("class") {
1238            let value: syn::LitStr = hint.value()?.parse()?;
1239            let names = crate::r_wrapper_builder::split_choice_list(&value.value());
1240            if names.is_empty() {
1241                return Err(syn::Error::new(
1242                    value.span(),
1243                    "`when(class = \"...\")` needs one or more class names",
1244                ));
1245            }
1246            classes.extend(names);
1247            Ok(())
1248        } else if hint.path.is_ident("message") {
1249            let value: syn::LitStr = hint.value()?.parse()?;
1250            set_hint_message(&mut message, &value)
1251        } else {
1252            Err(hint
1253                .error("unknown `when` option; expected `class = \"...\"` and `message = \"...\"`"))
1254        }
1255    })?;
1256    inherits_hint(classes, message).map_err(|msg| opt.error(msg))
1257}
1258
1259/// Method-level `no_default(p, q)` on an impl or trait method, whose
1260/// parameters cannot carry attributes: each entry names a `match_arg` /
1261/// `choices` parameter whose R formal has no default (#1828). Shared by the
1262/// inherent-impl and trait-impl method parsers; [`finalize_method_param_attrs`]
1263/// refuses a name that is no parameter, no choice or has a default.
1264pub(crate) fn parse_method_no_default(
1265    meta: &syn::meta::ParseNestedMeta,
1266    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
1267) -> syn::Result<()> {
1268    meta.parse_nested_meta(|entry| {
1269        let name = method_check_param(&entry)?;
1270        per_param.entry(name).or_default().no_default = true;
1271        Ok(())
1272    })
1273}
1274
1275/// Parse a method-level `formal(conf_int = "conf.int", ...)` (#1842): the R
1276/// formal of each named parameter, the method form of a standalone function's
1277/// per-parameter `formal = ".."`. Shared by the inherent-impl parser only:
1278/// trait methods take their formals from the trait declaration, and the
1279/// trait parsers refuse it. A parameter named twice is an error;
1280/// [`finalize_method_param_attrs`] refuses a name that is no parameter, and
1281/// [`crate::r_wrapper_builder::check_r_formals`] checks each formal.
1282pub(crate) fn parse_method_formal(
1283    meta: &syn::meta::ParseNestedMeta,
1284    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
1285) -> syn::Result<()> {
1286    meta.parse_nested_meta(|entry| {
1287        let name = method_check_param(&entry)?;
1288        let value = entry.value()?;
1289        let value: syn::LitStr = value
1290            .parse()
1291            .map_err(|e| syn::Error::new(e.span(), FORMAL_NOT_STRING))?;
1292        let attrs = per_param.entry(name).or_default();
1293        if attrs.formal.replace(value).is_some() {
1294            return Err(entry.error(FORMAL_TWICE));
1295        }
1296        Ok(())
1297    })
1298}
1299
1300/// The classes of a method-level `inherits` / `not_inherits` entry: a
1301/// comma-separated, non-empty list.
1302fn method_class_list(value: &syn::LitStr, check: ClassCheck) -> syn::Result<Vec<String>> {
1303    let classes = crate::r_wrapper_builder::split_choice_list(&value.value());
1304    if classes.is_empty() {
1305        return Err(syn::Error::new(
1306            value.span(),
1307            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}(param = \"...\")` needs one or more class names",
                check.keyword()))
    })format!(
1308                "`{}(param = \"...\")` needs one or more class names",
1309                check.keyword()
1310            ),
1311        ));
1312    }
1313    Ok(classes)
1314}
1315// endregion
1316
1317// region: per-parameter preconditions (#1566 §2)
1318
1319/// Method-level `preconditions(p, q)` / `no_preconditions(p)` on an impl or
1320/// trait method, whose parameters cannot carry attributes: each entry names a
1321/// parameter that keeps (`keep`) or drops its type-derived R-side checks.
1322/// Naming one parameter twice follows the pair rule: the last one written
1323/// wins. Shared by the inherent-impl and trait-impl method parsers; the bare
1324/// `preconditions` / `no_preconditions` (the whole method) is theirs to read.
1325pub(crate) fn parse_method_preconditions(
1326    meta: &syn::meta::ParseNestedMeta,
1327    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
1328    keep: bool,
1329) -> syn::Result<()> {
1330    meta.parse_nested_meta(|entry| {
1331        let name = method_check_param(&entry)?;
1332        per_param.entry(name).or_default().preconditions = Some(keep);
1333        Ok(())
1334    })
1335}
1336
1337/// The keyword spelling of a rank-1 precondition decision, for messages:
1338/// the per-parameter attribute on a function parameter, the method-level
1339/// list on a method.
1340fn preconditions_keyword(keep: bool, param: &str, method_list: bool) -> String {
1341    let key = if keep {
1342        "preconditions"
1343    } else {
1344        "no_preconditions"
1345    };
1346    if method_list {
1347        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}({1})`", key, param))
    })format!("`{key}({param})`")
1348    } else {
1349        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`#[miniextendr({0})]`", key))
    })format!("`#[miniextendr({key})]`")
1350    }
1351}
1352
1353/// Combine a parameter's `Checked<T>` / `Unchecked<T>` marker with its
1354/// keyword decision (`keyword`: the per-parameter `preconditions` /
1355/// `no_preconditions`, or the method's `preconditions(p)` /
1356/// `no_preconditions(p)`). A marker is a type, not one more flag of the pair,
1357/// so a marker and a keyword that disagree are an error, as a `Call` marker
1358/// and `call = ...` are; agreeing ones are one decision. The error points at
1359/// `ty`, the parameter's type as written.
1360pub(crate) fn merge_param_policy(
1361    markers: &[crate::type_inspect::ParamMarker],
1362    keyword: Option<bool>,
1363    param: &str,
1364    method_list: bool,
1365    ty: &syn::Type,
1366) -> syn::Result<Option<bool>> {
1367    let marker = crate::type_inspect::marker_in_family(
1368        markers,
1369        crate::type_inspect::ParamMarkerFamily::Preconditions,
1370    );
1371    let Some(marker) = marker else {
1372        return Ok(keyword);
1373    };
1374    let from_marker = marker.preconditions();
1375    if let Some(keep) = keyword
1376        && Some(keep) != from_marker
1377    {
1378        let (marker_does, keyword_does) = if keep {
1379            ("drops", "keeps")
1380        } else {
1381            ("keeps", "drops")
1382        };
1383        return Err(syn::Error::new_spanned(
1384            ty,
1385            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the `{0}` parameter `{2}` {3} the R-side type checks but {1} {4} them; keep one of them (or make them agree)",
                marker.name(),
                preconditions_keyword(keep, param, method_list), param,
                marker_does, keyword_does))
    })format!(
1386                "the `{}` parameter `{param}` {marker_does} the R-side type checks but {} \
1387                 {keyword_does} them; keep one of them (or make them agree)",
1388                marker.name(),
1389                preconditions_keyword(keep, param, method_list),
1390            ),
1391        ));
1392    }
1393    Ok(from_marker)
1394}
1395
1396/// Reject a parameter's rank-1 precondition decision where it has nothing to
1397/// act on: a `match_arg` / `choices` parameter (validated by `match.arg()`,
1398/// never by type checks) and a type without type-derived checks (`SEXP`,
1399/// `Missing<T>`, `ExternalPtr<T>`, `&Dots`, a type the check table does not
1400/// know). `ty` is the parameter's type with the markers peeled, which the
1401/// errors point at; `markers` are the ones it carried. A no-op when the
1402/// parameter decides nothing itself.
1403pub(crate) fn check_param_preconditions(
1404    attrs: &ParamAttrs,
1405    markers: &[crate::type_inspect::ParamMarker],
1406    param: &str,
1407    ty: &syn::Type,
1408    method_list: bool,
1409) -> syn::Result<()> {
1410    let Some(keep) = attrs.preconditions else {
1411        return Ok(());
1412    };
1413    let marker = crate::type_inspect::marker_in_family(
1414        markers,
1415        crate::type_inspect::ParamMarkerFamily::Preconditions,
1416    );
1417    let spelling = match marker {
1418        Some(m) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}<{1}>`", m.name(),
                crate::type_inspect::type_display(ty)))
    })format!("`{}<{}>`", m.name(), crate::type_inspect::type_display(ty)),
1419        None => preconditions_keyword(keep, param, method_list),
1420    };
1421    if attrs.is_choice() {
1422        return Err(syn::Error::new_spanned(
1423            ty,
1424            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} on parameter `{1}`: a match_arg/choices parameter is validated by `match.arg()`, not by type checks; `Checked` / `Unchecked` and `preconditions` / `no_preconditions` do not apply to it",
                spelling, param))
    })format!(
1425                "{spelling} on parameter `{param}`: a match_arg/choices parameter is validated by \
1426                 `match.arg()`, not by type checks; `Checked` / `Unchecked` and \
1427                 `preconditions` / `no_preconditions` do not apply to it"
1428            ),
1429        ));
1430    }
1431    if !crate::r_preconditions::has_type_check(ty) {
1432        return Err(syn::Error::new_spanned(
1433            ty,
1434            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{1} on parameter `{2}`: `{0}` has no R-side type check to keep or drop",
                crate::type_inspect::type_display(ty), spelling, param))
    })format!(
1435                "{spelling} on parameter `{param}`: `{}` has no R-side type check to keep or drop",
1436                crate::type_inspect::type_display(ty)
1437            ),
1438        ));
1439    }
1440    Ok(())
1441}
1442
1443/// `inputs` with each marked parameter's type replaced by the type under its
1444/// markers (`markers`, keyed by Rust name): what every R-side consumer and
1445/// the conversion see. The item re-emitted for the user keeps the markers.
1446pub(crate) fn peeled_inputs(
1447    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
1448    markers: &[(String, Vec<crate::type_inspect::ParamMarker>)],
1449) -> syn::Result<syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>> {
1450    let mut peeled = inputs.clone();
1451    for arg in peeled.iter_mut() {
1452        let syn::FnArg::Typed(pt) = arg else {
1453            continue;
1454        };
1455        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1456            continue;
1457        };
1458        let name = crate::naming::ident_name(&pat_ident.ident);
1459        if markers.iter().any(|(n, _)| *n == name) {
1460            let (_, inner) = crate::type_inspect::peel_param_markers(pt.ty.as_ref())?;
1461            *pt.ty = inner.clone();
1462        }
1463    }
1464    Ok(peeled)
1465}
1466
1467/// The markers of every parameter of `inputs` that carries one, keyed by
1468/// Rust name, in signature order. Nested or stacked markers are an error
1469/// (see [`crate::type_inspect::peel_param_markers`]).
1470pub(crate) fn collect_param_markers(
1471    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
1472) -> syn::Result<Vec<(String, Vec<crate::type_inspect::ParamMarker>)>> {
1473    let mut out = Vec::new();
1474    for arg in inputs {
1475        let syn::FnArg::Typed(pt) = arg else {
1476            continue;
1477        };
1478        let (markers, _) = crate::type_inspect::peel_param_markers(pt.ty.as_ref())?;
1479        if markers.is_empty() {
1480            continue;
1481        }
1482        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1483            continue;
1484        };
1485        out.push((crate::naming::ident_name(&pat_ident.ident), markers));
1486    }
1487    Ok(out)
1488}
1489// endregion
1490
1491// region: Function parsing
1492
1493/// Parsed + normalized Rust function item for `#[miniextendr]`.
1494///
1495/// This performs signature normalization that the wrapper generator depends on:
1496/// - `...` → a final `&miniextendr_api::dots::Dots` argument
1497/// - `_` wildcard patterns → synthetic identifiers (`__unused0`, `__unused1`, ...)
1498/// - Destructuring patterns (tuple, struct) → synthetic identifiers with let-binding in body
1499/// - consumes `#[miniextendr(coerce)]` parameter attributes and records which params had it
1500pub(crate) struct MiniextendrFunctionParsed {
1501    /// The normalized function item (with dots transformed, wildcards renamed).
1502    /// Re-emitted for the user, so it keeps the parameter markers.
1503    item: syn::ItemFn,
1504    /// `item`'s parameters with the parameter markers (`Checked<T>` /
1505    /// `Unchecked<T>`, #1566) peeled: what the R wrapper, the preconditions
1506    /// and the C wrapper's conversions see.
1507    inputs: syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
1508    /// The markers peeled off each marked parameter (Rust name), in
1509    /// signature order: the C wrapper wraps the converted value in them.
1510    param_markers: Vec<(String, Vec<crate::type_inspect::ParamMarker>)>,
1511    /// Rust binding of the `&Dots` parameter (R's `...`), from Rust `...` or
1512    /// an explicit `&Dots` parameter at any position (see
1513    /// [`find_dots_param`]).
1514    dots: Option<syn::Ident>,
1515    /// All per-parameter `#[miniextendr(...)]` options (coerce, match_arg,
1516    /// default, choices, several_ok), keyed by the (possibly synthesized) Rust
1517    /// parameter name. Replaces five parallel `HashSet` / `HashMap` fields.
1518    per_param: std::collections::HashMap<String, ParamAttrs>,
1519}
1520
1521/// Collapsed per-parameter attribute state for a single function parameter.
1522///
1523/// Built during parsing from `#[miniextendr(coerce | match_arg | several_ok |
1524/// no_default | default = "…" | choices("…"))]` on the argument. Accessors on
1525/// [`MiniextendrFunctionParsed`] query this struct rather than looking
1526/// through multiple side-tables.
1527#[derive(#[automatically_derived]
impl ::core::default::Default for ParamAttrs {
    #[inline]
    fn default() -> Self {
        ParamAttrs {
            coerce: ::core::default::Default::default(),
            match_arg: ::core::default::Default::default(),
            several_ok: ::core::default::Default::default(),
            choices: ::core::default::Default::default(),
            default: ::core::default::Default::default(),
            optional: ::core::default::Default::default(),
            omittable: ::core::default::Default::default(),
            no_default: ::core::default::Default::default(),
            either_noun: ::core::default::Default::default(),
            checks: ::core::default::Default::default(),
            preconditions: ::core::default::Default::default(),
            formal: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::fmt::Debug for ParamAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["coerce", "match_arg", "several_ok", "choices", "default",
                        "optional", "omittable", "no_default", "either_noun",
                        "checks", "preconditions", "formal"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.coerce, &self.match_arg, &self.several_ok, &self.choices,
                        &self.default, &self.optional, &self.omittable,
                        &self.no_default, &self.either_noun, &self.checks,
                        &self.preconditions, &&self.formal];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "ParamAttrs",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ParamAttrs {
    #[inline]
    fn clone(&self) -> Self {
        ParamAttrs {
            coerce: ::core::clone::Clone::clone(&self.coerce),
            match_arg: ::core::clone::Clone::clone(&self.match_arg),
            several_ok: ::core::clone::Clone::clone(&self.several_ok),
            choices: ::core::clone::Clone::clone(&self.choices),
            default: ::core::clone::Clone::clone(&self.default),
            optional: ::core::clone::Clone::clone(&self.optional),
            omittable: ::core::clone::Clone::clone(&self.omittable),
            no_default: ::core::clone::Clone::clone(&self.no_default),
            either_noun: ::core::clone::Clone::clone(&self.either_noun),
            checks: ::core::clone::Clone::clone(&self.checks),
            preconditions: ::core::clone::Clone::clone(&self.preconditions),
            formal: ::core::clone::Clone::clone(&self.formal),
        }
    }
}Clone)]
1528pub(crate) struct ParamAttrs {
1529    pub coerce: bool,
1530    pub match_arg: bool,
1531    pub several_ok: bool,
1532    pub choices: Option<Vec<String>>,
1533    pub default: Option<String>,
1534    /// `Option<..>` layer of a `match_arg` / `choices` parameter (#1473):
1535    /// `NULL` means no choice, so the prelude skips the check for it and Rust
1536    /// sees `None`. Unless [`Self::omittable`] is set too, the R formal is
1537    /// `NULL` instead of the choice vector. Set from the parameter type by
1538    /// [`classify_choice_param`]; together with `several_ok` only under an
1539    /// `Either` layer (`Option<Either<Vec<T>, R>>`), since a plain
1540    /// `several_ok` list already reads `NULL` as every choice.
1541    pub optional: bool,
1542    /// `Missing<..>` layer of a `match_arg` / `choices` parameter (#1551): the
1543    /// R formal keeps the choice vector, the prelude skips the check for an
1544    /// omitted argument, and Rust sees `Missing::Absent`; a single choice reads
1545    /// its formal passed whole the same way ([`Self::absent_formal`], #2152).
1546    /// Set by [`classify_choice_param`].
1547    pub omittable: bool,
1548    /// `no_default` on a `match_arg` / `choices` parameter (#1828): the R
1549    /// formal is the bare name instead of the choice vector (or `NULL`), so
1550    /// an omitted argument is `Missing::Absent` under `Missing<..>` and R's
1551    /// missing-argument error otherwise. The prelude and the choice list do
1552    /// not change. Set from the parameter's `no_default` or the method's
1553    /// `no_default(p)`.
1554    pub no_default: bool,
1555    /// `Either<.., R>` layer of a `match_arg` / `choices` parameter, over a
1556    /// scalar choice (`Either<T, R>`) or a `several_ok` list
1557    /// (`Either<Vec<T>, R>` / `Either<Box<[T]>, R>`): the R-facing name of the
1558    /// `R` arm (`"a data frame"`, see [`crate::type_inspect::r_value_noun`])
1559    /// for the `@param` line. The prelude checks only character or factor
1560    /// input; anything else, `NULL` included, reaches Rust unchanged and
1561    /// decodes as `R`. Set by [`classify_choice_param`].
1562    pub either_noun: Option<String>,
1563    /// R-side checks named by the author: `inherits` / `not_inherits` /
1564    /// `no_na`.
1565    pub checks: crate::r_preconditions::ExplicitChecks,
1566    /// This parameter's own decision on its type-derived R-side checks
1567    /// (#1566), rank 1 of [`crate::r_preconditions::resolve_type_checks`]:
1568    /// a `Checked<T>` / `Unchecked<T>` marker, the per-parameter
1569    /// `preconditions` / `no_preconditions`, or a method's
1570    /// `preconditions(x)` / `no_preconditions(x)`. `Some(true)` keeps them;
1571    /// `None` defers to the function, impl and crate defaults.
1572    pub preconditions: Option<bool>,
1573    /// The declared R formal (#1842): a standalone function's
1574    /// `formal = "conf.int"` or a method's `formal(conf_int = "conf.int")`,
1575    /// with the literal that declared it. Read through
1576    /// [`crate::r_wrapper_builder::Formals`], never directly.
1577    pub formal: Option<syn::LitStr>,
1578}
1579
1580impl ParamAttrs {
1581    /// Whether this parameter's options leave its argument unforced, as a
1582    /// `Quoted` / `Quosure` parameter needs (#1835): none but `formal`, which
1583    /// only names it (#1842), and `default` when `default_ok` (a
1584    /// `Missing<Quoted>` / `Missing<Quosure>` parameter, whose default only
1585    /// writes the formal).
1586    pub(crate) fn leaves_argument_unforced(&self, default_ok: bool) -> bool {
1587        let ParamAttrs {
1588            coerce,
1589            match_arg,
1590            several_ok,
1591            choices,
1592            default,
1593            optional,
1594            omittable,
1595            no_default,
1596            either_noun,
1597            checks,
1598            preconditions,
1599            formal: _,
1600        } = self;
1601        (default_ok || default.is_none())
1602            && !coerce
1603            && !match_arg
1604            && !several_ok
1605            && choices.is_none()
1606            && !optional
1607            && !omittable
1608            && !no_default
1609            && either_noun.is_none()
1610            && checks.is_empty()
1611            && preconditions.is_none()
1612    }
1613
1614    /// Whether this is a `match_arg` / `choices` parameter.
1615    pub(crate) fn is_choice(&self) -> bool {
1616        self.match_arg || self.choices.is_some()
1617    }
1618
1619    /// The R formal default of a choice parameter, given its choice list
1620    /// (`c("a", "b")` or the write-time placeholder): `NULL` for the
1621    /// `Option<T>` form (#1473), the choice list otherwise, including
1622    /// `Missing<Option<T>>`, whose omission is reported by `Missing` (#1551).
1623    /// `None` under `no_default` (#1828): the formal is the bare name. The
1624    /// prelude spells the choice list out either way, so a `match_arg`
1625    /// placeholder then lives in the prelude only.
1626    pub(crate) fn choice_formal(&self, choices: &str) -> Option<String> {
1627        if self.no_default {
1628            None
1629        } else if self.optional && !self.omittable {
1630            Some("NULL".to_string())
1631        } else {
1632            Some(choices.to_string())
1633        }
1634    }
1635
1636    /// The value that reads as omitted (#2152): `choices`, which is then the
1637    /// formal ([`Self::choice_formal`]), for a single-choice `Missing<..>`
1638    /// parameter. Its wrapper skips the check for a value `identical()` to
1639    /// it and forwards the missing-argument sentinel in its place, so passing
1640    /// the default shown in the usage line reaches Rust as `Missing::Absent`,
1641    /// like leaving it out. `None` for every other parameter: a `several_ok`
1642    /// list reads the whole vector as every choice, a parameter without
1643    /// `Missing` reads it as the first choice, and a `no_default` one has no
1644    /// vector in its formal.
1645    pub(crate) fn absent_formal<'c>(&self, choices: &'c str) -> Option<&'c str> {
1646        (self.is_choice() && self.omittable && !self.several_ok && !self.no_default)
1647            .then_some(choices)
1648    }
1649
1650    /// The R expression for this parameter's choice list, as its formal and
1651    /// its prelude check spell it: the write-time placeholder of a
1652    /// `match_arg` parameter (keyed by the C wrapper `c_ident` and the
1653    /// parameter's [`crate::match_arg_keys::param_key`] `key`), `c("a", "b")`
1654    /// for `choices(...)`, `None` for any other parameter.
1655    pub(crate) fn choice_list(&self, c_ident: &str, key: &str) -> Option<String> {
1656        if self.match_arg {
1657            return Some(crate::match_arg_keys::choices_placeholder(c_ident, key));
1658        }
1659        let quoted: Vec<String> = self
1660            .choices
1661            .as_ref()?
1662            .iter()
1663            .map(|c| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"", c))
    })format!("\"{c}\""))
1664            .collect();
1665        Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("c({0})", quoted.join(", ")))
    })format!("c({})", quoted.join(", ")))
1666    }
1667
1668    /// The text after the quoted choice list in the auto-generated `@param`
1669    /// line (`One of "a", "b"<suffix>.`): the other accepted values, then an
1670    /// omission note. `, or NULL for no choice` for `Option<T>`,
1671    /// `, or a data frame` for `Either<T, DataFrame>`,
1672    /// `, a data frame, or NULL for no choice` for both,
1673    /// `, or NULL; omitting the argument or passing the whole list means no
1674    /// choice` for `Missing<Option<T>>`, `; omitting the argument or passing
1675    /// the whole list means no choice` for `Missing<T>` (#2152; see
1676    /// [`Self::absent_formal`]), and nothing for a plain choice. A
1677    /// `Missing<Vec<T>>` `several_ok` list, whose whole vector selects every
1678    /// choice, says only `; omitting the argument means no choice`. Under
1679    /// `no_default` (#1828) the omission note goes (the author documents what
1680    /// omission means) and the rest stays.
1681    pub(crate) fn choice_doc_suffix(&self) -> String {
1682        let null_word = if self.omittable {
1683            "NULL"
1684        } else {
1685            "NULL for no choice"
1686        };
1687        let mut suffix =
1688            choice_alternatives_suffix(self.either_noun.as_deref(), self.optional, null_word);
1689        if self.omittable && !self.no_default {
1690            suffix.push_str(if self.several_ok {
1691                "; omitting the argument means no choice"
1692            } else {
1693                "; omitting the argument or passing the whole list means no choice"
1694            });
1695        }
1696        suffix
1697    }
1698
1699    /// The auto-generated `@param` text of a `choices(...)` parameter:
1700    /// `One of "a", "b"<suffix>.` (`One or more of` for `several_ok`), with
1701    /// [`Self::choice_doc_suffix`]. `None` without a literal choice list; a
1702    /// `match_arg` parameter's text is only known at write time (its
1703    /// placeholder, #210).
1704    pub(crate) fn literal_choices_doc(&self) -> Option<String> {
1705        let choices = self.choices.as_ref()?;
1706        let quoted: Vec<String> = choices.iter().map(|c| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"", c))
    })format!("\"{c}\"")).collect();
1707        let prefix = if self.several_ok {
1708            "One or more of"
1709        } else {
1710            "One of"
1711        };
1712        Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{2} {0}{1}.", quoted.join(", "),
                self.choice_doc_suffix(), prefix))
    })format!(
1713            "{prefix} {}{}.",
1714            quoted.join(", "),
1715            self.choice_doc_suffix()
1716        ))
1717    }
1718
1719    /// How the C wrapper decodes this parameter when a plain `TryFromSexp`
1720    /// cannot: a `match_arg` parameter with a `Missing` / `Option` / `Either`
1721    /// layer (`MatchArgSeveral` for a `several_ok` list, `Either<Vec<T>, R>`
1722    /// included), or a `choices` parameter with an `Either` layer (its string
1723    /// type or `several_ok` string list on the left). `None` for every other
1724    /// parameter (a `choices` string type converts through `TryFromSexp` with
1725    /// its `Missing` / `Option` layers; a plain `several_ok` container has its
1726    /// own path).
1727    pub(crate) fn layered_leaf(&self) -> Option<crate::rust_conversion_builder::ChoiceLeaf> {
1728        use crate::rust_conversion_builder::ChoiceLeaf;
1729        let either = self.either_noun.is_some();
1730        if self.match_arg && (self.optional || self.omittable || either) {
1731            return Some(if self.several_ok {
1732                ChoiceLeaf::MatchArgSeveral
1733            } else {
1734                ChoiceLeaf::MatchArg
1735            });
1736        }
1737        let choices = self.choices.as_ref().filter(|_| either)?;
1738        Some(ChoiceLeaf::Literal {
1739            choices: choices.clone(),
1740            several: self.several_ok,
1741        })
1742    }
1743}
1744
1745/// The other values a choice parameter accepts, after its quoted choices:
1746/// `, or a data frame` for an `Either<.., R>` layer (`noun`, the `R` arm),
1747/// `, or <null_word>` for an `Option<..>` layer (`optional`), `, a data frame,
1748/// or <null_word>` for both, nothing for neither. Shared by the parameter's
1749/// `@param` line ([`ParamAttrs::choice_doc_suffix`], which passes
1750/// `NULL for no choice`) and the argument error of an `Either` choice
1751/// parameter (which passes `NULL`), so the two word the alternatives alike.
1752pub(crate) fn choice_alternatives_suffix(
1753    noun: Option<&str>,
1754    optional: bool,
1755    null_word: &str,
1756) -> String {
1757    match (noun, optional) {
1758        (None, false) => String::new(),
1759        (Some(only), false) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", or {0}", only))
    })format!(", or {only}"),
1760        (None, true) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", or {0}", null_word))
    })format!(", or {null_word}"),
1761        (Some(noun), true) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(", {0}, or {1}", noun, null_word))
    })format!(", {noun}, or {null_word}"),
1762    }
1763}
1764
1765/// Record the layers of a `match_arg` / `choices` parameter's type on its
1766/// [`ParamAttrs`] (`optional`, `omittable`, `either_noun`) and reject the
1767/// shapes the codegen cannot serve, now that the type is known. Shared by the
1768/// standalone-fn parser and [`finalize_method_param_attrs`] so the two cannot
1769/// classify a type differently. A no-op for other parameters.
1770///
1771/// Accepted, outermost layer first: an optional `Missing<..>` (#1551), an
1772/// optional `Option<..>` (#1473), an optional `Either<.., R>`, then the
1773/// scalar choice type (so `T`, `Option<T>`, `Missing<Option<T>>`,
1774/// `Either<T, R>`, `Option<Either<T, R>>`, ...). A `several_ok` parameter
1775/// takes any container (`Vec<T>`, `Box<[T]>`, `&[T]`, `[T; N]`) bare, and
1776/// only the owned `Vec<T>` / `Box<[T]>` under a layer: `Missing<Vec<T>>`,
1777/// `Either<Vec<T>, R>` (#1612) and the `Missing` / `Option` stacks over that
1778/// `Either`, which follow the scalar `Either` rule. A bare
1779/// `Option<Vec<T>>` stays rejected: its `NULL` already means every choice,
1780/// while under `Either` it goes to `R`. `has_default` says whether the
1781/// parameter carries a `default`, which a choice with a `NULL` formal
1782/// (`Option<..>` without `Missing`) cannot, nor one under `Missing<..>`
1783/// ([`missing_choice_default_msg`]).
1784pub(crate) fn classify_choice_param(
1785    attrs: &mut ParamAttrs,
1786    param_name: &str,
1787    ty: &syn::Type,
1788    has_default: bool,
1789) -> syn::Result<()> {
1790    use syn::spanned::Spanned;
1791    if !attrs.is_choice() {
1792        return Ok(());
1793    }
1794    let layers = crate::type_inspect::choice_layers(ty);
1795    if let Some(layer) = crate::type_inspect::choice_layer_name(layers.value) {
1796        return Err(syn::Error::new(
1797            ty.span(),
1798            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("match_arg/choices parameter `{0}` has `{1}<..>` in an unsupported position; the wrappers go outermost first as `Missing<Option<Either<T, R>>>` (each one optional, the choice type `T` on the left of `Either`)",
                param_name, layer))
    })format!(
1799                "match_arg/choices parameter `{param_name}` has `{layer}<..>` in an unsupported \
1800                 position; the wrappers go outermost first as `Missing<Option<Either<T, R>>>` \
1801                 (each one optional, the choice type `T` on the left of `Either`)"
1802            ),
1803        ));
1804    }
1805    if attrs.several_ok {
1806        let either = layers.either_right.is_some();
1807        // Under `Either`, `NULL` goes to the `R` arm, so `Option<Either<Vec<T>, R>>`
1808        // has a `None` of its own; a bare `Option<Vec<T>>` does not.
1809        if layers.nullable && !either {
1810            return Err(syn::Error::new(
1811                ty.span(),
1812                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("several_ok parameter `{0}` cannot be `Option<..>`; an omitted argument and NULL both select every choice. Use `Missing<Vec<T>>` to tell an omitted argument apart",
                param_name))
    })format!(
1813                    "several_ok parameter `{param_name}` cannot be `Option<..>`; an omitted \
1814                     argument and NULL both select every choice. Use `Missing<Vec<T>>` to \
1815                     tell an omitted argument apart"
1816                ),
1817            ));
1818        }
1819        if !is_vector_like_type(layers.value) {
1820            // Under `Missing` / `Either` only the owned containers decode.
1821            let containers = if layers.missing || either {
1822                "`Vec<T>` or `Box<[T]>`"
1823            } else {
1824                "`Vec<T>`, `Box<[T]>`, `&[T]`, or `[T; N]`"
1825            };
1826            return Err(syn::Error::new(
1827                ty.span(),
1828                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("several_ok requires a vector type on parameter `{0}`; several_ok enables multi-value match.arg which returns a character vector. Use {1} instead of a scalar type",
                param_name, containers))
    })format!(
1829                    "several_ok requires a vector type on parameter `{param_name}`; \
1830                     several_ok enables multi-value match.arg which returns a character vector. \
1831                     Use {containers} instead of a scalar type"
1832                ),
1833            ));
1834        }
1835        let owned = #[allow(non_exhaustive_omitted_patterns)] match crate::classify_several_ok_container(layers.value)
    {
    Some((crate::SeveralOkContainer::Vec |
        crate::SeveralOkContainer::BoxedSlice, _)) => true,
    _ => false,
}matches!(
1836            crate::classify_several_ok_container(layers.value),
1837            Some((
1838                crate::SeveralOkContainer::Vec | crate::SeveralOkContainer::BoxedSlice,
1839                _
1840            ))
1841        );
1842        if either && !owned {
1843            return Err(syn::Error::new(
1844                ty.span(),
1845                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("several_ok parameter `{0}` can take another kind of value only as `Either<Vec<T>, R>` or `Either<Box<[T]>, R>`",
                param_name))
    })format!(
1846                    "several_ok parameter `{param_name}` can take another kind of value only \
1847                     as `Either<Vec<T>, R>` or `Either<Box<[T]>, R>`"
1848                ),
1849            ));
1850        }
1851        if layers.missing && !owned {
1852            return Err(syn::Error::new(
1853                ty.span(),
1854                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("several_ok parameter `{0}` can be omittable only as `Missing<Vec<T>>` or `Missing<Box<[T]>>`",
                param_name))
    })format!(
1855                    "several_ok parameter `{param_name}` can be omittable only as \
1856                     `Missing<Vec<T>>` or `Missing<Box<[T]>>`"
1857                ),
1858            ));
1859        }
1860    }
1861    // A `NULL` formal (`Option<..>` without `Missing`) makes a `default`
1862    // meaningless, for a scalar choice and for `Option<Either<Vec<T>, R>>`.
1863    if layers.nullable && !layers.missing && has_default {
1864        return Err(syn::Error::new(
1865            ty.span(),
1866            optional_choice_default_msg(param_name),
1867        ));
1868    }
1869    if layers.missing && has_default {
1870        return Err(syn::Error::new(
1871            ty.span(),
1872            missing_choice_default_msg(param_name),
1873        ));
1874    }
1875    attrs.optional = layers.nullable;
1876    attrs.omittable = layers.missing;
1877    attrs.either_noun = layers.either_right.map(crate::type_inspect::r_value_noun);
1878    Ok(())
1879}
1880
1881/// The entries of `per_param` in the order their parameters appear in
1882/// `inputs`. Code that emits one line or item per parameter walks this, never
1883/// the map itself: a `HashMap`'s order differs between maps and between
1884/// compiler runs, so the generated R wrapper (and the Rust items) would too.
1885pub(crate) fn per_param_in_signature_order<'a>(
1886    inputs: &'a syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
1887    per_param: &'a std::collections::HashMap<String, ParamAttrs>,
1888) -> impl Iterator<Item = (&'a String, &'a ParamAttrs)> {
1889    inputs.iter().filter_map(|arg| {
1890        let syn::FnArg::Typed(pt) = arg else {
1891            return None;
1892        };
1893        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1894            return None;
1895        };
1896        per_param.get_key_value(&crate::naming::ident_name(&pat_ident.ident))
1897    })
1898}
1899
1900/// The non-empty [`ParamAttrs::checks`] of `per_param` (keyed by Rust name),
1901/// re-keyed by R formal ([`crate::r_wrapper_builder::Formals::of`]) for
1902/// [`crate::r_preconditions::PreconditionOptions::explicit`].
1903pub(crate) fn explicit_checks_by_r_name(
1904    per_param: &std::collections::HashMap<String, ParamAttrs>,
1905) -> std::collections::HashMap<String, crate::r_preconditions::ExplicitChecks> {
1906    let formals = crate::r_wrapper_builder::Formals::from_param_attrs(per_param);
1907    per_param
1908        .iter()
1909        .filter(|(_, a)| !a.checks.is_empty())
1910        .map(|(name, a)| (formals.of(name), a.checks.clone()))
1911        .collect()
1912}
1913
1914/// The generated `@param` text of an undocumented `Missing<..>` parameter
1915/// with a formal default (#2161): the default only shows in the usage line,
1916/// and an omitted argument still reaches Rust as `Missing::Absent`.
1917pub(crate) const MISSING_DEFAULT_PARAM_DOC: &str =
1918    "Omitting the argument means the function's own handling.";
1919
1920/// R formal → [`MISSING_DEFAULT_PARAM_DOC`] for each `Missing<..>` parameter
1921/// of `inputs` that has a formal default in `defaults` (keyed by Rust name,
1922/// as the per-parameter options are) and is no choice parameter of
1923/// `per_param`, whose own line names the choices. The key is the formal
1924/// `formals` gives the parameter, a declared `formal` included (#1842).
1925/// `Missing<Quoted>` / `Missing<Quosure>` count too: their default is never
1926/// evaluated either.
1927pub(crate) fn missing_default_param_docs(
1928    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
1929    per_param: &std::collections::HashMap<String, ParamAttrs>,
1930    defaults: &std::collections::HashMap<String, String>,
1931    formals: &crate::r_wrapper_builder::Formals,
1932) -> std::collections::HashMap<String, String> {
1933    inputs
1934        .iter()
1935        .filter_map(|arg| {
1936            let syn::FnArg::Typed(pt) = arg else {
1937                return None;
1938            };
1939            let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1940                return None;
1941            };
1942            let rust_name = crate::naming::ident_name(&pat_ident.ident);
1943            if !is_missing_type(pt.ty.as_ref())
1944                || per_param.get(&rust_name).is_some_and(ParamAttrs::is_choice)
1945            {
1946                return None;
1947            }
1948            defaults.contains_key(&rust_name).then(|| {
1949                (
1950                    formals.of(&rust_name),
1951                    MISSING_DEFAULT_PARAM_DOC.to_string(),
1952                )
1953            })
1954        })
1955        .collect()
1956}
1957
1958/// The parameters of `per_param` (keyed by Rust name) whose wrapper reads a
1959/// value `identical()` to the formal as omitted
1960/// ([`ParamAttrs::absent_formal`], #2152), as R formal → the formal's
1961/// default: the `match_arg` placeholder keyed by the C wrapper `c_ident`, or
1962/// the literal `choices(...)` vector. For
1963/// [`crate::r_wrapper_builder::RArgumentBuilder::with_absent_formals`].
1964pub(crate) fn absent_formals_by_r_name(
1965    per_param: &std::collections::HashMap<String, ParamAttrs>,
1966    c_ident: &str,
1967) -> std::collections::HashMap<String, String> {
1968    let formals = crate::r_wrapper_builder::Formals::from_param_attrs(per_param);
1969    per_param
1970        .iter()
1971        .filter_map(|(name, a)| {
1972            let key = crate::match_arg_keys::param_key(name);
1973            let choices = a.choice_list(c_ident, &key)?;
1974            let absent = a.absent_formal(&choices)?.to_string();
1975            Some((formals.of(name), absent))
1976        })
1977        .collect()
1978}
1979
1980/// Check an impl or trait method's per-parameter attributes against its
1981/// signature, now that it is known. Every parameter a method-level
1982/// `match_arg(...)` / `choices(...)` / `no_default(...)` / `inherits(...)` /
1983/// `not_inherits(...)` / `no_na(...)` / `preconditions(...)` /
1984/// `no_preconditions(...)` / `formal(...)` names must exist (a typo would
1985/// otherwise drop the option without a word); then a `formal` is refused on
1986/// the `&Dots` parameter, whose formal is `...`, a `no_default(p)` on a parameter
1987/// that is no choice or has a default ([`no_default_conflict`]), the
1988/// choice parameters are classified (see [`classify_choice_param`]), a class
1989/// in both `inherits` and `not_inherits` is refused
1990/// ([`crate::r_preconditions::ExplicitChecks::class_conflict`]), and each
1991/// parameter's own precondition decision is checked
1992/// ([`check_param_preconditions`]). The standalone-fn path does the same
1993/// while parsing; this is the twin for method-level attributes, whose
1994/// parameter names arrive before the types. The inherent-impl and trait-impl
1995/// parsers both call it, so they reject an unknown name the same way.
1996/// `inputs` has the parameter markers peeled; `markers` are the ones peeled
1997/// (empty on a trait impl, whose signature is the trait's). `span` is where
1998/// the errors point: the first such attribute, else the method name.
1999pub(crate) fn finalize_method_param_attrs(
2000    per_param: &mut std::collections::HashMap<String, ParamAttrs>,
2001    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
2002    markers: &[(String, Vec<crate::type_inspect::ParamMarker>)],
2003    defaults: &std::collections::HashMap<String, String>,
2004    span: proc_macro2::Span,
2005) -> syn::Result<()> {
2006    let sig_names: std::collections::HashSet<String> = inputs
2007        .iter()
2008        .filter_map(|arg| match arg {
2009            syn::FnArg::Typed(pt) => match pt.pat.as_ref() {
2010                syn::Pat::Ident(pat_ident) => Some(crate::naming::ident_name(&pat_ident.ident)),
2011                _ => None,
2012            },
2013            syn::FnArg::Receiver(_) => None,
2014        })
2015        .collect();
2016    let mut unknown: Vec<&String> = per_param
2017        .iter()
2018        .filter(|(name, a)| {
2019            (a.match_arg
2020                || a.choices.is_some()
2021                || a.no_default
2022                || !a.checks.is_empty()
2023                || a.preconditions.is_some())
2024                && !sig_names.contains(name.as_str())
2025        })
2026        .map(|(name, _)| name)
2027        .collect();
2028    unknown.sort();
2029    if let Some(first) = unknown.first() {
2030        return Err(syn::Error::new(
2031            span,
2032            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("match_arg/choices/no_default/inherits/not_inherits/no_na/(no_)preconditions references non-existent parameter `{0}`",
                first))
    })format!(
2033                "match_arg/choices/no_default/inherits/not_inherits/no_na/(no_)preconditions \
2034                 references non-existent parameter `{first}`"
2035            ),
2036        ));
2037    }
2038    let mut unknown_formals: Vec<(&String, &syn::LitStr)> = per_param
2039        .iter()
2040        .filter(|(name, _)| !sig_names.contains(name.as_str()))
2041        .filter_map(|(name, a)| Some((name, a.formal.as_ref()?)))
2042        .collect();
2043    unknown_formals.sort_by_key(|(name, _)| *name);
2044    if let Some((name, formal)) = unknown_formals.first() {
2045        return Err(syn::Error::new(
2046            formal.span(),
2047            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`formal({1} = {0:?})` names a parameter this method doesn\'t have: there is no `{1}`",
                formal.value(), name))
    })format!(
2048                "`formal({name} = {:?})` names a parameter this method doesn't have: there is \
2049                 no `{name}`",
2050                formal.value()
2051            ),
2052        ));
2053    }
2054    // An `NArgs` parameter (#1860) takes no option: the method-level forms
2055    // name it like any parameter, and the wrapper, which never makes it a
2056    // formal, would silently drop them.
2057    check_nargs_params(inputs, |name| {
2058        per_param.contains_key(name) || defaults.contains_key(name)
2059    })?;
2060    // Nor does a `LazyDots` parameter (#1892), R's `...` passed unforced.
2061    check_lazy_dots_params(inputs, |name| {
2062        per_param.contains_key(name) || defaults.contains_key(name)
2063    })?;
2064    for arg in inputs {
2065        let syn::FnArg::Typed(pt) = arg else {
2066            continue;
2067        };
2068        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
2069            continue;
2070        };
2071        let name = crate::naming::ident_name(&pat_ident.ident);
2072        let Some(attrs) = per_param.get_mut(&name) else {
2073            continue;
2074        };
2075        if let Some(formal) = &attrs.formal
2076            && is_dots_type(pt.ty.as_ref())
2077        {
2078            return Err(dots_formal_error(formal, &name));
2079        }
2080        let has_default = attrs.default.is_some() || defaults.contains_key(&name);
2081        if attrs.no_default
2082            && let Some(msg) = no_default_conflict(&name, attrs.is_choice(), has_default, true)
2083        {
2084            return Err(syn::Error::new(span, msg));
2085        }
2086        classify_choice_param(attrs, &name, pt.ty.as_ref(), has_default)?;
2087        if let Some(msg) = attrs.checks.class_conflict(&name) {
2088            return Err(syn::Error::new(span, msg));
2089        }
2090        let param_markers = markers
2091            .iter()
2092            .find(|(n, _)| *n == name)
2093            .map_or(&[][..], |(_, m)| m.as_slice());
2094        check_param_preconditions(attrs, param_markers, &name, pt.ty.as_ref(), true)?;
2095    }
2096    Ok(())
2097}
2098
2099/// A `default` on a choice parameter under `Missing<..>` (#2161). An omitted
2100/// argument reaches Rust as `Missing::Absent`, so the default would pick no
2101/// choice; on a `choices(...)` list it would also replace the formal, the
2102/// choice vector that [`ParamAttrs::absent_formal`] compares a supplied value
2103/// with (#2152).
2104fn missing_choice_default_msg(param_name: &str) -> String {
2105    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`Missing<..>` parameter `{0}` with match_arg/choices cannot have a default; an omitted argument reaches Rust as `Missing::Absent`, so a default picks no choice, and the formal already shows the choices. Drop the `Missing` to make a choice the default, or drop the default",
                param_name))
    })format!(
2106        "`Missing<..>` parameter `{param_name}` with match_arg/choices cannot have a default; \
2107         an omitted argument reaches Rust as `Missing::Absent`, so a default picks no choice, \
2108         and the formal already shows the choices. Drop the `Missing` to make a choice the \
2109         default, or drop the default"
2110    )
2111}
2112
2113fn optional_choice_default_msg(param_name: &str) -> String {
2114    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`Option<T>` parameter `{0}` with match_arg/choices cannot have a default; its R formal defaults to NULL, which means no choice. Drop the `Option` to make a choice the default, or drop the default",
                param_name))
    })format!(
2115        "`Option<T>` parameter `{param_name}` with match_arg/choices cannot have a default; \
2116         its R formal defaults to NULL, which means no choice. Drop the `Option` to make a \
2117         choice the default, or drop the default"
2118    )
2119}
2120
2121/// Parses a Rust `fn` item from a token stream, performing all normalizations
2122/// required by the `#[miniextendr]` codegen pipeline.
2123///
2124/// # Normalizations performed
2125///
2126/// 1. **Variadic (`...`) rewriting**: Replaces Rust variadic syntax with a typed
2127///    `&miniextendr_api::dots::Dots` parameter. Named dots (`my_dots: ...`) preserve
2128///    the user's identifier; `_: ...` becomes `__miniextendr_dots`.
2129/// 2. **Wildcard pattern renaming**: `_` parameter patterns become `__unused0`,
2130///    `__unused1`, etc., so they can be passed by name to the C wrapper.
2131/// 3. **Destructuring expansion**: Tuple/struct destructuring patterns are replaced
2132///    with synthetic identifiers (`__param_0`, ...) and a `let` binding is prepended
2133///    to the function body.
2134/// 4. **Per-parameter attribute consumption**: `#[miniextendr(coerce)]`,
2135///    `#[miniextendr(match_arg)]`, `#[miniextendr(default = "...")]`, and
2136///    `#[miniextendr(choices(...))]` are consumed from parameters and recorded in
2137///    the corresponding `per_param_*` fields.
2138/// 5. **Validation**: Rejects `#[export_name]` on non-extern functions, rejects
2139///    unsupported parameter patterns, and validates that defaults reference existing
2140///    parameter names.
2141impl syn::parse::Parse for MiniextendrFunctionParsed {
2142    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
2143        use syn::spanned::Spanned;
2144
2145        let mut item: syn::ItemFn = input.parse()?;
2146
2147        // dots support: replace `...` with `&Dots` (the dots parameter itself
2148        // is found by type once wildcard patterns have their names).
2149        rewrite_variadic_dots(&mut item.sig)?;
2150
2151        // Reject #[export_name] for regular functions (not extern "C-unwind").
2152        // For extern functions, #[export_name] can be used as an alternative to #[no_mangle].
2153        let is_extern = item.sig.abi.is_some();
2154        if !is_extern {
2155            for attr in &item.attrs {
2156                if attr.path().is_ident("export_name") {
2157                    return Err(syn::Error::new_spanned(
2158                        attr,
2159                        "#[export_name] is not supported with #[miniextendr] on regular functions; \
2160                         use `#[miniextendr(c_symbol = \"...\")]` to customize the C symbol name. \
2161                         For extern \"C-unwind\" functions, #[export_name] is allowed.",
2162                    ));
2163                }
2164            }
2165        }
2166
2167        // Transform `_` wildcard patterns to synthetic identifiers, and consume
2168        // per-parameter `#[miniextendr(coerce)]`, `#[miniextendr(default = "...")]`,
2169        // and `#[miniextendr(choices(...))]` attributes.
2170        let mut per_param: std::collections::HashMap<String, ParamAttrs> =
2171            std::collections::HashMap::new();
2172        let mut per_param_default_spans: std::collections::HashMap<String, proc_macro2::Span> =
2173            std::collections::HashMap::new();
2174        let mut unused_counter = 0usize;
2175        let mut pattern_destructures: Vec<(Box<syn::Pat>, syn::Ident)> = Vec::new();
2176        let mut param_markers: Vec<(String, Vec<crate::type_inspect::ParamMarker>)> = Vec::new();
2177        for arg in &mut item.sig.inputs {
2178            let syn::FnArg::Typed(pat_type) = arg else {
2179                // Self parameters are not allowed in standalone functions.
2180                // Users should use #[miniextendr(env|r6|s3|s4|s7)] on impl blocks instead.
2181                // The error is raised in lib.rs c_wrapper_inputs generation.
2182                continue;
2183            };
2184
2185            // Consume the per-parameter miniextendr attributes (coerce,
2186            // match_arg, choices, several_ok, default, inherits, not_inherits,
2187            // no_na), merging
2188            // them when a parameter carries several; keep every other attribute.
2189            let mut param_attr = PerParamMiniextendrAttr::default();
2190            let mut kept_attrs = Vec::with_capacity(pat_type.attrs.len());
2191            for attr in std::mem::take(&mut pat_type.attrs) {
2192                match parse_per_param_attr(&attr)? {
2193                    Some(parsed) => param_attr
2194                        .merge(parsed)
2195                        .map_err(|msg| syn::Error::new_spanned(&attr, msg))?,
2196                    None => kept_attrs.push(attr),
2197                }
2198            }
2199            pat_type.attrs = kept_attrs;
2200            let PerParamMiniextendrAttr {
2201                has_coerce: had_coerce_attr,
2202                has_match_arg: had_match_arg_attr,
2203                default_value: default_with_span,
2204                choices: had_choices,
2205                has_several_ok: had_several_ok,
2206                has_no_default: had_no_default,
2207                checks: had_checks,
2208                preconditions: had_preconditions,
2209                formal: had_formal,
2210            } = param_attr;
2211
2212            // Parameter markers (`Checked<T>` / `Unchecked<T>`, #1566): the
2213            // parameter converts as the inner type, which every check below
2214            // and every R-side consumer sees; the item keeps the marker, and
2215            // the C wrapper wraps the converted value before the call.
2216            let (markers, inner_ty) =
2217                crate::type_inspect::peel_param_markers(pat_type.ty.as_ref())?;
2218            let inner_ty = inner_ty.clone();
2219            if is_extern && let Some(marker) = markers.first() {
2220                return Err(syn::Error::new_spanned(
2221                    &pat_type.ty,
2222                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}<T>` on an `extern \"C-unwind\"` function: it takes the R values as they are, with no generated conversion to unwrap the marker; take the inner type",
                marker.name()))
    })format!(
2223                        "`{}<T>` on an `extern \"C-unwind\"` function: it takes the R values \
2224                         as they are, with no generated conversion to unwrap the marker; take \
2225                         the inner type",
2226                        marker.name()
2227                    ),
2228                ));
2229            }
2230            // `NArgs` (#1860) is filled by the generated R wrapper, which an
2231            // extern function does not have.
2232            if is_extern && crate::type_inspect::is_nargs_marker(&inner_ty) {
2233                return Err(syn::Error::new_spanned(
2234                    &pat_type.ty,
2235                    "`NArgs` on an `extern \"C-unwind\"` function: the generated R wrapper \
2236                     passes `nargs()` for it, and an extern function has none (`.Call()` reaches \
2237                     it directly). Drop the `extern \"C-unwind\"` to let the macro generate the \
2238                     wrapper, or take a `SEXP` and pass the count yourself",
2239                ));
2240            }
2241
2242            // Validate type-based constraints (Missing nesting, Missing<Dots>)
2243            validate_param_type(&inner_ty, pat_type.ty.span())?;
2244
2245            // Resolve the Rust parameter name — either the user's identifier,
2246            // or a synthesized one for wildcard / destructuring patterns.
2247            let param_name: String = match pat_type.pat.as_ref() {
2248                syn::Pat::Ident(pat_ident) => crate::naming::ident_name(&pat_ident.ident),
2249                syn::Pat::Wild(_) => {
2250                    let synthetic_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__unused{0}", unused_counter))
    })format!("__unused{}", unused_counter);
2251                    unused_counter += 1;
2252                    let synthetic_ident = syn::Ident::new(&synthetic_name, pat_type.pat.span());
2253                    *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2254                        attrs: ::alloc::vec::Vec::new()vec![],
2255                        by_ref: None,
2256                        mutability: None,
2257                        ident: synthetic_ident,
2258                        subpat: None,
2259                    });
2260                    synthetic_name
2261                }
2262                syn::Pat::Tuple(_) | syn::Pat::TupleStruct(_) | syn::Pat::Struct(_) => {
2263                    let synthetic_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__param_{0}", unused_counter))
    })format!("__param_{}", unused_counter);
2264                    unused_counter += 1;
2265                    let synthetic_ident = syn::Ident::new(&synthetic_name, pat_type.pat.span());
2266                    let original_pat = pat_type.pat.clone();
2267                    *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2268                        attrs: ::alloc::vec::Vec::new()vec![],
2269                        by_ref: None,
2270                        mutability: None,
2271                        ident: synthetic_ident.clone(),
2272                        subpat: None,
2273                    });
2274                    pattern_destructures.push((original_pat, synthetic_ident));
2275                    synthetic_name
2276                }
2277                _ => {
2278                    return Err(syn::Error::new(
2279                        pat_type.pat.span(),
2280                        "miniextendr parameters must be identifiers or destructuring patterns (tuple, struct)",
2281                    ));
2282                }
2283            };
2284            // The marker and the per-parameter keyword are one decision.
2285            let preconditions = merge_param_policy(
2286                &markers,
2287                had_preconditions,
2288                &param_name,
2289                false,
2290                &pat_type.ty,
2291            )?;
2292
2293            // Validate per-parameter attribute conflicts (coerce+match_arg, coerce+choices, etc.)
2294            let per_param_combined = PerParamMiniextendrAttr {
2295                has_coerce: had_coerce_attr,
2296                has_match_arg: had_match_arg_attr,
2297                default_value: default_with_span.clone(),
2298                choices: had_choices.clone(),
2299                has_several_ok: had_several_ok,
2300                has_no_default: had_no_default,
2301                checks: had_checks.clone(),
2302                preconditions,
2303                formal: had_formal.clone(),
2304            };
2305            validate_per_param_attr_conflicts(
2306                &per_param_combined,
2307                &param_name,
2308                is_dots_type(&inner_ty),
2309                pat_type.ty.span(),
2310            )?;
2311
2312            // Record per-parameter attrs in one entry instead of five side-tables.
2313            if had_coerce_attr
2314                || had_match_arg_attr
2315                || had_several_ok
2316                || had_choices.is_some()
2317                || default_with_span.is_some()
2318                || !had_checks.is_empty()
2319                || preconditions.is_some()
2320                || had_formal.is_some()
2321            {
2322                let entry = per_param.entry(param_name.clone()).or_default();
2323                entry.formal = had_formal;
2324                entry.checks = had_checks;
2325                entry.coerce = had_coerce_attr;
2326                entry.match_arg = had_match_arg_attr;
2327                entry.several_ok = had_several_ok;
2328                entry.no_default = had_no_default;
2329                entry.choices = had_choices;
2330                entry.preconditions = preconditions;
2331                if let Some((default, span)) = default_with_span {
2332                    entry.default = Some(default);
2333                    per_param_default_spans.insert(param_name.clone(), span);
2334                }
2335                // The `Option` / `Missing` layers of a choice parameter (#1473, #1551).
2336                let has_default = entry.default.is_some();
2337                classify_choice_param(entry, &param_name, &inner_ty, has_default)?;
2338                check_param_preconditions(entry, &markers, &param_name, &inner_ty, false)?;
2339            }
2340            if !markers.is_empty() {
2341                param_markers.push((param_name, markers));
2342            }
2343        }
2344
2345        // Insert destructuring let-bindings for pattern parameters at the start of the function body
2346        for (pat, ident) in pattern_destructures.iter().rev() {
2347            item.block.stmts.insert(
2348                0,
2349                ::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_ident(&mut _s, "let");
        ::quote::ToTokens::to_tokens(&pat, &mut _s);
        ::quote::__private::push_eq(&mut _s);
        ::quote::ToTokens::to_tokens(&ident, &mut _s);
        ::quote::__private::push_semi(&mut _s);
        _s
    })syn::parse_quote! {
2350                    let #pat = #ident;
2351                },
2352            );
2353        }
2354
2355        // Validate: all defaults reference existing parameters
2356        let param_names: std::collections::HashSet<String> = item
2357            .sig
2358            .inputs
2359            .iter()
2360            .filter_map(|input| {
2361                if let syn::FnArg::Typed(pat_type) = input
2362                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
2363                {
2364                    Some(crate::naming::ident_name(&pat_ident.ident))
2365                } else {
2366                    None
2367                }
2368            })
2369            .collect();
2370
2371        let mut invalid_params: Vec<String> = per_param
2372            .iter()
2373            .filter_map(|(name, attrs)| {
2374                if attrs.default.is_some() && !param_names.contains(name) {
2375                    Some(name.clone())
2376                } else {
2377                    None
2378                }
2379            })
2380            .collect();
2381        invalid_params.sort();
2382
2383        if !invalid_params.is_empty() {
2384            // Use the span of the first invalid param's attribute for the error
2385            let error_span = invalid_params
2386                .first()
2387                .and_then(|p| per_param_default_spans.get(p).copied())
2388                .unwrap_or_else(|| item.sig.ident.span());
2389            return Err(syn::Error::new(
2390                error_span,
2391                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("default attribute(s) reference non-existent parameter(s): {0}",
                invalid_params.join(", ")))
    })format!(
2392                    "default attribute(s) reference non-existent parameter(s): {}",
2393                    invalid_params.join(", ")
2394                ),
2395            ));
2396        }
2397
2398        let dots = find_dots_param(&item.sig.inputs)?;
2399        crate::r_wrapper_builder::check_r_formals(
2400            &item.sig.inputs,
2401            &crate::r_wrapper_builder::Formals::from_param_attrs(&per_param),
2402            &[],
2403        )?;
2404        let inputs = peeled_inputs(&item.sig.inputs, &param_markers)?;
2405
2406        Ok(Self {
2407            item,
2408            inputs,
2409            param_markers,
2410            dots,
2411            per_param,
2412        })
2413    }
2414}
2415
2416/// Accessors and codegen helpers for [`MiniextendrFunctionParsed`].
2417///
2418/// Accessors are split into two groups:
2419/// - **Parsed metadata**: dots, coerce, match_arg, choices, and defaults from
2420///   per-parameter `#[miniextendr(...)]` attributes.
2421/// - **Signature components**: attrs, vis, abi, ident, generics, inputs, output
2422///   from the normalized `syn::ItemFn`.
2423///
2424/// Codegen helpers produce identifiers and perform mutations needed by the
2425/// `#[miniextendr]` expansion pipeline.
2426impl MiniextendrFunctionParsed {
2427    // region: Accessors for parsed metadata
2428
2429    /// Whether the function takes `...`: Rust `...` or an explicit `&Dots`
2430    /// parameter, at any position.
2431    pub(crate) fn has_dots(&self) -> bool {
2432        self.dots.is_some()
2433    }
2434
2435    /// The Rust binding of the dots: the user's name (`args: ...`,
2436    /// `rest: &Dots`), or `__miniextendr_dots` for `_: ...`.
2437    pub(crate) fn dots_ident(&self) -> Option<&syn::Ident> {
2438        self.dots.as_ref()
2439    }
2440
2441    /// Check if a parameter is the dots (`...`) param, whose R formal is `...`.
2442    pub(crate) fn is_dots_param(&self, ident: &syn::Ident) -> bool {
2443        self.dots_ident() == Some(ident)
2444    }
2445
2446    /// Whether a parameter carried any per-parameter `#[miniextendr(...)]`
2447    /// option. A `Call` / `CallerCall` marker (#1566) is bound from the call
2448    /// slot, not from an R argument, so none of them apply to it.
2449    pub(crate) fn has_param_attrs(&self, param_name: &str) -> bool {
2450        self.per_param.contains_key(param_name)
2451    }
2452
2453    /// Check if a parameter name had `#[miniextendr(coerce)]` attribute.
2454    pub(crate) fn has_coerce_attr(&self, param_name: &str) -> bool {
2455        self.per_param.get(param_name).is_some_and(|a| a.coerce)
2456    }
2457
2458    /// Check if a parameter name had `#[miniextendr(match_arg)]` attribute.
2459    pub(crate) fn has_match_arg_attr(&self, param_name: &str) -> bool {
2460        self.per_param.get(param_name).is_some_and(|a| a.match_arg)
2461    }
2462
2463    /// Iterator over parameter names annotated with `#[miniextendr(match_arg)]`,
2464    /// in signature order.
2465    pub(crate) fn match_arg_params(&self) -> impl Iterator<Item = &String> {
2466        per_param_in_signature_order(&self.item.sig.inputs, &self.per_param)
2467            .filter_map(|(name, a)| if a.match_arg { Some(name) } else { None })
2468    }
2469
2470    /// Iterator over parameter names annotated with `#[miniextendr(choices(…))]`,
2471    /// together with their choice lists, in signature order.
2472    pub(crate) fn choices_params(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
2473        per_param_in_signature_order(&self.item.sig.inputs, &self.per_param)
2474            .filter_map(|(name, a)| a.choices.as_ref().map(|c| (name, c)))
2475    }
2476
2477    /// Check if a parameter has `several_ok` (multi-value match.arg).
2478    pub(crate) fn has_several_ok(&self, param_name: &str) -> bool {
2479        self.per_param.get(param_name).is_some_and(|a| a.several_ok)
2480    }
2481
2482    /// The per-parameter attribute state of `param_name`, if it carried any
2483    /// `#[miniextendr(...)]` option. Choice parameters read their layers
2484    /// (`Option` / `Missing`, #1473 / #1551) from here.
2485    pub(crate) fn param_attrs(&self, param_name: &str) -> Option<&ParamAttrs> {
2486        self.per_param.get(param_name)
2487    }
2488
2489    /// The R formal of each parameter (#1842), from the declared
2490    /// `formal = ".."` options.
2491    pub(crate) fn formals(&self) -> crate::r_wrapper_builder::Formals {
2492        crate::r_wrapper_builder::Formals::from_param_attrs(&self.per_param)
2493    }
2494
2495    /// The parameters that read a value `identical()` to their formal as
2496    /// omitted, keyed by R formal (see [`absent_formals_by_r_name`]).
2497    pub(crate) fn absent_formals(
2498        &self,
2499        c_ident: &str,
2500    ) -> std::collections::HashMap<String, String> {
2501        absent_formals_by_r_name(&self.per_param, c_ident)
2502    }
2503
2504    /// The generated `@param` text of each `Missing<..>` parameter with a
2505    /// default, keyed by R formal (see [`missing_default_param_docs`]).
2506    pub(crate) fn missing_default_param_docs(&self) -> std::collections::HashMap<String, String> {
2507        missing_default_param_docs(
2508            &self.item.sig.inputs,
2509            &self.per_param,
2510            &self.param_defaults(),
2511            &self.formals(),
2512        )
2513    }
2514
2515    /// The `inherits` / `not_inherits` / `no_na` checks of every parameter,
2516    /// keyed by R name (see [`explicit_checks_by_r_name`]).
2517    pub(crate) fn explicit_checks(
2518        &self,
2519    ) -> std::collections::HashMap<String, crate::r_preconditions::ExplicitChecks> {
2520        explicit_checks_by_r_name(&self.per_param)
2521    }
2522
2523    /// R names of the `match_arg` and `choices(...)` parameters, whose
2524    /// type-derived preconditions are skipped because `match.arg()` already
2525    /// validates them. The method paths build the same set.
2526    pub(crate) fn precondition_skip_params(&self) -> std::collections::HashSet<String> {
2527        crate::r_class_formatter::match_arg_skip_set(&self.per_param)
2528    }
2529
2530    /// Returns all parameter defaults as an owned map from Rust parameter
2531    /// name to default value string (the raw R expression used in the wrapper
2532    /// formals, e.g. `"NULL"`, `"TRUE"`, `"\"Safe\""`). The R wrapper keys
2533    /// them by formal ([`crate::r_wrapper_builder::Formals::rekey`]), so a
2534    /// default on `_seed` is the formal `seed`'s.
2535    pub(crate) fn param_defaults(&self) -> std::collections::HashMap<String, String> {
2536        self.per_param
2537            .iter()
2538            .filter_map(|(name, a)| a.default.as_ref().map(|d| (name.clone(), d.clone())))
2539            .collect()
2540    }
2541    // endregion
2542
2543    // region: Accessors for signature components
2544
2545    /// Original attributes on the function item (doc comments, cfgs, etc.).
2546    pub(crate) fn attrs(&self) -> &[syn::Attribute] {
2547        &self.item.attrs
2548    }
2549
2550    /// Visibility of the function (`pub`, `pub(crate)`, or private).
2551    pub(crate) fn vis(&self) -> &syn::Visibility {
2552        &self.item.vis
2553    }
2554
2555    /// Explicit ABI, if the function was declared `extern "C-unwind"`.
2556    pub(crate) fn abi(&self) -> Option<&syn::Abi> {
2557        self.item.sig.abi.as_ref()
2558    }
2559
2560    /// Function identifier after normalization.
2561    pub(crate) fn ident(&self) -> &syn::Ident {
2562        &self.item.sig.ident
2563    }
2564
2565    /// Generic parameters on the function signature.
2566    pub(crate) fn generics(&self) -> &syn::Generics {
2567        &self.item.sig.generics
2568    }
2569
2570    /// Function inputs after normalization (dots rewritten, wildcards
2571    /// renamed), with the parameter markers peeled (see [`Self::param_markers`]).
2572    pub(crate) fn inputs(&self) -> &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]> {
2573        &self.inputs
2574    }
2575
2576    /// The `Checked<T>` / `Unchecked<T>` markers peeled off the parameters
2577    /// (Rust name → markers, outermost first), in signature order.
2578    pub(crate) fn param_markers(&self) -> &[(String, Vec<crate::type_inspect::ParamMarker>)] {
2579        &self.param_markers
2580    }
2581
2582    /// Function return type.
2583    pub(crate) fn output(&self) -> &syn::ReturnType {
2584        &self.item.sig.output
2585    }
2586
2587    /// The normalized function item (with original doc comments).
2588    pub(crate) fn item(&self) -> &syn::ItemFn {
2589        &self.item
2590    }
2591
2592    /// The normalized function item with roxygen tags stripped from doc comments.
2593    ///
2594    /// This is used for emitting the Rust function without R-specific documentation
2595    /// tags (e.g., `@param`, `@examples`) that don't belong in rustdoc.
2596    pub(crate) fn item_without_roxygen(&self) -> syn::ItemFn {
2597        let mut item = self.item.clone();
2598        item.attrs = crate::roxygen::strip_roxygen_from_attrs(&item.attrs);
2599        item
2600    }
2601    // endregion
2602
2603    // region: Codegen helpers
2604
2605    /// Returns `true` if this function needs an internal C wrapper (`C_<crate>_<name>` function).
2606    ///
2607    /// Rust-ABI functions (no explicit `extern`) need a generated `extern "C-unwind"` wrapper
2608    /// that handles SEXP conversion and error propagation. Functions already declared as
2609    /// `extern "C-unwind"` are passed through directly without wrapping.
2610    pub(crate) fn uses_internal_c_wrapper(&self) -> bool {
2611        self.abi().is_none()
2612    }
2613
2614    /// Returns the identifier for the generated `const &str` holding the R wrapper code.
2615    ///
2616    /// The R wrapper is a string constant containing the R function definition that
2617    /// calls `.Call(C_<crate>_<name>, ...)`. It is collected via linkme distributed slices to
2618    /// produce the `R/miniextendr-wrappers.R` file.
2619    pub(crate) fn r_wrapper_const_ident(&self) -> syn::Ident {
2620        r_wrapper_const_ident_for(self.ident())
2621    }
2622
2623    /// Returns the identifier for the C-callable entry point.
2624    ///
2625    /// - **Rust ABI functions**: Returns `C_<crate>_<name>` (the generated wrapper
2626    ///   function, crate-prefixed for webR cross-package symbol uniqueness — #1273).
2627    /// - **`extern "C-unwind"` functions**: Returns the function's own name, or the
2628    ///   value from `#[export_name = "..."]` if present. The user owns these symbols,
2629    ///   including their cross-package uniqueness under webR.
2630    pub(crate) fn c_wrapper_ident(&self) -> syn::Ident {
2631        if self.uses_internal_c_wrapper() {
2632            crate::naming::bare_fn_c_wrapper_ident(self.ident())
2633        } else {
2634            // For extern functions, check for #[export_name = "..."]
2635            self.export_name_ident()
2636                .unwrap_or_else(|| self.ident().clone())
2637        }
2638    }
2639
2640    /// Extracts the custom symbol name from `#[export_name = "..."]`, if present.
2641    ///
2642    /// Only meaningful for `extern "C-unwind"` functions, where `#[export_name]` is
2643    /// allowed as an alternative to `#[no_mangle]`. Returns `None` if no such attribute exists.
2644    pub(crate) fn export_name_ident(&self) -> Option<syn::Ident> {
2645        for attr in &self.item.attrs {
2646            if attr.path().is_ident("export_name")
2647                && let syn::Meta::NameValue(meta) = &attr.meta
2648                && let syn::Expr::Lit(syn::ExprLit {
2649                    lit: syn::Lit::Str(lit_str),
2650                    ..
2651                }) = &meta.value
2652            {
2653                return Some(syn::Ident::new(&lit_str.value(), lit_str.span()));
2654            }
2655        }
2656        None
2657    }
2658
2659    /// Add `#[inline(never)]` if no `#[inline(...)]` attribute is present.
2660    /// Only for Rust ABI functions - extern "C-unwind" functions are passed through as-is.
2661    ///
2662    /// Preventing inlining ensures:
2663    /// - Worker-dispatched functions retain a distinct call frame
2664    /// - Panic handling and unwinding retain the intended boundary
2665    /// - Stack traces show the actual function name
2666    pub(crate) fn add_inline_never_if_needed(&mut self) {
2667        let has_explicit_abi = self.item.sig.abi.is_some();
2668        let has_inline = self
2669            .item
2670            .attrs
2671            .iter()
2672            .any(|attr| attr.path().is_ident("inline"));
2673        if !has_inline && !has_explicit_abi {
2674            self.item.attrs.push(::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_pound(&mut _s);
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Bracket,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::__private::push_ident(&mut _s, "inline");
                ::quote::__private::push_group(&mut _s,
                    ::quote::__private::Delimiter::Parenthesis,
                    {
                        let mut _s = ::quote::__private::TokenStream::new();
                        ::quote::__private::push_ident(&mut _s, "never");
                        _s
                    });
                _s
            });
        _s
    })syn::parse_quote!(#[inline(never)]));
2675        }
2676    }
2677    // endregion
2678}
2679// endregion
2680
2681// region: Attribute parsing
2682
2683/// Parse the value of a `name = "..."` meta item as a string literal.
2684///
2685/// Returns a compile error spanning the offending token when the RHS is not a
2686/// `&str` literal. `field` is used in the diagnostic (e.g. `"c_symbol"`).
2687/// `postfix = "..."` must be a non-empty R identifier fragment: it is appended
2688/// verbatim to the Rust name, so anything outside letters, digits, `_` and `.`
2689/// would produce an R name that needs backticks.
2690pub(crate) fn validate_postfix(val: &str, span: &dyn quote::ToTokens) -> syn::Result<()> {
2691    if val.is_empty() {
2692        return Err(syn::Error::new_spanned(span, "postfix must not be empty"));
2693    }
2694    if !val
2695        .chars()
2696        .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
2697    {
2698        return Err(syn::Error::new_spanned(
2699            span,
2700            "postfix must be a valid R identifier fragment (letters, digits, `_`, `.`)",
2701        ));
2702    }
2703    Ok(())
2704}
2705
2706fn parse_lit_str(nv: &syn::MetaNameValue, field: &str) -> syn::Result<String> {
2707    match &nv.value {
2708        syn::Expr::Lit(syn::ExprLit {
2709            lit: syn::Lit::Str(lit),
2710            ..
2711        }) => Ok(lit.value()),
2712        syn::Expr::Lit(expr_lit) => Err(syn::Error::new_spanned(
2713            &expr_lit.lit,
2714            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} expects a string literal",
                field))
    })format!("{field} expects a string literal"),
2715        )),
2716        other => Err(syn::Error::new_spanned(
2717            other,
2718            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} expects a string literal",
                field))
    })format!("{field} expects a string literal"),
2719        )),
2720    }
2721}
2722
2723/// Comma-separated list of all fn-level boolean flags, for error messages.
2724///
2725/// Kept as a single constant so the three "unknown option" error paths (Path,
2726/// NameValue bool, parenthesized bool) all read from the same list and can't
2727/// drift.
2728const FN_BOOL_FLAGS_HELP: &str = "invisible, visible, check_interrupt, worker, no_worker, coerce, no_coerce, \
2729     rng, unwrap_in_r, serialize, serde_error, strict, no_strict, \
2730     preconditions, no_preconditions, internal, noexport, export, call_arg";
2731
2732/// Comma-separated list of fn-level nested options, for error messages.
2733const FN_NESTED_OPTIONS_HELP: &str = "`s3(...)`, `lifecycle(...)`, `defaults(...)`, `r_on_exit(...)`, \
2734     `serde_error(...)`, `rng(seed = ...)`";
2735
2736/// Parsed arguments for the `#[miniextendr(...)]` attribute on functions.
2737///
2738/// This is intentionally a small, "data-only" struct that:
2739/// - Owns the parsing rules for the attribute
2740/// - Produces a normalized, easy-to-consume representation for codegen
2741///
2742/// # Accepted flags
2743///
2744/// - `invisible` / `visible`: control whether the generated R wrapper returns invisibly
2745/// - `check_interrupt`: insert `R_CheckUserInterrupt()` before calling Rust
2746/// - `worker`: opt into worker-thread execution (default is main thread)
2747/// - `coerce`: enable automatic coercion for supported parameter types
2748/// - `rng`: enable RNG state management (GetRNGstate/PutRNGstate)
2749/// - `rng(seed = <param>)`: `rng`, plus the seed contract of `stats::simulate()`
2750///   in the R wrapper (#2198); `<param>` names the parameter, by its Rust
2751///   name, whose value goes to `set.seed()`
2752/// - `unwrap_in_r`: return `Result<T, E>` to R without unwrapping
2753/// - `prefer = "auto" | "list" | "externalptr" | "vector"`: prefer a specific `IntoR` path
2754/// - `no_preconditions` / `preconditions` (bare or `= true/false`): drop or
2755///   keep the R-side type checks. Without them `TryFromSexp` still raises on
2756///   bad input, with the same argument-error condition (#1591); the message
2757///   comes from the conversion. Saves one `isTRUE()` guard per check.
2758///   Hot-path opt-in. The last one written wins. A parameter decides for
2759///   itself first (`Checked<T>` / `Unchecked<T>`, or the per-parameter
2760///   `#[miniextendr(preconditions)]` / `#[miniextendr(no_preconditions)]`);
2761///   below the function come the crate's `[package.metadata.miniextendr]
2762///   preconditions = true | false` and then the `no-preconditions-default`
2763///   feature (`crate::r_preconditions::resolve_type_checks`).
2764/// - `call = wrapper | caller | none`: which call the wrapper attributes
2765///   conditions to (#1566), always as written. `wrapper` (the framework
2766///   default) passes `.call = sys.call()`; `caller` binds the caller's call
2767///   first and passes that (internal entry points behind a hand-written R
2768///   function; needs `noexport` / `internal`); `none` passes no call, so
2769///   every condition, the argument errors included, has a NULL call (#1851;
2770///   not with a `Call` / `CallerCall` parameter or `call_arg`). A `Call` /
2771///   `CallerCall` parameter is the marker spelling of `wrapper` / `caller`,
2772///   and `[package.metadata.miniextendr] call_attribution` the crate default;
2773///   see `CallAttribution::resolve`. On an S3 method `wrapper` reports the
2774///   generic's call (`CallAttribution::for_s3_method`).
2775/// - `call_arg`: the wrapper takes a trailing `.call = NULL` formal (#1834),
2776///   exported or not. `NULL` is the wrapper's own call; an R function
2777///   composing the function passes `.call = environment()` (or a call
2778///   object) and its conditions name that function's call instead. Not with
2779///   `call = caller` (whose wrapper already takes `.call`) or `s3(...)`.
2780///
2781/// # Note
2782///
2783/// Unknown flags are rejected with a compile error to avoid silently ignoring typos.
2784#[derive(#[automatically_derived]
impl ::core::default::Default for MiniextendrFnAttrs {
    #[inline]
    fn default() -> Self {
        MiniextendrFnAttrs {
            force_worker: ::core::default::Default::default(),
            force_invisible: ::core::default::Default::default(),
            check_interrupt: ::core::default::Default::default(),
            coerce_all: ::core::default::Default::default(),
            rng: ::core::default::Default::default(),
            rng_seed: ::core::default::Default::default(),
            unwrap_in_r: ::core::default::Default::default(),
            serialize: ::core::default::Default::default(),
            wrap: ::core::default::Default::default(),
            serde_error: ::core::default::Default::default(),
            preconditions: ::core::default::Default::default(),
            call_attribution: ::core::default::Default::default(),
            call_arg: ::core::default::Default::default(),
            return_pref: ::core::default::Default::default(),
            return_pref_span: ::core::default::Default::default(),
            s3_generic: ::core::default::Default::default(),
            s3_class: ::core::default::Default::default(),
            dots_spec: ::core::default::Default::default(),
            dots_policy: ::core::default::Default::default(),
            dots_span: ::core::default::Default::default(),
            lifecycle: ::core::default::Default::default(),
            strict: ::core::default::Default::default(),
            internal: ::core::default::Default::default(),
            noexport: ::core::default::Default::default(),
            export: ::core::default::Default::default(),
            doc: ::core::default::Default::default(),
            c_symbol: ::core::default::Default::default(),
            r_name: ::core::default::Default::default(),
            postfix: ::core::default::Default::default(),
            r_entry: ::core::default::Default::default(),
            r_post_checks: ::core::default::Default::default(),
            r_on_exit: ::core::default::Default::default(),
        }
    }
}Default)]
2785pub(crate) struct MiniextendrFnAttrs {
2786    /// Force execution on worker thread (set by `worker`).
2787    pub(crate) force_worker: bool,
2788    /// Override visibility; `Some(true)` makes the wrapper return invisibly, `Some(false)` forces visibility.
2789    pub(crate) force_invisible: Option<bool>,
2790    /// Insert `R_CheckUserInterrupt()` before calling the Rust function.
2791    pub(crate) check_interrupt: bool,
2792    /// Enable automatic coercion for all parameters that support it.
2793    pub(crate) coerce_all: bool,
2794    /// Enable RNG state management (GetRNGstate/PutRNGstate). Also set by
2795    /// `rng(seed = ..)`.
2796    pub(crate) rng: bool,
2797    /// `rng(seed = <param>)` (#2198): the parameter, by its Rust name, whose
2798    /// value the R wrapper passes to `set.seed()` before the call and records
2799    /// in the result's `seed` attribute, as `stats:::simulate.lm` does
2800    /// ([`check_rng_seed`] checks it against the signature).
2801    pub(crate) rng_seed: Option<syn::Ident>,
2802    /// Return `Result<T, E>` to R without unwrapping.
2803    pub(crate) unwrap_in_r: bool,
2804    /// Serialize the complete return value through `AsSerialize<T>`.
2805    pub(crate) serialize: bool,
2806    pub(crate) wrap: Option<crate::miniextendr_impl::ClassSystem>,
2807    /// Build the `Err` arm's condition from the error's serde output
2808    /// (`#[miniextendr(serde_error(shape = .., tag = .., prefix = .., ..))]`).
2809    pub(crate) serde_error: Option<SerdeErrorSpec>,
2810    /// Keep (`Some(true)`) or drop (`Some(false)`) the R-side type-check
2811    /// guards of the parameters that do not decide for themselves.
2812    ///
2813    /// `TryFromSexp` already raises the same argument-error condition on
2814    /// mismatched input (#1591), so the information isn't lost: it is
2815    /// worded by the conversion rather than by the R check. Useful for hot
2816    /// paths where the per-call precondition cost (one `isTRUE()` guard per
2817    /// check) dominates over actual work.
2818    ///
2819    /// `Some(true)` from `#[miniextendr(preconditions)]` (or
2820    /// `preconditions = true`, `no_preconditions = false`), `Some(false)` from
2821    /// `no_preconditions` (or `no_preconditions = true`, `preconditions =
2822    /// false`); the last one written wins. `None`: the crate default, then the
2823    /// `no-preconditions-default` feature decide, at codegen
2824    /// (`crate::r_preconditions::resolve_type_checks`).
2825    pub(crate) preconditions: Option<bool>,
2826    /// The attribution the attribute asked for, if any (#1566):
2827    /// `call = wrapper | caller | none`. `None` here means the attribute said
2828    /// nothing; the codegen then falls back to a `Call` / `CallerCall`
2829    /// parameter marker, the crate default and finally `wrapper`
2830    /// (`crate::r_wrapper_builder::CallAttribution::resolve`).
2831    pub(crate) call_attribution: Option<crate::r_wrapper_builder::CallAttribution>,
2832    /// `call_arg` (#1834): the wrapper takes a trailing `.call = NULL` formal,
2833    /// whose `NULL` is the wrapper's own call and through which an R function
2834    /// composing it passes another (`environment()`, a call object). Turns
2835    /// `wrapper` attribution into
2836    /// `crate::r_wrapper_builder::CallAttribution::Argument`.
2837    pub(crate) call_arg: bool,
2838    /// Preferred return conversion: forces `AsList`/`AsExternalPtr`/`AsRNative` wrapping
2839    /// of the return value before `IntoR::into_sexp` is called.
2840    pub(crate) return_pref: ReturnPref,
2841    /// Span of the `prefer = ...` attribute, for error reporting when the return type
2842    /// falls into a codegen category (`Option<T>`, `Result<T, E>`, `()`, `Self`, raw
2843    /// `SEXP`, ...) that can't honor it.
2844    pub(crate) return_pref_span: Option<proc_macro2::Span>,
2845    /// S3 generic name (if this function is an S3 method).
2846    ///
2847    /// Use `#[miniextendr(s3(generic = "vec_proxy", class = "my_vctr"))]` to mark a function
2848    /// as an S3 method for an existing generic. `pkg::generic` registers the
2849    /// method lazily, for a generic of a suggested package (#2154).
2850    pub(crate) s3_generic: Option<String>,
2851    /// S3 class suffix for the method (e.g., "my_vctr" or "my_vctr.my_vctr" for double-dispatch).
2852    pub(crate) s3_class: Option<String>,
2853    /// Typed list validation spec for dots parameter.
2854    ///
2855    /// Use `#[miniextendr(dots = typed_list!(...))]` to automatically validate dots
2856    /// at the start of the function and bind the result to `dots_typed`.
2857    pub(crate) dots_spec: Option<proc_macro2::TokenStream>,
2858    /// What the function's `...` takes: `dots = empty | empty(allow = [..]) |
2859    /// forward(allow = [..]) | data` (#2157, [`crate::dots_policy`]).
2860    pub(crate) dots_policy: Option<crate::dots_policy::DotsPolicy>,
2861    /// Span of the `dots = ...` attribute for error reporting.
2862    pub(crate) dots_span: Option<proc_macro2::Span>,
2863    /// Lifecycle specification for deprecation/experimental status.
2864    pub(crate) lifecycle: Option<crate::lifecycle::LifecycleSpec>,
2865    /// Strict output conversion: panic instead of lossy widening for i64/u64/isize/usize.
2866    pub(crate) strict: bool,
2867    /// Mark as internal: adds `@keywords internal`, suppresses `@export`.
2868    pub(crate) internal: bool,
2869    /// Suppress `@export` without adding `@keywords internal`.
2870    pub(crate) noexport: bool,
2871    /// Force `@export` even on non-pub functions. Antidote to `noexport`.
2872    pub(crate) export: bool,
2873    /// Custom roxygen documentation override.
2874    ///
2875    /// When set, replaces auto-extracted roxygen from Rust doc comments.
2876    /// Each `\n` in the string becomes a separate `#'` line.
2877    pub(crate) doc: Option<String>,
2878    /// Custom C symbol name for the generated wrapper.
2879    ///
2880    /// Overrides the default `C_<crate>_<fn_name>` naming convention. The value is used
2881    /// verbatim (no crate prefix) — the author owns cross-package uniqueness on webR (#1273).
2882    /// Must be a valid C identifier (alphanumeric + underscore, starting with letter or underscore).
2883    pub(crate) c_symbol: Option<String>,
2884    /// Override R wrapper function name.
2885    ///
2886    /// Use `#[miniextendr(r_name = "is.my_type")]` to give the R wrapper a different name
2887    /// than the Rust function. The C symbol is still derived from the Rust name.
2888    /// Cannot be combined with `s3(generic/class)` — use `generic`/`class` for S3 naming.
2889    pub(crate) r_name: Option<String>,
2890    /// Append a fixed suffix to the Rust name for the R wrapper
2891    /// (`#[miniextendr(noexport, postfix = "_impl")]` on `fn f` yields `f_impl`).
2892    /// States the "hand-written `f()` delegates to generated `f_impl()`"
2893    /// convention without repeating the name in `r_name`. Exclusive with
2894    /// `r_name` and `s3(...)`; the C symbol is unchanged.
2895    pub(crate) postfix: Option<String>,
2896    /// R code to inject at the very top of the wrapper body (before all built-in checks).
2897    ///
2898    /// Use `#[miniextendr(r_entry = "x <- as.integer(x)")]` to run R code before
2899    /// missing-default handling, lifecycle checks, preconditions, and match.arg.
2900    /// Multi-line via `\n`. No validation of R syntax.
2901    pub(crate) r_entry: Option<String>,
2902    /// R code to inject after all built-in checks, immediately before `.Call()`.
2903    ///
2904    /// Use `#[miniextendr(r_post_checks = "message('calling rust')")]` to run R code
2905    /// after all precondition checks but before the Rust function is invoked.
2906    /// Multi-line via `\n`. No validation of R syntax.
2907    pub(crate) r_post_checks: Option<String>,
2908    /// Register `on.exit()` cleanup code in the R wrapper.
2909    ///
2910    /// Short form: `#[miniextendr(r_on_exit = "close(con)")]` → `on.exit(close(con), add = TRUE)`
2911    ///
2912    /// Long form: `#[miniextendr(r_on_exit(expr = "close(con)", add = false))]`
2913    ///
2914    /// Defaults: `add = TRUE`, `after = TRUE`. Injected after `r_entry`, before other checks.
2915    pub(crate) r_on_exit: Option<ROnExit>,
2916}
2917
2918/// Parsed `r_on_exit` attribute for `on.exit()` cleanup code in R wrappers.
2919///
2920/// Two forms:
2921/// - Short: `r_on_exit = "expr"` → `ROnExit { expr, add: true, after: true }`
2922/// - Long: `r_on_exit(expr = "...", add = false, after = false)`
2923///
2924/// Defaults match R conventions for composable code: `add = TRUE`, `after = TRUE`.
2925#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ROnExit {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "ROnExit",
            "expr", &self.expr, "add", &self.add, "after", &&self.after)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ROnExit {
    #[inline]
    fn clone(&self) -> Self {
        ROnExit {
            expr: ::core::clone::Clone::clone(&self.expr),
            add: ::core::clone::Clone::clone(&self.add),
            after: ::core::clone::Clone::clone(&self.after),
        }
    }
}Clone)]
2926pub(crate) struct ROnExit {
2927    pub expr: String,
2928    pub add: bool,
2929    pub after: bool,
2930}
2931
2932impl ROnExit {
2933    /// Generate the R `on.exit(...)` call string.
2934    ///
2935    /// - `add = FALSE` (R default): `on.exit(expr)`
2936    /// - `add = TRUE, after = TRUE`: `on.exit(expr, add = TRUE)`
2937    /// - `add = TRUE, after = FALSE`: `on.exit(expr, add = TRUE, after = FALSE)`
2938    pub fn to_r_code(&self) -> String {
2939        if !self.add {
2940            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("on.exit({0})", self.expr))
    })format!("on.exit({})", self.expr)
2941        } else if !self.after {
2942            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("on.exit({0}, add = TRUE, after = FALSE)",
                self.expr))
    })format!("on.exit({}, add = TRUE, after = FALSE)", self.expr)
2943        } else {
2944            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("on.exit({0}, add = TRUE)",
                self.expr))
    })format!("on.exit({}, add = TRUE)", self.expr)
2945        }
2946    }
2947}
2948
2949/// `#[miniextendr(serde_error)]`: derive the `Err` arm's condition class and
2950/// data from the error type's `serde::Serialize` output instead of the
2951/// `RConditionData`/`Debug` probe.
2952///
2953/// The enum variant (external tagging, or the `tag` field of an internally
2954/// tagged enum) becomes the member class `<prefix>_<variant>`; the payload
2955/// fields become the condition's data. Defaults: `tag = "kind"`,
2956/// `prefix = "<crate>_error"` (from `CARGO_CRATE_NAME` at expansion time).
2957///
2958/// Field control (#1457, #2207): `skip("a", "b")` drops payload fields by
2959/// name, `rename(a = "b")` splices field `a` as `b`, and `as_character("a")`,
2960/// `as_integer(..)`, `as_double(..)`, `as_logical(..)` give a field that atomic
2961/// R type. All name the field as it serializes, before any rename; a variant
2962/// that lacks the field is unaffected. The macro cannot see the error type's
2963/// fields, so only the option grammar is checked here; a `rename` target may
2964/// not be one of the reserved condition slots, and a field takes one of `skip`
2965/// and the `as_*` options.
2966///
2967/// `shape = PATH` names a `miniextendr_api::condition::SerdeErrorShape` const
2968/// holding these options, applied before the function's own (#2207). The
2969/// generated code passes both to `serde_err_parts` as runtime data: the named
2970/// shape, then the function's options as an inline const shape.
2971///
2972/// The serde path itself needs no attribute: under the `serde` feature every
2973/// `Result<T, E>` with `E: Serialize + Display` takes it through the runtime
2974/// probe. A spec only exists to carry options, so the bare flag and the
2975/// boolean forms are rejected with [`SERDE_ERROR_BARE_HELP`].
2976#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SerdeErrorSpec {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["shape", "tag", "prefix", "skip", "rename", "types"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.shape, &self.tag, &self.prefix, &self.skip, &self.rename,
                        &&self.types];
        ::core::fmt::Formatter::debug_struct_fields_finish(f,
            "SerdeErrorSpec", names, values)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SerdeErrorSpec {
    #[inline]
    fn clone(&self) -> Self {
        SerdeErrorSpec {
            shape: ::core::clone::Clone::clone(&self.shape),
            tag: ::core::clone::Clone::clone(&self.tag),
            prefix: ::core::clone::Clone::clone(&self.prefix),
            skip: ::core::clone::Clone::clone(&self.skip),
            rename: ::core::clone::Clone::clone(&self.rename),
            types: ::core::clone::Clone::clone(&self.types),
        }
    }
}Clone, #[automatically_derived]
impl ::core::default::Default for SerdeErrorSpec {
    #[inline]
    fn default() -> Self {
        SerdeErrorSpec {
            shape: ::core::default::Default::default(),
            tag: ::core::default::Default::default(),
            prefix: ::core::default::Default::default(),
            skip: ::core::default::Default::default(),
            rename: ::core::default::Default::default(),
            types: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SerdeErrorSpec { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SerdeErrorSpec {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.shape == other.shape && self.tag == other.tag &&
                        self.prefix == other.prefix && self.skip == other.skip &&
                self.rename == other.rename && self.types == other.types
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SerdeErrorSpec {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Option<syn::Path>>;
        let _: ::core::cmp::AssertParamIsEq<Option<String>>;
        let _: ::core::cmp::AssertParamIsEq<Option<String>>;
        let _: ::core::cmp::AssertParamIsEq<Vec<String>>;
        let _: ::core::cmp::AssertParamIsEq<Vec<(String, String)>>;
        let _: ::core::cmp::AssertParamIsEq<Vec<(FieldType, String)>>;
    }
}Eq)]
2977pub(crate) struct SerdeErrorSpec {
2978    /// A named `SerdeErrorShape` (`shape = PATH`), applied before the options
2979    /// below.
2980    pub shape: Option<syn::Path>,
2981    /// Internally-tagged discriminator field name (`#[serde(tag = "...")]`).
2982    pub tag: Option<String>,
2983    /// Condition class prefix (family class).
2984    pub prefix: Option<String>,
2985    /// Payload fields dropped from the condition data.
2986    pub skip: Vec<String>,
2987    /// Payload fields spliced under another name: `(from, to)`.
2988    pub rename: Vec<(String, String)>,
2989    /// Payload fields given an atomic R type (`as_character(..)` & co.), in
2990    /// written order.
2991    pub types: Vec<(FieldType, String)>,
2992}
2993
2994/// The R type an `as_*` option gives a payload field.
2995#[derive(#[automatically_derived]
impl ::core::fmt::Debug for FieldType {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                FieldType::Logical => "Logical",
                FieldType::Integer => "Integer",
                FieldType::Double => "Double",
                FieldType::Character => "Character",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for FieldType { }
#[automatically_derived]
impl ::core::clone::Clone for FieldType {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for FieldType { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for FieldType { }
#[automatically_derived]
impl ::core::cmp::PartialEq for FieldType {
    #[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 FieldType { }Eq)]
2996pub(crate) enum FieldType {
2997    Logical,
2998    Integer,
2999    Double,
3000    Character,
3001}
3002
3003impl FieldType {
3004    const ALL: [FieldType; 4] = [
3005        FieldType::Logical,
3006        FieldType::Integer,
3007        FieldType::Double,
3008        FieldType::Character,
3009    ];
3010
3011    /// The option's name, which is also the `SerdeErrorShape` method's.
3012    pub(crate) fn option(self) -> &'static str {
3013        match self {
3014            FieldType::Logical => "as_logical",
3015            FieldType::Integer => "as_integer",
3016            FieldType::Double => "as_double",
3017            FieldType::Character => "as_character",
3018        }
3019    }
3020
3021    fn from_path(path: &syn::Path) -> Option<Self> {
3022        Self::ALL.into_iter().find(|ty| path.is_ident(ty.option()))
3023    }
3024}
3025
3026/// The condition's own slots, mirrored from
3027/// `miniextendr_api::condition::RESERVED_CONDITION_FIELDS` (the macros crate
3028/// cannot depend on the API crate). The runtime check remains the backstop.
3029const RESERVED_CONDITION_FIELDS: &[&str] = &["message", "call", "kind"];
3030
3031const SERDE_ERROR_OPTIONS_HELP: &str = "unknown serde_error option; expected `shape`, `tag`, \
3032    `prefix`, `skip(...)`, `rename(...)`, `as_character(...)`, `as_integer(...)`, \
3033    `as_double(...)` or `as_logical(...)`";
3034
3035const SERDE_ERROR_SHAPE_HELP: &str = "serde_error shape takes the path of a `SerdeErrorShape` \
3036    const: `shape = ENGINE`";
3037
3038/// The bare flag / boolean forms: the serde path is not something to switch
3039/// on per function.
3040pub(crate) const SERDE_ERROR_BARE_HELP: &str = "`serde_error` is not a switch: with the `serde` \
3041    feature every `Result<T, E>` whose `E: Serialize + Display` is already classed from its \
3042    serde shape. The attribute only carries options; write `serde_error(shape = ENGINE)`, \
3043    options such as `serde_error(prefix = \"..\", skip(..), as_character(..))`, or drop it";
3044
3045impl SerdeErrorSpec {
3046    /// Parse `serde_error(...)` contents: a comma-separated list of options.
3047    /// Every problem is reported at once.
3048    fn from_metas<'a>(
3049        metas: impl IntoIterator<Item = &'a syn::Meta>,
3050        span: proc_macro2::Span,
3051    ) -> syn::Result<Self> {
3052        let mut spec = SerdeErrorSpec::default();
3053        let mut errors = Vec::new();
3054        let mut any = false;
3055        for meta in metas {
3056            any = true;
3057            spec.apply(meta, &mut errors);
3058        }
3059        if !any {
3060            return Err(syn::Error::new(span, SERDE_ERROR_BARE_HELP));
3061        }
3062        let mut errors = errors.into_iter();
3063        match errors.next() {
3064            None => Ok(spec),
3065            Some(mut first) => {
3066                first.extend(errors);
3067                Err(first)
3068            }
3069        }
3070    }
3071
3072    fn apply(&mut self, meta: &syn::Meta, errors: &mut Vec<syn::Error>) {
3073        match meta {
3074            syn::Meta::NameValue(nv) if nv.path.is_ident("shape") => match &nv.value {
3075                syn::Expr::Path(syn::ExprPath {
3076                    qself: None, path, ..
3077                }) if self.shape.is_none() => self.shape = Some(path.clone()),
3078                syn::Expr::Path(syn::ExprPath { qself: None, .. }) => errors.push(
3079                    syn::Error::new_spanned(&nv.value, "serde_error shape is given twice"),
3080                ),
3081                other => errors.push(syn::Error::new_spanned(other, SERDE_ERROR_SHAPE_HELP)),
3082            },
3083            syn::Meta::NameValue(nv) if nv.path.is_ident("tag") => {
3084                match non_empty_lit_str(nv, "tag") {
3085                    Ok(tag) => self.tag = Some(tag),
3086                    Err(err) => errors.push(err),
3087                }
3088            }
3089            syn::Meta::NameValue(nv) if nv.path.is_ident("prefix") => {
3090                match non_empty_lit_str(nv, "prefix") {
3091                    Ok(prefix) => self.prefix = Some(prefix),
3092                    Err(err) => errors.push(err),
3093                }
3094            }
3095            syn::Meta::List(list) if list.path.is_ident("skip") => {
3096                for lit in field_names(list, "skip", errors) {
3097                    let name = lit.value();
3098                    if self.skip.contains(&name) {
3099                        errors.push(syn::Error::new_spanned(
3100                            &lit,
3101                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error skip names `{0}` twice",
                name))
    })format!("serde_error skip names `{name}` twice"),
3102                        ));
3103                    } else if let Some(ty) = self.field_type(&name) {
3104                        errors.push(both(
3105                            &lit,
3106                            ty.option(),
3107                            "skip",
3108                            "a skipped field has no R type",
3109                        ));
3110                    } else if self.rename.iter().any(|(from, _)| *from == name) {
3111                        errors.push(both(
3112                            &lit,
3113                            "rename",
3114                            "skip",
3115                            "a skipped field has no name to rename",
3116                        ));
3117                    } else {
3118                        self.skip.push(name);
3119                    }
3120                }
3121            }
3122            syn::Meta::List(list) if list.path.is_ident("rename") => {
3123                let pairs = match list.parse_args_with(
3124                    syn::punctuated::Punctuated::<RenamePair, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3125                ) {
3126                    Ok(pairs) => pairs,
3127                    Err(err) => return errors.push(err),
3128                };
3129                if pairs.is_empty() {
3130                    errors.push(syn::Error::new_spanned(
3131                        list,
3132                        "serde_error rename needs at least one pair: `rename(message = \"detail\")`",
3133                    ));
3134                }
3135                for RenamePair { from, to, span } in pairs {
3136                    let error = |message: String| syn::Error::new(span, message);
3137                    if from.is_empty() || to.is_empty() {
3138                        errors.push(error("serde_error rename names must not be empty".into()));
3139                    } else if RESERVED_CONDITION_FIELDS.contains(&to.as_str()) {
3140                        errors.push(error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error rename target `{0}` is reserved: `message`, `call` and `kind` are the condition\'s own slots",
                to))
    })format!(
3141                            "serde_error rename target `{to}` is reserved: `message`, `call` \
3142                             and `kind` are the condition's own slots"
3143                        )));
3144                    } else if self.rename.iter().any(|(f, _)| *f == from) {
3145                        errors.push(error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error rename names `{0}` twice",
                from))
    })format!("serde_error rename names `{from}` twice")));
3146                    } else if self.rename.iter().any(|(_, t)| *t == to) {
3147                        errors.push(error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error rename targets `{0}` twice; the condition would carry two `{0}` fields and R would read only the first",
                to))
    })format!(
3148                            "serde_error rename targets `{to}` twice; the condition would \
3149                             carry two `{to}` fields and R would read only the first"
3150                        )));
3151                    } else if self.skip.contains(&from) {
3152                        errors.push(error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error names `{0}` in both skip and rename; a skipped field has no name to rename",
                from))
    })format!(
3153                            "serde_error names `{from}` in both skip and rename; a skipped \
3154                             field has no name to rename"
3155                        )));
3156                    } else {
3157                        self.rename.push((from, to));
3158                    }
3159                }
3160            }
3161            syn::Meta::List(list) if FieldType::from_path(&list.path).is_some() => {
3162                let ty = FieldType::from_path(&list.path).expect("matched above");
3163                let option = ty.option();
3164                for lit in field_names(list, option, errors) {
3165                    let name = lit.value();
3166                    match self.field_type(&name) {
3167                        Some(other) if other == ty => errors.push(syn::Error::new_spanned(
3168                            &lit,
3169                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error {0} names `{1}` twice",
                option, name))
    })format!("serde_error {option} names `{name}` twice"),
3170                        )),
3171                        Some(other) => errors.push(both(
3172                            &lit,
3173                            other.option(),
3174                            option,
3175                            "a field has one R type",
3176                        )),
3177                        None if self.skip.contains(&name) => {
3178                            errors.push(both(&lit, "skip", option, "a skipped field has no R type"))
3179                        }
3180                        None => self.types.push((ty, name)),
3181                    }
3182                }
3183            }
3184            syn::Meta::NameValue(nv) if nv.path.is_ident("skip") => {
3185                errors.push(syn::Error::new_spanned(
3186                    nv,
3187                    "serde_error skip takes a list of field names: `skip(\"message\")`",
3188                ));
3189            }
3190            syn::Meta::NameValue(nv) if nv.path.is_ident("rename") => {
3191                errors.push(syn::Error::new_spanned(
3192                    nv,
3193                    "serde_error rename takes `from = \"to\"` pairs: `rename(message = \"detail\")`",
3194                ));
3195            }
3196            syn::Meta::NameValue(nv) if FieldType::from_path(&nv.path).is_some() => {
3197                let option = FieldType::from_path(&nv.path)
3198                    .expect("matched above")
3199                    .option();
3200                errors.push(syn::Error::new_spanned(
3201                    nv,
3202                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error {0} takes a list of field names: `{0}(\"rule\")`",
                option))
    })format!(
3203                        "serde_error {option} takes a list of field names: `{option}(\"rule\")`"
3204                    ),
3205                ));
3206            }
3207            other if other.path().is_ident("shape") => {
3208                errors.push(syn::Error::new_spanned(other, SERDE_ERROR_SHAPE_HELP));
3209            }
3210            other => {
3211                errors.push(syn::Error::new_spanned(
3212                    other.path(),
3213                    SERDE_ERROR_OPTIONS_HELP,
3214                ));
3215            }
3216        }
3217    }
3218
3219    /// A method's spec with `shape = Self::X` naming the impl's type: the C
3220    /// wrapper that reads the shape is a free function, where `Self` doesn't
3221    /// resolve. The type ident takes the `Self` token's span.
3222    pub(crate) fn with_self_type(&self, ty: &syn::Ident) -> Self {
3223        let mut spec = self.clone();
3224        if let Some(path) = &mut spec.shape
3225            && path.leading_colon.is_none()
3226            && path.segments.len() > 1
3227            && let Some(first) = path.segments.first_mut()
3228            && first.ident == "Self"
3229        {
3230            first.ident = syn::Ident::new(&ty.to_string(), first.ident.span());
3231        }
3232        spec
3233    }
3234
3235    /// The `as_*` option that names `name`, if any.
3236    fn field_type(&self, name: &str) -> Option<FieldType> {
3237        self.types
3238            .iter()
3239            .find(|(_, n)| n == name)
3240            .map(|(ty, _)| *ty)
3241    }
3242
3243    /// The function's own options as a `SerdeErrorShape` builder chain;
3244    /// `None` when it only names a shape.
3245    pub(crate) fn own_shape(&self) -> Option<proc_macro2::TokenStream> {
3246        let mut calls = Vec::new();
3247        if let Some(tag) = &self.tag {
3248            calls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "tag");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&tag, &mut _s);
            _s
        });
    _s
}quote::quote!(.tag(#tag)));
3249        }
3250        if let Some(prefix) = &self.prefix {
3251            calls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "prefix");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&prefix, &mut _s);
            _s
        });
    _s
}quote::quote!(.prefix(#prefix)));
3252        }
3253        if !self.skip.is_empty() {
3254            let skip = &self.skip;
3255            calls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "skip");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    {
                        use ::quote::__private::ext::*;
                        let mut _first = true;
                        let has_iter = ::quote::__private::HasIterator::<false>;
                        #[allow(unused_mut)]
                        let (mut skip, i) = skip.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let skip =
                                match skip.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            if !_first { ::quote::__private::push_comma(&mut _s); }
                            _first = false;
                            ::quote::ToTokens::to_tokens(&skip, &mut _s);
                        }
                    }
                    _s
                });
            _s
        });
    _s
}quote::quote!(.skip(&[#(#skip),*])));
3256        }
3257        if !self.rename.is_empty() {
3258            let (from, to): (Vec<&String>, Vec<&String>) =
3259                self.rename.iter().map(|(f, t)| (f, t)).unzip();
3260            calls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "rename");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    {
                        use ::quote::__private::ext::*;
                        let mut _first = true;
                        let has_iter = ::quote::__private::HasIterator::<false>;
                        #[allow(unused_mut)]
                        let (mut from, i) = from.quote_into_iter();
                        let has_iter = has_iter | i;
                        #[allow(unused_mut)]
                        let (mut to, i) = to.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let from =
                                match from.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            let to =
                                match to.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            if !_first { ::quote::__private::push_comma(&mut _s); }
                            _first = false;
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::ToTokens::to_tokens(&from, &mut _s);
                                    ::quote::__private::push_comma(&mut _s);
                                    ::quote::ToTokens::to_tokens(&to, &mut _s);
                                    _s
                                });
                        }
                    }
                    _s
                });
            _s
        });
    _s
}quote::quote!(.rename(&[#((#from, #to)),*])));
3261        }
3262        for ty in FieldType::ALL {
3263            let names: Vec<&String> = self
3264                .types
3265                .iter()
3266                .filter(|(t, _)| *t == ty)
3267                .map(|(_, name)| name)
3268                .collect();
3269            if !names.is_empty() {
3270                let method = match ::quote::__private::IdentFragmentAdapter(&ty.option()) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}", arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("{}", ty.option());
3271                calls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_dot(&mut _s);
    ::quote::ToTokens::to_tokens(&method, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    {
                        use ::quote::__private::ext::*;
                        let mut _first = true;
                        let has_iter = ::quote::__private::HasIterator::<false>;
                        #[allow(unused_mut)]
                        let (mut names, i) = names.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let names =
                                match names.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            if !_first { ::quote::__private::push_comma(&mut _s); }
                            _first = false;
                            ::quote::ToTokens::to_tokens(&names, &mut _s);
                        }
                    }
                    _s
                });
            _s
        });
    _s
}quote::quote!(.#method(&[#(#names),*])));
3272            }
3273        }
3274        (!calls.is_empty())
3275            .then(|| {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "condition");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "SerdeErrorShape");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "new");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut calls, i) = calls.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let calls =
                match calls.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&calls, &mut _s);
        }
    }
    _s
}quote::quote!(::miniextendr_api::condition::SerdeErrorShape::new() #(#calls)*))
3276    }
3277}
3278
3279/// The non-empty string literals of `skip(..)` / `as_*(..)`; problems go to
3280/// `errors`.
3281fn field_names(
3282    list: &syn::MetaList,
3283    option: &str,
3284    errors: &mut Vec<syn::Error>,
3285) -> Vec<syn::LitStr> {
3286    let names = match list.parse_args_with(
3287        syn::punctuated::Punctuated::<syn::LitStr, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3288    ) {
3289        Ok(names) => names,
3290        Err(err) => {
3291            errors.push(err);
3292            return Vec::new();
3293        }
3294    };
3295    if names.is_empty() {
3296        errors.push(syn::Error::new_spanned(
3297            list,
3298            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error {0} needs at least one field name: `{0}(\"message\")`",
                option))
    })format!("serde_error {option} needs at least one field name: `{option}(\"message\")`"),
3299        ));
3300    }
3301    names
3302        .into_iter()
3303        .filter(|lit| {
3304            let empty = lit.value().is_empty();
3305            if empty {
3306                errors.push(syn::Error::new_spanned(
3307                    lit,
3308                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error {0} field name must not be empty",
                option))
    })format!("serde_error {option} field name must not be empty"),
3309                ));
3310            }
3311            !empty
3312        })
3313        .collect()
3314}
3315
3316/// A field named by two options that exclude each other.
3317fn both(lit: &syn::LitStr, first: &str, second: &str, why: &str) -> syn::Error {
3318    syn::Error::new_spanned(
3319        lit,
3320        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error names `{0}` in both {1} and {2}; {3}",
                lit.value(), first, second, why))
    })format!(
3321            "serde_error names `{}` in both {first} and {second}; {why}",
3322            lit.value()
3323        ),
3324    )
3325}
3326
3327/// A `tag = "..."` / `prefix = "..."` value: a non-empty string literal.
3328fn non_empty_lit_str(nv: &syn::MetaNameValue, key: &str) -> syn::Result<String> {
3329    let val = parse_lit_str(nv, key)?;
3330    if val.is_empty() {
3331        return Err(syn::Error::new_spanned(
3332            &nv.value,
3333            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("serde_error {0} must not be empty",
                key))
    })format!("serde_error {key} must not be empty"),
3334        ));
3335    }
3336    Ok(val)
3337}
3338
3339/// One `from = "to"` entry of `rename(...)`. `from` is an identifier or, for a
3340/// serde-renamed field whose name is not one, a string literal.
3341struct RenamePair {
3342    from: String,
3343    to: String,
3344    span: proc_macro2::Span,
3345}
3346
3347impl syn::parse::Parse for RenamePair {
3348    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
3349        use syn::ext::IdentExt;
3350        let lookahead = input.lookahead1();
3351        let (from, span) = if lookahead.peek(syn::LitStr) {
3352            let lit: syn::LitStr = input.parse()?;
3353            (lit.value(), lit.span())
3354        } else if lookahead.peek(syn::Ident::peek_any) {
3355            let ident = input.call(syn::Ident::parse_any)?;
3356            (ident.to_string(), ident.span())
3357        } else {
3358            return Err(lookahead.error());
3359        };
3360        let _: ::syn::token::Eqsyn::Token![=] = input.parse()?;
3361        let to: syn::LitStr = input.parse()?;
3362        Ok(RenamePair {
3363            from,
3364            to: to.value(),
3365            span,
3366        })
3367    }
3368}
3369
3370/// Default family class for `serde_error`: `<crate>_error`, from the crate being
3371/// compiled (cargo sets `CARGO_CRATE_NAME` for the rustc invocation that runs
3372/// this proc macro).
3373pub(crate) fn default_serde_error_prefix() -> String {
3374    let krate = std::env::var("CARGO_CRATE_NAME").unwrap_or_else(|_| "rust".to_string());
3375    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}_error", krate))
    })format!("{krate}_error")
3376}
3377
3378/// The marker type `miniextendr_init!` defines at the crate root. A
3379/// `serde_error_shape!` declaration is an impl keyed by it, which is what lets
3380/// a crate declare a shape for an error type from another crate (#2207).
3381pub(crate) fn init_marker_ident() -> syn::Ident {
3382    syn::Ident::new("__MiniextendrInitMarker", proc_macro2::Span::call_site())
3383}
3384
3385/// `crate::__MiniextendrInitMarker`, as the generated `Err` arms pass it to
3386/// the API's probe macros. Those resolve it only under the API's `serde`
3387/// feature, where a shape can be declared.
3388pub(crate) fn init_marker_path() -> proc_macro2::TokenStream {
3389    let marker = init_marker_ident();
3390    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "crate");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&marker, &mut _s);
    _s
}quote::quote!(crate::#marker)
3391}
3392
3393/// Parse `serde_error(shape = .., tag = "...", prefix = "...", skip(...), rename(...), as_*(...))`
3394/// given as a `Meta::List`.
3395pub(crate) fn parse_serde_error_list(list: &syn::MetaList) -> syn::Result<SerdeErrorSpec> {
3396    use syn::spanned::Spanned;
3397    let metas = list.parse_args_with(
3398        syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3399    )?;
3400    SerdeErrorSpec::from_metas(&metas, list.span())
3401}
3402
3403/// Parse the tail of a `serde_error` option inside `parse_nested_meta`. Only
3404/// the option list `(shape = .., tag = "...", skip(...), as_character(...), ...)` is
3405/// accepted; the bare flag and `= true/false` are rejected with
3406/// [`SERDE_ERROR_BARE_HELP`], since the serde path is on for every eligible
3407/// error type under the `serde` feature.
3408pub(crate) fn parse_serde_error_nested(
3409    meta: &syn::meta::ParseNestedMeta,
3410) -> syn::Result<SerdeErrorSpec> {
3411    use syn::spanned::Spanned;
3412    let input = meta.input;
3413    if input.peek(syn::token::Paren) {
3414        let content;
3415        match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
3416        let metas =
3417            content.parse_terminated(<syn::Meta as syn::parse::Parse>::parse, ::syn::token::Commasyn::Token![,])?;
3418        return SerdeErrorSpec::from_metas(&metas, meta.path.span());
3419    }
3420    if input.peek(::syn::token::Eqsyn::Token![=]) {
3421        let _: ::syn::token::Eqsyn::Token![=] = input.parse()?;
3422        let _: syn::LitBool = input.parse()?;
3423    }
3424    Err(meta.error(SERDE_ERROR_BARE_HELP))
3425}
3426
3427/// Check the `NArgs` parameters of a signature (#1860): at most one, and none
3428/// that a per-parameter option names (`has_options`, keyed by Rust name). The
3429/// parameter is no R argument (the wrapper fills it with `nargs()`), so an
3430/// option would have nothing to act on.
3431///
3432/// Shared by standalone functions (`lib.rs`) and impl / trait methods
3433/// ([`finalize_method_param_attrs`]). Both errors point at the parameter,
3434/// also for a method, whose options are written on the method.
3435pub(crate) fn check_nargs_params(
3436    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3437    has_options: impl Fn(&str) -> bool,
3438) -> syn::Result<()> {
3439    let mut first: Option<&syn::Ident> = None;
3440    for arg in inputs {
3441        let syn::FnArg::Typed(pt) = arg else {
3442            continue;
3443        };
3444        if !crate::type_inspect::is_nargs_marker(&pt.ty) {
3445            continue;
3446        }
3447        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
3448            continue;
3449        };
3450        let ident = &pat_ident.ident;
3451        if let Some(first) = first {
3452            return Err(syn::Error::new_spanned(
3453                pt,
3454                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `#[miniextendr]` function takes at most one `NArgs` parameter: `{0}` already receives `nargs()`, and `{1}` would receive the same count",
                first, ident))
    })format!(
3455                    "a `#[miniextendr]` function takes at most one `NArgs` parameter: `{first}` \
3456                     already receives `nargs()`, and `{ident}` would receive the same count"
3457                ),
3458            ));
3459        }
3460        if has_options(&crate::naming::ident_name(ident)) {
3461            return Err(syn::Error::new_spanned(
3462                pt,
3463                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, `preconditions`, `formal`) do not apply to the `NArgs` parameter `{0}`: it is no R argument, the generated wrapper fills it with `nargs()`",
                ident))
    })format!(
3464                    "per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, \
3465                     `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, \
3466                     `preconditions`, `formal`) do not apply to the `NArgs` parameter \
3467                     `{ident}`: it is no R argument, the generated wrapper fills it with \
3468                     `nargs()`"
3469                ),
3470            ));
3471        }
3472        first = Some(ident);
3473    }
3474    Ok(())
3475}
3476
3477/// Check the `LazyDots` parameters of a signature (#1892): `LazyDots` is the
3478/// parameter's whole type, taken by value (`&LazyDots`, `Option<LazyDots>`,
3479/// `Vec<LazyDots>` are refused; `Missing<LazyDots>` is refused by
3480/// [`validate_param_type`]), and no per-parameter option names it
3481/// (`has_options`, keyed by Rust name): the wrapper passes its frame, and
3482/// every option would force the dots or has nothing to act on.
3483///
3484/// Shared by standalone functions (`lib.rs`) and impl methods
3485/// ([`finalize_method_param_attrs`]). A function takes at most one `...`
3486/// either way ([`find_dots_param`]).
3487pub(crate) fn check_lazy_dots_params(
3488    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3489    has_options: impl Fn(&str) -> bool,
3490) -> syn::Result<()> {
3491    for arg in inputs {
3492        let syn::FnArg::Typed(pt) = arg else {
3493            continue;
3494        };
3495        if !crate::type_inspect::is_lazy_dots_marker(&pt.ty) {
3496            if crate::type_inspect::mentions_lazy_dots(&pt.ty)
3497                && get_missing_inner_type(&pt.ty).is_none()
3498            {
3499                return Err(syn::Error::new_spanned(
3500                    &pt.ty,
3501                    "`LazyDots` must be the parameter's whole type, taken by value \
3502                     (`rest: LazyDots`): the wrapper passes the dots unforced only for that \
3503                     shape, and they are always present",
3504                ));
3505            }
3506            continue;
3507        }
3508        let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
3509            continue;
3510        };
3511        let ident = &pat_ident.ident;
3512        if has_options(&crate::naming::ident_name(ident)) {
3513            return Err(syn::Error::new_spanned(
3514                pt,
3515                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, `preconditions`, `formal`) do not apply to the `LazyDots` parameter `{0}`: it is R\'s `...`, which the wrapper passes unforced",
                ident))
    })format!(
3516                    "per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, \
3517                     `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, \
3518                     `preconditions`, `formal`) do not apply to the `LazyDots` parameter \
3519                     `{ident}`: it is R's `...`, which the wrapper passes unforced"
3520                ),
3521            ));
3522        }
3523    }
3524    Ok(())
3525}
3526
3527/// Check the RNG guard parameters of a signature (#2002): `&RngGuard` is
3528/// filled by a `#[miniextendr(rng)]` wrapper (`rng`) with the guard it holds,
3529/// so it needs `rng`, takes no per-parameter option (`has_options`, keyed by
3530/// Rust name: it is no R argument) and appears at most once. Any other shape
3531/// that names `RngGuard` (`&mut RngGuard`, by value, inside another type) is
3532/// refused: the wrapper lends its guard for the call.
3533///
3534/// Shared by standalone functions (`lib.rs`) and inherent methods
3535/// (`ParsedMethod::from_impl_item`). Every error points at the parameter.
3536pub(crate) fn check_rng_guard_params(
3537    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3538    rng: bool,
3539    has_options: impl Fn(&str) -> bool,
3540) -> syn::Result<()> {
3541    let mut first: Option<&syn::PatType> = None;
3542    for arg in inputs {
3543        let syn::FnArg::Typed(pt) = arg else {
3544            continue;
3545        };
3546        if !crate::type_inspect::is_rng_guard_param(&pt.ty) {
3547            if crate::type_inspect::mentions_rng_guard(&pt.ty) {
3548                return Err(syn::Error::new_spanned(
3549                    &pt.ty,
3550                    "take the RNG guard as `&RngGuard`: a `#[miniextendr(rng)]` wrapper lends \
3551                     its guard for the call, so `&mut RngGuard`, `RngGuard` by value or a guard \
3552                     inside another type is no parameter it can fill",
3553                ));
3554            }
3555            continue;
3556        }
3557        if first.is_some() {
3558            return Err(syn::Error::new_spanned(
3559                pt,
3560                "a `#[miniextendr]` function takes at most one `&RngGuard` parameter: every \
3561                 one would receive the same guard",
3562            ));
3563        }
3564        if !rng {
3565            return Err(syn::Error::new_spanned(
3566                pt,
3567                "a `&RngGuard` parameter needs `#[miniextendr(rng)]`: the `rng` wrapper loads \
3568                 R's RNG state and passes its guard here. Add `rng` to the `#[miniextendr(...)]` \
3569                 options, or call `with_rng(|rng| ...)` in the body",
3570            ));
3571        }
3572        if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
3573            && has_options(&crate::naming::ident_name(&pat_ident.ident))
3574        {
3575            return Err(syn::Error::new_spanned(
3576                pt,
3577                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, `preconditions`) do not apply to the `&RngGuard` parameter `{0}`: it is no R argument, the `rng` wrapper passes its guard",
                pat_ident.ident))
    })format!(
3578                    "per-parameter options (`coerce`, `match_arg`, `choices`, `several_ok`, \
3579                     `default`, `no_default`, `no_na`, `inherits`, `not_inherits`, \
3580                     `preconditions`) do not apply to the `&RngGuard` parameter `{}`: it is no \
3581                     R argument, the `rng` wrapper passes its guard",
3582                    pat_ident.ident
3583                ),
3584            ));
3585        }
3586        first = Some(pt);
3587    }
3588    Ok(())
3589}
3590
3591/// Refuse an `RngGuard` parameter on a trait method (#2002), in any shape:
3592/// the trait declaration and a trait impl. A trait method's arguments cross
3593/// the vtable as values its shim converts with `TryFromSexp`, and the guard is
3594/// no R value.
3595pub(crate) fn refuse_rng_guard_in_trait_method(
3596    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3597) -> syn::Result<()> {
3598    for arg in inputs {
3599        if let syn::FnArg::Typed(pt) = arg
3600            && crate::type_inspect::mentions_rng_guard(&pt.ty)
3601        {
3602            return Err(syn::Error::new_spanned(
3603                arg,
3604                "a trait method cannot take `&RngGuard`: its arguments cross the trait ABI as \
3605                 R values, and the guard is none. Load the state in the body with \
3606                 `with_rng(|rng| ...)` instead of the `rng` option, or take the guard on an \
3607                 inherent impl method or a standalone function",
3608            ));
3609        }
3610    }
3611    Ok(())
3612}
3613
3614/// Parse `rng(seed = <param>)` (#2198): the parameter, by its Rust name, whose
3615/// value the wrapper passes to `set.seed()`.
3616fn parse_rng_seed(list: &syn::MetaList) -> syn::Result<syn::Ident> {
3617    let mut seed: Option<syn::Ident> = None;
3618    list.parse_nested_meta(|meta| {
3619        if !meta.path.is_ident("seed") {
3620            return Err(meta.error("unknown `rng(...)` option; expected `seed = <parameter>`"));
3621        }
3622        let value = meta.value()?;
3623        let ident: syn::Ident = value.parse().map_err(|err| {
3624            syn::Error::new(
3625                err.span(),
3626                "`rng(seed = ..)` names the seed parameter by its Rust name, as an \
3627                 identifier: `rng(seed = seed)`",
3628            )
3629        })?;
3630        if seed.replace(ident).is_some() {
3631            return Err(meta.error("`seed` is set more than once"));
3632        }
3633        Ok(())
3634    })?;
3635    seed.ok_or_else(|| {
3636        syn::Error::new_spanned(
3637            list,
3638            "`rng(...)` takes `seed = <parameter>`; write `rng` alone for the RNG state \
3639             without the seed contract",
3640        )
3641    })
3642}
3643
3644/// Check `rng(seed = <param>)` (#2198) against a standalone function's
3645/// signature and return the seed's R formal. Every problem is reported at
3646/// once:
3647/// - `seed` names a parameter of `all_inputs`, by its Rust name;
3648/// - the parameter is an R argument the wrapper evaluates: one of the formals
3649///   of `r_inputs` (the signature without the condition-call marker and the
3650///   RNG guard), not `...`, an `NArgs` or a `Quoted` / `Quosure` argument
3651///   (`unevaluated`, by R name), since `set.seed()` forces it;
3652/// - the return can carry the `seed` attribute: a unit return
3653///   (`unit_return`) is `NULL`, which has none.
3654///
3655/// The R name comes from the formal list (`r_formal_names` with `formals`),
3656/// so the prelude names the formal the wrapper has, a declared `formal`
3657/// included (#1842).
3658pub(crate) fn check_rng_seed(
3659    seed: &syn::Ident,
3660    all_inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3661    r_inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3662    formals: &crate::r_wrapper_builder::Formals,
3663    unevaluated: &[String],
3664    unit_return: bool,
3665) -> syn::Result<String> {
3666    let rust_name = crate::naming::ident_name(seed);
3667    let is_param = all_inputs.iter().any(|arg| {
3668        #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::FnArg::Typed(pt) if
        #[allow(non_exhaustive_omitted_patterns)] match pt.pat.as_ref() {
            syn::Pat::Ident(p) if
                crate::naming::ident_name(&p.ident) == rust_name => true,
            _ => false,
        } => true,
    _ => false,
}matches!(arg, syn::FnArg::Typed(pt) if matches!(
3669            pt.pat.as_ref(),
3670            syn::Pat::Ident(p) if crate::naming::ident_name(&p.ident) == rust_name
3671        ))
3672    });
3673    let formal = crate::r_wrapper_builder::r_formal_names(r_inputs, formals)
3674        .find(|(_, ident)| crate::naming::ident_name(ident) == rust_name)
3675        .map(|(r_name, _)| r_name)
3676        .filter(|r_name| !unevaluated.contains(r_name));
3677
3678    let mut errors: Vec<syn::Error> = Vec::new();
3679    if !is_param {
3680        errors.push(syn::Error::new_spanned(
3681            seed,
3682            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`rng(seed = {0})` names no parameter of this function; name the parameter whose value goes to `set.seed()`, by its Rust name",
                rust_name))
    })format!(
3683                "`rng(seed = {rust_name})` names no parameter of this function; name the \
3684                 parameter whose value goes to `set.seed()`, by its Rust name"
3685            ),
3686        ));
3687    } else if formal.is_none() {
3688        errors.push(syn::Error::new_spanned(
3689            seed,
3690            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`rng(seed = {0})` needs an R argument the wrapper evaluates: it passes the value to `set.seed()`. `{0}` is not one (`...`, `NArgs`, a `Call` marker, the `&RngGuard` or a `Quoted` / `Quosure` argument)",
                rust_name))
    })format!(
3691                "`rng(seed = {rust_name})` needs an R argument the wrapper evaluates: it passes \
3692                 the value to `set.seed()`. `{rust_name}` is not one (`...`, `NArgs`, a `Call` \
3693                 marker, the `&RngGuard` or a `Quoted` / `Quosure` argument)"
3694            ),
3695        ));
3696    }
3697    if unit_return {
3698        errors.push(syn::Error::new_spanned(
3699            seed,
3700            "`rng(seed = ..)` records the seed in the result's `seed` attribute, and a unit \
3701             return is `NULL`, which can't carry one; return the simulated values",
3702        ));
3703    }
3704    match errors.into_iter().reduce(|mut all, error| {
3705        all.combine(error);
3706        all
3707    }) {
3708        Some(error) => Err(error),
3709        None => Ok(formal.expect("checked above")),
3710    }
3711}
3712
3713/// Refuse `rng(seed = ..)` on an impl or trait method (#2198), spanned on the
3714/// option: the seed contract is written in the R wrapper of a standalone
3715/// function or an `s3(...)` method. `meta` is `rng`, followed by its
3716/// parenthesized options.
3717pub(crate) fn refuse_rng_seed_on_method(
3718    meta: &syn::meta::ParseNestedMeta<'_>,
3719    method: &str,
3720) -> syn::Result<()> {
3721    let content;
3722    match ::syn::__private::parse_parens(&meta.input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in meta.input);
3723    let _: proc_macro2::TokenStream = content.parse()?;
3724    Err(meta.error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`rng(seed = ..)` is for standalone functions and their `s3(...)` methods, not {0}; write `rng` here, or give the seeded draws a standalone function (`#[miniextendr(rng(seed = seed), s3(generic = \"stats::simulate\", class = \"..\"))]`)",
                method))
    })format!(
3725        "`rng(seed = ..)` is for standalone functions and their `s3(...)` methods, not {method}; \
3726         write `rng` here, or give the seeded draws a standalone function \
3727         (`#[miniextendr(rng(seed = seed), s3(generic = \"stats::simulate\", class = \"..\"))]`)"
3728    )))
3729}
3730
3731/// Refuse a `LazyDots` parameter on a trait method (#1892), in any shape:
3732/// the trait declaration (`#[miniextendr_trait]`) and a trait impl. A trait
3733/// method's arguments cross the vtable as values its shim converts with
3734/// `TryFromSexp`, as for `&Dots`, which trait methods don't take either;
3735/// `LazyDots` is the generated wrapper's frame, which has no such value.
3736pub(crate) fn refuse_lazy_dots_in_trait_method(
3737    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3738) -> syn::Result<()> {
3739    for arg in inputs {
3740        if let syn::FnArg::Typed(pt) = arg
3741            && crate::type_inspect::mentions_lazy_dots(&pt.ty)
3742        {
3743            return Err(syn::Error::new_spanned(
3744                &pt.ty,
3745                "a trait method cannot take `LazyDots`: its arguments cross the trait ABI as \
3746                 converted values, and `LazyDots` is the generated wrapper's frame. Take it on \
3747                 an inherent impl method or a standalone function",
3748            ));
3749        }
3750    }
3751    Ok(())
3752}
3753
3754/// The error for `dots = typed_list!(..)` on a function whose `...` is a
3755/// `LazyDots` parameter (#1892): `typed_list!` validates the forced
3756/// `list(...)` of a `&Dots` parameter, and `LazyDots` forces nothing. `None`
3757/// when the dots are `&Dots` (or absent). `span` is the attribute's, else the
3758/// parameter's.
3759pub(crate) fn typed_list_on_lazy_dots(
3760    inputs: &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
3761    span: Option<proc_macro2::Span>,
3762) -> Option<syn::Error> {
3763    use syn::spanned::Spanned;
3764
3765    let pt = inputs.iter().find_map(|arg| match arg {
3766        syn::FnArg::Typed(pt) if crate::type_inspect::is_lazy_dots_marker(&pt.ty) => Some(pt),
3767        _ => None,
3768    })?;
3769    Some(syn::Error::new(
3770        span.unwrap_or_else(|| pt.span()),
3771        "`dots = typed_list!(..)` validates the forced `list(...)` of a `&Dots` parameter; a \
3772         `LazyDots` parameter passes the dots unforced. Take `&Dots` (or `name: ...`) to \
3773         validate them, or read the `LazyDots` elements yourself",
3774    ))
3775}
3776
3777/// Check that an S3 method for a replacement generic takes the new value last,
3778/// as `value`.
3779///
3780/// A replacement generic is one whose name ends in `<-` (`$<-`, `[[<-`,
3781/// `names<-`). R calls it with the new value as a named argument
3782/// (`x$f <- v` runs `` `$<-`(x, "f", value = v) ``), and `R CMD check`
3783/// (`tools::checkReplaceFuns()`) requires the last formal of every replacement
3784/// function, registered S3 methods included, to be `value`. The last
3785/// non-receiver parameter of `inputs` that is an R formal (an `NArgs`
3786/// parameter is none, #1860) must therefore become the R formal `value`
3787/// (`_value` does too, as does a declared `formal = "value"`, `formals`); a
3788/// trailing `&Dots` is `...`, so it can't be last. Any
3789/// other generic passes. `fallback` spans the error when there is no
3790/// parameter to point at.
3791///
3792/// Used by standalone `s3(generic = ..., class = ...)` functions (`lib.rs`,
3793/// once the condition-call markers have left the R formals) and by S3
3794/// impl-block instance methods (`miniextendr_impl.rs`).
3795pub(crate) fn check_replacement_value_param(
3796    generic: &str,
3797    inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
3798    formals: &crate::r_wrapper_builder::Formals,
3799    fallback: proc_macro2::Span,
3800) -> syn::Result<()> {
3801    use syn::spanned::Spanned;
3802
3803    if !generic.ends_with("<-") {
3804        return Ok(());
3805    }
3806    // The last R formal: an `NArgs` parameter is none (#1860), so `value`
3807    // may come before it.
3808    let last = inputs
3809        .iter()
3810        .enumerate()
3811        .rev()
3812        .find_map(|(idx, arg)| match arg {
3813            syn::FnArg::Typed(pat_type) if !crate::type_inspect::is_nargs_marker(&pat_type.ty) => {
3814                Some((idx, pat_type))
3815            }
3816            _ => None,
3817        });
3818    let found = match last {
3819        Some((idx, _)) if Some(idx) == dots_index(inputs) => "the last R argument is `...`".into(),
3820        Some((_, pat_type)) => match pat_type.pat.as_ref() {
3821            syn::Pat::Ident(pat_ident) => {
3822                let formal = formals.of_ident(&pat_ident.ident);
3823                if formal == "value" {
3824                    return Ok(());
3825                }
3826                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the last R argument is `{0}`",
                formal))
    })format!("the last R argument is `{formal}`")
3827            }
3828            _ => "the last parameter is not a plain name".into(),
3829        },
3830        None => "it takes no new-value parameter".to_string(),
3831    };
3832    let span = last.map_or(fallback, |(_, pat_type)| pat_type.span());
3833    Err(syn::Error::new(
3834        span,
3835        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("an S3 method for the replacement generic `{0}` must take the new value as its last parameter, named `value`, but {1}. R passes the new value as `value = ` (`x$f <- v` calls `` `$<-`(x, \"f\", value = v) ``), and `R CMD check` requires the last argument of a replacement function to be named `value`.",
                generic, found))
    })format!(
3836            "an S3 method for the replacement generic `{generic}` must take the new value as \
3837             its last parameter, named `value`, but {found}. R passes the new value as \
3838             `value = ` (`x$f <- v` calls `` `$<-`(x, \"f\", value = v) ``), and `R CMD check` \
3839             requires the last argument of a replacement function to be named `value`."
3840        ),
3841    ))
3842}
3843
3844#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ReturnPref { }
#[automatically_derived]
impl ::core::clone::Clone for ReturnPref {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ReturnPref { }Copy, #[automatically_derived]
impl ::core::default::Default for ReturnPref {
    #[inline]
    fn default() -> Self { Self::Auto }
}Default)]
3845/// Preferred return-conversion path for `IntoR`.
3846pub(crate) enum ReturnPref {
3847    /// Use the default `IntoR` implementation for the type.
3848    #[default]
3849    Auto,
3850    /// Force list conversion via the `AsList` wrapper.
3851    List,
3852    /// Force external pointer conversion via the `AsExternalPtr` wrapper.
3853    ExternalPtr,
3854    /// Force native vector/scalar conversion via the `AsRNative` wrapper.
3855    Native,
3856}
3857
3858/// Parses the comma-separated option list inside `#[miniextendr(...)]`.
3859///
3860/// Supports three syntactic forms for each option:
3861/// - **Bare identifier**: `#[miniextendr(invisible)]`
3862/// - **Name-value**: `#[miniextendr(prefer = "list")]` or `#[miniextendr(invisible = true)]`
3863/// - **Nested list**: `#[miniextendr(s3(generic = "...", class = "..."))]`
3864///
3865/// Options with negated forms (`no_worker`, `no_coerce`, `no_strict`) explicitly
3866/// disable the corresponding flag, which is useful for overriding feature-based
3867/// defaults.
3868///
3869/// An empty input (plain `#[miniextendr]`) resolves all options to their feature-based
3870/// defaults (e.g., `worker-default`, `coerce-default`, `strict-default`).
3871///
3872/// # Errors
3873///
3874/// Returns a compile error for:
3875/// - Unknown option names (prevents silent typos)
3876/// - Mutually exclusive options (`internal` + `noexport`)
3877/// - Invalid values for key-value options (e.g., bad `prefer` or `c_symbol`)
3878/// - Missing required sub-options (e.g., `s3(...)` without `class`)
3879impl syn::parse::Parse for MiniextendrFnAttrs {
3880    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
3881        use syn::spanned::Spanned;
3882        // Use Option<bool> for fields that support feature defaults.
3883        // None = not explicitly set → resolve from cfg!(feature = "...") at end.
3884        let mut force_worker: Option<bool> = None;
3885        let mut force_invisible: Option<bool> = None;
3886        let mut check_interrupt = false;
3887        let mut coerce_all: Option<bool> = None;
3888        let mut rng = false;
3889        let mut rng_seed = None;
3890        let mut unwrap_in_r = false;
3891        let mut serialize = false;
3892        let mut wrap = None;
3893        let mut serde_error: Option<SerdeErrorSpec> = None;
3894        let mut preconditions: Option<bool> = None;
3895        let mut return_pref = ReturnPref::Auto;
3896        let mut return_pref_span: Option<proc_macro2::Span> = None;
3897        let mut s3_generic = None;
3898        let mut s3_class = None;
3899        let mut dots_spec = None;
3900        let mut dots_policy = None;
3901        let mut dots_span = None;
3902        let mut lifecycle = None;
3903        let mut strict: Option<bool> = None;
3904        let mut internal = false;
3905        let mut noexport = false;
3906        let mut export = false;
3907        let mut doc = None;
3908        let mut c_symbol = None;
3909        let mut r_name = None;
3910        let mut postfix = None;
3911        let mut call_attr: Option<crate::r_wrapper_builder::CallAttribution> = None;
3912        let mut call_arg = false;
3913        let mut r_entry = None;
3914        let mut r_post_checks = None;
3915        let mut r_on_exit = None;
3916
3917        // Empty input (`#[miniextendr]`) → skip the parse loop and fall through
3918        // to the single Ok(Self {...}) at the bottom; every local is already
3919        // seeded with its default value above.
3920        let metas = if input.is_empty() {
3921            syn::punctuated::Punctuated::new()
3922        } else {
3923            syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated(input)?
3924        };
3925
3926        for meta in metas {
3927            match meta {
3928                // Simple identifiers: invisible, visible, check_interrupt, coerce, worker, rng
3929                syn::Meta::Path(path) => {
3930                    if let Some(ident) = path.get_ident() {
3931                        if ident == "invisible" {
3932                            force_invisible = Some(true);
3933                        } else if ident == "visible" {
3934                            force_invisible = Some(false);
3935                        } else if ident == "check_interrupt" {
3936                            check_interrupt = true;
3937                        } else if ident == "coerce" {
3938                            coerce_all = Some(true);
3939                        } else if ident == "no_coerce" {
3940                            coerce_all = Some(false);
3941                        } else if ident == "rng" {
3942                            rng = true;
3943                        } else if ident == "unwrap_in_r" {
3944                            unwrap_in_r = true;
3945                        } else if ident == "serialize" {
3946                            serialize = true;
3947                        } else if ident == "serde_error" {
3948                            return Err(syn::Error::new_spanned(&path, SERDE_ERROR_BARE_HELP));
3949                        } else if ident == "worker" {
3950                            force_worker = Some(true);
3951                            crate::deprecation::worker_option(ident.span());
3952                        } else if ident == "no_worker" {
3953                            force_worker = Some(false);
3954                        } else if ident == "strict" {
3955                            strict = Some(true);
3956                        } else if ident == "no_strict" {
3957                            strict = Some(false);
3958                        } else if ident == "preconditions" {
3959                            preconditions = Some(true);
3960                        } else if ident == "no_preconditions" {
3961                            preconditions = Some(false);
3962                        } else if ident == "internal" {
3963                            internal = true;
3964                        } else if ident == "noexport" {
3965                            noexport = true;
3966                        } else if ident == "export" {
3967                            export = true;
3968                        } else if ident == "call_arg" {
3969                            call_arg = true;
3970                        } else {
3971                            return Err(syn::Error::new_spanned(
3972                                ident,
3973                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `#[miniextendr]` option; expected one of: {0}",
                FN_BOOL_FLAGS_HELP))
    })format!(
3974                                    "unknown `#[miniextendr]` option; expected one of: {FN_BOOL_FLAGS_HELP}"
3975                                ),
3976                            ));
3977                        }
3978                    }
3979                }
3980                syn::Meta::NameValue(nv) => {
3981                    if nv.path.is_ident("wrap") {
3982                        let value = parse_lit_str(&nv, "wrap")?;
3983                        wrap = Some(crate::return_wrap::parse_system(&syn::LitStr::new(
3984                            &value,
3985                            nv.path.span(),
3986                        ))?);
3987                        continue;
3988                    }
3989                    // Check for boolean flag options: option = true / option = false
3990                    if let syn::Expr::Lit(syn::ExprLit {
3991                        lit: syn::Lit::Bool(lit_bool),
3992                        ..
3993                    }) = &nv.value
3994                    {
3995                        let val = lit_bool.value;
3996                        if let Some(ident) = nv.path.get_ident() {
3997                            if ident == "invisible" {
3998                                force_invisible = Some(val);
3999                            } else if ident == "visible" {
4000                                force_invisible = Some(!val);
4001                            } else if ident == "check_interrupt" {
4002                                check_interrupt = val;
4003                            } else if ident == "worker" {
4004                                force_worker = Some(val);
4005                                if val {
4006                                    crate::deprecation::worker_option(ident.span());
4007                                }
4008                            } else if ident == "no_worker" {
4009                                force_worker = Some(!val);
4010                            } else if ident == "coerce" {
4011                                coerce_all = Some(val);
4012                            } else if ident == "no_coerce" {
4013                                coerce_all = Some(!val);
4014                            } else if ident == "rng" {
4015                                rng = val;
4016                            } else if ident == "unwrap_in_r" {
4017                                unwrap_in_r = val;
4018                            } else if ident == "serialize" {
4019                                serialize = val;
4020                            } else if ident == "serde_error" {
4021                                return Err(syn::Error::new_spanned(&nv, SERDE_ERROR_BARE_HELP));
4022                            } else if ident == "strict" {
4023                                strict = Some(val);
4024                            } else if ident == "no_strict" {
4025                                strict = Some(!val);
4026                            } else if ident == "preconditions" {
4027                                preconditions = Some(val);
4028                            } else if ident == "no_preconditions" {
4029                                preconditions = Some(!val);
4030                            } else if ident == "internal" {
4031                                internal = val;
4032                            } else if ident == "noexport" {
4033                                noexport = val;
4034                            } else if ident == "export" {
4035                                export = val;
4036                            } else if ident == "call_arg" {
4037                                call_arg = val;
4038                            } else {
4039                                return Err(syn::Error::new_spanned(
4040                                    ident,
4041                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `#[miniextendr]` option `{0}`; expected one of: {1}",
                ident, FN_BOOL_FLAGS_HELP))
    })format!(
4042                                        "unknown `#[miniextendr]` option `{ident}`; expected one of: \
4043                                         {FN_BOOL_FLAGS_HELP}"
4044                                    ),
4045                                ));
4046                            }
4047                            continue;
4048                        }
4049                    }
4050
4051                    if nv.path.is_ident("prefer") {
4052                        let v = parse_lit_str(&nv, "prefer")?;
4053                        return_pref_span = Some(nv.span());
4054                        return_pref = match v.as_str() {
4055                            "list" => ReturnPref::List,
4056                            "externalptr" => ReturnPref::ExternalPtr,
4057                            "vector" | "native" => ReturnPref::Native,
4058                            "auto" => ReturnPref::Auto,
4059                            _ => {
4060                                return Err(syn::Error::new_spanned(
4061                                    &nv.value,
4062                                    "prefer must be one of: auto, list, externalptr, vector/native",
4063                                ));
4064                            }
4065                        };
4066                    } else if nv.path.is_ident("dots") {
4067                        // `dots = typed_list!(...)`, or the role of `...`
4068                        // (#2157). The span is kept for the signature checks.
4069                        dots_span = Some(nv.path.span());
4070                        match crate::dots_policy::parse_dots_attr(&nv.value)? {
4071                            crate::dots_policy::DotsAttr::TypedList(spec) => {
4072                                dots_spec = Some(spec);
4073                            }
4074                            crate::dots_policy::DotsAttr::Policy(policy) => {
4075                                dots_policy = Some(policy);
4076                            }
4077                        }
4078                    } else if nv.path.is_ident("lifecycle") {
4079                        // lifecycle = "stage"
4080                        if let Some(spec) = crate::lifecycle::parse_lifecycle_attr(
4081                            &syn::Meta::NameValue(nv.clone()),
4082                        )? {
4083                            lifecycle = Some(spec);
4084                        }
4085                    } else if nv.path.is_ident("doc") {
4086                        doc = Some(parse_lit_str(&nv, "doc")?);
4087                    } else if nv.path.is_ident("c_symbol") {
4088                        let val = parse_lit_str(&nv, "c_symbol")?;
4089                        if val.is_empty()
4090                            || (!val.starts_with(|c: char| c.is_ascii_alphabetic())
4091                                && !val.starts_with('_'))
4092                        {
4093                            return Err(syn::Error::new_spanned(
4094                                &nv.value,
4095                                "c_symbol must be a valid C identifier",
4096                            ));
4097                        }
4098                        if !val.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
4099                            return Err(syn::Error::new_spanned(
4100                                &nv.value,
4101                                "c_symbol must be a valid C identifier (alphanumeric and underscore only)",
4102                            ));
4103                        }
4104                        c_symbol = Some(val);
4105                    } else if nv.path.is_ident("r_name") {
4106                        let val = parse_lit_str(&nv, "r_name")?;
4107                        if val.is_empty() {
4108                            return Err(syn::Error::new_spanned(
4109                                &nv.value,
4110                                "r_name must not be empty",
4111                            ));
4112                        }
4113                        r_name = Some(val);
4114                    } else if nv.path.is_ident("postfix") {
4115                        let val = parse_lit_str(&nv, "postfix")?;
4116                        validate_postfix(&val, &nv.value)?;
4117                        postfix = Some(val);
4118                    } else if nv.path.is_ident("call") {
4119                        let name = match &nv.value {
4120                            syn::Expr::Path(p) => p.path.get_ident().map(|i| i.to_string()),
4121                            syn::Expr::Lit(syn::ExprLit {
4122                                lit: syn::Lit::Str(s),
4123                                ..
4124                            }) => Some(s.value()),
4125                            _ => None,
4126                        };
4127                        let Some(attribution) = name
4128                            .as_deref()
4129                            .and_then(crate::r_wrapper_builder::CallAttribution::parse_name)
4130                        else {
4131                            return Err(syn::Error::new_spanned(
4132                                &nv.value,
4133                                "`call = ...` accepts `wrapper` (the call as written, the \
4134                                 default), `caller` (attribute conditions to the wrapper's \
4135                                 caller) or `none` (conditions carry no call)",
4136                            ));
4137                        };
4138                        if call_attr.is_some() {
4139                            return Err(syn::Error::new_spanned(
4140                                &nv,
4141                                "`call = ...` is set more than once",
4142                            ));
4143                        }
4144                        call_attr = Some(attribution);
4145                    } else if nv.path.is_ident("r_entry") {
4146                        r_entry = Some(parse_lit_str(&nv, "r_entry")?);
4147                    } else if nv.path.is_ident("r_post_checks") {
4148                        r_post_checks = Some(parse_lit_str(&nv, "r_post_checks")?);
4149                    } else if nv.path.is_ident("r_on_exit") {
4150                        // Short form: r_on_exit = "expr" → on.exit(expr, add = TRUE)
4151                        r_on_exit = Some(ROnExit {
4152                            expr: parse_lit_str(&nv, "r_on_exit")?,
4153                            add: true,
4154                            after: true,
4155                        });
4156                    } else {
4157                        let key_name = nv
4158                            .path
4159                            .get_ident()
4160                            .map(|i| i.to_string())
4161                            .unwrap_or_default();
4162                        return Err(syn::Error::new_spanned(
4163                            nv,
4164                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `#[miniextendr]` key-value option `{0}`. Key-value options are: `prefer = \"...\"`, `dots = typed_list!(...)`, `lifecycle = \"...\"`, `doc = \"...\"`, `c_symbol = \"...\"`, `r_name = \"...\"`, `postfix = \"...\"`, `call = wrapper | caller | none`, `r_entry = \"...\"`, `r_post_checks = \"...\"`, `r_on_exit = \"...\"`",
                key_name))
    })format!(
4165                                "unknown `#[miniextendr]` key-value option `{}`. \
4166                                 Key-value options are: `prefer = \"...\"`, `dots = typed_list!(...)`, \
4167                                 `lifecycle = \"...\"`, `doc = \"...\"`, `c_symbol = \"...\"`, \
4168                                 `r_name = \"...\"`, `postfix = \"...\"`, `call = wrapper | caller | none`, `r_entry = \"...\"`, \
4169                                 `r_post_checks = \"...\"`, \
4170                                 `r_on_exit = \"...\"`",
4171                                key_name,
4172                            ),
4173                        ));
4174                    }
4175                }
4176                syn::Meta::List(list) => {
4177                    if list.path.is_ident("defaults") {
4178                        // Ignore defaults(...) - it's handled by impl method parsing
4179                        // This allows #[miniextendr(defaults(...))] on impl methods
4180                    } else if list.path.is_ident("lifecycle") {
4181                        // lifecycle(stage = "deprecated", when = "0.4.0", ...)
4182                        if let Some(spec) =
4183                            crate::lifecycle::parse_lifecycle_attr(&syn::Meta::List(list.clone()))?
4184                        {
4185                            lifecycle = Some(spec);
4186                        }
4187                    } else if list.path.is_ident("s3") {
4188                        // Parse s3(generic = "...", class = "...")
4189                        list.parse_nested_meta(|meta| {
4190                            if meta.path.is_ident("generic") {
4191                                let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4192                                let value: syn::LitStr = meta.input.parse()?;
4193                                s3_generic = Some(crate::naming::parse_generic(&value)?);
4194                            } else if meta.path.is_ident("class") {
4195                                let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4196                                let value: syn::LitStr = meta.input.parse()?;
4197                                s3_class = Some(value.value());
4198                            } else {
4199                                return Err(
4200                                    meta.error("unknown s3 option; expected `generic` or `class`")
4201                                );
4202                            }
4203                            Ok(())
4204                        })?;
4205                        // Validate: s3 requires class (generic can default to function name)
4206                        if s3_class.is_none() {
4207                            return Err(syn::Error::new_spanned(
4208                                &list,
4209                                "s3(...) requires `class = \"...\"` to specify the S3 class suffix; \
4210                                 `generic` is optional and defaults to the function name",
4211                            ));
4212                        }
4213                    } else if list.path.is_ident("r_on_exit") {
4214                        // Long form: r_on_exit(expr = "...", add = false, after = false)
4215                        let mut expr = None;
4216                        let mut add = true;
4217                        let mut after = true;
4218                        list.parse_nested_meta(|meta| {
4219                            if meta.path.is_ident("expr") {
4220                                let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4221                                let value: syn::LitStr = meta.input.parse()?;
4222                                expr = Some(value.value());
4223                            } else if meta.path.is_ident("add") {
4224                                let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4225                                let value: syn::LitBool = meta.input.parse()?;
4226                                add = value.value;
4227                            } else if meta.path.is_ident("after") {
4228                                let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4229                                let value: syn::LitBool = meta.input.parse()?;
4230                                after = value.value;
4231                            } else {
4232                                return Err(meta.error(
4233                                    "unknown r_on_exit option; expected `expr`, `add`, or `after`",
4234                                ));
4235                            }
4236                            Ok(())
4237                        })?;
4238                        let expr = expr.ok_or_else(|| {
4239                            syn::Error::new_spanned(
4240                                &list,
4241                                "r_on_exit(...) requires `expr = \"...\"` specifying the R expression",
4242                            )
4243                        })?;
4244                        r_on_exit = Some(ROnExit { expr, add, after });
4245                    } else if list.path.is_ident("serde_error") {
4246                        serde_error = Some(parse_serde_error_list(&list)?);
4247                    } else if list.path.is_ident("rng") {
4248                        rng_seed = Some(parse_rng_seed(&list)?);
4249                    } else if let Some(ident) = list.path.get_ident() {
4250                        // Bool-flag parenthesized form (e.g. `strict(true)`) is not
4251                        // supported — write `strict` alone or `strict = true` instead.
4252                        let opt_name = ident.to_string();
4253                        return Err(syn::Error::new_spanned(
4254                            &list,
4255                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` does not accept parenthesized arguments. Use `{0}` alone or `{0} = true/false`.",
                opt_name))
    })format!(
4256                                "`{opt_name}` does not accept parenthesized arguments. \
4257                                 Use `{opt_name}` alone or `{opt_name} = true/false`.",
4258                            ),
4259                        ));
4260                    } else {
4261                        // path(something) where path is not a single ident
4262                        return Err(syn::Error::new_spanned(
4263                            list,
4264                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unrecognized nested option. Nested options are: {0}",
                FN_NESTED_OPTIONS_HELP))
    })format!(
4265                                "unrecognized nested option. Nested options are: {FN_NESTED_OPTIONS_HELP}"
4266                            ),
4267                        ));
4268                    }
4269                }
4270            }
4271        }
4272
4273        // Validate: `internal` and `noexport` are redundant together
4274        if internal && noexport {
4275            return Err(syn::Error::new(
4276                proc_macro2::Span::call_site(),
4277                "`internal` and `noexport` cannot be used together. \
4278                 `internal` already suppresses @export and also adds @keywords internal. \
4279                 Use `internal` alone to mark as internal, or `noexport` alone to only suppress export.",
4280            ));
4281        }
4282
4283        // Validate: `export` conflicts with `noexport` and `internal`
4284        if export && noexport {
4285            return Err(syn::Error::new(
4286                proc_macro2::Span::call_site(),
4287                "`export` and `noexport` are contradictory.",
4288            ));
4289        }
4290        if export && internal {
4291            return Err(syn::Error::new(
4292                proc_macro2::Span::call_site(),
4293                "`export` and `internal` are contradictory.",
4294            ));
4295        }
4296
4297        // Validate: `r_name` is incompatible with S3 naming (`s3(generic/class)`)
4298        // Validate: `postfix` derives the wrapper name from the Rust name; it
4299        // cannot combine with another naming source.
4300        if postfix.is_some() && r_name.is_some() {
4301            return Err(syn::Error::new(
4302                proc_macro2::Span::call_site(),
4303                "`postfix` and `r_name` both set the R wrapper name; use one of them.",
4304            ));
4305        }
4306        if postfix.is_some() && (s3_generic.is_some() || s3_class.is_some()) {
4307            return Err(syn::Error::new(
4308                proc_macro2::Span::call_site(),
4309                "`postfix` cannot be used with `s3(generic = ..., class = ...)`. \
4310                 S3 method names are always `generic.class`.",
4311            ));
4312        }
4313
4314        // Validate: `call = caller` is for internal entry points only.
4315        use crate::r_wrapper_builder::CallAttribution;
4316        if call_attr == Some(CallAttribution::Caller) && !(noexport || internal) {
4317            return Err(syn::Error::new(
4318                proc_macro2::Span::call_site(),
4319                "`call = caller` attributes conditions to the wrapper's caller, which is only \
4320                 meaningful for a package-internal entry point; add `noexport` or `internal`.",
4321            ));
4322        }
4323        // `call_arg` (#1834): a `.call` formal whose default is the wrapper's
4324        // own call. A `caller` wrapper already has the formal, defaulting to
4325        // the caller's call, and an S3 method can't take a formal its generic
4326        // lacks.
4327        if call_arg && call_attr == Some(CallAttribution::Caller) {
4328            return Err(syn::Error::new(
4329                proc_macro2::Span::call_site(),
4330                "`call_arg` cannot be combined with `call = caller`: a `caller` wrapper already \
4331                 takes `.call`, where NULL means the caller's call. Keep `call = caller`, or drop \
4332                 it so that NULL means this function's own call.",
4333            ));
4334        }
4335        // `call = none` (#1851) passes no call, so there is nothing for a
4336        // `.call` argument to replace.
4337        if call_arg && call_attr == Some(CallAttribution::NoCall) {
4338            return Err(syn::Error::new(
4339                proc_macro2::Span::call_site(),
4340                "`call_arg` cannot be combined with `call = none`: `.call` names the call the \
4341                 conditions report, and `none` reports none. Keep one of them.",
4342            ));
4343        }
4344        if call_arg && (s3_generic.is_some() || s3_class.is_some()) {
4345            return Err(syn::Error::new(
4346                proc_macro2::Span::call_site(),
4347                "`call_arg` cannot be used with `s3(...)`: it adds a `.call` formal, and an S3 \
4348                 method can't take a formal its generic lacks.",
4349            ));
4350        }
4351        if r_name.is_some() && (s3_generic.is_some() || s3_class.is_some()) {
4352            return Err(syn::Error::new(
4353                proc_macro2::Span::call_site(),
4354                "`r_name` cannot be used with `s3(generic = ..., class = ...)`. \
4355                 S3 method names are always `generic.class`. Use `generic` and `class` instead.",
4356            ));
4357        }
4358
4359        if serialize && (unwrap_in_r || serde_error.is_some()) {
4360            return Err(syn::Error::new(
4361                proc_macro2::Span::call_site(),
4362                "`serialize` cannot be combined with `unwrap_in_r` or `serde_error`: it serializes the complete return value, including any Result variant",
4363            ));
4364        }
4365
4366        // Validate: `serde_error` classes the raised condition; `unwrap_in_r`
4367        // never raises (the Result is returned as a value), so combining them
4368        // is a contradiction.
4369        if serde_error.is_some() && unwrap_in_r {
4370            return Err(syn::Error::new(
4371                proc_macro2::Span::call_site(),
4372                "`serde_error` cannot be used with `unwrap_in_r`: `unwrap_in_r` returns the \
4373                 `Result` to R as a value, so there is no raised condition to class.",
4374            ));
4375        }
4376
4377        Ok(Self {
4378            force_worker: force_worker.unwrap_or(falsecfg!(feature = "worker-default")),
4379            force_invisible,
4380            check_interrupt,
4381            coerce_all: coerce_all.unwrap_or(falsecfg!(feature = "coerce-default")),
4382            rng: rng || rng_seed.is_some(),
4383            rng_seed,
4384            unwrap_in_r,
4385            serialize,
4386            wrap,
4387            serde_error,
4388            preconditions,
4389            call_attribution: call_attr,
4390            call_arg,
4391            return_pref,
4392            return_pref_span,
4393            s3_generic,
4394            s3_class,
4395            dots_spec,
4396            dots_policy,
4397            dots_span,
4398            lifecycle,
4399            strict: strict.unwrap_or(falsecfg!(feature = "strict-default")),
4400            internal,
4401            noexport,
4402            export,
4403            doc,
4404            c_symbol,
4405            r_name,
4406            postfix,
4407            r_entry,
4408            r_post_checks,
4409            r_on_exit,
4410        })
4411    }
4412}
4413// endregion