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
910use crate::r_wrapper_const_ident_for;
1112// region: Coercion analysis
1314/// 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.
17Numeric,
18/// Keep logical scalars and additionally accept integer zero or one.
19Bool,
20/// Keep logical vectors and additionally accept integer zeros and ones.
21BoolVec,
22/// Native `i32`: keep `INTSXP`, additionally accept whole-valued doubles,
23 /// logicals, and raws.
24NativeInt,
25/// Native `Vec<i32>`: element-wise form of [`Self::NativeInt`].
26NativeIntVec,
27/// Native `f64`: keep `REALSXP`, additionally accept integers, logicals, and raws.
28NativeReal,
29/// Native `Vec<f64>`: element-wise form of [`Self::NativeReal`].
30NativeRealVec,
31}
3233impl 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`.
42pub(crate) fn from_type(ty: &syn::Type) -> Option<Self> {
43let syn::Type::Path(type_path) = tyelse {
44return None;
45 };
46let seg = type_path.path.segments.last()?;
47let is_vec = seg.ident == "Vec";
48let ident = if is_vec {
49let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
50return None;
51 };
52let syn::GenericArgument::Type(syn::Type::Path(inner)) = args.args.first()? else {
53return None;
54 };
55&inner.path.segments.last()?.ident
56 } else {
57&seg.ident
58 };
59match ident.to_string().as_str() {
60"i8" | "i16" | "u16" | "u32" | "i64" | "u64" | "isize" | "usize" | "f32" => {
61Some(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}
7374// endregion
7576// region: Type inspection helpers
7778/// 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 {
83match ty {
84 syn::Type::Path(tp) => tp85 .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}
9495/// 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.
99Forced,
100/// `LazyDots`: the wrapper passes its own frame, `environment()`, and
101 /// nothing is forced (#1892).
102Lazy,
103}
104105impl DotsKind {
106/// The parameter type as written, for diagnostics.
107pub(crate) fn type_name(self) -> &'static str {
108match self {
109Self::Forced => "&Dots",
110Self::Lazy => "LazyDots",
111 }
112 }
113114/// The `.Call()` argument the R wrapper passes for the dots.
115pub(crate) fn r_call_arg(self) -> &'static str {
116match self {
117Self::Forced => "list(...)",
118Self::Lazy => "environment()",
119 }
120 }
121}
122123/// 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> {
128if crate::type_inspect::is_lazy_dots_marker(ty) {
129Some(DotsKind::Lazy)
130 } else if type_ends_with(ty, "Dots") {
131Some(DotsKind::Forced)
132 } else {
133None134 }
135}
136137/// 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 {
140dots_kind(ty).is_some()
141}
142143/// 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<()> {
157use syn::spanned::Spanned;
158159let Some(variadic) = sig.variadic.take() else {
160return Ok(());
161 };
162if let Some(syn::FnArg::Typed(existing)) = dots_index(&sig.inputs).map(|idx| &sig.inputs[idx]) {
163let kind = dots_kind(existing.ty.as_ref()).map_or("&Dots", DotsKind::type_name);
164let 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 };
168return Err(syn::Error::new(
169variadic.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 }
173let ident = match variadic.pat.map(|(pat, _)| *pat) {
174Some(syn::Pat::Ident(pat_ident)) => pat_ident.ident,
175// `_: ...` (rustc's fix for a bare `...`) and a bare `...`.
176Some(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.
179for arg in &sig.inputs {
180let syn::FnArg::Typed(pat_type) = arg else {
181continue;
182 };
183if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
184 && pat_ident.ident == "__miniextendr_dots"
185{
186return 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 }
195Some(pat) => {
196return Err(syn::Error::new(
197pat.span(),
198"variadic pattern must be a simple identifier (`args: ...`) or `_` (`_: ...`)",
199 ));
200 }
201 };
202sig.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));
204Ok(())
205}
206207/// 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> {
215inputs216 .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}
219220/// 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>> {
235use syn::spanned::Spanned;
236237let mut found: Option<(syn::Ident, DotsKind)> = None;
238for arg in inputs {
239let syn::FnArg::Typed(pat_type) = arg else {
240continue;
241 };
242let Some(kind) = dots_kind(pat_type.ty.as_ref()) else {
243continue;
244 };
245let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() else {
246return 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 };
254let ident = &pat_ident.ident;
255if let Some((first, first_kind)) = &found {
256let 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 };
269return Err(syn::Error::new(ident.span(), msg));
270 }
271 found = Some((ident.clone(), kind));
272 }
273Ok(found.map(|(ident, _)| ident))
274}
275276/// Check if a type is `Missing<T>`.
277pub(crate) fn is_missing_type(ty: &syn::Type) -> bool {
278type_ends_with(ty, "Missing")
279}
280281/// 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 {
289match ty {
290 syn::Type::Path(tp) => {
291let Some(seg) = tp.path.segments.last() else {
292return false;
293 };
294if seg.ident == "Vec" {
295return true;
296 }
297if seg.ident == "Box" {
298let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
299return false;
300 };
301return #[allow(non_exhaustive_omitted_patterns)] match args.args.first() {
Some(syn::GenericArgument::Type(syn::Type::Slice(_))) => true,
_ => false,
}matches!(
302 args.args.first(),
303Some(syn::GenericArgument::Type(syn::Type::Slice(_)))
304 );
305 }
306false
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}
314315/// 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> {
319let syn::Type::Path(tp) = tyelse {
320return None;
321 };
322let seg = tp.path.segments.last()?;
323if seg.ident != "Missing" {
324return None;
325 }
326let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
327return None;
328 };
329if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
330Some(inner)
331 } else {
332None333 }
334}
335336/// 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<()> {
342if crate::return_wrap::contains_marker(ty) {
343return Err(syn::Error::new_spanned(
344ty,
345"class return markers are return-position only",
346 ));
347 }
348if let Some(err) = crate::type_inspect::visibility_marker_error(ty, "argument") {
349return Err(err);
350 }
351if let Some(inner) = get_missing_inner_type(ty) {
352if is_missing_type(inner) {
353return Err(syn::Error::new(
354span,
355"Missing<T> cannot be nested; use Missing<T> with the inner type directly",
356 ));
357 }
358if crate::type_inspect::is_lazy_dots_marker(inner) {
359return Err(syn::Error::new(
360span,
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 }
365if is_dots_type(inner) {
366return Err(syn::Error::new(
367span,
368"Missing<T> cannot wrap Dots; variadic parameters (...) are always present when called",
369 ));
370 }
371 }
372Ok(())
373}
374375/// 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<()> {
396if attr.has_no_default
397 && let Some(msg) = no_default_conflict(
398param_name,
399attr.has_match_arg || attr.choices.is_some(),
400attr.default_value.is_some(),
401false,
402 )
403 {
404return Err(syn::Error::new(span, msg));
405 }
406if attr.has_coerce && attr.has_match_arg {
407return Err(syn::Error::new(
408span,
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 }
416if attr.has_coerce && attr.choices.is_some() {
417return Err(syn::Error::new(
418span,
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 }
426if attr.choices.is_some() && attr.default_value.is_some() {
427return Err(syn::Error::new(
428span,
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 }
436if attr.has_match_arg && attr.choices.is_some() {
437return Err(syn::Error::new(
438span,
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 }
447if attr.has_several_ok && attr.choices.is_none() && !attr.has_match_arg {
448return Err(syn::Error::new(
449span,
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 }
457if is_dots && !attr.checks.is_empty() {
458return Err(syn::Error::new(
459span,
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 }
467for check in [ClassCheck::Inherits, ClassCheck::NotInherits] {
468if let Some(classes) = check.classes(&attr.checks)
469 && (classes.is_empty() || classes.iter().any(String::is_empty))
470 {
471let key = check.keyword();
472return 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 }
481if let Some(msg) = attr.checks.class_conflict(param_name) {
482return Err(syn::Error::new(span, msg));
483 }
484if is_dots && attr.default_value.is_some() {
485return Err(syn::Error::new(
486span,
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 }
493if is_dots && let Some(formal) = &attr.formal {
494return Err(dots_formal_error(formal, param_name));
495 }
496Ok(())
497}
498499/// 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> {
511let (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 };
522if !is_choice {
523return 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 }
529if has_default {
530return 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 }
535None536}
537538/// 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(
541formal.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}
548549// endregion
550551// region: Per-parameter attribute parsing
552553/// 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";
561562/// 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\"`";
565566/// 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";
569570/// 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`).
579pub has_coerce: bool,
580/// Whether `match_arg` was present, generating R `match.arg()` validation for
581 /// string parameters against a set of allowed values.
582pub 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).
585pub default_value: Option<(String, proc_macro2::Span)>,
586/// Choices for string parameters: `#[miniextendr(choices("a", "b", "c"))]`.
587pub 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`.
590pub 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`.
594pub 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`]).
599pub 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.
603pub preconditions: Option<bool>,
604/// `formal = "conf.int"`: the parameter's R formal (#1842), checked by
605 /// [`crate::r_wrapper_builder::check_r_formals`].
606pub formal: Option<syn::LitStr>,
607}
608609impl 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.
615pub(crate) fn merge(&mut self, other: PerParamMiniextendrAttr) -> Result<(), String> {
616if let Some(formal) = other.formal {
617if self.formal.is_some() {
618return Err(FORMAL_TWICE.to_string());
619 }
620self.formal = Some(formal);
621 }
622if other.preconditions.is_some() {
623self.preconditions = other.preconditions;
624 }
625self.has_coerce |= other.has_coerce;
626self.has_match_arg |= other.has_match_arg;
627self.has_several_ok |= other.has_several_ok;
628self.has_no_default |= other.has_no_default;
629if self.default_value.is_none() {
630self.default_value = other.default_value;
631 }
632if self.choices.is_none() {
633self.choices = other.choices;
634 }
635self.checks.merge(other.checks)
636 }
637}
638639/// 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>> {
659use syn::spanned::Spanned;
660if !attr.path().is_ident("miniextendr") {
661return Ok(None);
662 }
663664let syn::Meta::List(meta_list) = &attr.meta else {
665return Ok(None);
666 };
667668let mut result = PerParamMiniextendrAttr::default();
669let mut is_per_param = false;
670671let Ok(metas) = meta_list.parse_args_with(
672 syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated,
673 ) else {
674return Ok(None);
675 };
676677for meta in &metas {
678match meta {
679 syn::Meta::Path(path) => {
680if 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) => {
705if 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.
715let syn::Expr::Lit(syn::ExprLit {
716 lit: syn::Lit::Str(lit_str),
717 ..
718 }) = &nv.value
719else {
720return Err(syn::Error::new_spanned(&nv.value, FORMAL_NOT_STRING));
721 };
722if result.formal.replace(lit_str.clone()).is_some() {
723return 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 {
741return 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) => {
753if list.path.is_ident("choices") {
754// Parse choices("a", "b", "c") — a comma-separated list of string literals
755let choice_lits = match list.parse_args_with(
756 syn::punctuated::Punctuated::<syn::LitStr, ::syn::token::Commasyn::Token![,]>::parse_terminated,
757 ) {
758Ok(lits) => lits,
759Err(_) => continue,
760 };
761let 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.
767let 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 = "...")
775let 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 }
786787if !is_per_param {
788return Ok(None);
789 }
790Ok(Some(result))
791}
792793/// 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}
802803impl ClassCheck {
804/// The check `path` names, if it is `inherits` or `not_inherits`.
805pub(crate) fn of(path: &syn::Path) -> Option<Self> {
806if path.is_ident("inherits") {
807Some(Self::Inherits)
808 } else if path.is_ident("not_inherits") {
809Some(Self::NotInherits)
810 } else {
811None812 }
813 }
814815/// The attribute keyword, for error messages.
816fn keyword(self) -> &'static str {
817match self {
818Self::Inherits => "inherits",
819Self::NotInherits => "not_inherits",
820 }
821 }
822823/// The `when(...)` hints in this check's list of options, for errors:
824 /// only `inherits` takes them.
825fn hints_option(self) -> &'static str {
826match self {
827Self::Inherits => ", `when(...)` hints",
828Self::NotInherits => "",
829 }
830 }
831832/// The error for an unknown option `key` in a parameter-level
833 /// `inherits(...)` / `not_inherits(...)`.
834fn 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 = \"...\"`",
838self.keyword(),
839self.hints_option()
840 )841 }
842843/// The error for an unknown option in a method-level
844 /// `inherits(p(...))` / `not_inherits(p(...))`.
845fn 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 = \"...\"`",
849self.keyword(),
850self.hints_option()
851 )852 }
853854/// This check's classes in `checks`, if it is requested.
855fn classes(self, checks: &crate::r_preconditions::ExplicitChecks) -> Option<&[String]> {
856match self {
857Self::Inherits => checks.inherits.as_deref(),
858Self::NotInherits => checks.not_inherits.as_deref(),
859 }
860 }
861862/// This check's classes in `checks`, to add to.
863fn classes_mut(
864self,
865 checks: &mut crate::r_preconditions::ExplicitChecks,
866 ) -> &mut Option<Vec<String>> {
867match self {
868Self::Inherits => &mut checks.inherits,
869Self::NotInherits => &mut checks.not_inherits,
870 }
871 }
872873/// The checks holding this check with `classes` and `message`.
874fn checks(
875self,
876 classes: Vec<String>,
877 message: Option<String>,
878 ) -> crate::r_preconditions::ExplicitChecks {
879let mut checks = crate::r_preconditions::ExplicitChecks::default();
880*self.classes_mut(&mut checks) = Some(classes);
881match self {
882Self::Inherits => checks.inherits_message = message,
883Self::NotInherits => checks.not_inherits_message = message,
884 }
885checks886 }
887}
888889/// `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> {
903use syn::parse::Parseras _;
904let key_word = check.keyword();
905let mut classes = Vec::new();
906let mut message = None;
907let mut hints = Vec::new();
908let parser = |input: syn::parse::ParseStream| -> syn::Result<()> {
909while !input.is_empty() {
910if input.peek(syn::LitStr) {
911let class: syn::LitStr = input.parse()?;
912 classes.push(class.value());
913 } else {
914let key: syn::Ident = input.parse()?;
915if key == "when" && input.peek(syn::token::Paren) {
916if check != ClassCheck::Inherits {
917return Err(syn::Error::new_spanned(&key, WHEN_ON_NOT_INHERITS));
918 }
919let content;
920match ::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![=]>()?;
924let value: syn::LitStr = input.parse()?;
925if key == "class" {
926 classes.push(value.value());
927 } else if key == "message" {
928 set_check_message(&mut message, &value, key_word)?;
929 } else {
930return Err(syn::Error::new_spanned(&key, check.unknown_option(&key)));
931 }
932 }
933 }
934if input.is_empty() {
935break;
936 }
937 input.parse::<::syn::token::Commasyn::Token![,]>()?;
938 }
939Ok(())
940 };
941 parser.parse2(list.tokens.clone())?;
942if message.is_some() && classes.is_empty() {
943return Err(syn::Error::new_spanned(
944list,
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 }
951if !hints.is_empty() && classes.is_empty() {
952return Err(syn::Error::new_spanned(
953list,
954"`when(...)` in `inherits(...)` needs a class to check, e.g. \
955 `inherits(class = \"pkg_obj\", when(class = \"data.frame\", message = \"...\"))`",
956 ));
957 }
958let mut checks = check.checks(classes, message);
959checks.inherits_hints = hints;
960Ok(checks)
961}
962963/// 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";
967968/// 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> {
976let mut classes = Vec::new();
977let mut message = None;
978while !content.is_empty() {
979if content.peek(syn::LitStr) {
980let class: syn::LitStr = content.parse()?;
981 classes.push(hint_class(&class)?);
982 } else {
983let opt: syn::Ident = content.parse()?;
984 content.parse::<::syn::token::Eqsyn::Token![=]>()?;
985let value: syn::LitStr = content.parse()?;
986if opt == "class" {
987 classes.push(hint_class(&value)?);
988 } else if opt == "message" {
989 set_hint_message(&mut message, &value)?;
990 } else {
991return 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 }
1000if content.is_empty() {
1001break;
1002 }
1003 content.parse::<::syn::token::Commasyn::Token![,]>()?;
1004 }
1005inherits_hint(classes, message).map_err(|msg| syn::Error::new_spanned(key, msg))
1006}
10071008/// One class name of a parameter-level `when(...)` hint: not empty.
1009fn hint_class(value: &syn::LitStr) -> syn::Result<String> {
1010let class = value.value();
1011if class.is_empty() {
1012return Err(syn::Error::new(
1013value.span(),
1014"a class name in `when(...)` must not be empty",
1015 ));
1016 }
1017Ok(class)
1018}
10191020/// 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> {
1025if classes.is_empty() {
1026return Err("`when(...)` needs the classes it is for, e.g. \
1027 `when(class = \"data.frame\", message = \"...\")`");
1028 }
1029let Some(message) = messageelse {
1030return Err(
1031"`when(...)` needs a `message = \"...\"`: the hint a value of its classes gets, \
1032 e.g. `when(class = \"data.frame\", message = \"...\")`",
1033 );
1034 };
1035Ok(crate::r_preconditions::InheritsHint { classes, message })
1036}
10371038/// `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> {
1041let mut message = None;
1042 list.parse_nested_meta(|opt| parse_no_na_option(&opt, &mut message))?;
1043Ok(crate::r_preconditions::ExplicitChecks {
1044 no_na: true,
1045 no_na_message: message,
1046 ..Default::default()
1047 })
1048}
10491050/// 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<()> {
1056if opt.path.is_ident("message") {
1057let value: syn::LitStr = opt.value()?.parse()?;
1058set_check_message(message, &value, "no_na")
1059 } else {
1060Err(opt.error("unknown `no_na` option; expected `message = \"...\"`"))
1061 }
1062}
10631064/// 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<()> {
1072set_message(
1073slot,
1074value,
1075check,
1076"leave it out to get the generated message",
1077 )
1078}
10791080/// 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<()> {
1084set_message(
1085slot,
1086value,
1087"when",
1088"it is the message a value of those classes gets",
1089 )
1090}
10911092/// 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<()> {
1100let text = value.value();
1101if slot.is_some() {
1102return Err(syn::Error::new(
1103value.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 }
1107if text.trim().is_empty() {
1108return Err(syn::Error::new(
1109value.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 }
1113if text.contains('\0') {
1114return Err(syn::Error::new(
1115value.span(),
1116"`message` must not contain a NUL character: an R string cannot hold one",
1117 ));
1118 }
1119*slot = Some(text);
1120Ok(())
1121}
11221123/// 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> {
1128Ok(crate::naming::ident_name(
1129 entry
1130 .path
1131 .get_ident()
1132 .ok_or_else(|| entry.error("expected parameter name"))?,
1133 ))
1134}
11351136/// 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<()> {
1144meta.parse_nested_meta(|entry| {
1145let name = method_check_param(&entry)?;
1146let mut message = None;
1147if entry.input.peek(syn::token::Paren) {
1148 entry.parse_nested_meta(|opt| parse_no_na_option(&opt, &mut message))?;
1149 }
1150let checks = crate::r_preconditions::ExplicitChecks {
1151 no_na: true,
1152 no_na_message: message,
1153 ..Default::default()
1154 };
1155per_param1156 .entry(name)
1157 .or_default()
1158 .checks
1159 .merge(checks)
1160 .map_err(|msg| entry.error(msg))
1161 })
1162}
11631164/// 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<()> {
1178let key_word = check.keyword();
1179meta.parse_nested_meta(|entry| {
1180let name = method_check_param(&entry)?;
1181let mut classes = Vec::new();
1182let mut message = None;
1183let mut hints = Vec::new();
1184if entry.input.peek(::syn::token::Eqsyn::Token![=]) {
1185let value: syn::LitStr = entry.value()?.parse()?;
1186classes = method_class_list(&value, check)?;
1187 } else if entry.input.peek(syn::token::Paren) {
1188 entry.parse_nested_meta(|opt| {
1189if opt.path.is_ident("class") {
1190let value: syn::LitStr = opt.value()?.parse()?;
1191 classes.extend(method_class_list(&value, check)?);
1192Ok(())
1193 } else if opt.path.is_ident("message") {
1194let value: syn::LitStr = opt.value()?.parse()?;
1195 set_check_message(&mut message, &value, key_word)
1196 } else if opt.path.is_ident("when") {
1197if check != ClassCheck::Inherits {
1198return Err(opt.error(WHEN_ON_NOT_INHERITS));
1199 }
1200 hints.push(parse_method_when(&opt)?);
1201Ok(())
1202 } else {
1203Err(opt.error(check.unknown_method_option()))
1204 }
1205 })?;
1206if classes.is_empty() {
1207return 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 {
1213return 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 }
1217let mut checks = check.checks(classes, message);
1218checks.inherits_hints = hints;
1219per_param1220 .entry(name)
1221 .or_default()
1222 .checks
1223 .merge(checks)
1224 .map_err(|msg| entry.error(msg))
1225 })
1226}
12271228/// 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> {
1234let mut classes = Vec::new();
1235let mut message = None;
1236 opt.parse_nested_meta(|hint| {
1237if hint.path.is_ident("class") {
1238let value: syn::LitStr = hint.value()?.parse()?;
1239let names = crate::r_wrapper_builder::split_choice_list(&value.value());
1240if names.is_empty() {
1241return Err(syn::Error::new(
1242 value.span(),
1243"`when(class = \"...\")` needs one or more class names",
1244 ));
1245 }
1246 classes.extend(names);
1247Ok(())
1248 } else if hint.path.is_ident("message") {
1249let value: syn::LitStr = hint.value()?.parse()?;
1250 set_hint_message(&mut message, &value)
1251 } else {
1252Err(hint
1253 .error("unknown `when` option; expected `class = \"...\"` and `message = \"...\"`"))
1254 }
1255 })?;
1256inherits_hint(classes, message).map_err(|msg| opt.error(msg))
1257}
12581259/// 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<()> {
1268meta.parse_nested_meta(|entry| {
1269let name = method_check_param(&entry)?;
1270per_param.entry(name).or_default().no_default = true;
1271Ok(())
1272 })
1273}
12741275/// 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<()> {
1286meta.parse_nested_meta(|entry| {
1287let name = method_check_param(&entry)?;
1288let value = entry.value()?;
1289let value: syn::LitStr = value
1290 .parse()
1291 .map_err(|e| syn::Error::new(e.span(), FORMAL_NOT_STRING))?;
1292let attrs = per_param.entry(name).or_default();
1293if attrs.formal.replace(value).is_some() {
1294return Err(entry.error(FORMAL_TWICE));
1295 }
1296Ok(())
1297 })
1298}
12991300/// 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>> {
1303let classes = crate::r_wrapper_builder::split_choice_list(&value.value());
1304if classes.is_empty() {
1305return Err(syn::Error::new(
1306value.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 }
1313Ok(classes)
1314}
1315// endregion
13161317// region: per-parameter preconditions (#1566 §2)
13181319/// 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<()> {
1330meta.parse_nested_meta(|entry| {
1331let name = method_check_param(&entry)?;
1332per_param.entry(name).or_default().preconditions = Some(keep);
1333Ok(())
1334 })
1335}
13361337/// 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 {
1341let key = if keep {
1342"preconditions"
1343} else {
1344"no_preconditions"
1345};
1346if 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}
13521353/// 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>> {
1367let marker = crate::type_inspect::marker_in_family(
1368markers,
1369crate::type_inspect::ParamMarkerFamily::Preconditions,
1370 );
1371let Some(marker) = markerelse {
1372return Ok(keyword);
1373 };
1374let from_marker = marker.preconditions();
1375if let Some(keep) = keyword1376 && Some(keep) != from_marker1377 {
1378let (marker_does, keyword_does) = if keep {
1379 ("drops", "keeps")
1380 } else {
1381 ("keeps", "drops")
1382 };
1383return Err(syn::Error::new_spanned(
1384ty,
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 }
1393Ok(from_marker)
1394}
13951396/// 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<()> {
1410let Some(keep) = attrs.preconditions else {
1411return Ok(());
1412 };
1413let marker = crate::type_inspect::marker_in_family(
1414markers,
1415crate::type_inspect::ParamMarkerFamily::Preconditions,
1416 );
1417let spelling = match marker {
1418Some(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)),
1419None => preconditions_keyword(keep, param, method_list),
1420 };
1421if attrs.is_choice() {
1422return Err(syn::Error::new_spanned(
1423ty,
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 }
1431if !crate::r_preconditions::has_type_check(ty) {
1432return Err(syn::Error::new_spanned(
1433ty,
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",
1436crate::type_inspect::type_display(ty)
1437 ),
1438 ));
1439 }
1440Ok(())
1441}
14421443/// `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![,]>> {
1450let mut peeled = inputs.clone();
1451for arg in peeled.iter_mut() {
1452let syn::FnArg::Typed(pt) = arg else {
1453continue;
1454 };
1455let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1456continue;
1457 };
1458let name = crate::naming::ident_name(&pat_ident.ident);
1459if markers.iter().any(|(n, _)| *n == name) {
1460let (_, inner) = crate::type_inspect::peel_param_markers(pt.ty.as_ref())?;
1461*pt.ty = inner.clone();
1462 }
1463 }
1464Ok(peeled)
1465}
14661467/// 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>)>> {
1473let mut out = Vec::new();
1474for arg in inputs {
1475let syn::FnArg::Typed(pt) = arg else {
1476continue;
1477 };
1478let (markers, _) = crate::type_inspect::peel_param_markers(pt.ty.as_ref())?;
1479if markers.is_empty() {
1480continue;
1481 }
1482let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1483continue;
1484 };
1485 out.push((crate::naming::ident_name(&pat_ident.ident), markers));
1486 }
1487Ok(out)
1488}
1489// endregion
14901491// region: Function parsing
14921493/// 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.
1503item: 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.
1510param_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`]).
1514dots: 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.
1518per_param: std::collections::HashMap<String, ParamAttrs>,
1519}
15201521/// 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 {
1529pub coerce: bool,
1530pub match_arg: bool,
1531pub several_ok: bool,
1532pub choices: Option<Vec<String>>,
1533pub 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.
1541pub 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`].
1547pub 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)`.
1554pub 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`].
1562pub either_noun: Option<String>,
1563/// R-side checks named by the author: `inherits` / `not_inherits` /
1564 /// `no_na`.
1565pub 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.
1572pub 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.
1577pub formal: Option<syn::LitStr>,
1578}
15791580impl 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).
1586pub(crate) fn leaves_argument_unforced(&self, default_ok: bool) -> bool {
1587let 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 && !coerce1603 && !match_arg1604 && !several_ok1605 && choices.is_none()
1606 && !optional1607 && !omittable1608 && !no_default1609 && either_noun.is_none()
1610 && checks.is_empty()
1611 && preconditions.is_none()
1612 }
16131614/// Whether this is a `match_arg` / `choices` parameter.
1615pub(crate) fn is_choice(&self) -> bool {
1616self.match_arg || self.choices.is_some()
1617 }
16181619/// 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.
1626pub(crate) fn choice_formal(&self, choices: &str) -> Option<String> {
1627if self.no_default {
1628None1629 } else if self.optional && !self.omittable {
1630Some("NULL".to_string())
1631 } else {
1632Some(choices.to_string())
1633 }
1634 }
16351636/// 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.
1645pub(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 }
16491650/// 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.
1655pub(crate) fn choice_list(&self, c_ident: &str, key: &str) -> Option<String> {
1656if self.match_arg {
1657return Some(crate::match_arg_keys::choices_placeholder(c_ident, key));
1658 }
1659let 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();
1665Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("c({0})", quoted.join(", ")))
})format!("c({})", quoted.join(", ")))
1666 }
16671668/// 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.
1681pub(crate) fn choice_doc_suffix(&self) -> String {
1682let null_word = if self.omittable {
1683"NULL"
1684} else {
1685"NULL for no choice"
1686};
1687let mut suffix =
1688choice_alternatives_suffix(self.either_noun.as_deref(), self.optional, null_word);
1689if self.omittable && !self.no_default {
1690suffix.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 }
1696suffix1697 }
16981699/// 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).
1704pub(crate) fn literal_choices_doc(&self) -> Option<String> {
1705let choices = self.choices.as_ref()?;
1706let quoted: Vec<String> = choices.iter().map(|c| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\"", c))
})format!("\"{c}\"")).collect();
1707let prefix = if self.several_ok {
1708"One or more of"
1709} else {
1710"One of"
1711};
1712Some(::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(", "),
1715self.choice_doc_suffix()
1716 ))
1717 }
17181719/// 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).
1727pub(crate) fn layered_leaf(&self) -> Option<crate::rust_conversion_builder::ChoiceLeaf> {
1728use crate::rust_conversion_builder::ChoiceLeaf;
1729let either = self.either_noun.is_some();
1730if self.match_arg && (self.optional || self.omittable || either) {
1731return Some(if self.several_ok {
1732 ChoiceLeaf::MatchArgSeveral1733 } else {
1734 ChoiceLeaf::MatchArg1735 });
1736 }
1737let choices = self.choices.as_ref().filter(|_| either)?;
1738Some(ChoiceLeaf::Literal {
1739 choices: choices.clone(),
1740 several: self.several_ok,
1741 })
1742 }
1743}
17441745/// 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 {
1757match (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}
17641765/// 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<()> {
1790use syn::spanned::Spanned;
1791if !attrs.is_choice() {
1792return Ok(());
1793 }
1794let layers = crate::type_inspect::choice_layers(ty);
1795if let Some(layer) = crate::type_inspect::choice_layer_name(layers.value) {
1796return Err(syn::Error::new(
1797ty.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 }
1805if attrs.several_ok {
1806let 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.
1809if layers.nullable && !either {
1810return Err(syn::Error::new(
1811ty.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 }
1819if !is_vector_like_type(layers.value) {
1820// Under `Missing` / `Either` only the owned containers decode.
1821let containers = if layers.missing || either {
1822"`Vec<T>` or `Box<[T]>`"
1823} else {
1824"`Vec<T>`, `Box<[T]>`, `&[T]`, or `[T; N]`"
1825};
1826return Err(syn::Error::new(
1827ty.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 }
1835let owned = #[allow(non_exhaustive_omitted_patterns)] match crate::classify_several_ok_container(layers.value)
{
Some((crate::SeveralOkContainer::Vec |
crate::SeveralOkContainer::BoxedSlice, _)) => true,
_ => false,
}matches!(
1836crate::classify_several_ok_container(layers.value),
1837Some((
1838crate::SeveralOkContainer::Vec | crate::SeveralOkContainer::BoxedSlice,
1839_
1840))
1841 );
1842if either && !owned {
1843return Err(syn::Error::new(
1844ty.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 }
1851if layers.missing && !owned {
1852return Err(syn::Error::new(
1853ty.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>>`.
1863if layers.nullable && !layers.missing && has_default {
1864return Err(syn::Error::new(
1865ty.span(),
1866optional_choice_default_msg(param_name),
1867 ));
1868 }
1869if layers.missing && has_default {
1870return Err(syn::Error::new(
1871ty.span(),
1872missing_choice_default_msg(param_name),
1873 ));
1874 }
1875attrs.optional = layers.nullable;
1876attrs.omittable = layers.missing;
1877attrs.either_noun = layers.either_right.map(crate::type_inspect::r_value_noun);
1878Ok(())
1879}
18801881/// 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)> {
1889inputs.iter().filter_map(|arg| {
1890let syn::FnArg::Typed(pt) = argelse {
1891return None;
1892 };
1893let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1894return None;
1895 };
1896per_param.get_key_value(&crate::naming::ident_name(&pat_ident.ident))
1897 })
1898}
18991900/// 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> {
1906let formals = crate::r_wrapper_builder::Formals::from_param_attrs(per_param);
1907per_param1908 .iter()
1909 .filter(|(_, a)| !a.checks.is_empty())
1910 .map(|(name, a)| (formals.of(name), a.checks.clone()))
1911 .collect()
1912}
19131914/// 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.";
19191920/// 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> {
1933inputs1934 .iter()
1935 .filter_map(|arg| {
1936let syn::FnArg::Typed(pt) = argelse {
1937return None;
1938 };
1939let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
1940return None;
1941 };
1942let rust_name = crate::naming::ident_name(&pat_ident.ident);
1943if !is_missing_type(pt.ty.as_ref())
1944 || per_param.get(&rust_name).is_some_and(ParamAttrs::is_choice)
1945 {
1946return None;
1947 }
1948defaults.contains_key(&rust_name).then(|| {
1949 (
1950formals.of(&rust_name),
1951MISSING_DEFAULT_PARAM_DOC.to_string(),
1952 )
1953 })
1954 })
1955 .collect()
1956}
19571958/// 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> {
1968let formals = crate::r_wrapper_builder::Formals::from_param_attrs(per_param);
1969per_param1970 .iter()
1971 .filter_map(|(name, a)| {
1972let key = crate::match_arg_keys::param_key(name);
1973let choices = a.choice_list(c_ident, &key)?;
1974let absent = a.absent_formal(&choices)?.to_string();
1975Some((formals.of(name), absent))
1976 })
1977 .collect()
1978}
19791980/// 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<()> {
2006let sig_names: std::collections::HashSet<String> = inputs2007 .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();
2016let mut unknown: Vec<&String> = per_param2017 .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();
2028unknown.sort();
2029if let Some(first) = unknown.first() {
2030return Err(syn::Error::new(
2031span,
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 }
2038let mut unknown_formals: Vec<(&String, &syn::LitStr)> = per_param2039 .iter()
2040 .filter(|(name, _)| !sig_names.contains(name.as_str()))
2041 .filter_map(|(name, a)| Some((name, a.formal.as_ref()?)))
2042 .collect();
2043unknown_formals.sort_by_key(|(name, _)| *name);
2044if let Some((name, formal)) = unknown_formals.first() {
2045return Err(syn::Error::new(
2046formal.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.
2057check_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.
2061check_lazy_dots_params(inputs, |name| {
2062 per_param.contains_key(name) || defaults.contains_key(name)
2063 })?;
2064for arg in inputs {
2065let syn::FnArg::Typed(pt) = arg else {
2066continue;
2067 };
2068let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
2069continue;
2070 };
2071let name = crate::naming::ident_name(&pat_ident.ident);
2072let Some(attrs) = per_param.get_mut(&name) else {
2073continue;
2074 };
2075if let Some(formal) = &attrs.formal
2076 && is_dots_type(pt.ty.as_ref())
2077 {
2078return Err(dots_formal_error(formal, &name));
2079 }
2080let has_default = attrs.default.is_some() || defaults.contains_key(&name);
2081if attrs.no_default
2082 && let Some(msg) = no_default_conflict(&name, attrs.is_choice(), has_default, true)
2083 {
2084return Err(syn::Error::new(span, msg));
2085 }
2086 classify_choice_param(attrs, &name, pt.ty.as_ref(), has_default)?;
2087if let Some(msg) = attrs.checks.class_conflict(&name) {
2088return Err(syn::Error::new(span, msg));
2089 }
2090let 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 }
2096Ok(())
2097}
20982099/// 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}
21122113fn 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}
21202121/// 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::Parsefor MiniextendrFunctionParsed {
2142fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
2143use syn::spanned::Spanned;
21442145let mut item: syn::ItemFn = input.parse()?;
21462147// dots support: replace `...` with `&Dots` (the dots parameter itself
2148 // is found by type once wildcard patterns have their names).
2149rewrite_variadic_dots(&mut item.sig)?;
21502151// 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].
2153let is_extern = item.sig.abi.is_some();
2154if !is_extern {
2155for attr in &item.attrs {
2156if attr.path().is_ident("export_name") {
2157return 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 }
21662167// Transform `_` wildcard patterns to synthetic identifiers, and consume
2168 // per-parameter `#[miniextendr(coerce)]`, `#[miniextendr(default = "...")]`,
2169 // and `#[miniextendr(choices(...))]` attributes.
2170let mut per_param: std::collections::HashMap<String, ParamAttrs> =
2171 std::collections::HashMap::new();
2172let mut per_param_default_spans: std::collections::HashMap<String, proc_macro2::Span> =
2173 std::collections::HashMap::new();
2174let mut unused_counter = 0usize;
2175let mut pattern_destructures: Vec<(Box<syn::Pat>, syn::Ident)> = Vec::new();
2176let mut param_markers: Vec<(String, Vec<crate::type_inspect::ParamMarker>)> = Vec::new();
2177for arg in &mut item.sig.inputs {
2178let 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.
2182continue;
2183 };
21842185// 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.
2189let mut param_attr = PerParamMiniextendrAttr::default();
2190let mut kept_attrs = Vec::with_capacity(pat_type.attrs.len());
2191for attr in std::mem::take(&mut pat_type.attrs) {
2192match parse_per_param_attr(&attr)? {
2193Some(parsed) => param_attr
2194 .merge(parsed)
2195 .map_err(|msg| syn::Error::new_spanned(&attr, msg))?,
2196None => kept_attrs.push(attr),
2197 }
2198 }
2199 pat_type.attrs = kept_attrs;
2200let 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;
22112212// 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.
2216let (markers, inner_ty) =
2217crate::type_inspect::peel_param_markers(pat_type.ty.as_ref())?;
2218let inner_ty = inner_ty.clone();
2219if is_extern && let Some(marker) = markers.first() {
2220return 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.
2232if is_extern && crate::type_inspect::is_nargs_marker(&inner_ty) {
2233return 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 }
22412242// Validate type-based constraints (Missing nesting, Missing<Dots>)
2243validate_param_type(&inner_ty, pat_type.ty.span())?;
22442245// Resolve the Rust parameter name — either the user's identifier,
2246 // or a synthesized one for wildcard / destructuring patterns.
2247let 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(_) => {
2250let synthetic_name = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("__unused{0}", unused_counter))
})format!("__unused{}", unused_counter);
2251 unused_counter += 1;
2252let 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(_) => {
2263let synthetic_name = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("__param_{0}", unused_counter))
})format!("__param_{}", unused_counter);
2264 unused_counter += 1;
2265let synthetic_ident = syn::Ident::new(&synthetic_name, pat_type.pat.span());
2266let 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_ => {
2278return 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.
2285let preconditions = merge_param_policy(
2286&markers,
2287 had_preconditions,
2288¶m_name,
2289false,
2290&pat_type.ty,
2291 )?;
22922293// Validate per-parameter attribute conflicts (coerce+match_arg, coerce+choices, etc.)
2294let 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¶m_name,
2308 is_dots_type(&inner_ty),
2309 pat_type.ty.span(),
2310 )?;
23112312// Record per-parameter attrs in one entry instead of five side-tables.
2313if 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 {
2322let 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;
2331if 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).
2336let has_default = entry.default.is_some();
2337 classify_choice_param(entry, ¶m_name, &inner_ty, has_default)?;
2338 check_param_preconditions(entry, &markers, ¶m_name, &inner_ty, false)?;
2339 }
2340if !markers.is_empty() {
2341 param_markers.push((param_name, markers));
2342 }
2343 }
23442345// Insert destructuring let-bindings for pattern parameters at the start of the function body
2346for (pat, ident) in pattern_destructures.iter().rev() {
2347 item.block.stmts.insert(
23480,
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! {
2350let #pat = #ident;
2351 },
2352 );
2353 }
23542355// Validate: all defaults reference existing parameters
2356let param_names: std::collections::HashSet<String> = item2357 .sig
2358 .inputs
2359 .iter()
2360 .filter_map(|input| {
2361if let syn::FnArg::Typed(pat_type) = input2362 && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
2363 {
2364Some(crate::naming::ident_name(&pat_ident.ident))
2365 } else {
2366None2367 }
2368 })
2369 .collect();
23702371let mut invalid_params: Vec<String> = per_param2372 .iter()
2373 .filter_map(|(name, attrs)| {
2374if attrs.default.is_some() && !param_names.contains(name) {
2375Some(name.clone())
2376 } else {
2377None2378 }
2379 })
2380 .collect();
2381invalid_params.sort();
23822383if !invalid_params.is_empty() {
2384// Use the span of the first invalid param's attribute for the error
2385let error_span = invalid_params2386 .first()
2387 .and_then(|p| per_param_default_spans.get(p).copied())
2388 .unwrap_or_else(|| item.sig.ident.span());
2389return Err(syn::Error::new(
2390error_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 }
23972398let dots = find_dots_param(&item.sig.inputs)?;
2399crate::r_wrapper_builder::check_r_formals(
2400&item.sig.inputs,
2401&crate::r_wrapper_builder::Formals::from_param_attrs(&per_param),
2402&[],
2403 )?;
2404let inputs = peeled_inputs(&item.sig.inputs, ¶m_markers)?;
24052406Ok(Self {
2407item,
2408inputs,
2409param_markers,
2410dots,
2411per_param,
2412 })
2413 }
2414}
24152416/// 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
24282429/// Whether the function takes `...`: Rust `...` or an explicit `&Dots`
2430 /// parameter, at any position.
2431pub(crate) fn has_dots(&self) -> bool {
2432self.dots.is_some()
2433 }
24342435/// The Rust binding of the dots: the user's name (`args: ...`,
2436 /// `rest: &Dots`), or `__miniextendr_dots` for `_: ...`.
2437pub(crate) fn dots_ident(&self) -> Option<&syn::Ident> {
2438self.dots.as_ref()
2439 }
24402441/// Check if a parameter is the dots (`...`) param, whose R formal is `...`.
2442pub(crate) fn is_dots_param(&self, ident: &syn::Ident) -> bool {
2443self.dots_ident() == Some(ident)
2444 }
24452446/// 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.
2449pub(crate) fn has_param_attrs(&self, param_name: &str) -> bool {
2450self.per_param.contains_key(param_name)
2451 }
24522453/// Check if a parameter name had `#[miniextendr(coerce)]` attribute.
2454pub(crate) fn has_coerce_attr(&self, param_name: &str) -> bool {
2455self.per_param.get(param_name).is_some_and(|a| a.coerce)
2456 }
24572458/// Check if a parameter name had `#[miniextendr(match_arg)]` attribute.
2459pub(crate) fn has_match_arg_attr(&self, param_name: &str) -> bool {
2460self.per_param.get(param_name).is_some_and(|a| a.match_arg)
2461 }
24622463/// Iterator over parameter names annotated with `#[miniextendr(match_arg)]`,
2464 /// in signature order.
2465pub(crate) fn match_arg_params(&self) -> impl Iterator<Item = &String> {
2466per_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 }
24692470/// Iterator over parameter names annotated with `#[miniextendr(choices(…))]`,
2471 /// together with their choice lists, in signature order.
2472pub(crate) fn choices_params(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
2473per_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 }
24762477/// Check if a parameter has `several_ok` (multi-value match.arg).
2478pub(crate) fn has_several_ok(&self, param_name: &str) -> bool {
2479self.per_param.get(param_name).is_some_and(|a| a.several_ok)
2480 }
24812482/// 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.
2485pub(crate) fn param_attrs(&self, param_name: &str) -> Option<&ParamAttrs> {
2486self.per_param.get(param_name)
2487 }
24882489/// The R formal of each parameter (#1842), from the declared
2490 /// `formal = ".."` options.
2491pub(crate) fn formals(&self) -> crate::r_wrapper_builder::Formals {
2492crate::r_wrapper_builder::Formals::from_param_attrs(&self.per_param)
2493 }
24942495/// The parameters that read a value `identical()` to their formal as
2496 /// omitted, keyed by R formal (see [`absent_formals_by_r_name`]).
2497pub(crate) fn absent_formals(
2498&self,
2499 c_ident: &str,
2500 ) -> std::collections::HashMap<String, String> {
2501absent_formals_by_r_name(&self.per_param, c_ident)
2502 }
25032504/// The generated `@param` text of each `Missing<..>` parameter with a
2505 /// default, keyed by R formal (see [`missing_default_param_docs`]).
2506pub(crate) fn missing_default_param_docs(&self) -> std::collections::HashMap<String, String> {
2507missing_default_param_docs(
2508&self.item.sig.inputs,
2509&self.per_param,
2510&self.param_defaults(),
2511&self.formals(),
2512 )
2513 }
25142515/// The `inherits` / `not_inherits` / `no_na` checks of every parameter,
2516 /// keyed by R name (see [`explicit_checks_by_r_name`]).
2517pub(crate) fn explicit_checks(
2518&self,
2519 ) -> std::collections::HashMap<String, crate::r_preconditions::ExplicitChecks> {
2520explicit_checks_by_r_name(&self.per_param)
2521 }
25222523/// 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.
2526pub(crate) fn precondition_skip_params(&self) -> std::collections::HashSet<String> {
2527crate::r_class_formatter::match_arg_skip_set(&self.per_param)
2528 }
25292530/// 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.
2535pub(crate) fn param_defaults(&self) -> std::collections::HashMap<String, String> {
2536self.per_param
2537 .iter()
2538 .filter_map(|(name, a)| a.default.as_ref().map(|d| (name.clone(), d.clone())))
2539 .collect()
2540 }
2541// endregion
25422543 // region: Accessors for signature components
25442545/// Original attributes on the function item (doc comments, cfgs, etc.).
2546pub(crate) fn attrs(&self) -> &[syn::Attribute] {
2547&self.item.attrs
2548 }
25492550/// Visibility of the function (`pub`, `pub(crate)`, or private).
2551pub(crate) fn vis(&self) -> &syn::Visibility {
2552&self.item.vis
2553 }
25542555/// Explicit ABI, if the function was declared `extern "C-unwind"`.
2556pub(crate) fn abi(&self) -> Option<&syn::Abi> {
2557self.item.sig.abi.as_ref()
2558 }
25592560/// Function identifier after normalization.
2561pub(crate) fn ident(&self) -> &syn::Ident {
2562&self.item.sig.ident
2563 }
25642565/// Generic parameters on the function signature.
2566pub(crate) fn generics(&self) -> &syn::Generics {
2567&self.item.sig.generics
2568 }
25692570/// Function inputs after normalization (dots rewritten, wildcards
2571 /// renamed), with the parameter markers peeled (see [`Self::param_markers`]).
2572pub(crate) fn inputs(&self) -> &syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]> {
2573&self.inputs
2574 }
25752576/// The `Checked<T>` / `Unchecked<T>` markers peeled off the parameters
2577 /// (Rust name → markers, outermost first), in signature order.
2578pub(crate) fn param_markers(&self) -> &[(String, Vec<crate::type_inspect::ParamMarker>)] {
2579&self.param_markers
2580 }
25812582/// Function return type.
2583pub(crate) fn output(&self) -> &syn::ReturnType {
2584&self.item.sig.output
2585 }
25862587/// The normalized function item (with original doc comments).
2588pub(crate) fn item(&self) -> &syn::ItemFn {
2589&self.item
2590 }
25912592/// 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.
2596pub(crate) fn item_without_roxygen(&self) -> syn::ItemFn {
2597let mut item = self.item.clone();
2598item.attrs = crate::roxygen::strip_roxygen_from_attrs(&item.attrs);
2599item2600 }
2601// endregion
26022603 // region: Codegen helpers
26042605/// 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.
2610pub(crate) fn uses_internal_c_wrapper(&self) -> bool {
2611self.abi().is_none()
2612 }
26132614/// 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.
2619pub(crate) fn r_wrapper_const_ident(&self) -> syn::Ident {
2620r_wrapper_const_ident_for(self.ident())
2621 }
26222623/// 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.
2630pub(crate) fn c_wrapper_ident(&self) -> syn::Ident {
2631if self.uses_internal_c_wrapper() {
2632crate::naming::bare_fn_c_wrapper_ident(self.ident())
2633 } else {
2634// For extern functions, check for #[export_name = "..."]
2635self.export_name_ident()
2636 .unwrap_or_else(|| self.ident().clone())
2637 }
2638 }
26392640/// 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.
2644pub(crate) fn export_name_ident(&self) -> Option<syn::Ident> {
2645for attr in &self.item.attrs {
2646if 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 {
2653return Some(syn::Ident::new(&lit_str.value(), lit_str.span()));
2654 }
2655 }
2656None2657 }
26582659/// 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
2666pub(crate) fn add_inline_never_if_needed(&mut self) {
2667let has_explicit_abi = self.item.sig.abi.is_some();
2668let has_inline = self2669 .item
2670 .attrs
2671 .iter()
2672 .any(|attr| attr.path().is_ident("inline"));
2673if !has_inline && !has_explicit_abi {
2674self.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
26802681// region: Attribute parsing
26822683/// 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<()> {
2691if val.is_empty() {
2692return Err(syn::Error::new_spanned(span, "postfix must not be empty"));
2693 }
2694if !val2695 .chars()
2696 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
2697 {
2698return Err(syn::Error::new_spanned(
2699span,
2700"postfix must be a valid R identifier fragment (letters, digits, `_`, `.`)",
2701 ));
2702 }
2703Ok(())
2704}
27052706fn parse_lit_str(nv: &syn::MetaNameValue, field: &str) -> syn::Result<String> {
2707match &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(
2717other,
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}
27222723/// 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";
27312732/// 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 = ...)`";
27352736/// 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`).
2787pub(crate) force_worker: bool,
2788/// Override visibility; `Some(true)` makes the wrapper return invisibly, `Some(false)` forces visibility.
2789pub(crate) force_invisible: Option<bool>,
2790/// Insert `R_CheckUserInterrupt()` before calling the Rust function.
2791pub(crate) check_interrupt: bool,
2792/// Enable automatic coercion for all parameters that support it.
2793pub(crate) coerce_all: bool,
2794/// Enable RNG state management (GetRNGstate/PutRNGstate). Also set by
2795 /// `rng(seed = ..)`.
2796pub(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).
2801pub(crate) rng_seed: Option<syn::Ident>,
2802/// Return `Result<T, E>` to R without unwrapping.
2803pub(crate) unwrap_in_r: bool,
2804/// Serialize the complete return value through `AsSerialize<T>`.
2805pub(crate) serialize: bool,
2806pub(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 = .., ..))]`).
2809pub(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`).
2825pub(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`).
2831pub(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`.
2837pub(crate) call_arg: bool,
2838/// Preferred return conversion: forces `AsList`/`AsExternalPtr`/`AsRNative` wrapping
2839 /// of the return value before `IntoR::into_sexp` is called.
2840pub(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.
2844pub(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).
2850pub(crate) s3_generic: Option<String>,
2851/// S3 class suffix for the method (e.g., "my_vctr" or "my_vctr.my_vctr" for double-dispatch).
2852pub(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`.
2857pub(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`]).
2860pub(crate) dots_policy: Option<crate::dots_policy::DotsPolicy>,
2861/// Span of the `dots = ...` attribute for error reporting.
2862pub(crate) dots_span: Option<proc_macro2::Span>,
2863/// Lifecycle specification for deprecation/experimental status.
2864pub(crate) lifecycle: Option<crate::lifecycle::LifecycleSpec>,
2865/// Strict output conversion: panic instead of lossy widening for i64/u64/isize/usize.
2866pub(crate) strict: bool,
2867/// Mark as internal: adds `@keywords internal`, suppresses `@export`.
2868pub(crate) internal: bool,
2869/// Suppress `@export` without adding `@keywords internal`.
2870pub(crate) noexport: bool,
2871/// Force `@export` even on non-pub functions. Antidote to `noexport`.
2872pub(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.
2877pub(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).
2883pub(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.
2889pub(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.
2895pub(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.
2901pub(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.
2907pub(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.
2915pub(crate) r_on_exit: Option<ROnExit>,
2916}
29172918/// 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 {
2927pub expr: String,
2928pub add: bool,
2929pub after: bool,
2930}
29312932impl 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)`
2938pub fn to_r_code(&self) -> String {
2939if !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}
29482949/// `#[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.
2980pub shape: Option<syn::Path>,
2981/// Internally-tagged discriminator field name (`#[serde(tag = "...")]`).
2982pub tag: Option<String>,
2983/// Condition class prefix (family class).
2984pub prefix: Option<String>,
2985/// Payload fields dropped from the condition data.
2986pub skip: Vec<String>,
2987/// Payload fields spliced under another name: `(from, to)`.
2988pub rename: Vec<(String, String)>,
2989/// Payload fields given an atomic R type (`as_character(..)` & co.), in
2990 /// written order.
2991pub types: Vec<(FieldType, String)>,
2992}
29932994/// 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}
30023003impl FieldType {
3004const ALL: [FieldType; 4] = [
3005 FieldType::Logical,
3006 FieldType::Integer,
3007 FieldType::Double,
3008 FieldType::Character,
3009 ];
30103011/// The option's name, which is also the `SerdeErrorShape` method's.
3012pub(crate) fn option(self) -> &'static str {
3013match self {
3014 FieldType::Logical => "as_logical",
3015 FieldType::Integer => "as_integer",
3016 FieldType::Double => "as_double",
3017 FieldType::Character => "as_character",
3018 }
3019 }
30203021fn from_path(path: &syn::Path) -> Option<Self> {
3022Self::ALL.into_iter().find(|ty| path.is_ident(ty.option()))
3023 }
3024}
30253026/// 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"];
30303031const 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(...)`";
30343035const SERDE_ERROR_SHAPE_HELP: &str = "serde_error shape takes the path of a `SerdeErrorShape` \
3036 const: `shape = ENGINE`";
30373038/// 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";
30443045impl SerdeErrorSpec {
3046/// Parse `serde_error(...)` contents: a comma-separated list of options.
3047 /// Every problem is reported at once.
3048fn from_metas<'a>(
3049 metas: impl IntoIterator<Item = &'a syn::Meta>,
3050 span: proc_macro2::Span,
3051 ) -> syn::Result<Self> {
3052let mut spec = SerdeErrorSpec::default();
3053let mut errors = Vec::new();
3054let mut any = false;
3055for meta in metas {
3056 any = true;
3057 spec.apply(meta, &mut errors);
3058 }
3059if !any {
3060return Err(syn::Error::new(span, SERDE_ERROR_BARE_HELP));
3061 }
3062let mut errors = errors.into_iter();
3063match errors.next() {
3064None => Ok(spec),
3065Some(mut first) => {
3066first.extend(errors);
3067Err(first)
3068 }
3069 }
3070 }
30713072fn apply(&mut self, meta: &syn::Meta, errors: &mut Vec<syn::Error>) {
3073match 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") => {
3084match non_empty_lit_str(nv, "tag") {
3085Ok(tag) => self.tag = Some(tag),
3086Err(err) => errors.push(err),
3087 }
3088 }
3089 syn::Meta::NameValue(nv) if nv.path.is_ident("prefix") => {
3090match non_empty_lit_str(nv, "prefix") {
3091Ok(prefix) => self.prefix = Some(prefix),
3092Err(err) => errors.push(err),
3093 }
3094 }
3095 syn::Meta::List(list) if list.path.is_ident("skip") => {
3096for lit in field_names(list, "skip", errors) {
3097let name = lit.value();
3098if 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 {
3118self.skip.push(name);
3119 }
3120 }
3121 }
3122 syn::Meta::List(list) if list.path.is_ident("rename") => {
3123let pairs = match list.parse_args_with(
3124 syn::punctuated::Punctuated::<RenamePair, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3125 ) {
3126Ok(pairs) => pairs,
3127Err(err) => return errors.push(err),
3128 };
3129if pairs.is_empty() {
3130errors.push(syn::Error::new_spanned(
3131list,
3132"serde_error rename needs at least one pair: `rename(message = \"detail\")`",
3133 ));
3134 }
3135for RenamePair { from, to, span } in pairs {
3136let error = |message: String| syn::Error::new(span, message);
3137if 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 {
3157self.rename.push((from, to));
3158 }
3159 }
3160 }
3161 syn::Meta::List(list) if FieldType::from_path(&list.path).is_some() => {
3162let ty = FieldType::from_path(&list.path).expect("matched above");
3163let option = ty.option();
3164for lit in field_names(list, option, errors) {
3165let name = lit.value();
3166match self.field_type(&name) {
3167Some(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 )),
3171Some(other) => errors.push(both(
3172&lit,
3173 other.option(),
3174 option,
3175"a field has one R type",
3176 )),
3177None if self.skip.contains(&name) => {
3178 errors.push(both(&lit, "skip", option, "a skipped field has no R type"))
3179 }
3180None => self.types.push((ty, name)),
3181 }
3182 }
3183 }
3184 syn::Meta::NameValue(nv) if nv.path.is_ident("skip") => {
3185errors.push(syn::Error::new_spanned(
3186nv,
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") => {
3191errors.push(syn::Error::new_spanned(
3192nv,
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() => {
3197let option = FieldType::from_path(&nv.path)
3198 .expect("matched above")
3199 .option();
3200errors.push(syn::Error::new_spanned(
3201nv,
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") => {
3208errors.push(syn::Error::new_spanned(other, SERDE_ERROR_SHAPE_HELP));
3209 }
3210 other => {
3211errors.push(syn::Error::new_spanned(
3212other.path(),
3213SERDE_ERROR_OPTIONS_HELP,
3214 ));
3215 }
3216 }
3217 }
32183219/// 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.
3222pub(crate) fn with_self_type(&self, ty: &syn::Ident) -> Self {
3223let mut spec = self.clone();
3224if 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{
3230first.ident = syn::Ident::new(&ty.to_string(), first.ident.span());
3231 }
3232spec3233 }
32343235/// The `as_*` option that names `name`, if any.
3236fn field_type(&self, name: &str) -> Option<FieldType> {
3237self.types
3238 .iter()
3239 .find(|(_, n)| n == name)
3240 .map(|(ty, _)| *ty)
3241 }
32423243/// The function's own options as a `SerdeErrorShape` builder chain;
3244 /// `None` when it only names a shape.
3245pub(crate) fn own_shape(&self) -> Option<proc_macro2::TokenStream> {
3246let mut calls = Vec::new();
3247if let Some(tag) = &self.tag {
3248calls.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 }
3250if let Some(prefix) = &self.prefix {
3251calls.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 }
3253if !self.skip.is_empty() {
3254let skip = &self.skip;
3255calls.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 }
3257if !self.rename.is_empty() {
3258let (from, to): (Vec<&String>, Vec<&String>) =
3259self.rename.iter().map(|(f, t)| (f, t)).unzip();
3260calls.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 }
3262for ty in FieldType::ALL {
3263let names: Vec<&String> = self
3264.types
3265 .iter()
3266 .filter(|(t, _)| *t == ty)
3267 .map(|(_, name)| name)
3268 .collect();
3269if !names.is_empty() {
3270let 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}
32783279/// 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> {
3286let names = match list.parse_args_with(
3287 syn::punctuated::Punctuated::<syn::LitStr, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3288 ) {
3289Ok(names) => names,
3290Err(err) => {
3291errors.push(err);
3292return Vec::new();
3293 }
3294 };
3295if names.is_empty() {
3296errors.push(syn::Error::new_spanned(
3297list,
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 }
3301names3302 .into_iter()
3303 .filter(|lit| {
3304let empty = lit.value().is_empty();
3305if empty {
3306errors.push(syn::Error::new_spanned(
3307lit,
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 !empty3312 })
3313 .collect()
3314}
33153316/// 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(
3319lit,
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}
33263327/// A `tag = "..."` / `prefix = "..."` value: a non-empty string literal.
3328fn non_empty_lit_str(nv: &syn::MetaNameValue, key: &str) -> syn::Result<String> {
3329let val = parse_lit_str(nv, key)?;
3330if val.is_empty() {
3331return 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 }
3336Ok(val)
3337}
33383339/// 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}
33463347impl syn::parse::Parsefor RenamePair {
3348fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
3349use syn::ext::IdentExt;
3350let lookahead = input.lookahead1();
3351let (from, span) = if lookahead.peek(syn::LitStr) {
3352let lit: syn::LitStr = input.parse()?;
3353 (lit.value(), lit.span())
3354 } else if lookahead.peek(syn::Ident::peek_any) {
3355let ident = input.call(syn::Ident::parse_any)?;
3356 (ident.to_string(), ident.span())
3357 } else {
3358return Err(lookahead.error());
3359 };
3360let _: ::syn::token::Eqsyn::Token![=] = input.parse()?;
3361let to: syn::LitStr = input.parse()?;
3362Ok(RenamePair {
3363from,
3364 to: to.value(),
3365span,
3366 })
3367 }
3368}
33693370/// 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 {
3374let 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}
33773378/// 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}
33843385/// `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 {
3389let 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}
33923393/// 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> {
3396use syn::spanned::Spanned;
3397let metas = list.parse_args_with(
3398 syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated,
3399 )?;
3400SerdeErrorSpec::from_metas(&metas, list.span())
3401}
34023403/// 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> {
3411use syn::spanned::Spanned;
3412let input = meta.input;
3413if input.peek(syn::token::Paren) {
3414let content;
3415match ::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);
3416let metas =
3417 content.parse_terminated(<syn::Meta as syn::parse::Parse>::parse, ::syn::token::Commasyn::Token![,])?;
3418return SerdeErrorSpec::from_metas(&metas, meta.path.span());
3419 }
3420if input.peek(::syn::token::Eqsyn::Token![=]) {
3421let _: ::syn::token::Eqsyn::Token![=] = input.parse()?;
3422let _: syn::LitBool = input.parse()?;
3423 }
3424Err(meta.error(SERDE_ERROR_BARE_HELP))
3425}
34263427/// 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<()> {
3439let mut first: Option<&syn::Ident> = None;
3440for arg in inputs {
3441let syn::FnArg::Typed(pt) = arg else {
3442continue;
3443 };
3444if !crate::type_inspect::is_nargs_marker(&pt.ty) {
3445continue;
3446 }
3447let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
3448continue;
3449 };
3450let ident = &pat_ident.ident;
3451if let Some(first) = first {
3452return 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 }
3460if has_options(&crate::naming::ident_name(ident)) {
3461return 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 }
3474Ok(())
3475}
34763477/// 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<()> {
3491for arg in inputs {
3492let syn::FnArg::Typed(pt) = arg else {
3493continue;
3494 };
3495if !crate::type_inspect::is_lazy_dots_marker(&pt.ty) {
3496if crate::type_inspect::mentions_lazy_dots(&pt.ty)
3497 && get_missing_inner_type(&pt.ty).is_none()
3498 {
3499return 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 }
3506continue;
3507 }
3508let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
3509continue;
3510 };
3511let ident = &pat_ident.ident;
3512if has_options(&crate::naming::ident_name(ident)) {
3513return 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 }
3524Ok(())
3525}
35263527/// 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<()> {
3541let mut first: Option<&syn::PatType> = None;
3542for arg in inputs {
3543let syn::FnArg::Typed(pt) = arg else {
3544continue;
3545 };
3546if !crate::type_inspect::is_rng_guard_param(&pt.ty) {
3547if crate::type_inspect::mentions_rng_guard(&pt.ty) {
3548return 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 }
3555continue;
3556 }
3557if first.is_some() {
3558return 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 }
3564if !rng {
3565return 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 }
3572if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
3573 && has_options(&crate::naming::ident_name(&pat_ident.ident))
3574 {
3575return 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 }
3588Ok(())
3589}
35903591/// 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<()> {
3598for arg in inputs {
3599if let syn::FnArg::Typed(pt) = arg
3600 && crate::type_inspect::mentions_rng_guard(&pt.ty)
3601 {
3602return 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 }
3611Ok(())
3612}
36133614/// 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> {
3617let mut seed: Option<syn::Ident> = None;
3618 list.parse_nested_meta(|meta| {
3619if !meta.path.is_ident("seed") {
3620return Err(meta.error("unknown `rng(...)` option; expected `seed = <parameter>`"));
3621 }
3622let value = meta.value()?;
3623let 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 })?;
3630if seed.replace(ident).is_some() {
3631return Err(meta.error("`seed` is set more than once"));
3632 }
3633Ok(())
3634 })?;
3635seed.ok_or_else(|| {
3636 syn::Error::new_spanned(
3637list,
3638"`rng(...)` takes `seed = <parameter>`; write `rng` alone for the RNG state \
3639 without the seed contract",
3640 )
3641 })
3642}
36433644/// 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> {
3666let rust_name = crate::naming::ident_name(seed);
3667let 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 });
3673let 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));
36773678let mut errors: Vec<syn::Error> = Vec::new();
3679if !is_param {
3680errors.push(syn::Error::new_spanned(
3681seed,
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() {
3688errors.push(syn::Error::new_spanned(
3689seed,
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 }
3697if unit_return {
3698errors.push(syn::Error::new_spanned(
3699seed,
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 }
3704match errors.into_iter().reduce(|mut all, error| {
3705all.combine(error);
3706all3707 }) {
3708Some(error) => Err(error),
3709None => Ok(formal.expect("checked above")),
3710 }
3711}
37123713/// 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<()> {
3721let content;
3722match ::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);
3723let _: proc_macro2::TokenStream = content.parse()?;
3724Err(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}
37303731/// 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<()> {
3739for arg in inputs {
3740if let syn::FnArg::Typed(pt) = arg
3741 && crate::type_inspect::mentions_lazy_dots(&pt.ty)
3742 {
3743return 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 }
3751Ok(())
3752}
37533754/// 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> {
3763use syn::spanned::Spanned;
37643765let 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 })?;
3769Some(syn::Error::new(
3770span.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}
37763777/// 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<()> {
3801use syn::spanned::Spanned;
38023803if !generic.ends_with("<-") {
3804return Ok(());
3805 }
3806// The last R formal: an `NArgs` parameter is none (#1860), so `value`
3807 // may come before it.
3808let last = inputs3809 .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) => {
3814Some((idx, pat_type))
3815 }
3816_ => None,
3817 });
3818let found = match last {
3819Some((idx, _)) if Some(idx) == dots_index(inputs) => "the last R argument is `...`".into(),
3820Some((_, pat_type)) => match pat_type.pat.as_ref() {
3821 syn::Pat::Ident(pat_ident) => {
3822let formal = formals.of_ident(&pat_ident.ident);
3823if formal == "value" {
3824return 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 },
3830None => "it takes no new-value parameter".to_string(),
3831 };
3832let span = last.map_or(fallback, |(_, pat_type)| pat_type.span());
3833Err(syn::Error::new(
3834span,
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}
38433844#[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]
3849Auto,
3850/// Force list conversion via the `AsList` wrapper.
3851List,
3852/// Force external pointer conversion via the `AsExternalPtr` wrapper.
3853ExternalPtr,
3854/// Force native vector/scalar conversion via the `AsRNative` wrapper.
3855Native,
3856}
38573858/// 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::Parsefor MiniextendrFnAttrs {
3880fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
3881use syn::spanned::Spanned;
3882// Use Option<bool> for fields that support feature defaults.
3883 // None = not explicitly set → resolve from cfg!(feature = "...") at end.
3884let mut force_worker: Option<bool> = None;
3885let mut force_invisible: Option<bool> = None;
3886let mut check_interrupt = false;
3887let mut coerce_all: Option<bool> = None;
3888let mut rng = false;
3889let mut rng_seed = None;
3890let mut unwrap_in_r = false;
3891let mut serialize = false;
3892let mut wrap = None;
3893let mut serde_error: Option<SerdeErrorSpec> = None;
3894let mut preconditions: Option<bool> = None;
3895let mut return_pref = ReturnPref::Auto;
3896let mut return_pref_span: Option<proc_macro2::Span> = None;
3897let mut s3_generic = None;
3898let mut s3_class = None;
3899let mut dots_spec = None;
3900let mut dots_policy = None;
3901let mut dots_span = None;
3902let mut lifecycle = None;
3903let mut strict: Option<bool> = None;
3904let mut internal = false;
3905let mut noexport = false;
3906let mut export = false;
3907let mut doc = None;
3908let mut c_symbol = None;
3909let mut r_name = None;
3910let mut postfix = None;
3911let mut call_attr: Option<crate::r_wrapper_builder::CallAttribution> = None;
3912let mut call_arg = false;
3913let mut r_entry = None;
3914let mut r_post_checks = None;
3915let mut r_on_exit = None;
39163917// 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.
3920let 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};
39253926for meta in metas {
3927match meta {
3928// Simple identifiers: invisible, visible, check_interrupt, coerce, worker, rng
3929syn::Meta::Path(path) => {
3930if let Some(ident) = path.get_ident() {
3931if 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" {
3948return Err(syn::Error::new_spanned(&path, SERDE_ERROR_BARE_HELP));
3949 } else if ident == "worker" {
3950 force_worker = Some(true);
3951crate::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 {
3971return 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) => {
3981if nv.path.is_ident("wrap") {
3982let value = parse_lit_str(&nv, "wrap")?;
3983 wrap = Some(crate::return_wrap::parse_system(&syn::LitStr::new(
3984&value,
3985 nv.path.span(),
3986 ))?);
3987continue;
3988 }
3989// Check for boolean flag options: option = true / option = false
3990if let syn::Expr::Lit(syn::ExprLit {
3991 lit: syn::Lit::Bool(lit_bool),
3992 ..
3993 }) = &nv.value
3994 {
3995let val = lit_bool.value;
3996if let Some(ident) = nv.path.get_ident() {
3997if 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);
4005if val {
4006crate::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" {
4021return 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 {
4039return 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 }
4047continue;
4048 }
4049 }
40504051if nv.path.is_ident("prefer") {
4052let 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_ => {
4060return 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.
4069dots_span = Some(nv.path.span());
4070match crate::dots_policy::parse_dots_attr(&nv.value)? {
4071crate::dots_policy::DotsAttr::TypedList(spec) => {
4072 dots_spec = Some(spec);
4073 }
4074crate::dots_policy::DotsAttr::Policy(policy) => {
4075 dots_policy = Some(policy);
4076 }
4077 }
4078 } else if nv.path.is_ident("lifecycle") {
4079// lifecycle = "stage"
4080if 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") {
4088let val = parse_lit_str(&nv, "c_symbol")?;
4089if val.is_empty()
4090 || (!val.starts_with(|c: char| c.is_ascii_alphabetic())
4091 && !val.starts_with('_'))
4092 {
4093return Err(syn::Error::new_spanned(
4094&nv.value,
4095"c_symbol must be a valid C identifier",
4096 ));
4097 }
4098if !val.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
4099return 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") {
4106let val = parse_lit_str(&nv, "r_name")?;
4107if val.is_empty() {
4108return 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") {
4115let val = parse_lit_str(&nv, "postfix")?;
4116 validate_postfix(&val, &nv.value)?;
4117 postfix = Some(val);
4118 } else if nv.path.is_ident("call") {
4119let 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 };
4127let Some(attribution) = name
4128 .as_deref()
4129 .and_then(crate::r_wrapper_builder::CallAttribution::parse_name)
4130else {
4131return 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 };
4138if call_attr.is_some() {
4139return 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)
4151r_on_exit = Some(ROnExit {
4152 expr: parse_lit_str(&nv, "r_on_exit")?,
4153 add: true,
4154 after: true,
4155 });
4156 } else {
4157let key_name = nv
4158 .path
4159 .get_ident()
4160 .map(|i| i.to_string())
4161 .unwrap_or_default();
4162return 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) => {
4177if 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", ...)
4182if let Some(spec) =
4183crate::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 = "...")
4189list.parse_nested_meta(|meta| {
4190if meta.path.is_ident("generic") {
4191let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4192let value: syn::LitStr = meta.input.parse()?;
4193 s3_generic = Some(crate::naming::parse_generic(&value)?);
4194 } else if meta.path.is_ident("class") {
4195let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4196let value: syn::LitStr = meta.input.parse()?;
4197 s3_class = Some(value.value());
4198 } else {
4199return Err(
4200 meta.error("unknown s3 option; expected `generic` or `class`")
4201 );
4202 }
4203Ok(())
4204 })?;
4205// Validate: s3 requires class (generic can default to function name)
4206if s3_class.is_none() {
4207return 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)
4215let mut expr = None;
4216let mut add = true;
4217let mut after = true;
4218 list.parse_nested_meta(|meta| {
4219if meta.path.is_ident("expr") {
4220let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4221let value: syn::LitStr = meta.input.parse()?;
4222 expr = Some(value.value());
4223 } else if meta.path.is_ident("add") {
4224let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4225let value: syn::LitBool = meta.input.parse()?;
4226 add = value.value;
4227 } else if meta.path.is_ident("after") {
4228let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
4229let value: syn::LitBool = meta.input.parse()?;
4230 after = value.value;
4231 } else {
4232return Err(meta.error(
4233"unknown r_on_exit option; expected `expr`, `add`, or `after`",
4234 ));
4235 }
4236Ok(())
4237 })?;
4238let 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.
4252let opt_name = ident.to_string();
4253return 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
4262return 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 }
42724273// Validate: `internal` and `noexport` are redundant together
4274if internal && noexport {
4275return 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 }
42824283// Validate: `export` conflicts with `noexport` and `internal`
4284if export && noexport {
4285return Err(syn::Error::new(
4286 proc_macro2::Span::call_site(),
4287"`export` and `noexport` are contradictory.",
4288 ));
4289 }
4290if export && internal {
4291return Err(syn::Error::new(
4292 proc_macro2::Span::call_site(),
4293"`export` and `internal` are contradictory.",
4294 ));
4295 }
42964297// 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.
4300if postfix.is_some() && r_name.is_some() {
4301return 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 }
4306if postfix.is_some() && (s3_generic.is_some() || s3_class.is_some()) {
4307return 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 }
43134314// Validate: `call = caller` is for internal entry points only.
4315use crate::r_wrapper_builder::CallAttribution;
4316if call_attr == Some(CallAttribution::Caller) && !(noexport || internal) {
4317return 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.
4327if call_arg && call_attr == Some(CallAttribution::Caller) {
4328return 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.
4337if call_arg && call_attr == Some(CallAttribution::NoCall) {
4338return 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 }
4344if call_arg && (s3_generic.is_some() || s3_class.is_some()) {
4345return 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 }
4351if r_name.is_some() && (s3_generic.is_some() || s3_class.is_some()) {
4352return 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 }
43584359if serialize && (unwrap_in_r || serde_error.is_some()) {
4360return 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 }
43654366// 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.
4369if serde_error.is_some() && unwrap_in_r {
4370return 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 }
43764377Ok(Self {
4378 force_worker: force_worker.unwrap_or(falsecfg!(feature = "worker-default")),
4379force_invisible,
4380check_interrupt,
4381 coerce_all: coerce_all.unwrap_or(falsecfg!(feature = "coerce-default")),
4382 rng: rng || rng_seed.is_some(),
4383rng_seed,
4384unwrap_in_r,
4385serialize,
4386wrap,
4387serde_error,
4388preconditions,
4389 call_attribution: call_attr,
4390call_arg,
4391return_pref,
4392return_pref_span,
4393s3_generic,
4394s3_class,
4395dots_spec,
4396dots_policy,
4397dots_span,
4398lifecycle,
4399 strict: strict.unwrap_or(falsecfg!(feature = "strict-default")),
4400internal,
4401noexport,
4402export,
4403doc,
4404c_symbol,
4405r_name,
4406postfix,
4407r_entry,
4408r_post_checks,
4409r_on_exit,
4410 })
4411 }
4412}
4413// endregion