Skip to main content

miniextendr_api/
condition.rs

1//! Condition macros and signal enum for the Rust→R condition pipeline.
2//!
3//! This module provides two things:
4//!
5//! 1. **[`enum@RCondition`] enum** — the internal panic payload used by `error!()`,
6//!    `warning!()`, `message!()`, and `condition!()` macros. Caught by
7//!    [`crate::unwind_protect::with_r_unwind_protect`] before the generic
8//!    panic→error path, then forwarded to R as a structured condition with
9//!    `rust_*` class layering via
10//!    [`crate::error_value::make_rust_condition_value`].
11//!
12//! 2. **[`AsRError`] struct** — wraps any `E: std::error::Error` and
13//!    preserves the full error chain (cause/source) when converting to an R
14//!    error message. Use as the `Err` type in `Result` returns.
15//!
16//! # When to reach for what
17//!
18//! There are three Rust→R error-emission paths and they are not
19//! interchangeable. The crate-level rationale (why tagged-SEXP at all, what
20//! `error_in_r` defaults imply, and the `with_r_unwind_protect` leak) lives
21//! on [`crate::error_value`]; the practical picking-one summary:
22//!
23//! - **`panic!`** — escape hatch. Becomes class `c("rust_error",
24//!   "simpleError", "error", "condition")` with `kind = "panic"`. Use for
25//!   genuine bugs or impossible states. Cheapest in source; coarsest in R
26//!   (callers can only match `rust_error` / `error`, not a specific class).
27//! - **`error!` / `warning!` / `message!` / `condition!`** (this module) —
28//!   typed conditions. Same transport, but allow an optional `class =
29//!   "name"` so R-side `tryCatch` can route by class. `warning!` /
30//!   `message!` / `condition!` are the only way to emit non-error
31//!   conditions; `panic!` is always an error.
32//! - **`Result<T, E>` with [`AsRError<E>`]** — value-style propagation
33//!   through Rust code. Converts at the boundary; `kind = "result_err"`.
34//!   Best when the failure path is real-and-recoverable in Rust and the
35//!   error chain (`std::error::Error::source`) is worth preserving.
36//!
37//! `Rf_error` is *not* on this list. Direct `Rf_error` skips Rust
38//! destructors unconditionally and is forbidden by lint MXL300; see
39//! [`crate::error_value`] for the full reasoning.
40//!
41//! # Macro-vs-module name collision
42//!
43//! `#[macro_export]` puts each macro at the *crate root*, where `error!` and
44//! `condition!` collide with the same-named modules `pub mod error` and `pub
45//! mod condition`. The practical implication: `use miniextendr_api::{error,
46//! condition}` imports the *modules*, not the macros, and a subsequent
47//! `error!(...)` call fails to resolve.
48//!
49//! Workarounds, in rough order of ergonomics:
50//!
51//! 1. Invoke via fully-qualified path: `miniextendr_api::error!("...")`.
52//! 2. `use miniextendr_api as mx;` then `mx::error!("...")`.
53//! 3. `warning!` and `message!` have no module conflict — `use
54//!    miniextendr_api::{warning, message};` works directly.
55//!
56//! See the individual macro docs for the per-macro reminder.
57//!
58//! # Condition macros
59//!
60//! The four macros are the user-facing API for raising non-panic conditions from
61//! Rust. They ride the tagged-condition transport that every `#[miniextendr]`
62//! function uses:
63//!
64//! ```ignore
65//! use miniextendr_api::{error, warning, message, condition};
66//!
67//! #[miniextendr]
68//! fn demo_error() {
69//!     error!("something went wrong: {}", 42);
70//! }
71//!
72//! #[miniextendr]
73//! fn demo_warning() {
74//!     warning!("something looks suspicious");
75//! }
76//!
77//! #[miniextendr]
78//! fn demo_message() {
79//!     message!("progress: {} of {}", 1, 10);
80//! }
81//!
82//! #[miniextendr]
83//! fn demo_condition() {
84//!     condition!("a signallable condition");
85//! }
86//! ```
87//!
88//! Optional `class =` extension for programmatic catching:
89//!
90//! ```ignore
91//! #[miniextendr]
92//! fn typed_error(name: &str) {
93//!     error!(class = "my_error", "missing field: {name}");
94//! }
95//! ```
96//!
97//! ```r
98//! tryCatch(typed_error("x"), my_error = function(e) "caught!")
99//! # [1] "caught!"
100//! ```
101//!
102//! Optional `data =` extension attaches structured fields readable as
103//! `e$<name>` in handlers (rlang-`abort()`-style):
104//!
105//! ```ignore
106//! #[miniextendr]
107//! fn validate(value: i32) {
108//!     if !(0..=100).contains(&value) {
109//!         miniextendr_api::error!(
110//!             class = "validation_error",
111//!             data = [("value", value), ("min", 0), ("max", 100)],
112//!             "value {value} out of range"
113//!         );
114//!     }
115//! }
116//! ```
117//!
118//! ```r
119//! tryCatch(validate(150L), validation_error = function(e) c(e$value, e$min, e$max))
120//! # [1] 150   0 100
121//! ```
122//!
123//! Supported `data` value types (anything with `RValue: From<_>`): scalars and
124//! `Vec`s of `i32`, `f64`, `bool`, `String` / `&str`; their NA-aware `Option` /
125//! `Vec<Option<_>>` forms (`None` → R `NA`); the wide-integer ladder (`i64` /
126//! `u32`, narrowed to `integer(1)` when it fits, `double(1)` otherwise); and the
127//! [`RValue::debug`](crate::RValue::debug) escape hatch, which stringifies any
128//! `T: Debug`. For nested lists or complex/raw/NA-bearing values build an
129//! [`RValue`](crate::RValue) directly. The payload is built as a Send-safe owned
130//! value at the call site and materialised as R objects on the main thread — so
131//! `data =` works from worker-thread code too.
132//!
133//! Three `data =` grammars are accepted (see [`crate::error!`]):
134//! - single pair: `data = ("name", value)`
135//! - bracketed list: `data = [("a", v1), ("b", v2)]`
136//! - keyed builder sugar: `data = { value = 42, code = 7 }` (bare-ident keys)
137//!
138//! # `AsRError`
139//!
140//! ```ignore
141//! use miniextendr_api::condition::AsRError;
142//!
143//! #[miniextendr]
144//! fn parse_config(path: &str) -> Result<i32, AsRError<std::io::Error>> {
145//!     let content = std::fs::read_to_string(path).map_err(AsRError)?;
146//!     Ok(content.len() as i32)
147//! }
148//! ```
149
150// region: ConditionData — Send-safe owned condition-data payload
151
152/// Named condition-data payload: an ordered list of `(name, value)` pairs.
153///
154/// Produced by the macros' `data = ...` form and consumed by
155/// [`crate::error_value::make_rust_condition_value`]. Each value is an
156/// [`RValue`](crate::RValue) — an owned, `Send`, R-native value tree. Send-safe
157/// by construction (no live `SEXP`), so the payload can travel through
158/// `panic_any` and cross the worker→main thread boundary; the R objects are
159/// materialised on the main thread at the unwind boundary.
160///
161/// The macros accept any value with `RValue: From<_>` (scalars and `Vec`s of
162/// `i32` / `f64` / `bool` / `String` / `&str`; their NA-aware `Option` /
163/// `Vec<Option<_>>` forms; the `i64` / `u32` wide-integer ladder); a scalar
164/// `7i32` becomes `integer(1)` and a `Vec<i32>` becomes `integer(n)`. Any
165/// `T: Debug` rides along via [`RValue::debug`](crate::RValue::debug). For
166/// nested lists or complex/raw values build an [`RValue`](crate::RValue)
167/// directly.
168pub type ConditionData = Vec<(String, crate::RValue)>;
169
170// endregion
171
172// region: ConditionCall — whether a condition reports a call
173
174/// Whether a condition reports a call (`conditionCall()`): the per-condition
175/// equivalent of R's `warning(..., call. = FALSE)` / `stop(..., call. = FALSE)`.
176///
177/// Every condition a `#[miniextendr]` function raises carries the call its
178/// transport captured: the wrapper's call as written (or the caller's, under
179/// `call = caller`), `NULL` in ALTREP and connection callbacks. A condition
180/// about the data rather than the call opts out with [`ConditionCall::None`];
181/// its siblings from the same function keep their call.
182///
183/// Spellings: `call = none` first in [`crate::warning!`], [`crate::error!`],
184/// [`crate::condition!`] and the `defer_*!` macros;
185/// [`RError::without_call`]; `#[condition(call = none)]` on a
186/// `#[derive(RCondition)]` type or variant; or an
187/// [`RConditionData::call`] impl.
188///
189/// On the tagged condition value the choice is the `call` slot itself:
190/// `FALSE` for [`ConditionCall::None`], read back by the R helper (and by the
191/// trait-ABI re-panic, [`RCondition::from_tagged_sexp`]) as "no call".
192#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ConditionCall {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ConditionCall::Inherit => "Inherit",
                ConditionCall::None => "None",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ConditionCall { }
#[automatically_derived]
impl ::core::clone::Clone for ConditionCall {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ConditionCall { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ConditionCall { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ConditionCall {
    #[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 ConditionCall { }Eq, #[automatically_derived]
impl ::core::default::Default for ConditionCall {
    #[inline]
    fn default() -> Self { Self::Inherit }
}Default)]
193pub enum ConditionCall {
194    /// The call the transport captured (the default).
195    #[default]
196    Inherit,
197    /// No call: `conditionCall()` is `NULL` and the printed condition has no
198    /// `In f(...) :` prefix.
199    None,
200}
201
202impl ConditionCall {
203    /// `call` when the condition keeps its call, `None` otherwise: the call to
204    /// hand a transport that takes the R call object directly.
205    #[inline]
206    pub fn apply(self, call: Option<crate::SEXP>) -> Option<crate::SEXP> {
207        match self {
208            ConditionCall::Inherit => call,
209            ConditionCall::None => Option::None,
210        }
211    }
212}
213
214// endregion
215
216// region: RCondition enum — structured condition payload
217
218/// Owned payload for structured R conditions.
219///
220/// `#[derive(RCondition)]` gives a struct or enum a conversion into this
221/// payload. Its `#[condition(kind = warning)]` (or `error` / `message` /
222/// `condition`) chooses the signal; an enum variant can override the kind.
223/// Convert with `RCondition::from(value)`, then call [`defer`](Self::defer)
224/// to accompany a return value, or [`raise`](Self::raise) to leave Rust.
225///
226/// Raised by the `error!()`, `warning!()`, `message!()`, and `condition!()`
227/// macros via `std::panic::panic_any`. Caught by `with_r_unwind_protect`
228/// before the generic panic→string path and forwarded to R as a tagged SEXP
229/// with `rust_*` class layering. The `defer_*` family
230/// ([`crate::deferred_condition`]) queues the same payloads instead of
231/// panicking, so the call still returns its value.
232///
233/// Every variant carries the same four parts: the message, the user classes
234/// (prepended to the `rust_*` layering, empty for none), the optional `data`
235/// fields and the [`ConditionCall`] choice. All four are owned and `Send`, so
236/// the payload can be queued from a worker thread. Each variant is one kind
237/// of the tagged condition value ([`crate::error_value::kind`]).
238#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RCondition {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Error {
                message: __self_0,
                class: __self_1,
                data: __self_2,
                call: __self_3 } =>
                ::core::fmt::Formatter::debug_struct_field4_finish(f, "Error",
                    "message", __self_0, "class", __self_1, "data", __self_2,
                    "call", &__self_3),
            Self::Warning {
                message: __self_0,
                class: __self_1,
                data: __self_2,
                call: __self_3 } =>
                ::core::fmt::Formatter::debug_struct_field4_finish(f,
                    "Warning", "message", __self_0, "class", __self_1, "data",
                    __self_2, "call", &__self_3),
            Self::Message {
                message: __self_0,
                class: __self_1,
                data: __self_2,
                call: __self_3 } =>
                ::core::fmt::Formatter::debug_struct_field4_finish(f,
                    "Message", "message", __self_0, "class", __self_1, "data",
                    __self_2, "call", &__self_3),
            Self::Condition {
                message: __self_0,
                class: __self_1,
                data: __self_2,
                call: __self_3 } =>
                ::core::fmt::Formatter::debug_struct_field4_finish(f,
                    "Condition", "message", __self_0, "class", __self_1, "data",
                    __self_2, "call", &__self_3),
            Self::Conversion {
                message: __self_0,
                class: __self_1,
                data: __self_2,
                call: __self_3 } =>
                ::core::fmt::Formatter::debug_struct_field4_finish(f,
                    "Conversion", "message", __self_0, "class", __self_1,
                    "data", __self_2, "call", &__self_3),
        }
    }
}Debug)]
239pub enum RCondition {
240    /// Raised by `error!(...)` / `error!(class = "...", ...)`, and by the
241    /// `Result<T, E>` Err arm once reconstructed across a package boundary.
242    Error {
243        /// Human-readable condition message.
244        message: String,
245        /// User classes, most specific first, before the framework's classes.
246        class: Vec<String>,
247        /// Named fields attached to the R condition.
248        data: Option<ConditionData>,
249        /// Call attribution policy; messages always omit the call.
250        call: ConditionCall,
251    },
252    /// Raised by `warning!(...)` / `warning!(class = "...", ...)`.
253    Warning {
254        /// Human-readable condition message.
255        message: String,
256        /// User classes, most specific first, before the framework's classes.
257        class: Vec<String>,
258        /// Named fields attached to the R condition.
259        data: Option<ConditionData>,
260        /// Call attribution policy; messages always omit the call.
261        call: ConditionCall,
262    },
263    /// Raised by `message!(...)`, and queued by [`crate::defer_message()`]. Only
264    /// the typed-payload path fills `class` (the macro has no `class =` form);
265    /// the R helper layers it in front of `rust_message` like the other kinds.
266    /// A message never reports a call, whatever `call` says.
267    Message {
268        /// Human-readable condition message.
269        message: String,
270        /// User classes, most specific first, before the framework's classes.
271        class: Vec<String>,
272        /// Named fields attached to the R condition.
273        data: Option<ConditionData>,
274        /// Call attribution policy; messages always omit the call.
275        call: ConditionCall,
276    },
277    /// Raised by `condition!(...)` / `condition!(class = "...", ...)`.
278    Condition {
279        /// Human-readable condition message.
280        message: String,
281        /// User classes, most specific first, before the framework's classes.
282        class: Vec<String>,
283        /// Named fields attached to the R condition.
284        data: Option<ConditionData>,
285        /// Call attribution policy; messages always omit the call.
286        call: ConditionCall,
287    },
288    /// An argument error, `kind = "conversion"`: raised by
289    /// [`crate::arg_error!`] / [`ArgError::raise`] (built by
290    /// [`RCondition::arg_error`]), and a failed argument conversion once
291    /// reconstructed across a package boundary.
292    Conversion {
293        /// Human-readable condition message.
294        message: String,
295        /// User classes, most specific first, before the framework's classes.
296        class: Vec<String>,
297        /// Named fields attached to the R condition.
298        data: Option<ConditionData>,
299        /// Call attribution policy; messages always omit the call.
300        call: ConditionCall,
301    },
302}
303
304impl RCondition {
305    /// Leave the current Rust body with this condition.
306    ///
307    /// The enclosing miniextendr guard forwards it to R with its kind and
308    /// metadata intact. Like `warning!`, raising a warning still exits Rust:
309    /// muffling it in R does not resume this Rust body. Use [`defer`](Self::defer)
310    /// to keep computing and return a value.
311    pub fn raise(self) -> ! {
312        std::panic::panic_any(self)
313    }
314
315    /// Queue this condition until the current guarded call returns.
316    ///
317    /// Preserves its declared kind, classes, fields and call policy, including
318    /// for a mixed-kind enum. Errors queued here stop R at that boundary.
319    pub fn defer(self) {
320        crate::deferred_condition::defer(self);
321    }
322
323    /// Split into the kind (one of [`crate::error_value::kind`]) and the
324    /// [`ErrParts`] (message, class, data, call choice) that
325    /// [`crate::error_value::condition_parts_value`] builds the tagged value
326    /// from.
327    #[doc(hidden)]
328    pub fn into_parts(self) -> (&'static str, ErrParts) {
329        use crate::error_value::kind;
330        let (kind, message, class, data, call) = match self {
331            RCondition::Error {
332                message,
333                class,
334                data,
335                call,
336            } => (kind::ERROR, message, class, data, call),
337            RCondition::Warning {
338                message,
339                class,
340                data,
341                call,
342            } => (kind::WARNING, message, class, data, call),
343            RCondition::Message {
344                message,
345                class,
346                data,
347                call,
348            } => (kind::MESSAGE, message, class, data, call),
349            RCondition::Condition {
350                message,
351                class,
352                data,
353                call,
354            } => (kind::CONDITION, message, class, data, call),
355            RCondition::Conversion {
356                message,
357                class,
358                data,
359                call,
360            } => (kind::CONVERSION, message, class, data, call),
361        };
362        (
363            kind,
364            ErrParts {
365                message,
366                class,
367                data,
368                call,
369            },
370        )
371    }
372
373    /// The condition message, as `conditionMessage()` would read it in R.
374    ///
375    /// Use it on a condition [`catch_condition`] caught or
376    /// [`capture`](crate::deferred_condition::capture) collected, for example
377    /// to put a cause's text into a new message.
378    pub fn message(&self) -> &str {
379        match self {
380            RCondition::Error { message, .. }
381            | RCondition::Warning { message, .. }
382            | RCondition::Message { message, .. }
383            | RCondition::Condition { message, .. }
384            | RCondition::Conversion { message, .. } => message,
385        }
386    }
387
388    /// This condition with `parent` as its cause, as `rlang::abort(parent = )`
389    /// chains conditions.
390    ///
391    /// R gets the cause as the field `e$parent`: a condition object with its
392    /// own classes, message and fields (and its own parent, if it has one),
393    /// built like the condition itself but not signalled. The cause's classes
394    /// stay out of this condition's class vector, so a handler written for
395    /// the cause's class does not catch this condition; code that wants the
396    /// cause branches on it: `inherits(e$parent, "pkg_error_config")`.
397    ///
398    /// The cause reports the same call as this condition, or none when it was
399    /// raised with `call = none`. A second `with_parent` replaces the first,
400    /// and the cause replaces a `data` field this condition names `parent`.
401    ///
402    /// ```ignore
403    /// match catch_condition(|| rebuild(&saved)) {
404    ///     Ok(value) => value,
405    ///     Err(cause) => RCondition::Error {
406    ///         message: format!("could not rebuild: {}", cause.message()),
407    ///         class: vec!["pkg_error_rebuild".into()],
408    ///         data: None,
409    ///         call: ConditionCall::Inherit,
410    ///     }
411    ///     .with_parent(cause)
412    ///     .raise(),
413    /// }
414    /// ```
415    pub fn with_parent(mut self, parent: RCondition) -> Self {
416        let encoded = parent.into_tagged_rvalue();
417        let data = match &mut self {
418            RCondition::Error { data, .. }
419            | RCondition::Warning { data, .. }
420            | RCondition::Message { data, .. }
421            | RCondition::Condition { data, .. }
422            | RCondition::Conversion { data, .. } => data,
423        };
424        let fields = data.get_or_insert_with(Vec::new);
425        fields.retain(|(name, _)| name != PARENT_FIELD);
426        fields.push((PARENT_FIELD.to_owned(), encoded));
427        self
428    }
429
430    /// The condition as the tagged value the R raise helper reads (`error`,
431    /// `kind`, `class`, `call`, `data`), in [`RValue`](crate::RValue) form so
432    /// it can travel inside another condition's data ([`Self::with_parent`]).
433    fn into_tagged_rvalue(self) -> crate::RValue {
434        use crate::RValue;
435        let (kind, parts) = self.into_parts();
436        let text = |value: String| RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(value)]))vec![Some(value)]);
437        let class = if parts.class.is_empty() {
438            RValue::Null
439        } else {
440            RValue::Character(parts.class.into_iter().map(Some).collect())
441        };
442        let call = match parts.call {
443            ConditionCall::Inherit => RValue::Null,
444            ConditionCall::None => RValue::Logical(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(false)]))vec![Some(false)]),
445        };
446        let data = parts.data.map_or(RValue::Null, |fields| {
447            RValue::List(
448                fields
449                    .into_iter()
450                    .map(|(name, value)| (Some(name), value))
451                    .collect(),
452            )
453        });
454        RValue::List(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(Some("error".to_owned()), text(parts.message)),
                (Some("kind".to_owned()), text(kind.to_owned())),
                (Some("class".to_owned()), class),
                (Some("call".to_owned()), call),
                (Some("data".to_owned()), data)]))vec![
455            (Some("error".to_owned()), text(parts.message)),
456            (Some("kind".to_owned()), text(kind.to_owned())),
457            (Some("class".to_owned()), class),
458            (Some("call".to_owned()), call),
459            (Some("data".to_owned()), data),
460        ])
461    }
462
463    /// Which call the condition reports when R raises it: see
464    /// [`ConditionCall`].
465    pub fn call(&self) -> ConditionCall {
466        match self {
467            RCondition::Error { call, .. }
468            | RCondition::Warning { call, .. }
469            | RCondition::Message { call, .. }
470            | RCondition::Condition { call, .. }
471            | RCondition::Conversion { call, .. } => *call,
472        }
473    }
474
475    /// The payload of [`crate::arg_error!`] and [`ArgError::raise`]: the
476    /// argument error of parameter `param` (its R name) with `message`.
477    ///
478    /// `kind = "conversion"`, `e$param` as the only data field, and the class
479    /// [`CONVERSION_ERROR_CLASS_MARKER`], which the generated R replaces with
480    /// the crate's `conversion_error_class`: the condition the wrapper's own
481    /// argument checks raise (`.miniextendr_arg_error`).
482    #[doc(hidden)]
483    pub fn arg_error(param: impl Into<String>, message: String, call: ConditionCall) -> Self {
484        RCondition::Conversion {
485            message,
486            class: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [CONVERSION_ERROR_CLASS_MARKER.to_string()]))vec![CONVERSION_ERROR_CLASS_MARKER.to_string()],
487            data: Some(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(CONVERSION_PARAM_FIELD.to_string(),
                    crate::RValue::from(param.into()))]))vec![(
488                CONVERSION_PARAM_FIELD.to_string(),
489                crate::RValue::from(param.into()),
490            )]),
491            call,
492        }
493    }
494}
495
496/// The class that stands for the crate's `conversion_error_class` in a tagged
497/// condition value (#1740).
498///
499/// A `macro_rules!` macro cannot read `[package.metadata.miniextendr]`, so
500/// [`crate::arg_error!`] cannot name the crate's classes the way the
501/// generated conversion arms do. It writes this class instead, and the
502/// generated R wrapper's `.miniextendr_raise_condition` replaces it with
503/// `.miniextendr_conversion_error_class`, the binding its own argument checks
504/// read. The string is that binding's name. Where no generated wrapper raises
505/// the condition (a raising guard: an ALTREP `RUnwind` callback,
506/// [`crate::unwind_protect::with_r_unwind_protect_or_raise`]) Rust replaces it
507/// with the classes `miniextendr_init!` registered for the package
508/// ([`resolve_conversion_error_class_marker`]).
509#[doc(hidden)]
510pub const CONVERSION_ERROR_CLASS_MARKER: &str = ".miniextendr_conversion_error_class";
511
512/// `class` with [`CONVERSION_ERROR_CLASS_MARKER`] replaced by `crate_classes`,
513/// in place, as the generated `.miniextendr_raise_condition` replaces it in R.
514/// A raising guard has no generated R to do it, so it calls this with
515/// [`crate_conversion_error_class`].
516pub(crate) fn resolve_conversion_error_class_marker(
517    class: Vec<String>,
518    crate_classes: &[&str],
519) -> Vec<String> {
520    let mut resolved = Vec::with_capacity(class.len() + crate_classes.len());
521    for c in class {
522        if c == CONVERSION_ERROR_CLASS_MARKER {
523            resolved.extend(crate_classes.iter().map(|&k| k.to_string()));
524        } else {
525            resolved.push(c);
526        }
527    }
528    resolved
529}
530
531/// The package crate's `conversion_error_class`, from the entry
532/// `miniextendr_init!` registers in
533/// [`MX_CONVERSION_ERROR_CLASS`](crate::registry::MX_CONVERSION_ERROR_CLASS)
534/// (each package links its own copy of this crate, so it is that package's),
535/// or on wasm32 from the `wasm_registry.rs` snapshot (#1768). Empty when the
536/// crate sets none.
537pub(crate) fn crate_conversion_error_class() -> &'static [&'static str] {
538    crate::registry::conversion_error_class()
539}
540
541/// Run `f`, returning a condition raised inside it as `Err` instead of
542/// letting it reach R; any other unwind carries on.
543///
544/// For a body that turns another function's failure into its own result: a
545/// refusal that names the reason, a fallback, a retry.
546///
547/// ```ignore
548/// use miniextendr_api::condition::catch_condition;
549///
550/// match catch_condition(|| rebuild(&snapshot)) {
551///     Ok(object) => object,
552///     Err(cause) => error!(
553///         class = "pkg_error_rebuild",
554///         data = ("cause", cause.message().to_string()),
555///         "cannot rebuild the object: {}", cause.message()
556///     ),
557/// }
558/// ```
559///
560/// **Caught:** every [`RCondition`](enum@RCondition) payload, with its kind, classes, data
561/// and call choice: [`error!`](crate::error!) / [`rust_error!`](crate::rust_error!),
562/// [`warning!`](crate::warning!), [`message!`](crate::message!),
563/// [`condition!`](crate::condition!), [`RCondition::raise`], the argument and
564/// conversion errors ([`RCondition::arg_error`]) and the `ExternalPtr` type
565/// errors. An `Err` can be raised again with [`RCondition::raise`] or queued
566/// with [`RCondition::defer`].
567///
568/// **Resumed** unchanged, with [`std::panic::resume_unwind`]:
569/// - an R error, interrupt or other R exit crossing the Rust frames (R code
570///   run through [`with_r_unwind_protect`](crate::unwind_protect::with_r_unwind_protect)
571///   or [`eval_with_handlers`](crate::expression::eval_with_handlers)), so
572///   R carries on to the context it was jumping to;
573/// - a plain `panic!()` or any other payload, which still becomes an R error
574///   at the boundary, with its source location.
575///
576/// Use [`try_eval_with_handlers`](crate::expression::try_eval_with_handlers)
577/// to get an R error back as a value.
578///
579/// Deferred conditions queued inside `f` stay queued (see
580/// [`deferred_condition::capture`](crate::deferred_condition::capture)). A
581/// caught raise still runs the panic hook: with `MINIEXTENDR_BACKTRACE=true`
582/// it prints the panic to stderr.
583///
584/// Generated `#[miniextendr(worker)]` wrappers run the body through it on the
585/// worker thread: the payload is `Send`, so it crosses back to the main
586/// thread as a value and is raised there with its class, data and call kept.
587pub fn catch_condition<T>(f: impl FnOnce() -> T) -> Result<T, RCondition> {
588    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
589        Ok(value) => Ok(value),
590        Err(payload) => match payload.downcast::<RCondition>() {
591            Ok(condition) => Err(*condition),
592            Err(payload) => std::panic::resume_unwind(payload),
593        },
594    }
595}
596
597// endregion
598
599// region: Macros
600
601/// Internal: one condition-data field name, validated against the reserved
602/// slots. Not part of the public API.
603///
604/// - `lit "name"` / `ident name`: the name is a literal, so the reserved-name
605///   check runs at compile time (`const` assertion) and the field costs one
606///   `to_string()` at runtime.
607/// - `expr <e>`: a computed name; checked at runtime by
608///   [`crate::condition::check_condition_field_name`].
609#[doc(hidden)]
610#[macro_export]
611macro_rules! __mx_condition_field_name {
612    (lit $name:literal) => {{
613        const _: () = ::core::assert!(
614            !$crate::condition::is_reserved_condition_field($name),
615            "condition data field name is reserved: `message`, `call` and `kind` are the condition's own slots, and `.mx_parent` carries its parent; rename the field"
616        );
617        ($name).to_string()
618    }};
619    (ident $name:ident) => {{
620        const _: () = ::core::assert!(
621            !$crate::condition::is_reserved_condition_field(::core::stringify!($name)),
622            "condition data field name is reserved: `message`, `call` and `kind` are the condition's own slots, and `.mx_parent` carries its parent; rename the field"
623        );
624        ::core::stringify!($name).to_string()
625    }};
626    (expr $name:expr) => {
627        $crate::condition::check_condition_field_name(($name).to_string())
628    };
629}
630
631/// Internal: one `(name, value)` pair of a bracketed `data = [...]` list; the
632/// name goes through the reserved-slot check (compile time for literals).
633#[doc(hidden)]
634#[macro_export]
635macro_rules! __mx_condition_pair {
636    (($name:literal, $value:expr $(,)?)) => {
637        ($crate::__mx_condition_field_name!(lit $name), $crate::RValue::from($value))
638    };
639    (($name:expr, $value:expr $(,)?)) => {
640        ($crate::__mx_condition_field_name!(expr $name), $crate::RValue::from($value))
641    };
642}
643
644/// Internal: normalise a macro `data = ...` argument into
645/// `Option<ConditionData>`. Not part of the public API.
646///
647/// Three forms are accepted:
648/// - a single pair: `("name", value)`
649/// - a bracketed list of pairs: `[("a", v1), ("b", v2)]`
650/// - keyed builder sugar: `{ name = value, other = value }` — the field name is
651///   a bare identifier (stringified by the macro), so `{ value = 42, code = 7 }`
652///   is shorthand for `[("value", 42), ("code", 7)]`.
653///
654/// Each `value` is converted via `RValue::from`, so any type with an `RValue`
655/// `From` impl (the scalar/vector/`Option`/wide-integer set, and tuples of
656/// those as unnamed lists) works without ceremony.
657///
658/// Field names are checked against the condition's own slots (`message` /
659/// `call` / `kind`): at compile time when the name is a literal or bare
660/// identifier, at runtime otherwise.
661#[doc(hidden)]
662#[macro_export]
663macro_rules! __mx_condition_data {
664    (($name:literal, $value:expr $(,)?)) => {
665        ::std::option::Option::Some(::std::vec![(
666            $crate::__mx_condition_field_name!(lit $name),
667            $crate::RValue::from($value),
668        )])
669    };
670    (($name:expr, $value:expr $(,)?)) => {
671        ::std::option::Option::Some(::std::vec![(
672            $crate::__mx_condition_field_name!(expr $name),
673            $crate::RValue::from($value),
674        )])
675    };
676    ([ $($pair:tt),* $(,)? ]) => {
677        ::std::option::Option::Some(::std::vec![
678            $( $crate::__mx_condition_pair!($pair), )*
679        ])
680    };
681    ({ $($name:ident = $value:expr),* $(,)? }) => {
682        ::std::option::Option::Some(::std::vec![
683            $(
684                (
685                    $crate::__mx_condition_field_name!(ident $name),
686                    $crate::RValue::from($value),
687                ),
688            )*
689        ])
690    };
691}
692
693/// Internal: parse the shared option grid of `error!` / `warning!` /
694/// `condition!` and the `defer_*!` macros into `(call, class, data, message)`.
695/// Not part of the public API.
696///
697/// Accepted orders (every part optional except the message):
698/// `call = none`, then `class = ..`, then `data = ..`, then the `format!`
699/// arguments. `class` takes anything implementing
700/// [`crate::condition::ConditionClass`] (one string or several). A `call`
701/// other than `none`, or one after `class` / `data`, is a compile error: a
702/// `format!` string must start with a literal, so `call` as the first token is
703/// never a message.
704///
705/// ```compile_fail
706/// # use miniextendr_api as mx;
707/// # fn f() {
708/// mx::warning!(call = wrapper, "no such call option");
709/// # }
710/// ```
711///
712/// ```compile_fail
713/// # use miniextendr_api as mx;
714/// # fn f() {
715/// mx::warning!(class = "pkg_warning", call = none, "`call` goes first");
716/// # }
717/// ```
718///
719/// ```
720/// # use miniextendr_api as mx;
721/// # fn f() {
722/// mx::warning!(call = none, class = "pkg_warning", data = { n = 1 }, "plain");
723/// # }
724/// ```
725#[doc(hidden)]
726#[macro_export]
727macro_rules! __mx_condition_parts {
728    // The option grid after the `call` choice: `(class, data, message)`.
729    (@options class = $class:expr, call $($rest:tt)*) => {
730        $crate::__mx_condition_parts!(@misplaced_call)
731    };
732    (@options class = $class:expr, data = $data:tt, call $($rest:tt)*) => {
733        $crate::__mx_condition_parts!(@misplaced_call)
734    };
735    (@options data = $data:tt, call $($rest:tt)*) => {
736        $crate::__mx_condition_parts!(@misplaced_call)
737    };
738    (@options class = $class:expr, data = $data:tt, $($arg:tt)*) => {
739        (
740            $crate::condition::ConditionClass::into_condition_class($class),
741            $crate::__mx_condition_data!($data),
742            ::std::format!($($arg)*),
743        )
744    };
745    (@options data = $data:tt, $($arg:tt)*) => {
746        (
747            ::std::vec::Vec::<::std::string::String>::new(),
748            $crate::__mx_condition_data!($data),
749            ::std::format!($($arg)*),
750        )
751    };
752    (@options class = $class:expr, $($arg:tt)*) => {
753        (
754            $crate::condition::ConditionClass::into_condition_class($class),
755            ::std::option::Option::<$crate::condition::ConditionData>::None,
756            ::std::format!($($arg)*),
757        )
758    };
759    (@options $($arg:tt)*) => {
760        (
761            ::std::vec::Vec::<::std::string::String>::new(),
762            ::std::option::Option::<$crate::condition::ConditionData>::None,
763            ::std::format!($($arg)*),
764        )
765    };
766    (@options call $($rest:tt)*) => {
767        ::core::compile_error!("condition macros: `call = none` is given twice")
768    };
769    (@misplaced_call) => {
770        ::core::compile_error!(
771            "condition macros: `call = none` comes first, before `class = ...` and `data = ...`: \
772             `warning!(call = none, class = \"...\", \"...\")`"
773        )
774    };
775    // Entry: the optional `call = none`, then the option grid.
776    (call = none, $($rest:tt)+) => {{
777        let (__mx_class, __mx_data, __mx_message) =
778            $crate::__mx_condition_parts!(@options $($rest)+);
779        (
780            $crate::condition::ConditionCall::None,
781            __mx_class,
782            __mx_data,
783            __mx_message,
784        )
785    }};
786    (call $($rest:tt)*) => {
787        ::core::compile_error!(
788            "condition macros: the only call option is `call = none` (no call, like R's \
789             `call. = FALSE`), and it comes first, followed by a comma: \
790             `warning!(call = none, \"...\")`"
791        )
792    };
793    ($($rest:tt)*) => {{
794        let (__mx_class, __mx_data, __mx_message) =
795            $crate::__mx_condition_parts!(@options $($rest)*);
796        (
797            $crate::condition::ConditionCall::Inherit,
798            __mx_class,
799            __mx_data,
800            __mx_message,
801        )
802    }};
803}
804
805/// Raise an R error from Rust with `rust_error` class layering.
806///
807/// Rides the tagged-condition transport that every `#[miniextendr]` function uses.
808/// The raised condition has class `c("rust_error", "simpleError", "error", "condition")`.
809///
810/// An optional `class = "name"` form prepends a custom class for programmatic catching:
811/// `c("name", "rust_error", "simpleError", "error", "condition")`. `class` also
812/// takes a vector (`class = ["pkg_error_missing_field", "pkg_error"]`, or any
813/// [`ConditionClass`] value) so handlers can catch the family or the member.
814///
815/// The framework layers (`rust_error`, `simpleError`, `error`, `condition`)
816/// always follow the user classes; no option leaves them out. Handlers and
817/// `inherits()` see the user classes as on an R `errorCondition()`; a test
818/// that compares the whole class vector with an R implementation's compares
819/// the leading classes instead.
820///
821/// # Structured `data = ...` payloads
822///
823/// An optional `data = ...` form (after `class`, before the message) attaches
824/// named fields to the condition object, rlang-`abort()`-style. Handlers read
825/// them as `e$<name>` instead of parsing the message string:
826///
827/// ```ignore
828/// // Single field:
829/// mx::error!(class = "range_error", data = ("value", value), "value {value} out of range");
830///
831/// // Multiple fields (bracketed list of pairs):
832/// mx::error!(
833///     class = "validation_error",
834///     data = [("value", value), ("min", 0), ("max", 100)],
835///     "value {value} out of range"
836/// );
837///
838/// // Keyed builder sugar (bare-ident keys, stringified by the macro):
839/// mx::error!(
840///     class = "validation_error",
841///     data = { value = value, min = 0, max = 100 },
842///     "value {value} out of range"
843/// );
844/// ```
845///
846/// ```r
847/// tryCatch(validate(150L), validation_error = function(e) c(e$value, e$min, e$max))
848/// # [1] 150   0 100
849/// ```
850///
851/// Argument order is fixed: `call = none` (optional), then `class = ...`
852/// (optional), then `data = ...` (optional), then the format message.
853///
854/// Field names `message`, `call` and `kind` are the condition's own slots and
855/// are rejected (at compile time for literal / bare-identifier names, at
856/// runtime otherwise); give the field another name (`rule` instead of `kind`,
857/// say). A downstream error type with such a field renames it where the
858/// payload is built (`#[serde(rename)]`, a different key).
859///
860/// **Supported value types**: scalars and `Vec`s of `i32`, `f64`, `bool`, and
861/// `String` (plus `&str` / `Vec<&str>`, converted to owned); their NA-aware
862/// `Option` / `Vec<Option<_>>` forms (→ R `NA`); the wide-integer ladder (`i64`
863/// / `u32`); and the [`RValue::debug`](crate::RValue::debug) escape hatch for
864/// any `T: Debug`. The payload must be `Send` — it travels through `panic_any`
865/// and may cross the worker→main thread boundary, so live `SEXP`s cannot ride
866/// along; the R objects are materialised on the main thread at the unwind
867/// boundary. For nested lists or complex/raw values build an
868/// [`RValue`](crate::RValue) directly.
869///
870/// # Conditions without a call
871///
872/// A leading `call = none` raises the condition with no call, like R's
873/// `stop(..., call. = FALSE)`: `conditionCall(e)` is `NULL` and the error
874/// prints as `Error: <message>` instead of `Error in f(...) : <message>`. Use
875/// it for a condition about the data rather than about the call. Classes and
876/// `data` are unchanged:
877///
878/// ```ignore
879/// mx::error!(call = none, class = "pkg_bad_row", data = { row = row }, "row {row} is malformed");
880/// ```
881///
882/// The same marker exists as [`RError::without_call`] for `Result` errors and
883/// as `#[condition(call = none)]` on a `#[derive(RCondition)]` type or
884/// variant; see [`ConditionCall`].
885///
886/// # See also
887///
888/// - [`crate::warning!`] / [`crate::message!`] / [`crate::condition!`] — the
889///   non-error sibling kinds (warning continues execution; message is muffled
890///   by `suppressMessages`; condition is silent without a handler).
891/// - [`std::panic!`] — escape hatch with the same `rust_error` class layering
892///   but no custom-class slot. Use for true bugs / impossible states; reach for
893///   `error!` when callers might want to route by class.
894/// - [`AsRError`] — wraps `Result<_, E: std::error::Error>` for value-style
895///   propagation through Rust code; converts at the boundary.
896/// - [`crate::error_value`] — module-level rationale for the tagged-SEXP
897///   transport and the `error_in_r` default.
898///
899/// **Name-collision note.** Because `pub mod error` exists at the crate root,
900/// `use miniextendr_api::error` imports the module rather than this macro, and
901/// glob imports (`use miniextendr_api::*;`) hit the same shadow. Prefer the
902/// collision-free alias [`crate::rust_error!`], which has the identical
903/// expansion; otherwise invoke via `miniextendr_api::error!(...)` (fully
904/// qualified) or `mx::error!(...)` after `use miniextendr_api as mx;`.
905///
906/// # Examples
907///
908/// ```ignore
909/// use miniextendr_api as mx;
910///
911/// #[miniextendr]
912/// fn fail() {
913///     mx::error!("something went wrong: {}", 42);
914/// }
915///
916/// // With a custom class for tryCatch:
917/// #[miniextendr]
918/// fn typed_fail(name: &str) {
919///     mx::error!(class = "my_error", "missing field: {name}");
920/// }
921/// ```
922///
923/// ```r
924/// tryCatch(fail(), rust_error = function(e) conditionMessage(e))
925/// # [1] "something went wrong: 42"
926///
927/// tryCatch(typed_fail("x"), my_error = function(e) "caught!")
928/// # [1] "caught!"
929/// ```
930#[macro_export]
931macro_rules! error {
932    ($($t:tt)*) => {{
933        let (__mx_call, __mx_class, __mx_data, __mx_message) =
934            $crate::__mx_condition_parts!($($t)*);
935        ::std::panic::panic_any($crate::condition::RCondition::Error {
936            message: __mx_message,
937            class: __mx_class,
938            data: __mx_data,
939            call: __mx_call,
940        })
941    }};
942}
943
944/// Collision-free alias for [`crate::error!`].
945///
946/// Identical expansion and grammar (`call = none`, `class = …`, `data = …`,
947/// and the plain `format!` forms) — it exists so that `use miniextendr_api::*;` or
948/// `use miniextendr_api::rust_error;` gives you a usable macro name. The bare
949/// `error!` name is shadowed at the crate root by `pub mod error` (see the
950/// name-collision note on [`crate::error!`]), so a glob or direct import
951/// resolves to the module, not the macro. `rust_error!` has no such clash.
952///
953/// # Example
954///
955/// ```ignore
956/// use miniextendr_api::rust_error;
957///
958/// #[miniextendr]
959/// fn typed_fail(name: &str) {
960///     rust_error!(class = "my_error", "missing field: {name}");
961/// }
962/// ```
963#[macro_export]
964macro_rules! rust_error {
965    ($($t:tt)*) => { $crate::error!($($t)*) };
966}
967
968/// Raise an R warning from Rust with `rust_warning` class layering.
969///
970/// Rides the tagged-condition transport that every `#[miniextendr]` function uses.
971/// Unlike `panic!`, execution continues after `warning!` is caught by a handler.
972/// The raised condition has class `c("rust_warning", "simpleWarning", "warning", "condition")`.
973///
974/// An optional `class = "name"` form prepends a custom class. An optional
975/// `data = ...` form (after `class`, before the message) attaches named fields
976/// readable as `w$<name>` in handlers — same grammar and supported value types
977/// as [`crate::error!`] (see there for details):
978///
979/// ```ignore
980/// warning!(class = "truncation", data = ("dropped", n), "dropped {n} rows");
981/// ```
982///
983/// A leading `call = none` raises the warning without a call, like R's
984/// `warning(..., call. = FALSE)`: `conditionCall(w)` is `NULL` and the
985/// printed warning has no `In f(...) :` prefix. Use it for a warning about the
986/// data rather than about the call (see
987/// [conditions without a call](crate::error!#conditions-without-a-call)):
988///
989/// ```ignore
990/// warning!(call = none, class = "pkg_override", data = { row = row }, "row {row} overrides an earlier row");
991/// ```
992///
993/// # See also
994///
995/// - [`crate::error!`] — fatal sibling; aborts the call instead of continuing.
996/// - [`crate::defer_warning!`] / [`crate::defer_warning()`] — queue the warning
997///   and still return the value (signalled when the call returns).
998/// - [`crate::message!`] / [`crate::condition!`] — softer signal kinds (muffled
999///   by `suppressMessages` / silent without handler, respectively).
1000/// - [`std::panic!`] — escape hatch when "continue after this" is not a sensible
1001///   semantic.
1002/// - [`crate::error_value`] — tagged-SEXP transport rationale.
1003///
1004/// No name-collision caveat: there is no `pub mod warning`, so
1005/// `use miniextendr_api::warning;` then `warning!(...)` works directly.
1006///
1007/// # Example
1008///
1009/// ```ignore
1010/// use miniextendr_api::warning;
1011///
1012/// #[miniextendr]
1013/// fn maybe_warn(x: i32) -> i32 {
1014///     if x > 100 {
1015///         warning!("x is large: {x}");
1016///     }
1017///     x * 2
1018/// }
1019/// ```
1020///
1021/// ```r
1022/// withCallingHandlers(
1023///   maybe_warn(200L),
1024///   warning = function(w) { cat("saw:", conditionMessage(w)); invokeRestart("muffleWarning") }
1025/// )
1026/// # saw: x is large: 200
1027/// # [1] 400
1028/// ```
1029#[macro_export]
1030macro_rules! warning {
1031    ($($t:tt)*) => {{
1032        let (__mx_call, __mx_class, __mx_data, __mx_message) =
1033            $crate::__mx_condition_parts!($($t)*);
1034        ::std::panic::panic_any($crate::condition::RCondition::Warning {
1035            message: __mx_message,
1036            class: __mx_class,
1037            data: __mx_data,
1038            call: __mx_call,
1039        })
1040    }};
1041}
1042
1043/// Emit an R message from Rust with `rust_message` class layering.
1044///
1045/// Rides the tagged-condition transport that every `#[miniextendr]` function uses.
1046/// The raised condition has class `c("rust_message", "simpleMessage", "message", "condition")`.
1047/// Muffled by `suppressMessages()` automatically (standard R restart mechanism).
1048///
1049/// An optional `data = ...` form (before the message) attaches named fields
1050/// readable as `m$<name>` in `withCallingHandlers` — same grammar and
1051/// supported value types as [`crate::error!`] (see there for details). There
1052/// is no `class =` form for `message!`, and no `call =` form: an R message
1053/// carries no call (`conditionCall(m)` is `NULL`), so there is nothing to drop.
1054///
1055/// # See also
1056///
1057/// - [`crate::warning!`] / [`crate::condition!`] — louder/quieter sibling kinds.
1058/// - [`crate::error!`] — for fatal failures.
1059/// - [`std::panic!`] — escape hatch.
1060/// - [`crate::error_value`] — tagged-SEXP transport rationale.
1061///
1062/// No name-collision caveat: there is no `pub mod message`, so
1063/// `use miniextendr_api::message;` then `message!(...)` works directly.
1064///
1065/// # Example
1066///
1067/// ```ignore
1068/// use miniextendr_api::message;
1069///
1070/// #[miniextendr]
1071/// fn log_step(step: i32) {
1072///     message!("step {} complete", step);
1073/// }
1074/// ```
1075///
1076/// ```r
1077/// log_step(3L)
1078/// # step 3 complete
1079///
1080/// suppressMessages(log_step(3L))  # no output
1081/// ```
1082#[macro_export]
1083macro_rules! message {
1084    (call $($rest:tt)*) => {
1085        ::core::compile_error!(
1086            "message!: there is no `call` option, an R message carries no call"
1087        )
1088    };
1089    (data = $data:tt, $($arg:tt)*) => {
1090        ::std::panic::panic_any($crate::condition::RCondition::Message {
1091            message: ::std::format!($($arg)*),
1092            class: ::std::vec::Vec::new(),
1093            data: $crate::__mx_condition_data!($data),
1094            call: $crate::condition::ConditionCall::Inherit,
1095        })
1096    };
1097    ($($arg:tt)*) => {
1098        ::std::panic::panic_any($crate::condition::RCondition::Message {
1099            message: ::std::format!($($arg)*),
1100            class: ::std::vec::Vec::new(),
1101            data: ::std::option::Option::None,
1102            call: $crate::condition::ConditionCall::Inherit,
1103        })
1104    };
1105}
1106
1107/// Signal a generic R condition from Rust with `rust_condition` class layering.
1108///
1109/// Rides the tagged-condition transport that every `#[miniextendr]` function uses.
1110/// Unlike `error!`, a bare condition is a silent no-op if there is no handler.
1111/// The raised condition has class `c("rust_condition", "simpleCondition", "condition")`.
1112///
1113/// An optional `class = "name"` form prepends a custom class. An optional
1114/// `data = ...` form (after `class`, before the message) attaches named fields
1115/// readable as `c$<name>` in handlers — same grammar and supported value types
1116/// as [`crate::error!`] (see there for details). A leading `call = none`
1117/// signals the condition without a call (`conditionCall(c)` is `NULL`), like
1118/// [`crate::error!`]'s.
1119///
1120/// # See also
1121///
1122/// - [`crate::error!`] / [`crate::warning!`] / [`crate::message!`] — louder
1123///   condition kinds. Pick `condition!` when "no handler = silent" is the
1124///   right default (progress events, structured logging hooks).
1125/// - [`std::panic!`] — escape hatch when the failure cannot be ignored.
1126/// - [`crate::error_value`] — tagged-SEXP transport rationale.
1127///
1128/// **Name-collision note.** Because `pub mod condition` exists at the crate
1129/// root, `use miniextendr_api::condition` imports the module rather than this
1130/// macro, and glob imports (`use miniextendr_api::*;`) hit the same shadow.
1131/// Prefer the collision-free alias [`crate::rust_condition!`], which has the
1132/// identical expansion; otherwise invoke via `miniextendr_api::condition!(...)`
1133/// (fully qualified) or `mx::condition!(...)` after `use miniextendr_api as mx;`.
1134///
1135/// # Example
1136///
1137/// ```ignore
1138/// use miniextendr_api::condition;
1139///
1140/// #[miniextendr]
1141/// fn signal_progress(n: i32) {
1142///     condition!(class = "my_progress", "processed {n} items");
1143/// }
1144/// ```
1145///
1146/// ```r
1147/// withCallingHandlers(
1148///   signal_progress(42L),
1149///   my_progress = function(c) cat("progress:", conditionMessage(c), "\n")
1150/// )
1151/// # progress: processed 42 items
1152/// ```
1153#[macro_export]
1154macro_rules! condition {
1155    ($($t:tt)*) => {{
1156        let (__mx_call, __mx_class, __mx_data, __mx_message) =
1157            $crate::__mx_condition_parts!($($t)*);
1158        ::std::panic::panic_any($crate::condition::RCondition::Condition {
1159            message: __mx_message,
1160            class: __mx_class,
1161            data: __mx_data,
1162            call: __mx_call,
1163        })
1164    }};
1165}
1166
1167/// Collision-free alias for [`crate::condition!`].
1168///
1169/// Identical expansion and grammar (`call = none`, `class = …`, `data = …`,
1170/// and the plain `format!` forms) — it exists so that `use miniextendr_api::*;` or
1171/// `use miniextendr_api::rust_condition;` gives you a usable macro name. The
1172/// bare `condition!` name is shadowed at the crate root by `pub mod condition`
1173/// (see the name-collision note on [`crate::condition!`]), so a glob or direct
1174/// import resolves to the module, not the macro. `rust_condition!` has no such
1175/// clash.
1176///
1177/// # Example
1178///
1179/// ```ignore
1180/// use miniextendr_api::rust_condition;
1181///
1182/// #[miniextendr]
1183/// fn signal_progress(n: i32) {
1184///     rust_condition!(class = "my_progress", "processed {n} items");
1185/// }
1186/// ```
1187#[macro_export]
1188macro_rules! rust_condition {
1189    ($($t:tt)*) => { $crate::condition!($($t)*) };
1190}
1191
1192/// Raise the argument error the generated R wrapper raises for a bad
1193/// argument, from a `#[miniextendr]` function body.
1194///
1195/// `arg_error!(param = "mode", "'mode' should be one of {}", choices)` raises
1196/// the condition of the wrapper's own argument checks (`match_arg`,
1197/// `inherits`, `no_na`, type preconditions) and of a failed argument
1198/// conversion: `kind = "conversion"`, `e$param` set to `param` (the
1199/// argument's R name), the message as formatted, and the crate's
1200/// `conversion_error_class` (`[package.metadata.miniextendr]`) before the
1201/// `rust_error` layering:
1202///
1203/// ```r
1204/// class(e)
1205/// # [1] "pkg_error_argument" "pkg_error" "rust_error" "simpleError" "error" "condition"
1206/// ```
1207///
1208/// Use it for a check that applies on some paths only, where a
1209/// `#[miniextendr]` attribute on the parameter would check every call: an
1210/// argument passed on to another function's body, say, which skips that
1211/// function's R wrapper and its checks. [`crate::match_arg_param`] matches a
1212/// raw choice argument with the wrapper's semantics and message and raises
1213/// through this macro.
1214///
1215/// It diverges like [`crate::error!`] (the same `panic_any` transport), and
1216/// conditions queued with [`crate::defer_warning!`] & co. are signalled first.
1217/// The call is the one the wrapper's checks report: the wrapper's call as
1218/// written, or under `#[miniextendr(call = caller)]` its caller's. A leading
1219/// `call = none` raises it without a call, as for the other condition macros.
1220/// There is no `class =` or `data =`: the classes are the crate's and the only
1221/// field is `param`, so the condition is the wrapper's.
1222///
1223/// ```ignore
1224/// use miniextendr_api::{arg_error, miniextendr};
1225///
1226/// #[miniextendr]
1227/// pub fn in_metres(x: f64, unit: &str) -> f64 {
1228///     match unit {
1229///         "m" => x,
1230///         "cm" => x / 100.0,
1231///         _ => arg_error!(param = "unit", "'unit' should be one of \"m\", \"cm\""),
1232///     }
1233/// }
1234/// ```
1235///
1236/// # How the crate's classes get there
1237///
1238/// A `macro_rules!` macro cannot read `[package.metadata.miniextendr]`, so the
1239/// tagged condition value carries the marker class
1240/// [`CONVERSION_ERROR_CLASS_MARKER`]
1241/// and the generated wrapper's `.miniextendr_raise_condition` replaces it with
1242/// `.miniextendr_conversion_error_class`, the binding its own checks use. Across
1243/// a trait-ABI call the consumer package's wrapper raises the condition, so
1244/// its crate's classes apply. A raising guard (an ALTREP `RUnwind` callback,
1245/// `with_r_unwind_protect_or_raise`) has no generated R: it resolves the
1246/// marker in Rust against the classes `miniextendr_init!` registered (on
1247/// wasm32, the copy in the `wasm_registry.rs` snapshot), then raises the same
1248/// condition, `kind = "conversion"` included (#1768). A connection callback
1249/// returns its fallback, as for any panic.
1250///
1251/// The generated `no_na` check after a conversion builds the same condition
1252/// with [`crate::error_value::arg_check_condition_value`], which takes the
1253/// classes the macro read at expansion time.
1254///
1255/// # Grammar
1256///
1257/// `arg_error!([call = none,] param = <expr>, <format string>, <args>...)`.
1258/// `param` is anything `Into<String>`. A `call` anywhere but first, a repeated
1259/// one, a missing `param`, and `class =` / `data =` are compile errors:
1260///
1261/// ```compile_fail
1262/// # use miniextendr_api as mx;
1263/// # fn f() {
1264/// mx::arg_error!(param = "mode", call = none, "bad");
1265/// # }
1266/// ```
1267///
1268/// ```compile_fail
1269/// # use miniextendr_api as mx;
1270/// # fn f() {
1271/// mx::arg_error!("'mode' is bad");
1272/// # }
1273/// ```
1274///
1275/// ```compile_fail
1276/// # use miniextendr_api as mx;
1277/// # fn f() {
1278/// mx::arg_error!(param = "mode", class = "pkg_error", "bad");
1279/// # }
1280/// ```
1281///
1282/// ```
1283/// # use miniextendr_api as mx;
1284/// # fn f(mode: &str) -> ! {
1285/// mx::arg_error!(call = none, param = "mode", "'mode' is {mode:?}")
1286/// # }
1287/// ```
1288#[macro_export]
1289macro_rules! arg_error {
1290    // Internal: after the optional `call = none`.
1291    (@param $call:expr; call $($rest:tt)*) => {
1292        ::core::compile_error!("arg_error!: `call = none` is given twice")
1293    };
1294    (@param $call:expr; param = $param:expr, call $($rest:tt)*) => {
1295        ::core::compile_error!(
1296            "arg_error!: `call = none` comes first, before `param`: \
1297             `arg_error!(call = none, param = \"mode\", \"...\")`"
1298        )
1299    };
1300    (@param $call:expr; param = $param:expr, class $($rest:tt)*) => {
1301        $crate::arg_error!(@no_class_or_data)
1302    };
1303    (@param $call:expr; param = $param:expr, data $($rest:tt)*) => {
1304        $crate::arg_error!(@no_class_or_data)
1305    };
1306    (@param $call:expr; param = $param:expr, $($arg:tt)+) => {
1307        ::std::panic::panic_any($crate::condition::RCondition::arg_error(
1308            $param,
1309            ::std::format!($($arg)+),
1310            $call,
1311        ))
1312    };
1313    (@param $call:expr; $($rest:tt)*) => {
1314        ::core::compile_error!(
1315            "arg_error!: name the argument, then give the message: \
1316             `arg_error!(param = \"mode\", \"'mode' should be one of ...\")`"
1317        )
1318    };
1319    (@no_class_or_data) => {
1320        ::core::compile_error!(
1321            "arg_error!: takes no `class` or `data`: its classes are the crate's \
1322             `conversion_error_class` and its only field is `param`, as for the wrapper's \
1323             own argument errors; use `error!` for a classed error"
1324        )
1325    };
1326    // Entry: the optional `call = none`, then `param = ...` and the message.
1327    (call = none, $($rest:tt)*) => {
1328        $crate::arg_error!(@param $crate::condition::ConditionCall::None; $($rest)*)
1329    };
1330    (call $($rest:tt)*) => {
1331        ::core::compile_error!(
1332            "arg_error!: the only call option is `call = none` (no call, like R's \
1333             `call. = FALSE`), and it comes first, followed by a comma: \
1334             `arg_error!(call = none, param = \"mode\", \"...\")`"
1335        )
1336    };
1337    ($($rest:tt)*) => {
1338        $crate::arg_error!(@param $crate::condition::ConditionCall::Inherit; $($rest)*)
1339    };
1340}
1341
1342// endregion
1343
1344// region: Class vectors and reserved field names
1345
1346/// What the condition macros' `class = …` (and [`RError::class`]) accept: one
1347/// class or several, as `&str` / `String` / arrays / `Vec` / slices.
1348///
1349/// The classes are prepended in order to the `rust_*` layering, so put the most
1350/// specific first: `class = ["pkg_error_missing_field", "pkg_error"]` renders
1351/// as `c("pkg_error_missing_field", "pkg_error", "rust_error", …)` and both
1352/// `tryCatch(pkg_error_missing_field = …)` and `tryCatch(pkg_error = …)` match.
1353pub trait ConditionClass {
1354    /// The class vector, most specific first.
1355    fn into_condition_class(self) -> Vec<String>;
1356}
1357
1358impl ConditionClass for &str {
1359    fn into_condition_class(self) -> Vec<String> {
1360        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [self.to_string()]))vec![self.to_string()]
1361    }
1362}
1363impl ConditionClass for String {
1364    fn into_condition_class(self) -> Vec<String> {
1365        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [self]))vec![self]
1366    }
1367}
1368impl ConditionClass for &String {
1369    fn into_condition_class(self) -> Vec<String> {
1370        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [self.clone()]))vec![self.clone()]
1371    }
1372}
1373impl<const N: usize> ConditionClass for [&str; N] {
1374    fn into_condition_class(self) -> Vec<String> {
1375        self.iter().map(|s| s.to_string()).collect()
1376    }
1377}
1378impl<const N: usize> ConditionClass for [String; N] {
1379    fn into_condition_class(self) -> Vec<String> {
1380        self.into_iter().collect()
1381    }
1382}
1383impl ConditionClass for Vec<&str> {
1384    fn into_condition_class(self) -> Vec<String> {
1385        self.into_iter().map(str::to_string).collect()
1386    }
1387}
1388impl ConditionClass for Vec<String> {
1389    fn into_condition_class(self) -> Vec<String> {
1390        self
1391    }
1392}
1393impl ConditionClass for &[&str] {
1394    fn into_condition_class(self) -> Vec<String> {
1395        self.iter().map(|s| s.to_string()).collect()
1396    }
1397}
1398impl ConditionClass for &[String] {
1399    fn into_condition_class(self) -> Vec<String> {
1400        self.to_vec()
1401    }
1402}
1403
1404/// Condition-data field names that would overwrite the condition object's own
1405/// slots when the R helper splices `data` in (`utils::modifyList` over
1406/// `list(message, call, kind)`).
1407pub const RESERVED_CONDITION_FIELDS: &[&str] = &["message", "call", "kind"];
1408
1409/// `true` for [`RESERVED_CONDITION_FIELDS`]. `const` so the macros can reject a
1410/// literal field name at compile time:
1411///
1412/// ```compile_fail
1413/// # use miniextendr_api as mx;
1414/// # fn f() {
1415/// mx::rust_error!(data = ("kind", 1), "boom");
1416/// # }
1417/// ```
1418///
1419/// Any other name is fine, so the fix for a clash is a rename at the source:
1420///
1421/// ```
1422/// # use miniextendr_api as mx;
1423/// # fn f() {
1424/// mx::rust_error!(data = ("rule", 1), "boom");
1425/// # }
1426/// ```
1427pub const fn is_reserved_condition_field(name: &str) -> bool {
1428    str_eq(name, "message")
1429        || str_eq(name, "call")
1430        || str_eq(name, "kind")
1431        || str_eq(name, PARENT_FIELD)
1432}
1433
1434/// `a == b`, usable in `const fn`.
1435const fn str_eq(a: &str, b: &str) -> bool {
1436    let (a, b) = (a.as_bytes(), b.as_bytes());
1437    if a.len() != b.len() {
1438        return false;
1439    }
1440    let mut i = 0;
1441    while i < a.len() {
1442        if a[i] != b[i] {
1443            return false;
1444        }
1445        i += 1;
1446    }
1447    true
1448}
1449
1450/// The `data` field under which [`RCondition::with_parent`] sends a cause to
1451/// R; the raise helper turns it into the condition object `e$parent`.
1452const PARENT_FIELD: &str = ".mx_parent";
1453
1454/// Runtime half of the reserved-name check, for computed field names (the
1455/// macros' `expr` path and [`RConditionData::data`] on user types). Panics
1456/// when `name` is reserved; the panic becomes a plain `rust_error` in R,
1457/// replacing the former silent overwrite.
1458#[track_caller]
1459pub fn check_condition_field_name(name: String) -> String {
1460    if is_reserved_condition_field(&name) {
1461        {
    ::core::panicking::panic_fmt(format_args!("condition data field `{0}` is reserved: `message`, `call` and `kind` are the condition\'s own slots, and `.mx_parent` carries its parent; rename the field.",
            name));
};panic!(
1462            "condition data field `{name}` is reserved: `message`, `call` and `kind` are the \
1463             condition's own slots, and `.mx_parent` carries its parent; rename the field."
1464        );
1465    }
1466    name
1467}
1468
1469/// Apply [`check_condition_field_name`] to every field of a payload.
1470#[track_caller]
1471pub fn check_condition_data(data: Option<ConditionData>) -> Option<ConditionData> {
1472    data.map(|fields| {
1473        fields
1474            .into_iter()
1475            .map(|(name, value)| (check_condition_field_name(name), value))
1476            .collect()
1477    })
1478}
1479
1480// endregion
1481
1482// region: Classed `Result` errors — RConditionData + RError
1483
1484/// Give a `Result<T, E>` error type an R class vector and structured fields.
1485///
1486/// Every `#[miniextendr]` function or method returning `Result<T, E>` raises
1487/// `Err(e)` as an R error. Implement this trait for `E` (or return
1488/// [`RError`], which implements it) and the `Err` arm raises
1489/// `c(<class()…>, "rust_error", "simpleError", "error", "condition")` with
1490/// every `data()` field readable as `e$<name>`, so a thiserror-style error enum
1491/// can keep `?` composition *and* give R handlers something to dispatch on.
1492///
1493/// Without an impl, the `Err` arm falls back in two steps. Under the `serde`
1494/// feature an `E: serde::Serialize + Display` is classed from its serde shape
1495/// (see `serde_err_parts`: variant → member class, fields → data, `Display`
1496/// → message). Anything else is a bare `rust_error` whose message is
1497/// `format!("{e:?}")`.
1498///
1499/// ```ignore
1500/// use miniextendr_api::condition::{ConditionData, RConditionData};
1501///
1502/// #[derive(Debug, thiserror::Error)]
1503/// pub enum PkgError {
1504///     #[error("column `{column}` is missing")]
1505///     MissingColumn { column: String },
1506///     #[error("{value} exceeds {max}")]
1507///     TooLarge { value: f64, max: f64 },
1508/// }
1509///
1510/// impl RConditionData for PkgError {
1511///     fn message(&self) -> String { self.to_string() }
1512///     fn class(&self) -> Vec<String> {
1513///         let member = match self {
1514///             PkgError::MissingColumn { .. } => "pkg_error_missing_column",
1515///             PkgError::TooLarge { .. } => "pkg_error_too_large",
1516///         };
1517///         vec![member.into(), "pkg_error".into()]
1518///     }
1519///     fn data(&self) -> Option<ConditionData> {
1520///         Some(match self {
1521///             PkgError::MissingColumn { column } => vec![("column".into(), column.clone().into())],
1522///             PkgError::TooLarge { value, max } => vec![("value".into(), (*value).into()), ("max".into(), (*max).into())],
1523///         })
1524///     }
1525/// }
1526///
1527/// #[miniextendr]
1528/// pub fn check(value: f64) -> Result<f64, PkgError> { /* uses `?` freely */ }
1529/// ```
1530///
1531/// ```r
1532/// tryCatch(check(1e9), pkg_error_too_large = function(e) e$max)   # member
1533/// tryCatch(check(1e9), pkg_error = function(e) conditionMessage(e)) # family
1534/// ```
1535///
1536/// Detection is by trait, not by attribute: the generated `Err` arm probes
1537/// `E: RConditionData` first and falls back to the `Debug` rendering
1538/// otherwise, so existing `Result<T, String>` / `Result<T, MyDebugError>`
1539/// functions are unchanged. The condition's `kind` stays `"result_err"`.
1540///
1541/// The same impl classes argument-conversion failures: when a parameter
1542/// type's `TryFromSexp::Error` implements this trait, a failed conversion
1543/// raises its classes and fields with `kind = "conversion"`, the message
1544/// `'<p>' must be <expected>: <message()>` (or `invalid '<p>' argument:
1545/// <message()>` when the argument's type has no R-facing expectation), the
1546/// parameter's R name as `e$param` and the Rust type as `e$rust_type`
1547/// (unless `data()` has a field of the same name, which then wins). An error
1548/// type without the impl renders with `Display` and keeps the plain
1549/// `rust_error` class vector.
1550///
1551/// `data()` field names must not be `message`, `call` or `kind` (see
1552/// [`RESERVED_CONDITION_FIELDS`]); a reserved name raises a plain
1553/// `rust_error` explaining the clash, so rename such a field at the source.
1554///
1555/// [`call()`](RConditionData::call) returning [`ConditionCall::None`] drops
1556/// the call (`conditionCall(e)` is `NULL`), for errors and warnings about the
1557/// data rather than the call.
1558pub trait RConditionData {
1559    /// The condition message (`conditionMessage(e)`).
1560    fn message(&self) -> String;
1561    /// User classes, most specific first; empty for none.
1562    fn class(&self) -> Vec<String> {
1563        Vec::new()
1564    }
1565    /// Structured fields spliced into the condition object (`e$<name>`).
1566    fn data(&self) -> Option<ConditionData> {
1567        None
1568    }
1569    /// Whether the condition reports a call. [`ConditionCall::None`] makes
1570    /// `conditionCall()` `NULL`, like R's `call. = FALSE`, for a condition
1571    /// about the data rather than the call; the default keeps the call the
1572    /// transport captured. Honoured by `defer_*` and by `Result` returns;
1573    /// an argument-conversion error always keeps the call (see
1574    /// [`conversion_err_parts`]). `#[condition(call = none)]` on a derived
1575    /// type or variant overrides this.
1576    fn call(&self) -> ConditionCall {
1577        ConditionCall::Inherit
1578    }
1579    /// The whole message when this error refuses an argument, or `None`.
1580    ///
1581    /// When the error comes out of a parameter's conversion (the type's
1582    /// `TryFromSexp`, or its [`check_sexp`](crate::TryFromSexpElement::check_sexp)
1583    /// under `Option<T>` / `Vec<T>` / `Vec<Option<T>>`), a `Some` is the
1584    /// condition's message as given, in place of
1585    /// `'<p>' must be <expected>: <message()>`. The classes, `kind`,
1586    /// `e$param`, `e$rust_type` and the call are unchanged. This is how a type
1587    /// words a refusal once for every parameter of that type, as
1588    /// `#[miniextendr(inherits(..., message = ...))]` does for one parameter:
1589    /// both use the author's message as given and name the argument in
1590    /// `e$param`. Ignored everywhere else (`Result` returns, `defer_*`), and
1591    /// inside an `Either`, whose message names both arms. For a refused
1592    /// element of a list of objects (`Vec<T>` of a newtype over `List`), it
1593    /// is that element's reason, before the element's position, in the
1594    /// prefixed batched message: an element is not the whole argument.
1595    fn argument_message(&self) -> Option<String> {
1596        None
1597    }
1598}
1599
1600/// `#[derive(RCondition)]`: [`RConditionData`] generated from a type's shape
1601/// (`#[condition(class = "…")]`, `#[condition(message = "…")]`, the
1602/// `rename` / `skip` / `debug` field options) and `From<T>` for
1603/// [`RCondition`](enum@RCondition) with the declared kind
1604/// (`#[condition(kind = …)]`, default `error`).
1605#[doc(inline)]
1606pub use miniextendr_macros::RCondition;
1607
1608/// A ready-made classed error value for `Result<T, RError>` returns.
1609///
1610/// Carries a message, an optional class vector, structured fields and the
1611/// call choice, and implements [`RConditionData`]. Any
1612/// `std::error::Error` converts into it with `?` / [`From`] (the message keeps
1613/// the `caused by:` chain, like [`AsRError`]), after which the builder methods
1614/// add the R-facing parts ([`RError::class`], [`RError::data`],
1615/// [`RError::without_call`]):
1616///
1617/// ```ignore
1618/// use miniextendr_api::condition::RError;
1619///
1620/// #[miniextendr]
1621/// pub fn parse_port(s: &str) -> Result<i32, RError> {
1622///     let port: i32 = s
1623///         .parse()
1624///         .map_err(|e| RError::from(e).class(["pkg_bad_port", "pkg_error"]).data("input", s))?;
1625///     Ok(port)
1626/// }
1627/// ```
1628///
1629/// ```r
1630/// e <- tryCatch(parse_port("x"), pkg_bad_port = function(e) e)
1631/// e$input          # "x"
1632/// class(e)[1:2]    # "pkg_bad_port" "pkg_error"
1633/// ```
1634///
1635/// `RError` deliberately does **not** implement `std::error::Error`: that
1636/// keeps the blanket `From<E: Error>` coherent. It does implement `Display`
1637/// (the message), so it also works with `#[miniextendr(unwrap_in_r)]`.
1638#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f, "RError",
            "message", &self.message, "class", &self.class, "data",
            &self.data, "call", &self.call, "argument_message",
            &&self.argument_message)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RError {
    #[inline]
    fn clone(&self) -> Self {
        RError {
            message: ::core::clone::Clone::clone(&self.message),
            class: ::core::clone::Clone::clone(&self.class),
            data: ::core::clone::Clone::clone(&self.data),
            call: ::core::clone::Clone::clone(&self.call),
            argument_message: ::core::clone::Clone::clone(&self.argument_message),
        }
    }
}Clone)]
1639pub struct RError {
1640    message: String,
1641    class: Vec<String>,
1642    data: ConditionData,
1643    call: ConditionCall,
1644    argument_message: Option<String>,
1645}
1646
1647impl RError {
1648    /// A classless error with `message`.
1649    pub fn new(message: impl Into<String>) -> Self {
1650        RError {
1651            message: message.into(),
1652            class: Vec::new(),
1653            data: Vec::new(),
1654            call: ConditionCall::Inherit,
1655            argument_message: None,
1656        }
1657    }
1658
1659    /// Raise the condition without a call: `conditionCall(e)` is `NULL` and
1660    /// the printed condition has no `In f(...) :` prefix, like R's
1661    /// `stop(..., call. = FALSE)`. For an error or warning about the data, not
1662    /// the call. Applies to `Result` returns and the `defer_*` functions; see
1663    /// [`ConditionCall`].
1664    pub fn without_call(mut self) -> Self {
1665        self.call = ConditionCall::None;
1666        self
1667    }
1668
1669    /// Append user classes (most specific first). Accepts one string or a
1670    /// vector, see [`ConditionClass`].
1671    pub fn class(mut self, class: impl ConditionClass) -> Self {
1672        self.class.extend(class.into_condition_class());
1673        self
1674    }
1675
1676    /// Attach a structured field readable as `e$<name>`. Any value with an
1677    /// [`RValue`](crate::RValue) `From` impl works. The name must not be
1678    /// `message`, `call` or `kind`; see [`RESERVED_CONDITION_FIELDS`].
1679    pub fn data(mut self, name: impl Into<String>, value: impl Into<crate::RValue>) -> Self {
1680        self.data.push((name.into(), value.into()));
1681        self
1682    }
1683
1684    /// The whole message of the argument error when this error refuses an
1685    /// argument, used as given instead of `'<p>' must be <expected>:
1686    /// <message>`; see [`RConditionData::argument_message`]. `message` stays
1687    /// the message everywhere else.
1688    ///
1689    /// ```ignore
1690    /// fn model_object(x: SEXP) -> Result<(), RError> {
1691    ///     if x.inherits_class(c"data.frame") {
1692    ///         return Err(RError::new("got a data frame")
1693    ///             .argument_message("use model_from_df() for a data frame"));
1694    ///     }
1695    ///     Ok(())
1696    /// }
1697    /// ```
1698    pub fn argument_message(mut self, message: impl Into<String>) -> Self {
1699        self.argument_message = Some(message.into());
1700        self
1701    }
1702
1703    /// The message.
1704    pub fn message_str(&self) -> &str {
1705        &self.message
1706    }
1707
1708    /// The whole argument-error message, if one was given
1709    /// ([`RError::argument_message`]).
1710    pub fn argument_message_str(&self) -> Option<&str> {
1711        self.argument_message.as_deref()
1712    }
1713
1714    /// The user classes, most specific first.
1715    pub fn classes(&self) -> &[String] {
1716        &self.class
1717    }
1718
1719    /// The structured fields as attached (unprefixed).
1720    pub fn fields(&self) -> &ConditionData {
1721        &self.data
1722    }
1723}
1724
1725impl<E: std::error::Error> From<E> for RError {
1726    /// Message = the error's `Display` plus its `source()` chain, one
1727    /// `caused by:` line per link (same rendering as [`AsRError`]).
1728    fn from(err: E) -> Self {
1729        let mut message = err.to_string();
1730        let mut current: &dyn std::error::Error = &err;
1731        while let Some(source) = current.source() {
1732            message.push_str("\n  caused by: ");
1733            message.push_str(&source.to_string());
1734            current = source;
1735        }
1736        RError::new(message)
1737    }
1738}
1739
1740impl std::fmt::Display for RError {
1741    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1742        f.write_str(&self.message)
1743    }
1744}
1745
1746impl RConditionData for RError {
1747    fn message(&self) -> String {
1748        self.message.clone()
1749    }
1750    fn class(&self) -> Vec<String> {
1751        self.class.clone()
1752    }
1753    fn data(&self) -> Option<ConditionData> {
1754        (!self.data.is_empty()).then(|| self.data.clone())
1755    }
1756    fn call(&self) -> ConditionCall {
1757        self.call
1758    }
1759    fn argument_message(&self) -> Option<String> {
1760        self.argument_message.clone()
1761    }
1762}
1763
1764/// Any classed error (an [`RError`], a `#[derive(RCondition)]` type)
1765/// converts into a [`SexpError::Condition`](crate::from_r::SexpError::Condition)
1766/// with its message, classes, fields and call choice, so a newtype's
1767/// [`check_sexp`](crate::TryFromSexpElement::check_sexp) can refuse with it
1768/// and a conversion whose error converts into `SexpError` keeps its classes.
1769impl<E: RConditionData> From<E> for crate::from_r::SexpError {
1770    fn from(e: E) -> Self {
1771        crate::from_r::SexpError::Condition(RError {
1772            message: e.message(),
1773            class: e.class(),
1774            data: e.data().unwrap_or_default(),
1775            call: e.call(),
1776            argument_message: e.argument_message(),
1777        })
1778    }
1779}
1780
1781/// An argument error as a value: the parameter's R name and the message.
1782/// [`ArgError::raise`] raises it as the generated wrapper's own argument
1783/// error, through [`crate::arg_error!`].
1784///
1785/// [`crate::match_arg_param`] returns it for a choice argument the body
1786/// matches itself. Raise it where the argument is bad, or handle it:
1787///
1788/// ```ignore
1789/// use miniextendr_api::{SEXP, match_arg_param, miniextendr};
1790///
1791/// #[miniextendr]
1792/// pub fn prepare(data: SEXP, mode: SEXP) -> String {
1793///     if is_data_frame(data) {
1794///         let mode: Mode = match_arg_param(mode, "mode").unwrap_or_else(|e| e.raise());
1795///         // ...
1796///     }
1797///     // ...
1798/// }
1799/// ```
1800///
1801/// It does not implement [`RConditionData`]: returned as a body's `Err` it
1802/// would take the `Result` path (`kind = "result_err"`), not the argument
1803/// error's.
1804#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ArgError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "ArgError",
            "param", &self.param, "message", &&self.message)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ArgError {
    #[inline]
    fn clone(&self) -> Self {
        ArgError {
            param: ::core::clone::Clone::clone(&self.param),
            message: ::core::clone::Clone::clone(&self.message),
        }
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ArgError { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ArgError {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.param == other.param && self.message == other.message
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ArgError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<String>;
    }
}Eq)]
1805pub struct ArgError {
1806    param: String,
1807    message: String,
1808}
1809
1810impl ArgError {
1811    /// The argument error of parameter `param` (its R name) with `message`,
1812    /// used as given.
1813    pub fn new(param: impl Into<String>, message: impl Into<String>) -> Self {
1814        ArgError {
1815            param: param.into(),
1816            message: message.into(),
1817        }
1818    }
1819
1820    /// The parameter's R name (`e$param`).
1821    pub fn param(&self) -> &str {
1822        &self.param
1823    }
1824
1825    /// The message (`conditionMessage(e)`).
1826    pub fn message(&self) -> &str {
1827        &self.message
1828    }
1829
1830    /// Raise it: `arg_error!(param = <param>, "{}", <message>)`. The
1831    /// condition is the one the generated wrapper raises for that parameter;
1832    /// see [`crate::arg_error!`].
1833    pub fn raise(self) -> ! {
1834        std::panic::panic_any(RCondition::arg_error(
1835            self.param,
1836            self.message,
1837            ConditionCall::Inherit,
1838        ))
1839    }
1840}
1841
1842impl std::fmt::Display for ArgError {
1843    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1844        f.write_str(&self.message)
1845    }
1846}
1847
1848impl std::error::Error for ArgError {}
1849
1850/// The parts of a condition the generated `Err` arms hand to
1851/// [`crate::error_value::result_err_condition_value`] /
1852/// [`crate::error_value::conversion_condition_value`], and that
1853/// [`RCondition::into_parts`] splits a payload into: message, user classes,
1854/// `data` fields and the [`ConditionCall`] choice.
1855#[doc(hidden)]
1856#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ErrParts {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field4_finish(f, "ErrParts",
            "message", &self.message, "class", &self.class, "data",
            &self.data, "call", &&self.call)
    }
}Debug)]
1857pub struct ErrParts {
1858    pub message: String,
1859    pub class: Vec<String>,
1860    pub data: Option<ConditionData>,
1861    pub call: ConditionCall,
1862}
1863
1864/// Autoref-specialisation probe, first stage, preferred arm: `E: RConditionData`.
1865///
1866/// Under the `serde` feature a declared-shape stage runs before this one: a
1867/// shape that `serde_error_shape!` declared for `E` wins over the impl (see
1868/// `ErrShapeDeclared`).
1869///
1870/// The generated code calls `(&e).__mx_classed_parts()` with both first-stage
1871/// traits in scope. Method resolution tries the receiver type `&E` by value
1872/// first, which only this impl (on `E`, taking `&self`) satisfies; the
1873/// [`ErrPartsUnclassed`] arm on `&E` needs one more auto-ref and is only
1874/// reached when `E` does not implement [`RConditionData`]. It yields `None`,
1875/// and the second stage ([`ErrPartsSerde`] / [`ErrPartsDebug`]) decides.
1876///
1877/// Two two-way probes rather than one three-way probe: the `Serialize` and
1878/// `Debug` bounds both hold for `&E` whenever they hold for `E`, so a single
1879/// method name with three impls at increasing reference depth is ambiguous at
1880/// the first step. Each stage has exactly one impl on `E` and one on `&E`.
1881#[doc(hidden)]
1882pub trait ErrPartsClassed {
1883    fn __mx_classed_parts(&self) -> Option<ErrParts>;
1884}
1885
1886impl<E: RConditionData> ErrPartsClassed for E {
1887    #[track_caller]
1888    fn __mx_classed_parts(&self) -> Option<ErrParts> {
1889        Some(ErrParts {
1890            message: self.message(),
1891            class: self.class(),
1892            data: check_condition_data(self.data()),
1893            call: self.call(),
1894        })
1895    }
1896}
1897
1898/// First stage, fallback arm: no [`RConditionData`] impl.
1899#[doc(hidden)]
1900pub trait ErrPartsUnclassed {
1901    fn __mx_classed_parts(&self) -> Option<ErrParts>;
1902}
1903
1904impl<E> ErrPartsUnclassed for &E {
1905    fn __mx_classed_parts(&self) -> Option<ErrParts> {
1906        None
1907    }
1908}
1909
1910/// Second stage, preferred arm: `E: Serialize + Display` under the `serde`
1911/// feature, classed via [`serde_err_parts`] with the defaults (`kind` tag,
1912/// the `<crate>_error` prefix the macro passes in, no shapes).
1913/// `#[miniextendr(serde_error(..))]` bypasses the probe with explicit options.
1914///
1915/// The trait exists without the feature so the generated `use` resolves; it
1916/// then has no impl and every type takes the [`ErrPartsDebug`] arm.
1917#[doc(hidden)]
1918pub trait ErrPartsSerde {
1919    fn __mx_fallback_parts(&self, prefix: &str) -> ErrParts;
1920}
1921
1922#[cfg(feature = "serde")]
1923impl<E: ::serde::Serialize + std::fmt::Display> ErrPartsSerde for E {
1924    #[track_caller]
1925    fn __mx_fallback_parts(&self, prefix: &str) -> ErrParts {
1926        serde_err_parts(self, prefix, &[])
1927    }
1928}
1929
1930/// Second stage, fallback arm: any `E: Debug` (the historical
1931/// `format!("{e:?}")` rendering, no class, no data).
1932#[doc(hidden)]
1933pub trait ErrPartsDebug {
1934    fn __mx_fallback_parts(&self, prefix: &str) -> ErrParts;
1935}
1936
1937impl<E: std::fmt::Debug> ErrPartsDebug for &E {
1938    fn __mx_fallback_parts(&self, _prefix: &str) -> ErrParts {
1939        ErrParts {
1940            message: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", self))
    })format!("{self:?}"),
1941            class: Vec::new(),
1942            data: None,
1943            call: ConditionCall::Inherit,
1944        }
1945    }
1946}
1947
1948/// Internal: the `Err(e)` probe used by generated wrappers. Not public API.
1949///
1950/// `$prefix` is the family class for the serde arms (`<crate>_error`, computed
1951/// by the macro from `CARGO_CRATE_NAME` at expansion time). `$marker` is the
1952/// calling crate's marker type (`crate::__MiniextendrInitMarker`), under which
1953/// [`serde_error_shape!`](crate::serde_error_shape) declarations are looked up
1954/// first, under the `serde` feature: a declared shape wins over the stages
1955/// below (the `ErrShapeDeclared` probe).
1956#[doc(hidden)]
1957#[macro_export]
1958macro_rules! __mx_result_err_parts {
1959    ($e:expr, $prefix:expr, $marker:ty) => {{
1960        #[allow(unused_imports)]
1961        use $crate::condition::{
1962            ErrPartsClassed as _, ErrPartsDebug as _, ErrPartsSerde as _, ErrPartsUnclassed as _,
1963        };
1964        match &$e {
1965            __mx_e => match $crate::__mx_declared_err_parts!(__mx_e, $prefix, $marker) {
1966                ::core::option::Option::Some(parts) => parts,
1967                ::core::option::Option::None => match __mx_e.__mx_classed_parts() {
1968                    ::core::option::Option::Some(parts) => parts,
1969                    ::core::option::Option::None => __mx_e.__mx_fallback_parts($prefix),
1970                },
1971            },
1972        }
1973    }};
1974}
1975
1976// endregion
1977
1978// region: Classed argument-conversion errors
1979
1980/// Conversion probe, preferred arm: `E: RConditionData`.
1981///
1982/// When an argument fails its `TryFromSexp` conversion, the generated wrapper
1983/// calls `(&e).__mx_conversion_parts(expected_known)` with the three
1984/// conversion-probe traits in scope (see [`crate::__mx_conversion_err_parts!`]).
1985/// This impl, on `E` taking `&self`, matches the receiver `&E` by value, so a
1986/// `TryFromSexp::Error` that implements [`RConditionData`] contributes its
1987/// class vector, message and data. [`ConversionErrBuiltin`] matches at the
1988/// same step for the built-in conversion errors, which never implement
1989/// [`RConditionData`], so the two never compete. [`ConversionErrDisplay`]
1990/// needs one more auto-ref and is only reached otherwise.
1991///
1992/// Unlike the `Result` probe ([`crate::__mx_result_err_parts!`]) there is no
1993/// serde stage and the fallback is `Display`, not `Debug`: the generated code
1994/// has always rendered conversion errors with `Display`, which every built-in
1995/// error type (`SexpError`, `MatchArgError`, ...) implements.
1996#[doc(hidden)]
1997pub trait ConversionErrClassed {
1998    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts;
1999}
2000
2001impl<E: RConditionData> ConversionErrClassed for E {
2002    #[track_caller]
2003    fn __mx_conversion_parts(&self, _expected_known: bool) -> ErrParts {
2004        ErrParts {
2005            message: self.message(),
2006            class: self.class(),
2007            data: check_condition_data(self.data()),
2008            call: self.call(),
2009        }
2010    }
2011}
2012
2013/// Conversion probe, built-in arm: the framework's own conversion errors
2014/// ([`SexpError`](crate::from_r::SexpError) and the
2015/// [`SexpTypeError`](crate::from_r::SexpTypeError) /
2016/// [`SexpClassError`](crate::from_r::SexpClassError) /
2017/// [`SexpLengthError`](crate::from_r::SexpLengthError) /
2018/// [`SexpNaError`](crate::from_r::SexpNaError) it wraps, and
2019/// [`MatchArgError`](crate::match_arg::MatchArgError)), reworded for R
2020/// users (#1591).
2021///
2022/// Their `Display` text is written for the package author: SEXPTYPE names
2023/// (`expected INTSXP, got STRSXP`) and a variant prefix (`invalid value: ...`).
2024/// The condition message says instead what the argument should have been,
2025/// with the reason from `SexpError::r_reason` (`got character`,
2026/// `got length 2`, `NA is not allowed`, the invalid value's own text, ...)
2027/// or `MatchArgError::r_reason` (`got "zzz"`).
2028///
2029/// `expected_known` is set by the macro when it knows the argument's R-facing
2030/// expectation and puts it in the message prefix (`'x' must be a single
2031/// integer`); the reason then does not repeat it. The type names are those of
2032/// [`sexptype_name`](crate::typed_list::sexptype_name) (`numeric` for a
2033/// double, `list`, `NULL`, ...).
2034#[doc(hidden)]
2035pub trait ConversionErrBuiltin {
2036    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts;
2037}
2038
2039/// The R-facing reason for a built-in conversion error, see
2040/// [`ConversionErrBuiltin`].
2041fn builtin_reason_parts(message: String) -> ErrParts {
2042    ErrParts {
2043        message,
2044        class: Vec::new(),
2045        data: None,
2046        call: ConditionCall::Inherit,
2047    }
2048}
2049
2050impl ConversionErrBuiltin for crate::from_r::SexpTypeError {
2051    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts {
2052        builtin_reason_parts(self.r_reason(expected_known))
2053    }
2054}
2055
2056impl ConversionErrBuiltin for crate::from_r::SexpClassError {
2057    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts {
2058        builtin_reason_parts(self.r_reason(expected_known))
2059    }
2060}
2061
2062impl ConversionErrBuiltin for crate::from_r::SexpLengthError {
2063    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts {
2064        builtin_reason_parts(self.r_reason(expected_known))
2065    }
2066}
2067
2068impl ConversionErrBuiltin for crate::from_r::SexpNaError {
2069    fn __mx_conversion_parts(&self, _expected_known: bool) -> ErrParts {
2070        builtin_reason_parts("NA is not allowed".to_string())
2071    }
2072}
2073
2074/// A `SexpError` also carries the classes and fields of the check refusals
2075/// ([`SexpError::Condition`](crate::from_r::SexpError::Condition)) whose
2076/// reasons its message reports, directly or from an `Either` arm.
2077impl ConversionErrBuiltin for crate::from_r::SexpError {
2078    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts {
2079        let mut parts = builtin_reason_parts(self.r_reason(expected_known));
2080        let mut data = ConditionData::new();
2081        for refusal in self.reported_conditions() {
2082            for class in refusal.classes() {
2083                if !parts.class.contains(class) {
2084                    parts.class.push(class.clone());
2085                }
2086            }
2087            for (name, value) in refusal.fields() {
2088                if !data.iter().any(|(seen, _)| seen == name) {
2089                    data.push((name.clone(), value.clone()));
2090                }
2091            }
2092        }
2093        parts.data = check_condition_data((!data.is_empty()).then_some(data));
2094        parts
2095    }
2096}
2097
2098impl ConversionErrBuiltin for crate::match_arg::MatchArgError {
2099    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts {
2100        builtin_reason_parts(self.r_reason(expected_known))
2101    }
2102}
2103
2104/// Conversion probe, fallback arm: any `E: Display`. The message is the
2105/// error's `Display` text, with no class and no data, so the condition keeps
2106/// the plain `rust_error` class vector.
2107#[doc(hidden)]
2108pub trait ConversionErrDisplay {
2109    fn __mx_conversion_parts(&self, expected_known: bool) -> ErrParts;
2110}
2111
2112impl<E: std::fmt::Display> ConversionErrDisplay for &E {
2113    fn __mx_conversion_parts(&self, _expected_known: bool) -> ErrParts {
2114        ErrParts {
2115            message: self.to_string(),
2116            class: Vec::new(),
2117            data: None,
2118            call: ConditionCall::Inherit,
2119        }
2120    }
2121}
2122
2123/// Internal: the argument-conversion `Err(e)` probe used by generated
2124/// wrappers. Not public API. Yields the error's own [`ErrParts`];
2125/// [`crate::error_value::conversion_condition_value`] adds the parameter
2126/// context.
2127///
2128/// The second argument is `expected_known` (see [`ConversionErrBuiltin`]):
2129/// `true` when the message prefix already states what the argument should
2130/// be. It defaults to `false`.
2131#[doc(hidden)]
2132#[macro_export]
2133macro_rules! __mx_conversion_err_parts {
2134    ($e:expr) => {
2135        $crate::__mx_conversion_err_parts!($e, false)
2136    };
2137    ($e:expr, $expected_known:expr) => {{
2138        #[allow(unused_imports)]
2139        use $crate::condition::{
2140            ConversionErrBuiltin as _, ConversionErrClassed as _, ConversionErrDisplay as _,
2141        };
2142        match &$e {
2143            __mx_e => __mx_e.__mx_conversion_parts($expected_known),
2144        }
2145    }};
2146}
2147
2148/// Expectation probe, built-in arm: an error that knows what the value should
2149/// have been. A `match_arg` choice error (`MatchArgError`, or a `SexpError`
2150/// carrying one, as `#[derive(MatchArg)]`'s `TryFromSexp` returns) names its
2151/// choices: `one of "fast", "slow"`; a class error names the class
2152/// (`a data frame`); an `Either` whose arms both refused the kind of value
2153/// names both (`one of "fast", "slow", or numeric`).
2154#[doc(hidden)]
2155pub trait ConversionExpectBuiltin {
2156    fn __mx_conversion_expectation(&self) -> Option<String>;
2157}
2158
2159impl ConversionExpectBuiltin for crate::from_r::SexpError {
2160    fn __mx_conversion_expectation(&self) -> Option<String> {
2161        self.r_expectation()
2162    }
2163}
2164
2165impl ConversionExpectBuiltin for crate::match_arg::MatchArgError {
2166    fn __mx_conversion_expectation(&self) -> Option<String> {
2167        Some(self.expectation())
2168    }
2169}
2170
2171/// Expectation probe, fallback arm: any other error type says nothing about
2172/// what the value should have been.
2173#[doc(hidden)]
2174pub trait ConversionExpectNone {
2175    fn __mx_conversion_expectation(&self) -> Option<String>;
2176}
2177
2178impl<E> ConversionExpectNone for &E {
2179    fn __mx_conversion_expectation(&self) -> Option<String> {
2180        None
2181    }
2182}
2183
2184/// Internal: what a failed argument conversion's error says the value should
2185/// have been, for a parameter whose Rust type gives the macro no R-facing
2186/// expectation (#1594). `Some("one of \"fast\", \"slow\"")` for a `match_arg`
2187/// choice error, `Some("a data frame")` for a class error, the joined
2188/// expectation of an `Either` whose arms both refused the kind of value,
2189/// `None` otherwise. Not public API.
2190#[doc(hidden)]
2191#[macro_export]
2192macro_rules! __mx_conversion_expectation {
2193    ($e:expr) => {{
2194        #[allow(unused_imports)]
2195        use $crate::condition::{ConversionExpectBuiltin as _, ConversionExpectNone as _};
2196        match &$e {
2197            __mx_e => __mx_e.__mx_conversion_expectation(),
2198        }
2199    }};
2200}
2201
2202/// Whole-message probe, built-in arm: a `match_arg` choice error, as a
2203/// [`MatchArgError`](crate::match_arg::MatchArgError) or at the top of a
2204/// `SexpError` (`#[derive(MatchArg)]`'s `TryFromSexp` returns one), words the
2205/// whole argument error the way the wrapper's R-side `match_arg` check
2206/// (`.miniextendr_match_arg`) does, from the same
2207/// [`MatchArgError::arg_error`](crate::match_arg::MatchArgError::arg_error)
2208/// (#1767): `'mode' should be one of "fast", "slow"`, `'mode' must be of
2209/// length 1`, `'mode' must be NULL or a character vector`. So a `match_arg`
2210/// type converted without `#[miniextendr(match_arg)]` and one checked by the
2211/// wrapper raise the same message.
2212///
2213/// A choice error inside an `Either` is not one: the `Either` names both of
2214/// its arms (`'x' must be one of "oral", "bolus", or a single double: got
2215/// logical`).
2216#[doc(hidden)]
2217pub trait ConversionArgMessageBuiltin {
2218    fn __mx_conversion_arg_message(&self, name: &str) -> Option<String>;
2219}
2220
2221impl ConversionArgMessageBuiltin for crate::match_arg::MatchArgError {
2222    fn __mx_conversion_arg_message(&self, name: &str) -> Option<String> {
2223        Some(self.arg_error(name).message().to_string())
2224    }
2225}
2226
2227impl ConversionArgMessageBuiltin for crate::from_r::SexpError {
2228    fn __mx_conversion_arg_message(&self, name: &str) -> Option<String> {
2229        match self {
2230            crate::from_r::SexpError::MatchArg(e) => Some(e.arg_error(name).message().to_string()),
2231            // A type's check refusal (`check_sexp` under `Option<T>` /
2232            // `Vec<T>`, or a conversion whose error converted into
2233            // `SexpError`) with its own whole message (#1833).
2234            crate::from_r::SexpError::Condition(e) => e.argument_message_str().map(str::to_string),
2235            _ => None,
2236        }
2237    }
2238}
2239
2240/// Whole-message probe, classed arm: any [`RConditionData`] answers with its
2241/// [`argument_message`](RConditionData::argument_message), so a type's own
2242/// conversion error can word the whole argument error (#1833). Same autoref
2243/// level as [`ConversionArgMessageBuiltin`], like [`ConversionErrClassed`]:
2244/// no built-in error type implements [`RConditionData`].
2245#[doc(hidden)]
2246pub trait ConversionArgMessageClassed {
2247    fn __mx_conversion_arg_message(&self, name: &str) -> Option<String>;
2248}
2249
2250impl<E: RConditionData> ConversionArgMessageClassed for E {
2251    fn __mx_conversion_arg_message(&self, _name: &str) -> Option<String> {
2252        self.argument_message()
2253    }
2254}
2255
2256/// Whole-message probe, fallback arm: any other error leaves the message to
2257/// the prefix and the error's reason.
2258#[doc(hidden)]
2259pub trait ConversionArgMessageNone {
2260    fn __mx_conversion_arg_message(&self, name: &str) -> Option<String>;
2261}
2262
2263impl<E> ConversionArgMessageNone for &E {
2264    fn __mx_conversion_arg_message(&self, _name: &str) -> Option<String> {
2265        None
2266    }
2267}
2268
2269/// Internal: the whole message of a failed argument conversion whose error
2270/// words it itself (a `match_arg` choice error, see
2271/// [`ConversionArgMessageBuiltin`], or an error with an
2272/// [`argument_message`](RConditionData::argument_message), see
2273/// [`ConversionArgMessageClassed`]), for the argument `$name`; `None`
2274/// otherwise. [`crate::error_value::conversion_condition_value`] then uses it
2275/// in place of `<prefix>: <reason>`. Not public API.
2276#[doc(hidden)]
2277#[macro_export]
2278macro_rules! __mx_conversion_arg_message {
2279    ($e:expr, $name:expr) => {{
2280        #[allow(unused_imports)]
2281        use $crate::condition::{
2282            ConversionArgMessageBuiltin as _, ConversionArgMessageClassed as _,
2283            ConversionArgMessageNone as _,
2284        };
2285        match &$e {
2286            __mx_e => __mx_e.__mx_conversion_arg_message($name),
2287        }
2288    }};
2289}
2290
2291/// The message prefix of a conversion failure whose expectation comes from
2292/// the error at run time (see [`crate::__mx_conversion_expectation!`]):
2293/// `'<p>' must be <expected>` (`'<p>' must be NULL or <expected>` for an
2294/// `Option<_>` parameter, `nullable`), else `invalid '<p>' argument`.
2295#[doc(hidden)]
2296pub fn conversion_prefix(param: &str, nullable: bool, expected: Option<&str>) -> String {
2297    match expected {
2298        Some(expected) if nullable => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\'{0}\' must be NULL or {1}",
                param, expected))
    })format!("'{param}' must be NULL or {expected}"),
2299        Some(expected) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\'{0}\' must be {1}", param,
                expected))
    })format!("'{param}' must be {expected}"),
2300        None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid \'{0}\' argument", param))
    })format!("invalid '{param}' argument"),
2301    }
2302}
2303
2304/// The name of the structured field that carries the failing parameter's R
2305/// name on every argument-conversion condition (`e$param`).
2306const CONVERSION_PARAM_FIELD: &str = "param";
2307
2308/// The name of the structured field that carries the Rust type an argument
2309/// failed to convert to (`e$rust_type`, #1591).
2310const CONVERSION_RUST_TYPE_FIELD: &str = "rust_type";
2311
2312/// Combine an argument-conversion error's own parts with the wrapper's
2313/// parameter context.
2314///
2315/// - The message becomes `<prefix>: <error message>`. For an argument the
2316///   macro writes the prefix in R terms (#1591): `'<p>' must be <expected>`
2317///   when it knows what the argument should be (`'x' must be a single
2318///   integer`, `'dv' must be numeric`), `invalid '<p>' argument` otherwise.
2319///   A sidecar setter passes its own prefix. An error that words the whole
2320///   argument error itself, a `match_arg` choice error (#1767) or one with an
2321///   [`argument_message`](RConditionData::argument_message) (#1833), gives
2322///   `arg_message` ([`crate::__mx_conversion_arg_message!`]), which is the
2323///   message instead.
2324/// - The class vector is the error's own (empty for the built-in and
2325///   `Display` arms), followed by `crate_class`, the crate's
2326///   `conversion_error_class` from `[package.metadata.miniextendr]`, minus
2327///   any class the error already names. The R helper appends the
2328///   `rust_error` layering after both.
2329/// - `param` (the parameter's R name) is added as the first data field,
2330///   `e$param`, and `rust_type` (the Rust type as written in the signature,
2331///   when given) after it, `e$rust_type`. Neither is added when the error's
2332///   own data already has a field of that name: the type author's value is
2333///   kept, since the framework never overwrites a field the type author
2334///   chose, and R's `e$name` reads only the first of two same-named fields.
2335/// - The call is always the wrapper's ([`ConditionCall::Inherit`]), whatever
2336///   the error type's [`RConditionData::call`] says: the R-side checks
2337///   (`.miniextendr_arg_error`) raise the same condition with the call
2338///   (#1591), and an argument error must not differ by which side caught it.
2339#[doc(hidden)]
2340pub fn conversion_err_parts(
2341    prefix: &str,
2342    param: &str,
2343    rust_type: Option<&str>,
2344    crate_class: &[&str],
2345    parts: ErrParts,
2346    arg_message: Option<String>,
2347) -> ErrParts {
2348    let ErrParts {
2349        message,
2350        mut class,
2351        data,
2352        call: _,
2353    } = parts;
2354    let message = arg_message.unwrap_or_else(|| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}: {1}", prefix, message))
    })format!("{prefix}: {message}"));
2355    for extra in crate_class {
2356        if !class.iter().any(|c| c == extra) {
2357            class.push((*extra).to_string());
2358        }
2359    }
2360    let mut fields = data.unwrap_or_default();
2361    let has_field =
2362        |fields: &ConditionData, field: &str| fields.iter().any(|(name, _)| name == field);
2363    let mut at = 0;
2364    if !has_field(&fields, CONVERSION_PARAM_FIELD) {
2365        fields.insert(0, (CONVERSION_PARAM_FIELD.to_string(), param.into()));
2366        at = 1;
2367    }
2368    if let Some(rust_type) = rust_type
2369        && !has_field(&fields, CONVERSION_RUST_TYPE_FIELD)
2370    {
2371        fields.insert(
2372            at,
2373            (CONVERSION_RUST_TYPE_FIELD.to_string(), rust_type.into()),
2374        );
2375    }
2376    ErrParts {
2377        message,
2378        class,
2379        data: Some(fields),
2380        call: ConditionCall::Inherit,
2381    }
2382}
2383
2384/// The parts of an argument check that failed in Rust after the conversion:
2385/// `#[miniextendr(no_na)]` on a reading marker (`AsNumeric*`,
2386/// `AsCharacter*`) whose converted value holds an `NA` that R's `anyNA()`
2387/// did not see (the hidden `TryFromSexp::__mx_has_na` says so), or on an
2388/// `Either`, which has no R guard, an `NA` the arm taken refuses
2389/// (`TryFromSexp::__mx_input_has_na`).
2390///
2391/// The shape is the R guard's (`.miniextendr_arg_error`): `message` as given
2392/// (the macro writes `'<p>' must not be NA` or the author's own message),
2393/// the classes `crate_class` (the crate's `conversion_error_class`), and
2394/// `param` as the only data field, `e$param`. No `rust_type`: the R guard
2395/// has none, and the two paths raise one condition.
2396#[doc(hidden)]
2397pub fn arg_check_parts(message: &str, param: &str, crate_class: &[&str]) -> ErrParts {
2398    ErrParts {
2399        message: message.to_string(),
2400        class: crate_class.iter().map(|c| (*c).to_string()).collect(),
2401        data: Some(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(CONVERSION_PARAM_FIELD.to_string(), param.into())]))vec![(CONVERSION_PARAM_FIELD.to_string(), param.into())]),
2402        call: ConditionCall::Inherit,
2403    }
2404}
2405
2406// endregion
2407
2408// region: SerdeErrorShape — `serde_error` options as a shared const (#2207)
2409
2410/// The options of `#[miniextendr(serde_error(..))]` as a value: declared once,
2411/// shared by every function that returns one error type.
2412///
2413/// ```
2414/// use miniextendr_api::condition::SerdeErrorShape;
2415///
2416/// pub const ENGINE: SerdeErrorShape = SerdeErrorShape::new()
2417///     .prefix("engine")
2418///     .skip(&["message"])
2419///     .as_character(&["rule", "column"])
2420///     .as_integer(&["n"]);
2421/// ```
2422///
2423/// Declare it for the error type with
2424/// [`serde_error_shape!`](crate::serde_error_shape), and every
2425/// `#[miniextendr]` function or method of the crate that returns that type
2426/// uses it, without naming it:
2427///
2428/// ```ignore
2429/// miniextendr_api::serde_error_shape!(EngineError = ENGINE);
2430///
2431/// #[miniextendr]
2432/// pub fn check(x: f64) -> Result<f64, EngineError> { /* ... */ }
2433/// ```
2434///
2435/// A function can override it for itself: with options of its own, or with
2436/// another shape named by path, which applies after the declared one:
2437///
2438/// ```ignore
2439/// #[miniextendr(serde_error(as_double("n")))]
2440/// pub fn rate(x: f64) -> Result<f64, EngineError> { /* ... */ }
2441///
2442/// #[miniextendr(serde_error(shape = STRICT, as_double("n")))]
2443/// pub fn strict_rate(x: f64) -> Result<f64, EngineError> { /* ... */ }
2444/// ```
2445///
2446/// The path resolves where the function is defined (`STRICT`,
2447/// `crate::errors::STRICT`, or `Self::SHAPE` on a method for an associated
2448/// const of its type), so a misspelt one is a compile error. The options act
2449/// on the serde-classed `Err` arm, which needs the `serde` feature.
2450///
2451/// # Options
2452///
2453/// One method per `serde_error` option. Fields are named as the error
2454/// serializes them, before any `rename`:
2455///
2456/// - [`tag`](Self::tag): the internally tagged discriminator field consumed
2457///   as the variant; `"kind"` when unset.
2458/// - [`prefix`](Self::prefix): the family class; `<crate>_error`, from the
2459///   function's crate, when unset.
2460/// - [`skip`](Self::skip): fields to drop.
2461/// - [`rename`](Self::rename): `(from, to)` pairs; field `from` reaches R as
2462///   `e$to`.
2463/// - [`as_character`](Self::as_character), [`as_integer`](Self::as_integer),
2464///   [`as_double`](Self::as_double), [`as_logical`](Self::as_logical): the
2465///   field's usual serialization as one vector of that R type, as the
2466///   `miniextendr_api::serde::as_character` & co. field helpers give it from
2467///   the error type's own `#[serde(serialize_with = ..)]`: `character(0)` for
2468///   an empty sequence, `NA` for a `None` element. A value that doesn't fit
2469///   (a string under `as_integer`) raises an R error naming the field and the
2470///   option.
2471///
2472/// A field that a variant doesn't carry is a no-op for that variant, so one
2473/// shape covers an enum whose variants differ.
2474///
2475/// # Precedence
2476///
2477/// Shapes apply in order: the one declared for the error type, then
2478/// `serde_error(shape = ..)`, then the function's own options; for a field
2479/// set by more than one, the later wins. A later `skip` drops a field an
2480/// earlier shape types or renames, and a later `as_*` or `rename` keeps a
2481/// field an earlier shape skips. A type and a new name compose: the
2482/// function's `rename(n = "count")` keeps the shape's `as_integer(&["n"])`.
2483/// A later `tag` or `prefix` replaces an earlier one.
2484///
2485/// # Contradictions don't compile
2486///
2487/// Each method checks its fields against the options already set and panics
2488/// during const evaluation, so a `const` holding a contradictory shape is a
2489/// compile error (E0080) naming the field:
2490///
2491/// - a field in two `as_*` lists;
2492/// - a field skipped and also typed or renamed;
2493/// - a field listed twice, renamed twice, or two renames onto one name;
2494/// - a `rename` target that is one of the condition's own slots (`message`,
2495///   `call`, `kind`);
2496/// - an empty list or name, and an option set twice.
2497///
2498/// ```compile_fail,E0080
2499/// use miniextendr_api::condition::SerdeErrorShape;
2500///
2501/// const ENGINE: SerdeErrorShape = SerdeErrorShape::new()
2502///     .as_integer(&["n"])
2503///     .as_double(&["n"]);
2504/// ```
2505#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SerdeErrorShape { }
#[automatically_derived]
impl ::core::clone::Clone for SerdeErrorShape {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<Option<&'static str>>;
        let _: ::core::clone::AssertParamIsClone<Option<&'static str>>;
        let _: ::core::clone::AssertParamIsClone<&'static [&'static str]>;
        let _:
                ::core::clone::AssertParamIsClone<&'static [(&'static str,
                &'static str)]>;
        let _:
                ::core::clone::AssertParamIsClone<[&'static [&'static str]; 4]>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SerdeErrorShape { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for SerdeErrorShape {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f,
            "SerdeErrorShape", "tag", &self.tag, "prefix", &self.prefix,
            "skip", &self.skip, "rename", &self.rename, "types", &&self.types)
    }
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SerdeErrorShape { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SerdeErrorShape {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        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 SerdeErrorShape {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Option<&'static str>>;
        let _: ::core::cmp::AssertParamIsEq<Option<&'static str>>;
        let _: ::core::cmp::AssertParamIsEq<&'static [&'static str]>;
        let _:
                ::core::cmp::AssertParamIsEq<&'static [(&'static str,
                &'static str)]>;
        let _: ::core::cmp::AssertParamIsEq<[&'static [&'static str]; 4]>;
    }
}Eq)]
2506pub struct SerdeErrorShape {
2507    tag: Option<&'static str>,
2508    prefix: Option<&'static str>,
2509    skip: &'static [&'static str],
2510    rename: &'static [(&'static str, &'static str)],
2511    /// The `as_*` lists, in [`SHAPE_TYPE_OPTIONS`] order.
2512    types: [&'static [&'static str]; 4],
2513}
2514
2515/// The `as_*` options, in the order of [`SerdeErrorShape`]'s type lists.
2516const SHAPE_TYPE_OPTIONS: [&str; 4] = ["as_logical", "as_integer", "as_double", "as_character"];
2517
2518impl SerdeErrorShape {
2519    /// The discriminator field when no shape names one.
2520    #[cfg(feature = "serde")]
2521    const DEFAULT_TAG: &'static str = "kind";
2522
2523    /// A shape that sets nothing.
2524    pub const fn new() -> Self {
2525        SerdeErrorShape {
2526            tag: None,
2527            prefix: None,
2528            skip: &[],
2529            rename: &[],
2530            types: [&[]; 4],
2531        }
2532    }
2533
2534    /// The internally tagged discriminator field (`#[serde(tag = "...")]`),
2535    /// consumed as the variant.
2536    #[track_caller]
2537    pub const fn tag(mut self, tag: &'static str) -> Self {
2538        self.tag = Some(scalar_option("tag", self.tag, tag));
2539        self
2540    }
2541
2542    /// The family class: conditions get `c("<prefix>_<variant>", "<prefix>")`.
2543    #[track_caller]
2544    pub const fn prefix(mut self, prefix: &'static str) -> Self {
2545        self.prefix = Some(scalar_option("prefix", self.prefix, prefix));
2546        self
2547    }
2548
2549    /// Drop these payload fields.
2550    #[track_caller]
2551    pub const fn skip(mut self, fields: &'static [&'static str]) -> Self {
2552        check_field_list("skip", self.skip.is_empty(), fields);
2553        let mut i = 0;
2554        while i < fields.len() {
2555            let name = fields[i];
2556            if let Some(option) = self.type_option(name) {
2557                conflict(name, option, "skip", "a skipped field has no R type");
2558            }
2559            if self.renames(name) {
2560                conflict(name, "rename", "skip", "a skipped field has no name");
2561            }
2562            i += 1;
2563        }
2564        self.skip = fields;
2565        self
2566    }
2567
2568    /// Splice field `from` as `to`, for each `(from, to)` pair.
2569    #[track_caller]
2570    pub const fn rename(mut self, pairs: &'static [(&'static str, &'static str)]) -> Self {
2571        if !self.rename.is_empty() {
2572            set_twice("rename");
2573        }
2574        if pairs.is_empty() {
2575            fail(ShapeMessage::new().push("`rename` needs at least one pair"));
2576        }
2577        let mut i = 0;
2578        while i < pairs.len() {
2579            let (from, to) = pairs[i];
2580            if from.is_empty() || to.is_empty() {
2581                fail(ShapeMessage::new().push("`rename` has an empty name"));
2582            }
2583            if is_reserved_condition_field(to) {
2584                fail(ShapeMessage::new().push("`rename` target `").push(to).push(
2585                    "` is reserved: `message`, `call` and `kind` are the condition's own slots",
2586                ));
2587            }
2588            let mut j = 0;
2589            while j < i {
2590                if str_eq(pairs[j].0, from) {
2591                    fail(
2592                        ShapeMessage::new()
2593                            .push("field `")
2594                            .push(from)
2595                            .push("` is renamed twice"),
2596                    );
2597                }
2598                if str_eq(pairs[j].1, to) {
2599                    fail(
2600                        ShapeMessage::new()
2601                            .push("two `rename` pairs target `")
2602                            .push(to)
2603                            .push("`; R would read only the first"),
2604                    );
2605                }
2606                j += 1;
2607            }
2608            if const_contains(self.skip, from) {
2609                conflict(from, "skip", "rename", "a skipped field has no name");
2610            }
2611            i += 1;
2612        }
2613        self.rename = pairs;
2614        self
2615    }
2616
2617    /// Give these fields an R `logical` vector.
2618    #[track_caller]
2619    pub const fn as_logical(self, fields: &'static [&'static str]) -> Self {
2620        self.with_type(0, fields)
2621    }
2622
2623    /// Give these fields an R `integer` vector; a value outside R's integer
2624    /// range is an error, not a double.
2625    #[track_caller]
2626    pub const fn as_integer(self, fields: &'static [&'static str]) -> Self {
2627        self.with_type(1, fields)
2628    }
2629
2630    /// Give these fields an R `double` vector; integers widen.
2631    #[track_caller]
2632    pub const fn as_double(self, fields: &'static [&'static str]) -> Self {
2633        self.with_type(2, fields)
2634    }
2635
2636    /// Give these fields an R `character` vector.
2637    #[track_caller]
2638    pub const fn as_character(self, fields: &'static [&'static str]) -> Self {
2639        self.with_type(3, fields)
2640    }
2641
2642    #[track_caller]
2643    const fn with_type(mut self, index: usize, fields: &'static [&'static str]) -> Self {
2644        let option = SHAPE_TYPE_OPTIONS[index];
2645        check_field_list(option, self.types[index].is_empty(), fields);
2646        let mut i = 0;
2647        while i < fields.len() {
2648            let name = fields[i];
2649            if const_contains(self.skip, name) {
2650                conflict(name, "skip", option, "a skipped field has no R type");
2651            }
2652            if let Some(other) = self.type_option(name) {
2653                conflict(name, other, option, "a field has one R type");
2654            }
2655            i += 1;
2656        }
2657        self.types[index] = fields;
2658        self
2659    }
2660
2661    /// The `as_*` option that lists `name`.
2662    const fn type_option(&self, name: &str) -> Option<&'static str> {
2663        let mut i = 0;
2664        while i < self.types.len() {
2665            if const_contains(self.types[i], name) {
2666                return Some(SHAPE_TYPE_OPTIONS[i]);
2667            }
2668            i += 1;
2669        }
2670        None
2671    }
2672
2673    /// Whether a `rename` pair renames `name`.
2674    const fn renames(&self, name: &str) -> bool {
2675        let mut i = 0;
2676        while i < self.rename.len() {
2677            if str_eq(self.rename[i].0, name) {
2678                return true;
2679            }
2680            i += 1;
2681        }
2682        false
2683    }
2684}
2685
2686impl Default for SerdeErrorShape {
2687    fn default() -> Self {
2688        Self::new()
2689    }
2690}
2691
2692/// `tag` / `prefix`: set once, never empty.
2693#[track_caller]
2694const fn scalar_option(
2695    option: &str,
2696    current: Option<&'static str>,
2697    value: &'static str,
2698) -> &'static str {
2699    if current.is_some() {
2700        set_twice(option);
2701    }
2702    if value.is_empty() {
2703        fail(
2704            ShapeMessage::new()
2705                .push("`")
2706                .push(option)
2707                .push("` must not be empty"),
2708        );
2709    }
2710    value
2711}
2712
2713/// A field list: set once, not empty, each name non-empty and listed once.
2714#[track_caller]
2715const fn check_field_list(option: &str, unset: bool, fields: &[&str]) {
2716    if !unset {
2717        set_twice(option);
2718    }
2719    if fields.is_empty() {
2720        fail(
2721            ShapeMessage::new()
2722                .push("`")
2723                .push(option)
2724                .push("` needs at least one field"),
2725        );
2726    }
2727    let mut i = 0;
2728    while i < fields.len() {
2729        if fields[i].is_empty() {
2730            fail(
2731                ShapeMessage::new()
2732                    .push("`")
2733                    .push(option)
2734                    .push("` has an empty field name"),
2735            );
2736        }
2737        let mut j = 0;
2738        while j < i {
2739            if str_eq(fields[j], fields[i]) {
2740                fail(
2741                    ShapeMessage::new()
2742                        .push("field `")
2743                        .push(fields[i])
2744                        .push("` is listed twice in `")
2745                        .push(option)
2746                        .push("`"),
2747                );
2748            }
2749            j += 1;
2750        }
2751        i += 1;
2752    }
2753}
2754
2755#[track_caller]
2756const fn set_twice(option: &str) -> ! {
2757    fail(
2758        ShapeMessage::new()
2759            .push("`")
2760            .push(option)
2761            .push("` is set twice; set each option once"),
2762    )
2763}
2764
2765#[track_caller]
2766const fn conflict(name: &str, first: &str, second: &str, why: &str) -> ! {
2767    fail(
2768        ShapeMessage::new()
2769            .push("field `")
2770            .push(name)
2771            .push("` is in both `")
2772            .push(first)
2773            .push("` and `")
2774            .push(second)
2775            .push("`; ")
2776            .push(why),
2777    )
2778}
2779
2780const fn const_contains(list: &[&str], name: &str) -> bool {
2781    let mut i = 0;
2782    while i < list.len() {
2783        if str_eq(list[i], name) {
2784            return true;
2785        }
2786        i += 1;
2787    }
2788    false
2789}
2790
2791/// A const-evaluation panic message. `const fn` can't `format!`, so the parts
2792/// are copied into a fixed buffer.
2793struct ShapeMessage {
2794    buf: [u8; 256],
2795    len: usize,
2796}
2797
2798impl ShapeMessage {
2799    const fn new() -> Self {
2800        ShapeMessage {
2801            buf: [0; 256],
2802            len: 0,
2803        }
2804        .push("SerdeErrorShape: ")
2805    }
2806
2807    const fn push(mut self, text: &str) -> Self {
2808        let bytes = text.as_bytes();
2809        let mut i = 0;
2810        while i < bytes.len() && self.len < self.buf.len() {
2811            self.buf[self.len] = bytes[i];
2812            self.len += 1;
2813            i += 1;
2814        }
2815        self
2816    }
2817}
2818
2819/// Panic with `message`. `#[track_caller]` here and on every caller puts the
2820/// compile error at the user's `const`, not in this crate.
2821#[track_caller]
2822const fn fail(message: ShapeMessage) -> ! {
2823    let (text, _) = message.buf.split_at(message.len);
2824    match core::str::from_utf8(text) {
2825        Ok(text) => { ::core::panicking::panic_display(&text); }panic!("{}", text),
2826        // The buffer ended inside a long field name's character.
2827        Err(_) => {
2828            {
    ::core::panicking::panic_fmt(format_args!("SerdeErrorShape: contradictory options; a field name is too long to quote"));
}panic!("SerdeErrorShape: contradictory options; a field name is too long to quote")
2829        }
2830    }
2831}
2832
2833// endregion
2834
2835// region: Serde-tagged Result errors (#[miniextendr(serde_error)])
2836
2837/// Build the `Err`-arm parts from the error's serde shape.
2838///
2839/// Reached three ways: for a `Result<T, E>` whose `E` has a shape declared
2840/// by [`serde_error_shape!`](crate::serde_error_shape) (the
2841/// [`ErrShapeDeclared`] probe arm, that one shape); automatically, for every
2842/// other `E` that is `Serialize + Display` without an [`RConditionData`] impl
2843/// (the [`ErrPartsSerde`] probe arm, no shapes); or explicitly through
2844/// `#[miniextendr(serde_error(..))]`, whose options arrive as `shapes`: the
2845/// declared shape first (one that sets nothing when there is none), then the
2846/// named `shape = ..`, then the function's own options as an inline const,
2847/// each applied in order (see [`SerdeErrorShape`], "Precedence").
2848/// `prefix` is the macro's `<crate>_error`, used when no shape sets one.
2849///
2850/// Message from `Display`; classes `[<prefix>_<variant>, <prefix>]` when the
2851/// serialized value carries a variant, `[<prefix>]` otherwise; data from the
2852/// serialized fields (checked against the reserved names). `tag` names the
2853/// internally-tagged discriminator field (`#[serde(tag = "kind")]`), which is
2854/// consumed as the member class rather than spliced as data; externally tagged
2855/// enums (serde's default) yield the variant name directly. See
2856/// `docs/CONDITIONS.md`, "Classed `Result` errors".
2857///
2858/// Field control, applied to the payload fields before the reserved-name
2859/// check (`serde_error(skip(..), rename(..), as_*(..))`, #1457, #2207):
2860///
2861/// - `skip`: fields with these names are dropped.
2862/// - `as_character` / `as_integer` / `as_double` / `as_logical`: the field's
2863///   value becomes one vector of that type, through the same conversion as
2864///   the `serde::as_*` field helpers; a value that doesn't fit panics, naming
2865///   the field and the option.
2866/// - `rename`: `(from, to)` pairs; a field named `from` is spliced as `to`.
2867/// - A remaining field named `message` whose value is the single string equal
2868///   to the `Display` output is dropped: it duplicates the condition's own
2869///   `message` slot, so a wrapped parser error of the shape
2870///   `Variant { message: String }` reaches R without any option.
2871///
2872/// Names that a given variant does not carry are a no-op for that variant.
2873/// Any other collision with `message`, `call` or `kind` still panics via
2874/// [`check_condition_data`].
2875///
2876/// After `skip` / `rename` the field names must be distinct: a `rename` whose
2877/// target is a name the variant already carries (or two payload fields that
2878/// serialize under one name) would give the condition two entries with the
2879/// same name, of which R's `e$name` reads only the first. That panics too,
2880/// naming the field and the rename that produced it (#1459).
2881///
2882/// Serialization failing (a map with non-string keys, a custom `Serialize`
2883/// erroring) panics with both failures in the message: the original error
2884/// text must not be lost behind the reporting problem.
2885#[cfg(feature = "serde")]
2886#[doc(hidden)]
2887#[track_caller]
2888pub fn serde_err_parts<E: ?Sized + ::serde::Serialize + std::fmt::Display>(
2889    e: &E,
2890    prefix: &str,
2891    shapes: &[&SerdeErrorShape],
2892) -> ErrParts {
2893    let message = e.to_string();
2894    let tag = shapes
2895        .iter()
2896        .rev()
2897        .find_map(|s| s.tag)
2898        .unwrap_or(SerdeErrorShape::DEFAULT_TAG);
2899    let prefix = shapes.iter().rev().find_map(|s| s.prefix).unwrap_or(prefix);
2900    let (member, fields) = match crate::serde::rvalue_ser::tagged_parts(e, tag) {
2901        Ok(parts) => parts,
2902        Err(err) => panic!(
2903            "#[miniextendr(serde_error)]: serializing `{}` for its condition data failed ({err}); \
2904             the original error was: {message}",
2905            std::any::type_name::<E>()
2906        ),
2907    };
2908    let mut class = Vec::with_capacity(2);
2909    if let Some(member) = member {
2910        class.push(format!("{prefix}_{member}"));
2911    }
2912    class.push(prefix.to_string());
2913    let mut renames = Vec::new();
2914    let mut data: ConditionData = Vec::with_capacity(fields.len());
2915    for (name, value) in fields {
2916        let Some(field) = FieldShape::resolve(shapes, &name) else {
2917            continue;
2918        };
2919        let value = match field.atomic {
2920            None => value,
2921            Some(atomic) => match atomic.try_coerce(value) {
2922                Ok(value) => value,
2923                Err(element) => panic!(
2924                    "#[miniextendr(serde_error)]: `{}` on condition data field `{name}` failed: \
2925                     {}; the original error was: {message}",
2926                    atomic.helper(),
2927                    atomic.mismatch(element)
2928                ),
2929            },
2930        };
2931        if let Some((from, to)) = field.rename {
2932            renames.push((from, to));
2933            data.push((to.to_string(), value));
2934            continue;
2935        }
2936        let echoes_display = name == "message"
2937            && matches!(&value, crate::RValue::Character(v)
2938                if v.len() == 1 && v[0].as_deref() == Some(message.as_str()));
2939        if !echoes_display {
2940            data.push((name, value));
2941        }
2942    }
2943    check_distinct_field_names(&data, &renames);
2944    ErrParts {
2945        message,
2946        class,
2947        data: check_condition_data((!data.is_empty()).then_some(data)),
2948        call: ConditionCall::Inherit,
2949    }
2950}
2951
2952/// What the shapes, applied in order, give one payload field.
2953#[cfg(feature = "serde")]
2954#[derive(Default)]
2955struct FieldShape {
2956    atomic: Option<crate::serde::rvalue_ser::Atomic>,
2957    rename: Option<(&'static str, &'static str)>,
2958}
2959
2960#[cfg(feature = "serde")]
2961impl FieldShape {
2962    /// `None` when the last shape that names the field skips it. A later
2963    /// `skip` drops what earlier shapes set; a later type or rename replaces
2964    /// that setting and keeps the field.
2965    fn resolve(shapes: &[&SerdeErrorShape], name: &str) -> Option<Self> {
2966        let mut skipped = false;
2967        let mut field = FieldShape::default();
2968        for shape in shapes {
2969            if shape.skip.contains(&name) {
2970                skipped = true;
2971                field = FieldShape::default();
2972            }
2973            if let Some(i) = shape.types.iter().position(|list| list.contains(&name)) {
2974                skipped = false;
2975                field.atomic = Some(crate::serde::rvalue_ser::Atomic::ALL[i]);
2976            }
2977            if let Some(&pair) = shape.rename.iter().find(|(from, _)| *from == name) {
2978                skipped = false;
2979                field.rename = Some(pair);
2980            }
2981        }
2982        (!skipped).then_some(field)
2983    }
2984}
2985
2986/// Panic when two payload fields share a name after `skip` / `rename`.
2987///
2988/// R's `e$<name>` returns the first entry with that name and the second is
2989/// unreachable, which is the silent-overwrite failure the reserved-name rule
2990/// exists to remove (#1440). Neither first-wins nor last-wins would report it.
2991#[cfg(feature = "serde")]
2992#[track_caller]
2993fn check_distinct_field_names(fields: &[(String, crate::RValue)], rename: &[(&str, &str)]) {
2994    let mut seen = std::collections::HashSet::with_capacity(fields.len());
2995    for (name, _) in fields {
2996        if seen.insert(name.as_str()) {
2997            continue;
2998        }
2999        let renames: Vec<String> = rename
3000            .iter()
3001            .filter(|(_, to)| *to == name)
3002            .map(|(from, to)| format!("`rename({from} = \"{to}\")`"))
3003            .collect();
3004        if renames.is_empty() {
3005            panic!(
3006                "#[miniextendr(serde_error)]: condition data field `{name}` appears twice in the \
3007                 serialized payload; R's `e${name}` would read only the first. Give the fields \
3008                 distinct names or `skip` one of them."
3009            );
3010        }
3011        panic!(
3012            "#[miniextendr(serde_error)]: condition data field `{name}` appears twice: {} targets \
3013             a name this variant already carries; R's `e${name}` would read only the first. Pick \
3014             another target or `skip` the existing field.",
3015            renames.join(" and ")
3016        );
3017    }
3018}
3019
3020// endregion
3021
3022// region: Declared shapes — one SerdeErrorShape per error type (#2207)
3023
3024/// The [`SerdeErrorShape`] declared for the error type `Self` by the crate
3025/// whose marker type is `M`. Implemented by
3026/// [`serde_error_shape!`](crate::serde_error_shape), not by hand.
3027///
3028/// `M` is a type local to the declaring crate (`miniextendr_init!` defines it
3029/// at the crate root). That is what lets an R-facing crate declare a shape
3030/// for an error type from a crate that doesn't depend on miniextendr: the
3031/// orphan rule accepts `impl ForeignTrait<LocalType> for ForeignType`. It also
3032/// scopes the declaration to the crate: another crate returning the same type
3033/// looks up its own declaration.
3034///
3035/// The supertraits make a declaration for a type that can't be serialized, or
3036/// has no `Display` for the condition message, an error at the declaration.
3037#[cfg(feature = "serde")]
3038pub trait SerdeErrorShapeFor<M>: ::serde::Serialize + std::fmt::Display {
3039    /// The declared shape.
3040    const SHAPE: &'static SerdeErrorShape;
3041}
3042
3043/// Declare the [`SerdeErrorShape`] of an error type once, for every
3044/// `#[miniextendr]` function and method of this crate that returns it.
3045///
3046/// ```ignore
3047/// use miniextendr_api::condition::SerdeErrorShape;
3048///
3049/// pub const ENGINE: SerdeErrorShape = SerdeErrorShape::new()
3050///     .prefix("engine")
3051///     .as_character(&["rule"])
3052///     .as_integer(&["n"]);
3053///
3054/// miniextendr_api::serde_error_shape!(engine::Error = ENGINE);
3055///
3056/// // Both raise with ENGINE's field types; neither names it.
3057/// #[miniextendr]
3058/// pub fn check(x: f64) -> Result<f64, engine::Error> { /* ... */ }
3059///
3060/// #[miniextendr]
3061/// pub fn rate(x: f64) -> Result<f64, engine::Error> { /* ... */ }
3062/// ```
3063///
3064/// The right-hand side is any constant expression of type
3065/// `SerdeErrorShape`: a path, as above, or the builder chain itself. The type
3066/// must be `Serialize + Display` and may come from any crate, including one
3067/// that can't depend on miniextendr. The declaration goes anywhere in the
3068/// crate that calls `miniextendr_init!`, which defines the marker type it is
3069/// keyed by (see [`SerdeErrorShapeFor`]); it applies to that crate's
3070/// functions only.
3071///
3072/// # Precedence
3073///
3074/// Every `Err` of the type, from a function with or without
3075/// `#[miniextendr(serde_error(..))]`, is classed through serde with the
3076/// declared shape applied first. A function's `serde_error(shape = X)` applies
3077/// next and its own options last; for a field set more than once, the later
3078/// setting wins, by the rules under [`SerdeErrorShape`], "Precedence".
3079///
3080/// The declaration also wins over an [`RConditionData`] impl of the type: it is
3081/// the R-facing crate's choice for its own functions, where the impl is the
3082/// error crate's general one.
3083///
3084/// # One declaration per type
3085///
3086/// A second declaration for a type in the same crate is the compiler's
3087/// conflicting-implementations error:
3088///
3089/// ```compile_fail,E0119
3090/// use miniextendr_api::condition::SerdeErrorShape;
3091///
3092/// miniextendr_api::miniextendr_init!(pkg);
3093///
3094/// const A: SerdeErrorShape = SerdeErrorShape::new().prefix("a");
3095/// const B: SerdeErrorShape = SerdeErrorShape::new().prefix("b");
3096///
3097/// miniextendr_api::serde_error_shape!(String = A);
3098/// miniextendr_api::serde_error_shape!(String = B);
3099///
3100/// fn main() {}
3101/// ```
3102#[cfg(feature = "serde")]
3103#[macro_export]
3104// `crate::` is meant: the marker is the calling crate's.
3105#[allow(clippy::crate_in_macro_def)]
3106macro_rules! serde_error_shape {
3107    ($error:ty = $shape:expr $(,)?) => {
3108        impl $crate::condition::SerdeErrorShapeFor<crate::__MiniextendrInitMarker> for $error {
3109            const SHAPE: &'static $crate::condition::SerdeErrorShape = &$shape;
3110        }
3111    };
3112}
3113
3114/// Declare the `SerdeErrorShape` of an error type once, for every
3115/// `#[miniextendr]` function and method of this crate that returns it. Needs
3116/// the `serde` feature, without which a declaration is a compile error.
3117#[cfg(not(feature = "serde"))]
3118#[macro_export]
3119macro_rules! serde_error_shape {
3120    ($($input:tt)*) => {
3121        ::core::compile_error!(
3122            "`serde_error_shape!` needs the `serde` feature of miniextendr-api; \
3123             enable it to class errors through serde"
3124        );
3125    };
3126}
3127
3128/// The receiver of the declared-shape probe: the error, and the marker `M` of
3129/// the crate whose declaration to look up.
3130#[cfg(feature = "serde")]
3131#[doc(hidden)]
3132pub struct DeclaredShapeProbe<'a, M, E>(pub &'a E, pub std::marker::PhantomData<fn() -> M>);
3133
3134/// Declared-shape probe, preferred arm: `E` has a shape declared under `M`.
3135///
3136/// The generated code calls `(&probe).__mx_declared_parts(..)` with both
3137/// traits in scope. This impl, on the probe taking `&self`, matches the
3138/// receiver `&DeclaredShapeProbe` by value; [`ErrShapeUndeclared`] on
3139/// `&DeclaredShapeProbe` needs one more auto-ref and is only reached when
3140/// nothing is declared.
3141#[cfg(feature = "serde")]
3142#[doc(hidden)]
3143pub trait ErrShapeDeclared {
3144    /// The parts under the declared shape, for an `Err` arm without
3145    /// `serde_error(..)`. The call to [`serde_err_parts`] lives here, where
3146    /// `E: Serialize + Display` holds, so an undeclared `E` needn't be.
3147    fn __mx_declared_parts(&self, prefix: &str) -> Option<ErrParts>;
3148    /// The declared shape, first in `serde_error(..)`'s shape list.
3149    fn __mx_declared_shape(&self) -> &'static SerdeErrorShape;
3150}
3151
3152#[cfg(feature = "serde")]
3153impl<M, E: SerdeErrorShapeFor<M>> ErrShapeDeclared for DeclaredShapeProbe<'_, M, E> {
3154    #[track_caller]
3155    fn __mx_declared_parts(&self, prefix: &str) -> Option<ErrParts> {
3156        Some(serde_err_parts(self.0, prefix, &[E::SHAPE]))
3157    }
3158
3159    fn __mx_declared_shape(&self) -> &'static SerdeErrorShape {
3160        E::SHAPE
3161    }
3162}
3163
3164/// Declared-shape probe, fallback arm: no declaration. No parts, and a shape
3165/// that sets nothing.
3166#[cfg(feature = "serde")]
3167#[doc(hidden)]
3168pub trait ErrShapeUndeclared {
3169    fn __mx_declared_parts(&self, prefix: &str) -> Option<ErrParts>;
3170    fn __mx_declared_shape(&self) -> &'static SerdeErrorShape;
3171}
3172
3173#[cfg(feature = "serde")]
3174impl<M, E> ErrShapeUndeclared for &DeclaredShapeProbe<'_, M, E> {
3175    fn __mx_declared_parts(&self, _prefix: &str) -> Option<ErrParts> {
3176        None
3177    }
3178
3179    fn __mx_declared_shape(&self) -> &'static SerdeErrorShape {
3180        const NONE: &SerdeErrorShape = &SerdeErrorShape::new();
3181        NONE
3182    }
3183}
3184
3185/// Internal: the declared-shape stage of [`crate::__mx_result_err_parts!`].
3186/// Without the `serde` feature it is `None` and `$marker` is never resolved,
3187/// so a crate without `miniextendr_init!` (a test crate) still compiles.
3188#[cfg(feature = "serde")]
3189#[doc(hidden)]
3190#[macro_export]
3191macro_rules! __mx_declared_err_parts {
3192    ($e:ident, $prefix:expr, $marker:ty) => {{
3193        #[allow(unused_imports)]
3194        use $crate::condition::{ErrShapeDeclared as _, ErrShapeUndeclared as _};
3195        (&$crate::condition::DeclaredShapeProbe::<$marker, _>($e, ::core::marker::PhantomData))
3196            .__mx_declared_parts($prefix)
3197    }};
3198}
3199
3200/// Internal: the declared-shape stage of [`crate::__mx_result_err_parts!`];
3201/// nothing can be declared without the `serde` feature.
3202#[cfg(not(feature = "serde"))]
3203#[doc(hidden)]
3204#[macro_export]
3205macro_rules! __mx_declared_err_parts {
3206    ($e:ident, $prefix:expr, $marker:ty) => {
3207        ::core::option::Option::None::<$crate::condition::ErrParts>
3208    };
3209}
3210
3211/// Internal: the shape `serde_error_shape!` declared for the type of `$e`
3212/// under `$marker`, or one that sets nothing. It leads the shape list of a
3213/// `#[miniextendr(serde_error(..))]` `Err` arm.
3214#[cfg(feature = "serde")]
3215#[doc(hidden)]
3216#[macro_export]
3217macro_rules! __mx_declared_serde_shape {
3218    ($e:expr, $marker:ty) => {{
3219        #[allow(unused_imports)]
3220        use $crate::condition::{ErrShapeDeclared as _, ErrShapeUndeclared as _};
3221        (&$crate::condition::DeclaredShapeProbe::<$marker, _>(&$e, ::core::marker::PhantomData))
3222            .__mx_declared_shape()
3223    }};
3224}
3225
3226/// Internal: without the `serde` feature, a shape that sets nothing.
3227/// `serde_error(..)` needs the feature anyway: its `serde_err_parts` call
3228/// reports that, and `$marker` is never resolved.
3229#[cfg(not(feature = "serde"))]
3230#[doc(hidden)]
3231#[macro_export]
3232macro_rules! __mx_declared_serde_shape {
3233    ($e:expr, $marker:ty) => {{
3234        const NONE: &$crate::condition::SerdeErrorShape =
3235            &$crate::condition::SerdeErrorShape::new();
3236        NONE
3237    }};
3238}
3239
3240// endregion
3241
3242// region: from_tagged_sexp + repanic_if_rust_error — shim re-panic helpers
3243
3244impl RCondition {
3245    /// Reconstruct an [`RCondition::Error`] from a tagged SEXP produced by
3246    /// [`crate::error_value::make_rust_condition_value`].
3247    ///
3248    /// Returns `Some(RCondition)` when `sexp` has class `"rust_condition_value"` AND
3249    /// the `"__rust_condition__"` attribute is `TRUE`. Returns `None` for all other
3250    /// SEXPs (normal return values, `R_NilValue`, etc.).
3251    ///
3252    /// Reconstructs the matching variant for each kind: `"error"`/`"panic"`/
3253    /// `"result_err"`/`"none_err"`/`"other_rust_error"` →
3254    /// [`RCondition::Error`] (class, message, data and a [`ConditionCall::None`]
3255    /// choice kept; the kind itself is not carried by the variant);
3256    /// `"conversion"` → [`RCondition::Conversion`], so an argument error keeps
3257    /// `kind = "conversion"` across a package boundary (and an
3258    /// [`crate::arg_error!`] its class marker, which the consumer's wrapper
3259    /// resolves); `"warning"` → [`RCondition::Warning`]; `"message"` →
3260    /// [`RCondition::Message`]; `"condition"` → [`RCondition::Condition`].
3261    /// Unknown kinds degrade to [`RCondition::Error`] with the kind string
3262    /// prefixed to the message.
3263    ///
3264    /// # Safety
3265    ///
3266    /// Must be called from R's main thread.
3267    pub unsafe fn from_tagged_sexp(sexp: crate::SEXP) -> Option<Self> {
3268        use crate::SexpExt;
3269        use crate::from_r::TryFromSexp;
3270
3271        // Use SexpExt::inherits_class — wraps Rf_inherits, already main-thread.
3272        if !sexp.inherits_class(c"rust_condition_value") {
3273            return None;
3274        }
3275
3276        // Belt-and-suspenders PROTECT across the full inspection window. The reads
3277        // below are nominally non-allocating, but R-devel's GC is aggressive enough
3278        // (see MEMORY.md "Common gotchas") that a defensive guard is cheap and
3279        // closes the door on subtle regressions if the read path ever changes.
3280        let _guard = unsafe { crate::gc_protect::OwnedProtect::new(sexp) };
3281
3282        // Verify the __rust_condition__ marker attribute is TRUE (a length-1 LGLSXP
3283        // with value 1). This guards against coincidental class attribute collisions.
3284        let attr_sym = crate::cached_class::rust_condition_attr_symbol();
3285        let marker = sexp.get_attr(attr_sym);
3286        // marker should be a scalar logical TRUE: is_logical() and logical_elt(0) == 1
3287        if !marker.is_logical() || marker.logical_elt(0) != 1 {
3288            return None;
3289        }
3290
3291        // It's a tagged SEXP. Read the elements.
3292        // Both 3-element (legacy) and 4-element (condition) forms have:
3293        //   [0] = error message (STRSXP)
3294        //   [1] = kind string (STRSXP)
3295        //   [2] = class name or NULL (only in 4-element form; absent in legacy)
3296
3297        let len = sexp.len();
3298
3299        // Defense-in-depth: a tagged SEXP must have at least the message and kind
3300        // slots. inherits_class + __rust_condition__ marker should already imply this,
3301        // but a corrupted/spoofed SEXP that satisfies both checks shouldn't OOB
3302        // the vector_elt reads below.
3303        if len < 2 {
3304            return None;
3305        }
3306
3307        let msg_sexp = sexp.vector_elt(0);
3308        let msg: String = msg_sexp
3309            .string_elt_str(0)
3310            .unwrap_or("<invalid error message>")
3311            .to_string();
3312
3313        let kind_sexp = sexp.vector_elt(1);
3314        let kind: &str = kind_sexp
3315            .string_elt_str(0)
3316            .unwrap_or(crate::error_value::kind::PANIC);
3317
3318        // Class slot is element [2] in the 4-element form (NULL in legacy form):
3319        // a STRSXP of any length (the user class vector, family last).
3320        let class: Vec<String> = if len >= 4 {
3321            let class_sexp = sexp.vector_elt(2);
3322            if class_sexp.is_nil() || !class_sexp.is_character() {
3323                Vec::new()
3324            } else {
3325                (0..class_sexp.len() as isize)
3326                    .filter_map(|i| class_sexp.string_elt_str(i).map(str::to_string))
3327                    .collect()
3328            }
3329        } else {
3330            Vec::new()
3331        };
3332
3333        use crate::error_value::kind as kind_const;
3334
3335        // Slot [4] is the optional named-list condition data, present when `len >= 5`.
3336        //
3337        // Each field value is decoded through the single SEXP→owned-tree walker,
3338        // [`RValue::try_from_sexp`], so structured fields survive the cross-package
3339        // trait-ABI re-panic path (`repanic_if_rust_error`): the consumer's outer
3340        // `with_r_unwind_protect` guard rebuilds the tagged SEXP from the
3341        // reconstructed `RCondition`, which now carries the data — so `e$field_name`
3342        // is accessible in R handlers even when the error crossed a package boundary.
3343        //
3344        // `RValue` is NA-aware (logical/integer/character carry `None`; double
3345        // carries the `NA_REAL` bit), so NA-bearing fields now round-trip faithfully
3346        // rather than being dropped. Fields whose name is missing/empty, or whose
3347        // value is not R data (closures, environments, …, which `try_from_sexp`
3348        // rejects) are dropped — safe degradation that preserves message/class/kind.
3349        //
3350        // All reads here are non-allocating copies into owned Rust values, so no new
3351        // SEXPs are created and the existing `_guard` OwnedProtect suffices.
3352        let data: Option<ConditionData> = if len >= 5 {
3353            let data_sexp = sexp.vector_elt(4);
3354            if data_sexp.is_nil() || !data_sexp.is_list() {
3355                None
3356            } else {
3357                let data_len = data_sexp.len();
3358                let names_sexp = data_sexp.get_names();
3359                let mut fields: ConditionData = Vec::with_capacity(data_len);
3360                for i in 0..data_len as isize {
3361                    // Read the field name from the names attribute. If missing/empty, skip.
3362                    let name: String = if names_sexp.is_nil() || !names_sexp.is_character() {
3363                        continue;
3364                    } else {
3365                        match names_sexp.string_elt_str(i) {
3366                            Some(s) if !s.is_empty() => s.to_string(),
3367                            _ => continue,
3368                        }
3369                    };
3370                    if let Ok(value) = crate::RValue::try_from_sexp(data_sexp.vector_elt(i)) {
3371                        fields.push((name, value));
3372                    }
3373                }
3374                if fields.is_empty() {
3375                    None
3376                } else {
3377                    Some(fields)
3378                }
3379            }
3380        } else {
3381            None
3382        };
3383
3384        // Slot [3] is the call. Only the "no call" marker (`FALSE`, see
3385        // `make_rust_condition_value_with_data`) survives the re-panic: a call
3386        // object captured by the producer names the vtable shim's frame, and the
3387        // consumer's outer guard attributes the condition to its own call.
3388        let call = if len >= 4 && is_no_call_marker(sexp.vector_elt(3)) {
3389            ConditionCall::None
3390        } else {
3391            ConditionCall::Inherit
3392        };
3393
3394        let cond = match kind {
3395            kind_const::ERROR
3396            | kind_const::PANIC
3397            | kind_const::RESULT_ERR
3398            | kind_const::NONE_ERR
3399            | kind_const::OTHER_RUST_ERROR => RCondition::Error {
3400                message: msg,
3401                class,
3402                data,
3403                call,
3404            },
3405            kind_const::CONVERSION => RCondition::Conversion {
3406                message: msg,
3407                class,
3408                data,
3409                call,
3410            },
3411            kind_const::WARNING => RCondition::Warning {
3412                message: msg,
3413                class,
3414                data,
3415                call,
3416            },
3417            kind_const::MESSAGE => RCondition::Message {
3418                message: msg,
3419                class,
3420                data,
3421                call,
3422            },
3423            kind_const::CONDITION => RCondition::Condition {
3424                message: msg,
3425                class,
3426                data,
3427                call,
3428            },
3429            other => {
3430                // Unknown kind — degrade to error
3431                RCondition::Error {
3432                    message: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("[{0}] {1}", other, msg))
    })format!("[{other}] {msg}"),
3433                    class,
3434                    data,
3435                    call,
3436                }
3437            }
3438        };
3439        Some(cond)
3440    }
3441}
3442
3443/// `true` for the tagged value's "no call" marker in the `call` slot: a
3444/// length-1 logical `FALSE` ([`ConditionCall::None`]). A call object or `NULL`
3445/// is not the marker.
3446fn is_no_call_marker(call: crate::SEXP) -> bool {
3447    use crate::SexpExt;
3448    call.is_logical() && call.len() == 1 && call.logical_elt(0) == 0
3449}
3450
3451/// Inspect a SEXP returned by a trait-ABI vtable shim and, if it is a tagged
3452/// error value, re-panic with the reconstructed [`enum@RCondition`].
3453///
3454/// This is the "re-panic at the View boundary" step of Approach 1 from the
3455/// issue-345 plan. The caller (a generated View method wrapper) does:
3456///
3457/// ```ignore
3458/// let result = { vtable_call };
3459/// ::miniextendr_api::trait_abi::repanic_if_rust_error(result);
3460/// // ... convert result normally if we reach here
3461/// ```
3462///
3463/// When `sexp` is a tagged error value:
3464/// - `RCondition::Error` / `RCondition::Warning` / etc. → `panic_any!(cond)`.
3465///   The outer `with_r_unwind_protect` in the consumer's C entry point will
3466///   catch this and produce a tagged SEXP for the consumer's R wrapper.
3467///
3468/// When `sexp` is a normal value: this is a no-op.
3469///
3470/// # Safety
3471///
3472/// Must be called from R's main thread. `sexp` must be a valid (possibly
3473/// tagged) SEXP.
3474pub unsafe fn repanic_if_rust_error(sexp: crate::SEXP) {
3475    if let Some(cond) = unsafe { RCondition::from_tagged_sexp(sexp) } {
3476        std::panic::panic_any(cond);
3477    }
3478}
3479
3480// endregion
3481
3482// region: AsRError struct — wraps std::error::Error for Result returns
3483
3484/// Structured error wrapper that preserves the `std::error::Error` cause chain.
3485///
3486/// When displayed, formats the error message with its full source chain:
3487/// ```text
3488/// top-level message
3489///   caused by: middle error
3490///   caused by: root cause
3491/// ```
3492///
3493/// Implements `From<E>` so it works with `?` and `.map_err(AsRError)`.
3494///
3495/// # Example
3496///
3497/// ```ignore
3498/// use miniextendr_api::condition::AsRError;
3499/// use std::num::ParseIntError;
3500///
3501/// #[miniextendr]
3502/// fn parse_number(s: &str) -> Result<i32, AsRError<ParseIntError>> {
3503///     s.parse::<i32>().map_err(AsRError)
3504/// }
3505/// ```
3506pub struct AsRError<E: std::error::Error>(pub E);
3507
3508impl<E: std::error::Error> From<E> for AsRError<E> {
3509    #[inline]
3510    fn from(err: E) -> Self {
3511        AsRError(err)
3512    }
3513}
3514
3515impl<E: std::error::Error> std::fmt::Display for AsRError<E> {
3516    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3517        // Write the top-level message
3518        f.write_fmt(format_args!("{0}", self.0))write!(f, "{}", self.0)?;
3519
3520        // Walk the cause chain
3521        let mut current: &dyn std::error::Error = &self.0;
3522        while let Some(source) = current.source() {
3523            f.write_fmt(format_args!("\n  caused by: {0}", source))write!(f, "\n  caused by: {source}")?;
3524            current = source;
3525        }
3526
3527        Ok(())
3528    }
3529}
3530
3531impl<E: std::error::Error> std::fmt::Debug for AsRError<E> {
3532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3533        f.write_fmt(format_args!("AsRError<{0}>({1})", std::any::type_name::<E>(),
        self))write!(f, "AsRError<{}>({})", std::any::type_name::<E>(), self)
3534    }
3535}
3536
3537impl<E: std::error::Error> AsRError<E> {
3538    /// Get the inner error.
3539    #[inline]
3540    pub fn into_inner(self) -> E {
3541        self.0
3542    }
3543
3544    /// Get the Rust type name of the wrapped error (for programmatic matching).
3545    #[inline]
3546    pub fn rust_type_name(&self) -> &'static str {
3547        std::any::type_name::<E>()
3548    }
3549
3550    /// Collect the full cause chain as a `Vec<String>`.
3551    pub fn cause_chain(&self) -> Vec<String> {
3552        let mut chain = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [self.0.to_string()]))vec![self.0.to_string()];
3553        let mut current: &dyn std::error::Error = &self.0;
3554        while let Some(source) = current.source() {
3555            chain.push(source.to_string());
3556            current = source;
3557        }
3558        chain
3559    }
3560}
3561
3562// endregion
3563
3564// region: Tests — macro grammar + payload contents (no R runtime needed)
3565
3566#[cfg(test)]
3567mod condition_macro_tests {
3568    use super::{ConditionData, RCondition};
3569    use crate::RValue;
3570
3571    /// The marker type of a crate that declares no `serde_error_shape!`, for
3572    /// the `Err` probe's marker argument (resolved only under `serde`).
3573    #[cfg(feature = "serde")]
3574    enum NoShapes {}
3575
3576    /// Catch the `panic_any(RCondition)` raised by a macro invocation and
3577    /// return the payload. No R runtime needed — the macros panic before any
3578    /// R API call.
3579    fn catch(f: impl FnOnce() + std::panic::UnwindSafe) -> RCondition {
3580        let payload = std::panic::catch_unwind(f).expect_err("macro must panic");
3581        *payload
3582            .downcast::<RCondition>()
3583            .expect("payload must be RCondition")
3584    }
3585
3586    fn assert_data(data: &Option<ConditionData>, expected: &[(&str, RValue)]) {
3587        let data = data.as_ref().expect("data must be Some");
3588        assert_eq!(data.len(), expected.len());
3589        for ((name, value), (exp_name, exp_value)) in data.iter().zip(expected) {
3590            assert_eq!(name, exp_name);
3591            // RValue has no PartialEq (f64); compare via Debug.
3592            assert_eq!(format!("{value:?}"), format!("{exp_value:?}"));
3593        }
3594    }
3595
3596    /// `catch_condition` returns every condition kind as `Err` with its
3597    /// classes and data, and the value on success.
3598    #[test]
3599    fn catch_condition_returns_each_kind() {
3600        use super::catch_condition;
3601        use crate::error_value::kind;
3602        assert_eq!(catch_condition(|| 3).unwrap(), 3);
3603        let error = catch_condition::<()>(|| {
3604            crate::error!(
3605                class = ["member", "family"],
3606                data = ("n", 2),
3607                "failed {}",
3608                1
3609            )
3610        })
3611        .unwrap_err();
3612        let (error_kind, parts) = error.into_parts();
3613        assert_eq!(error_kind, kind::ERROR);
3614        assert_eq!(parts.message, "failed 1");
3615        assert_eq!(parts.class, ["member", "family"]);
3616        assert_data(&parts.data, &[("n", RValue::from(2))]);
3617        let kinds = [
3618            catch_condition::<()>(|| crate::warning!("w")),
3619            catch_condition::<()>(|| crate::message!("m")),
3620            catch_condition::<()>(|| crate::condition!("c")),
3621            catch_condition::<()>(|| {
3622                RCondition::arg_error("x", "bad".to_string(), super::ConditionCall::Inherit).raise()
3623            }),
3624        ]
3625        .map(|caught| caught.unwrap_err().into_parts().0);
3626        assert_eq!(
3627            kinds,
3628            [
3629                kind::WARNING,
3630                kind::MESSAGE,
3631                kind::CONDITION,
3632                kind::CONVERSION
3633            ]
3634        );
3635    }
3636
3637    /// Any other payload, a plain `panic!()` included, resumes unchanged.
3638    #[test]
3639    fn catch_condition_resumes_other_panics() {
3640        use super::catch_condition;
3641        struct Foreign(u8);
3642        let payload = std::panic::catch_unwind(|| catch_condition::<()>(|| panic!("plain")))
3643            .expect_err("a plain panic must resume");
3644        assert_eq!(payload.downcast_ref::<&str>(), Some(&"plain"));
3645        let payload = std::panic::catch_unwind(|| {
3646            catch_condition::<()>(|| std::panic::panic_any(Foreign(9)))
3647        })
3648        .expect_err("a foreign payload must resume");
3649        assert_eq!(payload.downcast_ref::<Foreign>().map(|f| f.0), Some(9));
3650    }
3651
3652    /// `arg_error!` (#1740): the conversion kind, the crate-class marker as
3653    /// the only class, `param` as the only field, and the call choice.
3654    #[test]
3655    fn arg_error_payload() {
3656        use super::{CONVERSION_ERROR_CLASS_MARKER, ConditionCall};
3657        let choices = r#""fast", "slow""#;
3658        let check = |cond: RCondition, call: ConditionCall| {
3659            let RCondition::Conversion {
3660                message,
3661                class,
3662                data,
3663                call: got_call,
3664            } = cond
3665            else {
3666                panic!("expected RCondition::Conversion, got {cond:?}");
3667            };
3668            assert_eq!(message, r#"'mode' should be one of "fast", "slow""#);
3669            assert_eq!(class, [CONVERSION_ERROR_CLASS_MARKER]);
3670            assert_data(&data, &[("param", RValue::from("mode"))]);
3671            assert_eq!(got_call, call);
3672        };
3673        check(
3674            catch(|| crate::arg_error!(param = "mode", "'mode' should be one of {choices}")),
3675            ConditionCall::Inherit,
3676        );
3677        let param = String::from("mode");
3678        check(
3679            catch(move || {
3680                crate::arg_error!(
3681                    call = none,
3682                    param = param,
3683                    "'mode' should be one of {}",
3684                    choices
3685                )
3686            }),
3687            ConditionCall::None,
3688        );
3689        check(
3690            catch(|| {
3691                super::ArgError::new("mode", format!("'mode' should be one of {choices}")).raise()
3692            }),
3693            ConditionCall::Inherit,
3694        );
3695
3696        let (kind, parts) = catch(|| crate::arg_error!(param = "x", "bad")).into_parts();
3697        assert_eq!(kind, crate::error_value::kind::CONVERSION);
3698        assert_eq!(parts.message, "bad");
3699    }
3700
3701    /// A raising guard resolves the marker in Rust: the crate's classes take
3702    /// its place, every other class keeps its position, and with no crate
3703    /// classes the marker just goes.
3704    #[test]
3705    fn raising_guard_resolves_the_conversion_class_marker() {
3706        use super::CONVERSION_ERROR_CLASS_MARKER as MARKER;
3707        use super::resolve_conversion_error_class_marker as resolve;
3708        let class = || vec!["a".to_string(), MARKER.to_string(), "b".to_string()];
3709        assert_eq!(
3710            resolve(class(), &["pkg_error_argument", "pkg_error"]),
3711            ["a", "pkg_error_argument", "pkg_error", "b"]
3712        );
3713        assert_eq!(resolve(class(), &[]), ["a", "b"]);
3714        assert_eq!(
3715            resolve(vec![MARKER.to_string()], &["pkg_error"]),
3716            ["pkg_error"]
3717        );
3718        assert_eq!(resolve(vec!["a".to_string()], &["pkg_error"]), ["a"]);
3719    }
3720
3721    #[test]
3722    fn error_class_vector_and_keyed_data() {
3723        let cond = catch(|| {
3724            crate::error!(
3725                class = ["member", "family"],
3726                data = { rule = 1, detail = "inner" },
3727                "layered"
3728            )
3729        });
3730        match cond {
3731            RCondition::Error {
3732                message,
3733                class,
3734                data,
3735                call: super::ConditionCall::Inherit,
3736            } => {
3737                assert_eq!(message, "layered");
3738                assert_eq!(class, vec!["member", "family"]);
3739                assert_data(
3740                    &data,
3741                    &[
3742                        ("rule", RValue::Integer(vec![Some(1)])),
3743                        ("detail", RValue::Character(vec![Some("inner".to_string())])),
3744                    ],
3745                );
3746            }
3747            other => panic!("wrong variant: {other:?}"),
3748        }
3749    }
3750
3751    #[test]
3752    fn error_class_vector_from_vec_string() {
3753        let classes = vec!["a".to_string(), "b".to_string()];
3754        let cond = catch(move || crate::warning!(class = classes, "w"));
3755        match cond {
3756            RCondition::Warning { class, .. } => assert_eq!(class, vec!["a", "b"]),
3757            other => panic!("wrong variant: {other:?}"),
3758        }
3759    }
3760
3761    #[test]
3762    fn reserved_field_name_runtime_check_panics() {
3763        let name = String::from("kind");
3764        let payload = std::panic::catch_unwind(move || crate::error!(data = (name, 1), "x"))
3765            .expect_err("must panic");
3766        let msg = payload
3767            .downcast_ref::<String>()
3768            .cloned()
3769            .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
3770            .expect("plain panic message");
3771        assert!(msg.contains("reserved"), "got: {msg}");
3772        assert!(msg.contains("rename"), "got: {msg}");
3773    }
3774
3775    #[cfg(feature = "serde")]
3776    mod serde_err_parts {
3777        use super::super::{SerdeErrorShape, serde_err_parts};
3778        use crate::RValue;
3779        use ::serde::Serialize;
3780
3781        #[derive(Serialize)]
3782        #[serde(tag = "kind", rename_all = "snake_case")]
3783        enum Tagged {
3784            MissingField { field: String },
3785            Io,
3786        }
3787
3788        impl std::fmt::Display for Tagged {
3789            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3790                match self {
3791                    Tagged::MissingField { field } => write!(f, "field `{field}` is missing"),
3792                    Tagged::Io => f.write_str("io"),
3793                }
3794            }
3795        }
3796
3797        #[derive(Serialize)]
3798        struct Plain {
3799            code: i32,
3800        }
3801
3802        impl std::fmt::Display for Plain {
3803            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3804                write!(f, "plain {}", self.code)
3805            }
3806        }
3807
3808        #[derive(Serialize)]
3809        enum Clash {
3810            Bad { kind: String },
3811        }
3812
3813        impl std::fmt::Display for Clash {
3814            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3815                f.write_str("clash")
3816            }
3817        }
3818
3819        #[test]
3820        fn internally_tagged_variant_becomes_member_class_and_fields_become_data() {
3821            let parts =
3822                serde_err_parts(&Tagged::MissingField { field: "id".into() }, "engine", &[]);
3823            assert_eq!(parts.message, "field `id` is missing");
3824            assert_eq!(parts.class, ["engine_missing_field", "engine"]);
3825            let data = parts.data.expect("fields become data");
3826            assert_eq!(data.len(), 1);
3827            assert_eq!(data[0].0, "field");
3828            assert!(
3829                matches!(&data[0].1, RValue::Character(v) if v == &vec![Some("id".to_string())])
3830            );
3831        }
3832
3833        #[test]
3834        fn unit_variant_has_classes_but_no_data() {
3835            let parts = serde_err_parts(&Tagged::Io, "engine", &[]);
3836            assert_eq!(parts.class, ["engine_io", "engine"]);
3837            assert!(parts.data.is_none());
3838        }
3839
3840        #[test]
3841        fn value_without_variant_gets_only_the_family_class() {
3842            let parts = serde_err_parts(&Plain { code: 3 }, "engine", &[]);
3843            assert_eq!(parts.message, "plain 3");
3844            assert_eq!(parts.class, ["engine"]);
3845            let data = parts.data.expect("struct fields become data");
3846            assert_eq!(data[0].0, "code");
3847        }
3848
3849        #[test]
3850        fn reserved_payload_field_is_rejected() {
3851            let err = std::panic::AssertUnwindSafe(Clash::Bad { kind: "x".into() });
3852            let payload = std::panic::catch_unwind(move || {
3853                serde_err_parts(&err.0, "p", &[&SerdeErrorShape::new().tag("type")])
3854            })
3855            .expect_err("must panic");
3856            let msg = payload
3857                .downcast_ref::<String>()
3858                .cloned()
3859                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
3860                .unwrap_or_default();
3861            assert!(msg.contains("reserved"), "{msg}");
3862            assert!(msg.contains("`kind`"), "{msg}");
3863        }
3864
3865        /// The wrapped-parser-error shape (#1457): a `message` payload field.
3866        #[derive(Serialize)]
3867        #[serde(tag = "kind", rename_all = "snake_case")]
3868        enum Wrapped {
3869            /// `Display` adds context, so `message` is not redundant.
3870            Parse {
3871                message: String,
3872                line: u32,
3873            },
3874            /// `Display` is the field verbatim.
3875            Echo {
3876                message: String,
3877            },
3878            Bad {
3879                call: String,
3880            },
3881        }
3882
3883        impl std::fmt::Display for Wrapped {
3884            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3885                match self {
3886                    Wrapped::Parse { message, line } => write!(f, "line {line}: {message}"),
3887                    Wrapped::Echo { message } => f.write_str(message),
3888                    Wrapped::Bad { .. } => f.write_str("bad"),
3889                }
3890            }
3891        }
3892
3893        fn panic_message(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
3894            let payload = std::panic::catch_unwind(f).expect_err("must panic");
3895            payload
3896                .downcast_ref::<String>()
3897                .cloned()
3898                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
3899                .unwrap_or_default()
3900        }
3901
3902        fn names(parts: &super::super::ErrParts) -> Vec<&str> {
3903            parts
3904                .data
3905                .as_deref()
3906                .unwrap_or_default()
3907                .iter()
3908                .map(|(n, _)| n.as_str())
3909                .collect()
3910        }
3911
3912        #[test]
3913        fn message_field_equal_to_display_is_dropped_without_options() {
3914            let parts = serde_err_parts(
3915                &Wrapped::Echo {
3916                    message: "boom".into(),
3917                },
3918                "p",
3919                &[],
3920            );
3921            assert_eq!(parts.message, "boom");
3922            assert_eq!(parts.class, ["p_echo", "p"]);
3923            assert!(parts.data.is_none(), "{:?}", parts.data);
3924        }
3925
3926        #[test]
3927        fn message_field_differing_from_display_is_still_rejected() {
3928            let err = std::panic::AssertUnwindSafe(Wrapped::Parse {
3929                message: "unexpected token".into(),
3930                line: 3,
3931            });
3932            let msg = panic_message(move || {
3933                serde_err_parts(&err.0, "p", &[]);
3934            });
3935            assert!(msg.contains("reserved"), "{msg}");
3936            assert!(msg.contains("`message`"), "{msg}");
3937        }
3938
3939        #[test]
3940        fn skip_drops_the_named_field_and_is_a_no_op_elsewhere() {
3941            let parts = serde_err_parts(
3942                &Wrapped::Parse {
3943                    message: "unexpected token".into(),
3944                    line: 3,
3945                },
3946                "p",
3947                &[&SerdeErrorShape::new().skip(&["message", "absent"])],
3948            );
3949            assert_eq!(parts.message, "line 3: unexpected token");
3950            assert_eq!(parts.class, ["p_parse", "p"]);
3951            assert_eq!(names(&parts), ["line"]);
3952
3953            // `skip` runs before the Display-equality rule; same outcome.
3954            let parts = serde_err_parts(
3955                &Wrapped::Echo {
3956                    message: "boom".into(),
3957                },
3958                "p",
3959                &[&SerdeErrorShape::new().skip(&["message"])],
3960            );
3961            assert!(parts.data.is_none());
3962
3963            // A variant without the skipped field but with another reserved
3964            // name still fails the reserved-name check.
3965            let err = std::panic::AssertUnwindSafe(Wrapped::Bad { call: "f()".into() });
3966            let msg = panic_message(move || {
3967                serde_err_parts(&err.0, "p", &[&SerdeErrorShape::new().skip(&["message"])]);
3968            });
3969            assert!(msg.contains("`call`"), "{msg}");
3970        }
3971
3972        #[test]
3973        fn rename_splices_the_field_under_the_new_name() {
3974            let parts = serde_err_parts(
3975                &Wrapped::Parse {
3976                    message: "unexpected token".into(),
3977                    line: 3,
3978                },
3979                "p",
3980                &[&SerdeErrorShape::new().rename(&[("message", "detail"), ("absent", "other")])],
3981            );
3982            assert_eq!(names(&parts), ["detail", "line"]);
3983            let data = parts.data.expect("data");
3984            assert!(matches!(
3985                &data[0].1,
3986                RValue::Character(v) if v == &vec![Some("unexpected token".to_string())]
3987            ));
3988
3989            // `rename` wins over the Display-equality drop: the caller asked
3990            // for the field explicitly.
3991            let parts = serde_err_parts(
3992                &Wrapped::Echo {
3993                    message: "boom".into(),
3994                },
3995                "p",
3996                &[&SerdeErrorShape::new().rename(&[("message", "detail")])],
3997            );
3998            assert_eq!(names(&parts), ["detail"]);
3999        }
4000
4001        /// The probe's second stage under the `serde` feature: an
4002        /// `E: Serialize + Display` is classed with the defaults and the
4003        /// macro-supplied prefix; a `String` error gets the family class and
4004        /// its text unquoted.
4005        #[test]
4006        fn err_parts_probe_classes_serialize_display_types() {
4007            let parts = crate::__mx_result_err_parts!(Tagged::Io, "p", super::NoShapes);
4008            assert_eq!(parts.message, "io");
4009            assert_eq!(parts.class, ["p_io", "p"]);
4010            assert!(parts.data.is_none());
4011
4012            let parts = crate::__mx_result_err_parts!(
4013                Tagged::MissingField { field: "id".into() },
4014                "p",
4015                super::NoShapes
4016            );
4017            assert_eq!(parts.class, ["p_missing_field", "p"]);
4018            assert_eq!(names(&parts), ["field"]);
4019
4020            let parts =
4021                crate::__mx_result_err_parts!(String::from("bad input"), "p", super::NoShapes);
4022            assert_eq!(parts.message, "bad input");
4023            assert_eq!(parts.class, ["p"]);
4024            assert!(parts.data.is_none());
4025
4026            let parts = crate::__mx_result_err_parts!("static text", "p", super::NoShapes);
4027            assert_eq!(parts.message, "static text");
4028            assert_eq!(parts.class, ["p"]);
4029        }
4030
4031        /// #1459: a `rename` target the variant already carries would give the
4032        /// condition two `line` entries; R reads only the first.
4033        #[test]
4034        fn rename_onto_a_name_the_variant_carries_panics() {
4035            let err = std::panic::AssertUnwindSafe(Wrapped::Parse {
4036                message: "unexpected token".into(),
4037                line: 3,
4038            });
4039            let msg = panic_message(move || {
4040                serde_err_parts(
4041                    &err.0,
4042                    "p",
4043                    &[&SerdeErrorShape::new().rename(&[("message", "line")])],
4044                );
4045            });
4046            assert!(msg.contains("field `line` appears twice"), "{msg}");
4047            assert!(msg.contains("`rename(message = \"line\")`"), "{msg}");
4048
4049            // The same attribute is fine on a variant without `line`.
4050            let parts = serde_err_parts(
4051                &Wrapped::Echo {
4052                    message: "boom".into(),
4053                },
4054                "p",
4055                &[&SerdeErrorShape::new().rename(&[("message", "line")])],
4056            );
4057            assert_eq!(names(&parts), ["line"]);
4058        }
4059
4060        /// A target that only *another* variant carries is no collision, and
4061        /// `skip` frees a name for `rename` because both run before the check.
4062        #[test]
4063        fn rename_target_is_checked_per_variant_after_skip() {
4064            let parts = serde_err_parts(
4065                &Wrapped::Bad { call: "f()".into() },
4066                "p",
4067                &[&SerdeErrorShape::new().rename(&[("call", "line")])],
4068            );
4069            assert_eq!(names(&parts), ["line"]);
4070
4071            let parts = serde_err_parts(
4072                &Wrapped::Parse {
4073                    message: "unexpected token".into(),
4074                    line: 3,
4075                },
4076                "p",
4077                &[&SerdeErrorShape::new()
4078                    .skip(&["line"])
4079                    .rename(&[("message", "line")])],
4080            );
4081            assert_eq!(names(&parts), ["line"]);
4082            let data = parts.data.expect("data");
4083            assert!(matches!(
4084                &data[0].1,
4085                RValue::Character(v) if v == &vec![Some("unexpected token".to_string())]
4086            ));
4087        }
4088    }
4089
4090    /// `SerdeErrorShape`'s const checks (#2207). Called at run time here, the
4091    /// same panics a `const` turns into a compile error.
4092    mod serde_error_shape_checks {
4093        use super::super::SerdeErrorShape;
4094
4095        fn panic_message(f: impl FnOnce() -> SerdeErrorShape + std::panic::UnwindSafe) -> String {
4096            let payload = std::panic::catch_unwind(f).expect_err("must panic");
4097            payload
4098                .downcast_ref::<String>()
4099                .cloned()
4100                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
4101                .unwrap_or_default()
4102        }
4103
4104        #[test]
4105        fn a_consistent_shape_builds_in_a_const() {
4106            const ENGINE: SerdeErrorShape = SerdeErrorShape::new()
4107                .tag("type")
4108                .prefix("engine")
4109                .skip(&["message"])
4110                .rename(&[("source", "cause")])
4111                .as_character(&["rule", "column", "source"])
4112                .as_integer(&["n"])
4113                .as_double(&["rate"])
4114                .as_logical(&["exact"]);
4115            assert_ne!(ENGINE, SerdeErrorShape::new());
4116            assert_eq!(SerdeErrorShape::default(), SerdeErrorShape::new());
4117        }
4118
4119        #[test]
4120        fn a_field_in_two_type_lists_is_refused() {
4121            let msg = panic_message(|| {
4122                SerdeErrorShape::new()
4123                    .as_integer(&["rule", "n"])
4124                    .as_double(&["n"])
4125            });
4126            assert_eq!(
4127                msg,
4128                "SerdeErrorShape: field `n` is in both `as_integer` and `as_double`; a field has \
4129                 one R type"
4130            );
4131        }
4132
4133        #[test]
4134        fn a_skipped_field_takes_no_type_or_name() {
4135            let msg = panic_message(|| SerdeErrorShape::new().skip(&["n"]).as_integer(&["n"]));
4136            assert!(
4137                msg.contains("field `n` is in both `skip` and `as_integer`"),
4138                "{msg}"
4139            );
4140            let msg = panic_message(|| SerdeErrorShape::new().as_logical(&["n"]).skip(&["n"]));
4141            assert!(
4142                msg.contains("field `n` is in both `as_logical` and `skip`"),
4143                "{msg}"
4144            );
4145            let msg = panic_message(|| {
4146                SerdeErrorShape::new()
4147                    .skip(&["message"])
4148                    .rename(&[("message", "detail")])
4149            });
4150            assert!(
4151                msg.contains("field `message` is in both `skip` and `rename`"),
4152                "{msg}"
4153            );
4154            let msg = panic_message(|| {
4155                SerdeErrorShape::new()
4156                    .rename(&[("message", "detail")])
4157                    .skip(&["message"])
4158            });
4159            assert!(
4160                msg.contains("field `message` is in both `rename` and `skip`"),
4161                "{msg}"
4162            );
4163        }
4164
4165        #[test]
4166        fn rename_mirrors_the_attribute_grammar() {
4167            let msg = panic_message(|| SerdeErrorShape::new().rename(&[("detail", "kind")]));
4168            assert!(msg.contains("target `kind` is reserved"), "{msg}");
4169            let msg = panic_message(|| SerdeErrorShape::new().rename(&[("a", "b"), ("a", "c")]));
4170            assert!(msg.contains("field `a` is renamed twice"), "{msg}");
4171            let msg = panic_message(|| SerdeErrorShape::new().rename(&[("a", "b"), ("c", "b")]));
4172            assert!(msg.contains("two `rename` pairs target `b`"), "{msg}");
4173            let msg = panic_message(|| SerdeErrorShape::new().rename(&[("a", "")]));
4174            assert!(msg.contains("`rename` has an empty name"), "{msg}");
4175            let msg = panic_message(|| SerdeErrorShape::new().rename(&[]));
4176            assert!(msg.contains("`rename` needs at least one pair"), "{msg}");
4177        }
4178
4179        #[test]
4180        fn lists_and_options_are_set_once_and_not_empty() {
4181            let msg = panic_message(|| SerdeErrorShape::new().skip(&["a"]).skip(&["b"]));
4182            assert!(msg.contains("`skip` is set twice"), "{msg}");
4183            let msg = panic_message(|| SerdeErrorShape::new().as_double(&["a"]).as_double(&["b"]));
4184            assert!(msg.contains("`as_double` is set twice"), "{msg}");
4185            let msg = panic_message(|| SerdeErrorShape::new().prefix("a").prefix("b"));
4186            assert!(msg.contains("`prefix` is set twice"), "{msg}");
4187            let msg = panic_message(|| SerdeErrorShape::new().tag(""));
4188            assert!(msg.contains("`tag` must not be empty"), "{msg}");
4189            let msg = panic_message(|| SerdeErrorShape::new().as_character(&[]));
4190            assert!(
4191                msg.contains("`as_character` needs at least one field"),
4192                "{msg}"
4193            );
4194            let msg = panic_message(|| SerdeErrorShape::new().skip(&[""]));
4195            assert!(msg.contains("`skip` has an empty field name"), "{msg}");
4196            let msg = panic_message(|| SerdeErrorShape::new().as_integer(&["n", "n"]));
4197            assert!(
4198                msg.contains("field `n` is listed twice in `as_integer`"),
4199                "{msg}"
4200            );
4201        }
4202
4203        /// The message buffer is 256 bytes; a name it cuts inside a
4204        /// character falls back to a message without the name.
4205        #[test]
4206        fn a_long_field_name_still_panics_with_a_message() {
4207            // "SerdeErrorShape: field `" is 24 bytes; the buffer ends inside `é`.
4208            let name: &'static str = Box::leak(format!("{}é", "x".repeat(231)).into_boxed_str());
4209            let names: &'static [&'static str] = Box::leak(Box::new([name]));
4210            let msg = panic_message(move || SerdeErrorShape::new().as_integer(names).skip(names));
4211            assert_eq!(
4212                msg,
4213                "SerdeErrorShape: contradictory options; a field name is too long to quote"
4214            );
4215        }
4216    }
4217
4218    /// The `as_*` options and shape precedence at the condition boundary
4219    /// (#2207), on a hand-written `Serialize` that uses no field helper.
4220    #[cfg(feature = "serde")]
4221    mod serde_error_shape {
4222        use super::super::{SHAPE_TYPE_OPTIONS, SerdeErrorShape, serde_err_parts};
4223        use crate::RValue;
4224        use crate::serde::rvalue_ser::Atomic;
4225
4226        /// The shape of an error type in a crate that can't depend on
4227        /// miniextendr: internally tagged by hand.
4228        struct Engine {
4229            rule: Vec<String>,
4230            n: u32,
4231            rate: i64,
4232            flags: Vec<Option<bool>>,
4233        }
4234
4235        impl ::serde::Serialize for Engine {
4236            fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
4237                use ::serde::ser::SerializeStruct;
4238                let mut st = s.serialize_struct("Engine", 5)?;
4239                st.serialize_field("kind", "rule")?;
4240                st.serialize_field("rule", &self.rule)?;
4241                st.serialize_field("n", &self.n)?;
4242                st.serialize_field("rate", &self.rate)?;
4243                st.serialize_field("flags", &self.flags)?;
4244                st.end()
4245            }
4246        }
4247
4248        impl std::fmt::Display for Engine {
4249            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4250                f.write_str("rule failed")
4251            }
4252        }
4253
4254        fn engine() -> Engine {
4255            Engine {
4256                rule: Vec::new(),
4257                n: 3,
4258                rate: 5,
4259                flags: vec![Some(true), None],
4260            }
4261        }
4262
4263        fn field<'a>(parts: &'a super::super::ErrParts, name: &str) -> &'a RValue {
4264            parts
4265                .data
4266                .as_deref()
4267                .unwrap_or_default()
4268                .iter()
4269                .find(|(n, _)| n == name)
4270                .map(|(_, v)| v)
4271                .unwrap_or_else(|| panic!("no field `{name}` in {:?}", parts.data))
4272        }
4273
4274        fn panic_message(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
4275            let payload = std::panic::catch_unwind(f).expect_err("must panic");
4276            payload
4277                .downcast_ref::<String>()
4278                .cloned()
4279                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
4280                .unwrap_or_default()
4281        }
4282
4283        #[test]
4284        fn atomic_order_matches_the_shape_lists() {
4285            for (atomic, option) in Atomic::ALL.iter().zip(SHAPE_TYPE_OPTIONS) {
4286                assert_eq!(atomic.helper(), option);
4287            }
4288        }
4289
4290        #[test]
4291        fn without_options_the_fields_keep_their_usual_serialization() {
4292            let parts = serde_err_parts(&engine(), "p", &[]);
4293            assert_eq!(parts.class, ["p_rule", "p"]);
4294            assert!(matches!(field(&parts, "rule"), RValue::List(v) if v.is_empty()));
4295            assert!(matches!(field(&parts, "n"), RValue::Integer(v) if v == &[Some(3)]));
4296            assert!(matches!(field(&parts, "flags"), RValue::List(v) if v.len() == 2));
4297        }
4298
4299        #[test]
4300        fn each_type_option_gives_its_vector() {
4301            const SHAPE: SerdeErrorShape = SerdeErrorShape::new()
4302                .as_character(&["rule"])
4303                .as_double(&["n"])
4304                .as_integer(&["rate"])
4305                .as_logical(&["flags"]);
4306            let parts = serde_err_parts(&engine(), "p", &[&SHAPE]);
4307            assert!(matches!(field(&parts, "rule"), RValue::Character(v) if v.is_empty()));
4308            assert!(matches!(field(&parts, "n"), RValue::Double(v) if v == &[3.0]));
4309            assert!(matches!(field(&parts, "rate"), RValue::Integer(v) if v == &[Some(5)]));
4310            assert!(matches!(
4311                field(&parts, "flags"),
4312                RValue::Logical(v) if v == &[Some(true), None]
4313            ));
4314        }
4315
4316        #[test]
4317        fn a_value_that_does_not_fit_names_the_field_and_the_option() {
4318            let shape = SerdeErrorShape::new().as_integer(&["rate"]);
4319            let err = std::panic::AssertUnwindSafe(Engine {
4320                rate: 1 << 40,
4321                ..engine()
4322            });
4323            let msg = panic_message(move || {
4324                serde_err_parts(&err.0, "p", &[&shape]);
4325            });
4326            assert_eq!(
4327                msg,
4328                "#[miniextendr(serde_error)]: `as_integer` on condition data field `rate` \
4329                 failed: the field is not an integer that fits an R integer or `None`; the \
4330                 original error was: rule failed"
4331            );
4332
4333            let shape = SerdeErrorShape::new().as_logical(&["rule"]);
4334            let err = std::panic::AssertUnwindSafe(Engine {
4335                rule: vec!["a".into(), "b".into()],
4336                ..engine()
4337            });
4338            let msg = panic_message(move || {
4339                serde_err_parts(&err.0, "p", &[&shape]);
4340            });
4341            assert!(
4342                msg.contains(
4343                    "`as_logical` on condition data field `rule` failed: the field is not a logical"
4344                ),
4345                "{msg}"
4346            );
4347        }
4348
4349        /// The keys are the names as the error serializes them: an `as_*`
4350        /// option and a `rename` of one field compose.
4351        #[test]
4352        fn type_and_rename_compose_on_the_serialized_name() {
4353            let shape = SerdeErrorShape::new()
4354                .rename(&[("rule", "rules")])
4355                .as_character(&["rule"]);
4356            let parts = serde_err_parts(&engine(), "p", &[&shape]);
4357            assert!(matches!(field(&parts, "rules"), RValue::Character(v) if v.is_empty()));
4358        }
4359
4360        /// The function's options (the last shape) win for each setting of a
4361        /// field; what only the named shape sets stays.
4362        #[test]
4363        fn later_shapes_win_per_field() {
4364            let named = SerdeErrorShape::new()
4365                .tag("type")
4366                .prefix("engine")
4367                .skip(&["n"])
4368                .as_integer(&["rate"])
4369                .rename(&[("rule", "rules")])
4370                .as_character(&["rule"]);
4371            let own = SerdeErrorShape::new().tag("kind").as_double(&["n", "rate"]);
4372            let parts = serde_err_parts(&engine(), "p", &[&named, &own]);
4373            assert_eq!(parts.class, ["engine_rule", "engine"]);
4374            assert!(matches!(field(&parts, "n"), RValue::Double(v) if v == &[3.0]));
4375            assert!(matches!(field(&parts, "rate"), RValue::Double(v) if v == &[5.0]));
4376            assert!(matches!(field(&parts, "rules"), RValue::Character(v) if v.is_empty()));
4377
4378            // A later `skip` drops a field the earlier shape typed; a later
4379            // `rename` keeps the earlier type.
4380            let named = SerdeErrorShape::new()
4381                .as_integer(&["n"])
4382                .as_double(&["rate"]);
4383            let own = SerdeErrorShape::new()
4384                .prefix("own")
4385                .skip(&["rate"])
4386                .rename(&[("n", "count")]);
4387            let parts = serde_err_parts(&engine(), "p", &[&named, &own]);
4388            assert_eq!(parts.class, ["own_rule", "own"]);
4389            let names: Vec<&str> = parts
4390                .data
4391                .as_deref()
4392                .unwrap_or_default()
4393                .iter()
4394                .map(|(n, _)| n.as_str())
4395                .collect();
4396            assert_eq!(names, ["rule", "count", "flags"]);
4397            assert!(matches!(field(&parts, "count"), RValue::Integer(v) if v == &[Some(3)]));
4398
4399            // A later rename keeps a field an earlier shape skipped.
4400            let named = SerdeErrorShape::new().skip(&["n"]);
4401            let own = SerdeErrorShape::new().rename(&[("n", "count")]);
4402            let parts = serde_err_parts(&engine(), "p", &[&named, &own]);
4403            assert!(matches!(field(&parts, "count"), RValue::Integer(v) if v == &[Some(3)]));
4404        }
4405
4406        /// A field the payload lacks is a no-op, as for `skip` / `rename`.
4407        #[test]
4408        fn a_field_the_payload_lacks_is_a_no_op() {
4409            let shape = SerdeErrorShape::new()
4410                .as_character(&["column"])
4411                .as_integer(&["absent"]);
4412            let parts = serde_err_parts(&engine(), "p", &[&shape]);
4413            assert_eq!(parts.data.as_deref().unwrap_or_default().len(), 4);
4414        }
4415    }
4416
4417    #[test]
4418    fn rerror_reserved_field_rejected_at_err_arm() {
4419        use super::RError;
4420        let err = std::panic::AssertUnwindSafe(RError::new("m").data("kind", 2));
4421        let payload =
4422            std::panic::catch_unwind(move || crate::__mx_result_err_parts!(err.0, "p", NoShapes))
4423                .expect_err("must panic");
4424        let msg = payload
4425            .downcast_ref::<String>()
4426            .cloned()
4427            .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
4428            .expect("plain panic message");
4429        assert!(msg.contains("reserved"), "got: {msg}");
4430        assert!(msg.contains("`kind`"), "got: {msg}");
4431    }
4432
4433    /// The probe's first stage: an `RConditionData` impl wins outright.
4434    #[test]
4435    fn err_parts_probe_prefers_rconditionerror() {
4436        use super::RError;
4437        let e = RError::new("m")
4438            .class(["member", "family"])
4439            .data("input", 1);
4440        let parts = crate::__mx_result_err_parts!(e, "p", NoShapes);
4441        assert_eq!(parts.message, "m");
4442        assert_eq!(parts.class, ["member", "family"]);
4443        assert_eq!(parts.data.expect("data")[0].0, "input");
4444    }
4445
4446    /// The probe's last resort: a type that is neither `RConditionData` nor
4447    /// `Serialize` renders with `Debug`, no class, no data.
4448    #[test]
4449    fn err_parts_probe_falls_back_to_debug() {
4450        #[derive(Debug)]
4451        struct Opaque;
4452        let parts = crate::__mx_result_err_parts!(Opaque, "p", NoShapes);
4453        assert_eq!(parts.message, "Opaque");
4454        assert!(parts.class.is_empty());
4455        assert!(parts.data.is_none());
4456    }
4457
4458    /// The conversion probe's built-in arm (#1591): the framework's own
4459    /// conversion errors are not `RConditionData`, keep the plain class
4460    /// vector and no data, and read in R terms instead of their `Display`
4461    /// text. Other error types still render with `Display` (not `Debug`).
4462    #[test]
4463    fn conversion_probe_rewords_builtin_errors() {
4464        use crate::SEXPTYPE;
4465        use crate::from_r::{
4466            SexpClassError, SexpError, SexpLengthError, SexpNaError, SexpTypeError,
4467        };
4468
4469        // The probe takes the owned error, as in the generated `Err(e)` arm.
4470        let message = |e: &SexpError, known: bool| {
4471            crate::__mx_conversion_err_parts!(e.clone(), known).message
4472        };
4473
4474        let e = SexpError::InvalidValue("non-numeric value(s): \"BLQ\" (element 2)".into());
4475        let parts = crate::__mx_conversion_err_parts!(e, true);
4476        assert_eq!(parts.message, "non-numeric value(s): \"BLQ\" (element 2)");
4477        assert!(parts.class.is_empty());
4478        assert!(parts.data.is_none());
4479        assert_eq!(message(&e, false), message(&e, true));
4480
4481        let e = SexpError::Type(SexpTypeError {
4482            expected: SEXPTYPE::INTSXP,
4483            actual: SEXPTYPE::STRSXP,
4484        });
4485        assert_eq!(message(&e, true), "got character");
4486        assert_eq!(message(&e, false), "expected integer, got character");
4487        // The macro states a type error's expectation; the error gives none.
4488        assert_eq!(e.r_expectation(), None);
4489        // A class error names the class, as the expectation and in the
4490        // per-element reason.
4491        let e = SexpError::Class(SexpClassError {
4492            expected: "a data frame",
4493            actual: SEXPTYPE::VECSXP,
4494        });
4495        assert_eq!(message(&e, true), "got list");
4496        assert_eq!(message(&e, false), "expected a data frame, got list");
4497        assert_eq!(e.r_expectation().as_deref(), Some("a data frame"));
4498        let e = SexpError::Length(SexpLengthError {
4499            expected: 1,
4500            actual: 2,
4501        });
4502        assert_eq!(message(&e, true), "got length 2");
4503        assert_eq!(message(&e, false), "expected length 1, got length 2");
4504        let e = SexpError::Na(SexpNaError {
4505            sexp_type: SEXPTYPE::REALSXP,
4506        });
4507        assert_eq!(message(&e, true), "NA is not allowed");
4508        assert_eq!(message(&e, false), "NA is not allowed");
4509        assert_eq!(
4510            message(&SexpError::MissingField("id".into()), true),
4511            "missing field 'id'"
4512        );
4513        assert_eq!(
4514            message(&SexpError::DuplicateName("id".into()), true),
4515            "duplicate name 'id'"
4516        );
4517        // The bare error structs some conversions return take the same arm.
4518        let e = SexpTypeError {
4519            expected: SEXPTYPE::REALSXP,
4520            actual: SEXPTYPE::VECSXP,
4521        };
4522        assert_eq!(
4523            crate::__mx_conversion_err_parts!(e, true).message,
4524            "got list"
4525        );
4526        // The one-argument form is the no-expectation form.
4527        assert_eq!(
4528            crate::__mx_conversion_err_parts!(e).message,
4529            "expected numeric, got list"
4530        );
4531        let e = SexpClassError {
4532            expected: "a data frame",
4533            actual: SEXPTYPE::INTSXP,
4534        };
4535        assert_eq!(
4536            crate::__mx_conversion_err_parts!(e, true).message,
4537            "got integer"
4538        );
4539        assert_eq!(
4540            crate::__mx_conversion_err_parts!(e).message,
4541            "expected a data frame, got integer"
4542        );
4543
4544        // `MatchArgError` takes the built-in arm too: R-worded, and after the
4545        // `one of ...` expectation just the input.
4546        use crate::match_arg::MatchArgError;
4547        const CHOICES: &[&str] = &["fast", "slow"];
4548        let e = MatchArgError::IsNa {
4549            choices: CHOICES,
4550            aliases: &[],
4551        };
4552        let parts = crate::__mx_conversion_err_parts!(e.clone());
4553        assert_eq!(parts.message, r#"expected one of "fast", "slow", got NA"#);
4554        assert!(parts.class.is_empty());
4555        assert!(parts.data.is_none());
4556        assert_eq!(
4557            crate::__mx_conversion_err_parts!(e, true).message,
4558            "NA is not allowed"
4559        );
4560        let e = MatchArgError::NoMatch {
4561            input: "zzz".into(),
4562            choices: CHOICES,
4563            aliases: &[],
4564        };
4565        assert_eq!(
4566            crate::__mx_conversion_err_parts!(e.clone()).message,
4567            r#"expected one of "fast", "slow", got "zzz""#
4568        );
4569        assert_eq!(
4570            crate::__mx_conversion_err_parts!(e, true).message,
4571            r#"got "zzz""#
4572        );
4573        let e = MatchArgError::InvalidType {
4574            actual: SEXPTYPE::REALSXP,
4575            choices: CHOICES,
4576            aliases: &[],
4577        };
4578        assert_eq!(
4579            crate::__mx_conversion_err_parts!(e.clone()).message,
4580            r#"expected one of "fast", "slow" (a string or factor), got numeric"#
4581        );
4582        assert_eq!(
4583            crate::__mx_conversion_err_parts!(e, true).message,
4584            "got numeric"
4585        );
4586        let e = MatchArgError::InvalidLength {
4587            actual: 2,
4588            choices: CHOICES,
4589            aliases: &[],
4590        };
4591        assert_eq!(
4592            crate::__mx_conversion_err_parts!(e, true).message,
4593            "got length 2"
4594        );
4595
4596        // A plain `&str` / `String` error (neither `Serialize`-classed nor
4597        // `Debug`-quoted, unlike the `Result` probe).
4598        let parts = crate::__mx_conversion_err_parts!(String::from("bad input"), true);
4599        assert_eq!(parts.message, "bad input");
4600        assert!(parts.class.is_empty());
4601    }
4602
4603    /// A `match_arg` choice error knows what the value should have been, as a
4604    /// `MatchArgError` or inside the `SexpError` `#[derive(MatchArg)]`'s
4605    /// `TryFromSexp` returns; other errors do not (#1594). The prefix built
4606    /// from it reads like a statically known one.
4607    #[test]
4608    fn conversion_expectation_probe_names_match_arg_choices() {
4609        use super::conversion_prefix;
4610        use crate::from_r::SexpError;
4611        use crate::match_arg::MatchArgError;
4612        let e = MatchArgError::NoMatch {
4613            input: "zzz".into(),
4614            choices: &["fast", "slow"],
4615            aliases: &[],
4616        };
4617        let expected = Some(r#"one of "fast", "slow""#.to_string());
4618        assert_eq!(crate::__mx_conversion_expectation!(e.clone()), expected);
4619        let e = SexpError::from(e);
4620        assert_eq!(crate::__mx_conversion_expectation!(e.clone()), expected);
4621        assert_eq!(
4622            crate::__mx_conversion_err_parts!(e, true).message,
4623            r#"got "zzz""#
4624        );
4625        assert_eq!(
4626            crate::__mx_conversion_expectation!(SexpError::InvalidValue("x".into())),
4627            None
4628        );
4629        assert_eq!(
4630            crate::__mx_conversion_expectation!(String::from("bad input")),
4631            None
4632        );
4633
4634        assert_eq!(
4635            conversion_prefix("mode", false, expected.as_deref()),
4636            r#"'mode' must be one of "fast", "slow""#
4637        );
4638        assert_eq!(
4639            conversion_prefix("mode", true, expected.as_deref()),
4640            r#"'mode' must be NULL or one of "fast", "slow""#
4641        );
4642        assert_eq!(conversion_prefix("x", true, None), "invalid 'x' argument");
4643    }
4644
4645    /// An `Either` whose arms both refused the kind of value (a type or class
4646    /// error, a `match_arg` refusal of a non-string, or a nested `Either` of
4647    /// those) names both arms, as the reason and as the expectation; an arm
4648    /// that got further gives the reason instead.
4649    #[cfg(feature = "either")]
4650    #[test]
4651    fn either_conversion_names_both_arms_that_refused_the_kind() {
4652        use crate::SEXPTYPE;
4653        use crate::from_r::{SexpClassError, SexpError, SexpLengthError, SexpTypeError};
4654        use crate::match_arg::MatchArgError;
4655
4656        let either = |l: SexpError, r: SexpError| SexpError::EitherConversion {
4657            left_error: Box::new(l),
4658            right_error: Box::new(r),
4659        };
4660        let ty = |expected, actual| SexpError::Type(SexpTypeError { expected, actual });
4661        let frame = |actual| {
4662            SexpError::Class(SexpClassError {
4663                expected: "a data frame",
4664                actual,
4665            })
4666        };
4667        let refused = |actual, choices| {
4668            SexpError::MatchArg(MatchArgError::InvalidType {
4669                actual,
4670                choices,
4671                aliases: &[],
4672            })
4673        };
4674        const ROUTES: &[&str] = &["oral", "bolus"];
4675        const MODES: &[&str] = &["fast", "bolus"];
4676        // (error, reason with the expectation known, reason without it,
4677        // expectation)
4678        let check = |e: SexpError, known: &str, unknown: &str, expected: Option<&str>| {
4679            assert_eq!(e.r_reason(true), known, "{e}");
4680            assert_eq!(e.r_reason(false), unknown, "{e}");
4681            assert_eq!(e.r_expectation().as_deref(), expected, "{e}");
4682        };
4683        use SEXPTYPE::{INTSXP, LGLSXP, REALSXP, STRSXP, VECSXP};
4684
4685        check(
4686            either(ty(INTSXP, REALSXP), ty(STRSXP, REALSXP)),
4687            "got numeric",
4688            "expected integer or character, got numeric",
4689            Some("integer or character"),
4690        );
4691        check(
4692            either(ty(REALSXP, VECSXP), frame(VECSXP)),
4693            "got list",
4694            "expected numeric or a data frame, got list",
4695            Some("numeric or a data frame"),
4696        );
4697        check(
4698            either(frame(INTSXP), ty(STRSXP, INTSXP)),
4699            "got integer",
4700            "expected a data frame or character, got integer",
4701            Some("a data frame or character"),
4702        );
4703        check(
4704            either(refused(LGLSXP, ROUTES), ty(REALSXP, LGLSXP)),
4705            "got logical",
4706            r#"expected one of "oral", "bolus", or numeric, got logical"#,
4707            Some(r#"one of "oral", "bolus", or numeric"#),
4708        );
4709        check(
4710            either(ty(REALSXP, LGLSXP), refused(LGLSXP, ROUTES)),
4711            "got logical",
4712            r#"expected numeric or one of "oral", "bolus", got logical"#,
4713            Some(r#"numeric or one of "oral", "bolus""#),
4714        );
4715        // Two choice lists read as one, each choice once.
4716        check(
4717            either(refused(REALSXP, ROUTES), refused(REALSXP, MODES)),
4718            "got numeric",
4719            r#"expected one of "oral", "bolus", "fast", got numeric"#,
4720            Some(r#"one of "oral", "bolus", "fast""#),
4721        );
4722        // The listed aliases follow the choices, before the other arm; an
4723        // unlisted one is left out (#2148). Two choice lists keep both
4724        // lists' aliases, in the order of the joined choices.
4725        use crate::match_arg::MatchArgAlias;
4726        const ROUTE_ALIASES: &[MatchArgAlias] = &[
4727            MatchArgAlias::new("po", "oral"),
4728            MatchArgAlias::unlisted("iv", "bolus"),
4729        ];
4730        const MODE_ALIASES: &[MatchArgAlias] = &[MatchArgAlias::new("quick", "fast")];
4731        let aliased = |actual, choices, aliases| {
4732            SexpError::MatchArg(MatchArgError::InvalidType {
4733                actual,
4734                choices,
4735                aliases,
4736            })
4737        };
4738        check(
4739            either(aliased(LGLSXP, ROUTES, ROUTE_ALIASES), ty(REALSXP, LGLSXP)),
4740            "got logical",
4741            r#"expected one of "oral", "bolus" (or "po" for "oral"), or numeric, got logical"#,
4742            Some(r#"one of "oral", "bolus" (or "po" for "oral"), or numeric"#),
4743        );
4744        check(
4745            either(ty(REALSXP, LGLSXP), aliased(LGLSXP, ROUTES, ROUTE_ALIASES)),
4746            "got logical",
4747            r#"expected numeric or one of "oral", "bolus" (or "po" for "oral"), got logical"#,
4748            Some(r#"numeric or one of "oral", "bolus" (or "po" for "oral")"#),
4749        );
4750        check(
4751            either(
4752                aliased(REALSXP, MODES, MODE_ALIASES),
4753                aliased(REALSXP, ROUTES, ROUTE_ALIASES),
4754            ),
4755            "got numeric",
4756            r#"expected one of "fast", "bolus", "oral" (or "quick" for "fast", "po" for "oral"), got numeric"#,
4757            Some(r#"one of "fast", "bolus", "oral" (or "quick" for "fast", "po" for "oral")"#),
4758        );
4759        // A nested `Either` whose arms both refused the kind is one arm.
4760        let inner = either(refused(VECSXP, ROUTES), ty(REALSXP, VECSXP));
4761        check(
4762            either(ty(LGLSXP, VECSXP), inner),
4763            "got list",
4764            r#"expected logical or one of "oral", "bolus", or numeric, got list"#,
4765            Some(r#"logical or one of "oral", "bolus", or numeric"#),
4766        );
4767
4768        // A string that matched no choice got further than a type error: its
4769        // reason, and no joined expectation.
4770        let no_match = SexpError::MatchArg(MatchArgError::NoMatch {
4771            input: "zzz".into(),
4772            choices: ROUTES,
4773            aliases: &[],
4774        });
4775        check(
4776            either(no_match.clone(), ty(REALSXP, STRSXP)),
4777            r#"got "zzz""#,
4778            r#"expected one of "oral", "bolus", got "zzz""#,
4779            None,
4780        );
4781        let length = SexpError::Length(SexpLengthError {
4782            expected: 1,
4783            actual: 2,
4784        });
4785        check(
4786            either(refused(REALSXP, ROUTES), length.clone()),
4787            "got length 2",
4788            "expected length 1, got length 2",
4789            None,
4790        );
4791        let bad_value = SexpError::InvalidValue("data.frame has no column names".into());
4792        check(
4793            either(ty(REALSXP, VECSXP), bad_value.clone()),
4794            "data.frame has no column names",
4795            "data.frame has no column names",
4796            None,
4797        );
4798        check(
4799            either(bad_value, length),
4800            "data.frame has no column names; got length 2",
4801            "data.frame has no column names; expected length 1, got length 2",
4802            None,
4803        );
4804        // A nested `Either` that got further is not a kind mismatch.
4805        check(
4806            either(ty(LGLSXP, STRSXP), either(no_match, ty(REALSXP, STRSXP))),
4807            r#"got "zzz""#,
4808            r#"expected one of "oral", "bolus", got "zzz""#,
4809            None,
4810        );
4811    }
4812
4813    /// An `Either` parameter is worded side by side from what the wrapper
4814    /// knows (`from_r::either_expectation`): a known side by its text,
4815    /// whatever its error, so the text is the same for every failure; an
4816    /// opaque side by what its type declares, else by what its own error
4817    /// expected (a kind, the choices of a `match_arg` enum), else by its noun.
4818    /// `None` only when no side is named by more than its noun.
4819    #[cfg(feature = "either")]
4820    #[test]
4821    fn either_expectation_words_each_side() {
4822        use crate::SEXPTYPE::{REALSXP, STRSXP, VECSXP};
4823        use crate::from_r::{
4824            ExpectedArm, SexpClassError, SexpError, SexpLengthError, SexpTypeError,
4825            either_expectation,
4826        };
4827        use crate::match_arg::MatchArgError;
4828
4829        let either = |l: SexpError, r: SexpError| SexpError::EitherConversion {
4830            left_error: Box::new(l),
4831            right_error: Box::new(r),
4832        };
4833        let ty = |expected, actual| SexpError::Type(SexpTypeError { expected, actual });
4834        let frame = || {
4835            SexpError::Class(SexpClassError {
4836                expected: "a data frame",
4837                actual: REALSXP,
4838            })
4839        };
4840        let length = || {
4841            SexpError::Length(SexpLengthError {
4842                expected: 1,
4843                actual: 2,
4844            })
4845        };
4846        let bad_value = || SexpError::InvalidValue("invalid UUID".into());
4847        const ROUTES: &[&str] = &["oral", "bolus"];
4848        let no_match = || {
4849            SexpError::MatchArg(MatchArgError::NoMatch {
4850                input: "zzz".into(),
4851                choices: ROUTES,
4852                aliases: &[],
4853            })
4854        };
4855
4856        let number = ExpectedArm::Known("a single number");
4857        let table = ExpectedArm::Known("a data frame");
4858        let dose = ExpectedArm::Opaque {
4859            declared: Some("a single number"),
4860            noun: "a `Dose`",
4861        };
4862        let route = ExpectedArm::Opaque {
4863            declared: None,
4864            noun: "a `Route`",
4865        };
4866        let doc_id = ExpectedArm::Opaque {
4867            declared: None,
4868            noun: "a `DocId`",
4869        };
4870        let pattern = ExpectedArm::Opaque {
4871            declared: None,
4872            noun: "a `Pattern`",
4873        };
4874        let words = |arms: &ExpectedArm<'_>, e: SexpError| either_expectation(&e, arms);
4875
4876        // Known sides: the same text whichever side got further.
4877        let plain = ExpectedArm::Either(&number, &table);
4878        for e in [
4879            either(length(), frame()),
4880            either(ty(REALSXP, VECSXP), frame()),
4881        ] {
4882            assert_eq!(
4883                words(&plain, e).as_deref(),
4884                Some("a single number or a data frame")
4885            );
4886        }
4887        // A newtype side declares its inner type's text.
4888        let newtype = ExpectedArm::Either(&dose, &table);
4889        assert_eq!(
4890            words(&newtype, either(length(), frame())).as_deref(),
4891            Some("a single number or a data frame")
4892        );
4893        // An opaque side named by its error: a type error's kind, a
4894        // `match_arg` enum's choices whether or not the value was a string.
4895        let by_kind = ExpectedArm::Either(&doc_id, &number);
4896        assert_eq!(
4897            words(&by_kind, either(ty(STRSXP, VECSXP), length())).as_deref(),
4898            Some("character or a single number")
4899        );
4900        let choices = ExpectedArm::Either(&route, &number);
4901        for e in [either(no_match(), frame()), either(no_match(), length())] {
4902            assert_eq!(
4903                words(&choices, e).as_deref(),
4904                Some(r#"one of "oral", "bolus", or a single number"#)
4905            );
4906        }
4907        // An opaque side whose error names nothing: its noun, beside a side
4908        // that is known.
4909        assert_eq!(
4910            words(&by_kind, either(bad_value(), ty(REALSXP, STRSXP))).as_deref(),
4911            Some("a `DocId` or a single number")
4912        );
4913        // `NULL or` an optional side, and a nested `Either` side by side.
4914        let optional = ExpectedArm::Nullable(&table);
4915        let nullable = ExpectedArm::Either(&number, &optional);
4916        assert_eq!(
4917            words(&nullable, either(length(), frame())).as_deref(),
4918            Some("a single number or NULL or a data frame")
4919        );
4920        let inner = ExpectedArm::Either(&route, &table);
4921        let nested = ExpectedArm::Either(&number, &inner);
4922        assert_eq!(
4923            words(&nested, either(length(), either(no_match(), frame()))).as_deref(),
4924            Some(r#"a single number or one of "oral", "bolus", or a data frame"#)
4925        );
4926        // No side named by more than its noun: no expectation.
4927        let opaque = ExpectedArm::Either(&doc_id, &pattern);
4928        assert_eq!(words(&opaque, either(bad_value(), length())), None);
4929        // An error that is not an `Either` one does not fit the sides.
4930        assert_eq!(words(&plain, length()), None);
4931    }
4932
4933    /// The conversion probe's preferred arm: an `RConditionData` error type
4934    /// contributes its class vector, message and data.
4935    #[test]
4936    fn conversion_probe_prefers_rconditionerror() {
4937        use super::RError;
4938        let e = RError::new("must be positive")
4939            .class(["pkg_bad_arg", "pkg_error"])
4940            .data("value", -1);
4941        let parts = crate::__mx_conversion_err_parts!(e, true);
4942        assert_eq!(parts.message, "must be positive");
4943        assert_eq!(parts.class, ["pkg_bad_arg", "pkg_error"]);
4944        assert_eq!(parts.data.expect("data")[0].0, "value");
4945    }
4946
4947    /// A reserved data name on a conversion error is rejected like on a
4948    /// `Result` error, instead of overwriting `e$kind`.
4949    #[test]
4950    fn conversion_probe_rejects_reserved_fields() {
4951        use super::RError;
4952        let err = std::panic::AssertUnwindSafe(RError::new("m").data("kind", 2));
4953        let payload = std::panic::catch_unwind(move || crate::__mx_conversion_err_parts!(err.0))
4954            .expect_err("must panic");
4955        let msg = payload
4956            .downcast_ref::<String>()
4957            .cloned()
4958            .expect("plain panic message");
4959        assert!(msg.contains("reserved"), "got: {msg}");
4960    }
4961
4962    fn field_names(parts: &super::ErrParts) -> Vec<&str> {
4963        parts
4964            .data
4965            .as_ref()
4966            .map(|d| d.iter().map(|(n, _)| n.as_str()).collect())
4967            .unwrap_or_default()
4968    }
4969
4970    fn character_field<'a>(parts: &'a super::ErrParts, name: &str) -> Option<&'a str> {
4971        parts
4972            .data
4973            .as_ref()?
4974            .iter()
4975            .find(|(n, _)| n == name)
4976            .and_then(|(_, v)| match v {
4977                crate::RValue::Character(v) => v.first()?.as_deref(),
4978                _ => None,
4979            })
4980    }
4981
4982    /// `conversion_err_parts`: the prefix leads the message, the class is
4983    /// kept, `param` comes first in the data and `rust_type` second.
4984    #[test]
4985    fn conversion_err_parts_adds_prefix_param_and_rust_type() {
4986        use super::{ErrParts, conversion_err_parts};
4987        use crate::RValue;
4988
4989        let plain = conversion_err_parts(
4990            "'dv' must be numeric",
4991            "dv",
4992            Some("AsNumericVec"),
4993            &[],
4994            ErrParts {
4995                message: "non-numeric value(s): \"BLQ\" (element 2)".into(),
4996                class: Vec::new(),
4997                data: None,
4998                call: super::ConditionCall::Inherit,
4999            },
5000            None,
5001        );
5002        assert_eq!(
5003            plain.message,
5004            "'dv' must be numeric: non-numeric value(s): \"BLQ\" (element 2)"
5005        );
5006        assert!(plain.class.is_empty());
5007        assert_eq!(field_names(&plain), ["param", "rust_type"]);
5008        assert_eq!(character_field(&plain, "param"), Some("dv"));
5009        assert_eq!(character_field(&plain, "rust_type"), Some("AsNumericVec"));
5010
5011        let classed = conversion_err_parts(
5012            "invalid 'x' argument",
5013            "x",
5014            Some("Count"),
5015            &[],
5016            ErrParts {
5017                message: "must be positive".into(),
5018                class: vec!["pkg_bad_arg".into(), "pkg_error".into()],
5019                data: Some(vec![("value".into(), RValue::from(-1))]),
5020                call: super::ConditionCall::Inherit,
5021            },
5022            None,
5023        );
5024        assert_eq!(classed.message, "invalid 'x' argument: must be positive");
5025        assert_eq!(classed.class, ["pkg_bad_arg", "pkg_error"]);
5026        assert_eq!(field_names(&classed), ["param", "rust_type", "value"]);
5027
5028        // Without a Rust type only `param` is added.
5029        let untyped = conversion_err_parts(
5030            "'value' must be a number",
5031            "value",
5032            None,
5033            &[],
5034            ErrParts {
5035                message: "got \"abc\"".into(),
5036                class: Vec::new(),
5037                data: None,
5038                call: super::ConditionCall::Inherit,
5039            },
5040            None,
5041        );
5042        assert_eq!(untyped.message, "'value' must be a number: got \"abc\"");
5043        assert_eq!(field_names(&untyped), ["param"]);
5044    }
5045
5046    /// An argument error keeps the call whatever the error type's `call()`
5047    /// says: the R-side checks raise the same condition with the call.
5048    #[test]
5049    fn conversion_err_parts_keeps_the_call() {
5050        use super::{ConditionCall, ErrParts, conversion_err_parts};
5051
5052        let parts = conversion_err_parts(
5053            "invalid 'x' argument",
5054            "x",
5055            None,
5056            &[],
5057            ErrParts {
5058                message: "bad".into(),
5059                class: Vec::new(),
5060                data: None,
5061                call: ConditionCall::None,
5062            },
5063            None,
5064        );
5065        assert_eq!(parts.call, ConditionCall::Inherit);
5066    }
5067
5068    /// The crate's `conversion_error_class` follows the error's own classes,
5069    /// skipping those it already names; a plain error gets only the crate's.
5070    #[test]
5071    fn conversion_err_parts_appends_the_crate_class() {
5072        use super::{ErrParts, conversion_err_parts};
5073
5074        let crate_class = ["pkg_error_argument", "pkg_error"];
5075        let plain = conversion_err_parts(
5076            "'x' must be a single integer",
5077            "x",
5078            Some("i32"),
5079            &crate_class,
5080            ErrParts {
5081                message: "got character".into(),
5082                class: Vec::new(),
5083                data: None,
5084                call: super::ConditionCall::Inherit,
5085            },
5086            None,
5087        );
5088        assert_eq!(plain.class, ["pkg_error_argument", "pkg_error"]);
5089
5090        let classed = conversion_err_parts(
5091            "invalid 'x' argument",
5092            "x",
5093            Some("Count"),
5094            &crate_class,
5095            ErrParts {
5096                message: "must be positive".into(),
5097                class: vec!["pkg_error_negative".into(), "pkg_error".into()],
5098                data: None,
5099                call: super::ConditionCall::Inherit,
5100            },
5101            None,
5102        );
5103        assert_eq!(
5104            classed.class,
5105            ["pkg_error_negative", "pkg_error", "pkg_error_argument"]
5106        );
5107    }
5108
5109    /// `arg_check_parts`: the message as given, the crate class alone, and
5110    /// `param` as the only field (no `rust_type`), as the R guard raises it.
5111    #[test]
5112    fn arg_check_parts_matches_the_r_guard() {
5113        use super::arg_check_parts;
5114
5115        let parts = arg_check_parts("'dv' must not contain NA", "dv", &[]);
5116        assert_eq!(parts.message, "'dv' must not contain NA");
5117        assert!(parts.class.is_empty());
5118        assert_eq!(field_names(&parts), ["param"]);
5119        assert_eq!(character_field(&parts, "param"), Some("dv"));
5120
5121        let custom = "`x` can't be NA: 100% \"sure\"\ncaf\u{e9}()";
5122        let parts = arg_check_parts(custom, "x", &["pkg_error_argument", "pkg_error"]);
5123        assert_eq!(parts.message, custom);
5124        assert_eq!(parts.class, ["pkg_error_argument", "pkg_error"]);
5125        assert_eq!(field_names(&parts), ["param"]);
5126    }
5127
5128    /// A `param` or `rust_type` field of the error type's own wins: the
5129    /// framework's field is not added, so `e$param` / `e$rust_type` read the
5130    /// type's value.
5131    #[test]
5132    fn conversion_err_parts_keeps_the_types_own_fields() {
5133        use super::{ErrParts, conversion_err_parts};
5134        use crate::RValue;
5135
5136        let parts = conversion_err_parts(
5137            "invalid 'hyper' argument",
5138            "hyper",
5139            Some("Hyperparams"),
5140            &[],
5141            ErrParts {
5142                message: "hyperparameter 'beta' must be non-negative".into(),
5143                class: Vec::new(),
5144                data: Some(vec![
5145                    ("value".into(), RValue::from(-2.0)),
5146                    ("param".into(), RValue::from("beta")),
5147                ]),
5148                call: super::ConditionCall::Inherit,
5149            },
5150            None,
5151        );
5152        assert_eq!(field_names(&parts), ["rust_type", "value", "param"]);
5153        assert_eq!(character_field(&parts, "param"), Some("beta"));
5154
5155        let parts = conversion_err_parts(
5156            "invalid 'x' argument",
5157            "x",
5158            Some("Wrapped"),
5159            &[],
5160            ErrParts {
5161                message: "bad".into(),
5162                class: Vec::new(),
5163                data: Some(vec![("rust_type".into(), RValue::from("Inner"))]),
5164                call: super::ConditionCall::Inherit,
5165            },
5166            None,
5167        );
5168        assert_eq!(field_names(&parts), ["param", "rust_type"]);
5169        assert_eq!(character_field(&parts, "rust_type"), Some("Inner"));
5170    }
5171
5172    /// An error that words the whole argument error gives the message in
5173    /// place of `<prefix>: <reason>`; class and fields are as for any other.
5174    #[test]
5175    fn conversion_err_parts_takes_a_whole_message() {
5176        use super::{ErrParts, conversion_err_parts};
5177
5178        let parts = conversion_err_parts(
5179            "'mode' must be one of \"fast\", \"slow\"",
5180            "mode",
5181            Some("Mode"),
5182            &["pkg_error_argument"],
5183            ErrParts {
5184                message: "got \"zzz\"".into(),
5185                class: Vec::new(),
5186                data: None,
5187                call: super::ConditionCall::Inherit,
5188            },
5189            Some("'mode' should be one of \"fast\", \"slow\"".into()),
5190        );
5191        assert_eq!(parts.message, "'mode' should be one of \"fast\", \"slow\"");
5192        assert_eq!(parts.class, ["pkg_error_argument"]);
5193        assert_eq!(field_names(&parts), ["param", "rust_type"]);
5194    }
5195
5196    /// A `match_arg` choice error, bare or at the top of a `SexpError`, words
5197    /// the whole argument error as `.miniextendr_match_arg` does (#1767);
5198    /// other errors do not.
5199    #[test]
5200    fn conversion_arg_message_probe_words_match_arg_errors_as_the_check() {
5201        use crate::SEXPTYPE;
5202        use crate::from_r::SexpError;
5203        use crate::match_arg::MatchArgError;
5204
5205        const CHOICES: &[&str] = &["fast", "slow"];
5206        let one_of = Some(r#"'mode' should be one of "fast", "slow""#.to_string());
5207        let no_match = MatchArgError::NoMatch {
5208            input: "zzz".into(),
5209            choices: CHOICES,
5210            aliases: &[],
5211        };
5212        assert_eq!(
5213            crate::__mx_conversion_arg_message!(no_match.clone(), "mode"),
5214            one_of
5215        );
5216        assert_eq!(
5217            crate::__mx_conversion_arg_message!(SexpError::from(no_match), "mode"),
5218            one_of
5219        );
5220        let na = MatchArgError::IsNa {
5221            choices: CHOICES,
5222            aliases: &[],
5223        };
5224        assert_eq!(crate::__mx_conversion_arg_message!(na, "mode"), one_of);
5225        let long = MatchArgError::InvalidLength {
5226            actual: 2,
5227            choices: CHOICES,
5228            aliases: &[],
5229        };
5230        assert_eq!(
5231            crate::__mx_conversion_arg_message!(SexpError::from(long), "mode").as_deref(),
5232            Some("'mode' must be of length 1")
5233        );
5234        let number = MatchArgError::InvalidType {
5235            actual: SEXPTYPE::REALSXP,
5236            choices: CHOICES,
5237            aliases: &[],
5238        };
5239        assert_eq!(
5240            crate::__mx_conversion_arg_message!(number, "speed").as_deref(),
5241            Some("'speed' must be NULL or a character vector")
5242        );
5243
5244        assert_eq!(
5245            crate::__mx_conversion_arg_message!(SexpError::InvalidValue("x".into()), "mode"),
5246            None
5247        );
5248        assert_eq!(
5249            crate::__mx_conversion_arg_message!(String::from("bad input"), "mode"),
5250            None
5251        );
5252    }
5253
5254    /// A choice error inside an `Either` leaves the message to the `Either`,
5255    /// which names both arms.
5256    #[cfg(feature = "either")]
5257    #[test]
5258    fn conversion_arg_message_probe_leaves_an_either_alone() {
5259        use crate::from_r::SexpError;
5260        use crate::match_arg::MatchArgError;
5261
5262        let either = SexpError::EitherConversion {
5263            left_error: Box::new(SexpError::from(MatchArgError::NoMatch {
5264                input: "zzz".into(),
5265                choices: &["fast", "slow"],
5266                aliases: &[],
5267            })),
5268            right_error: Box::new(SexpError::InvalidValue("x".into())),
5269        };
5270        assert_eq!(crate::__mx_conversion_arg_message!(either, "mode"), None);
5271    }
5272
5273    /// A classed error with an `argument_message` words the whole argument
5274    /// error, directly or carried in a `SexpError` from a type's
5275    /// `check_sexp`; without one, or inside an `Either`, it does not (#1833).
5276    #[test]
5277    fn conversion_arg_message_probe_takes_a_classed_errors_argument_message() {
5278        use super::{ConditionCall, RConditionData, RError};
5279        use crate::from_r::SexpError;
5280
5281        let advice = "use model_from_df() for a data frame";
5282        let refusal = || {
5283            RError::new("got a data frame")
5284                .class("mx_model_refused")
5285                .argument_message(advice)
5286        };
5287        assert_eq!(
5288            crate::__mx_conversion_arg_message!(refusal(), "fit").as_deref(),
5289            Some(advice)
5290        );
5291        let carried = SexpError::from(refusal());
5292        assert_eq!(
5293            crate::__mx_conversion_arg_message!(carried, "fit").as_deref(),
5294            Some(advice)
5295        );
5296        assert_eq!(
5297            crate::__mx_conversion_arg_message!(RError::new("got a data frame"), "fit"),
5298            None
5299        );
5300        assert_eq!(
5301            crate::__mx_conversion_arg_message!(SexpError::from(RError::new("no")), "fit"),
5302            None
5303        );
5304
5305        // A hand-written impl answers through the trait method.
5306        struct ModelRefused;
5307        impl RConditionData for ModelRefused {
5308            fn message(&self) -> String {
5309                "got a data frame".into()
5310            }
5311            fn argument_message(&self) -> Option<String> {
5312                Some("use model_from_df() for a data frame".into())
5313            }
5314        }
5315        assert_eq!(
5316            crate::__mx_conversion_arg_message!(ModelRefused, "fit").as_deref(),
5317            Some(advice)
5318        );
5319        // The conversion into `SexpError` keeps it, and the call choice.
5320        let SexpError::Condition(kept) = SexpError::from(ModelRefused) else {
5321            panic!("a classed error converts into SexpError::Condition");
5322        };
5323        assert_eq!(kept.argument_message_str(), Some(advice));
5324        assert!(matches!(kept.call(), ConditionCall::Inherit));
5325    }
5326
5327    /// A type's refusal inside an `Either` leaves the message to the
5328    /// `Either`, which names both arms.
5329    #[cfg(feature = "either")]
5330    #[test]
5331    fn conversion_arg_message_probe_leaves_a_classed_refusal_in_an_either_alone() {
5332        use super::RError;
5333        use crate::from_r::SexpError;
5334
5335        let either = SexpError::EitherConversion {
5336            left_error: Box::new(SexpError::from(
5337                RError::new("got a data frame").argument_message("use model_from_df()"),
5338            )),
5339            right_error: Box::new(SexpError::InvalidValue("x".into())),
5340        };
5341        assert_eq!(crate::__mx_conversion_arg_message!(either, "fit"), None);
5342    }
5343
5344    #[test]
5345    fn rerror_builder_and_from_error() {
5346        use super::{RConditionData, RError};
5347        let e: RError = "x".parse::<i32>().unwrap_err().into();
5348        let e = e.class(["member", "family"]).data("input", "x");
5349        assert!(e.message_str().contains("invalid digit"));
5350        assert_eq!(RConditionData::class(&e), vec!["member", "family"]);
5351        let data = RConditionData::data(&e).expect("data");
5352        assert_eq!(data[0].0, "input");
5353
5354        assert!(RConditionData::data(&RError::new("m")).is_none());
5355        assert!(super::is_reserved_condition_field("call"));
5356        assert!(!super::is_reserved_condition_field("calls"));
5357    }
5358
5359    #[test]
5360    fn error_message_only_backcompat() {
5361        let cond = catch(|| crate::error!("plain {}", 42));
5362        match cond {
5363            RCondition::Error {
5364                message,
5365                class,
5366                data,
5367                call: super::ConditionCall::Inherit,
5368            } => {
5369                assert_eq!(message, "plain 42");
5370                assert!(class.is_empty());
5371                assert!(data.is_none());
5372            }
5373            other => panic!("wrong variant: {other:?}"),
5374        }
5375    }
5376
5377    #[test]
5378    fn error_class_only_backcompat() {
5379        let cond = catch(|| crate::error!(class = "my_error", "missing field: {}", "x"));
5380        match cond {
5381            RCondition::Error {
5382                message,
5383                class,
5384                data,
5385                call: super::ConditionCall::Inherit,
5386            } => {
5387                assert_eq!(message, "missing field: x");
5388                assert_eq!(class, vec!["my_error"]);
5389                assert!(data.is_none());
5390            }
5391            other => panic!("wrong variant: {other:?}"),
5392        }
5393    }
5394
5395    #[test]
5396    fn error_single_data_pair() {
5397        let value = 41_i32;
5398        let cond = catch(move || crate::error!(data = ("value", value), "v = {value}"));
5399        match cond {
5400            RCondition::Error {
5401                message,
5402                class,
5403                data,
5404                call: super::ConditionCall::Inherit,
5405            } => {
5406                assert_eq!(message, "v = 41");
5407                assert!(class.is_empty());
5408                assert_data(&data, &[("value", RValue::Integer(vec![Some(41)]))]);
5409            }
5410            other => panic!("wrong variant: {other:?}"),
5411        }
5412    }
5413
5414    #[test]
5415    fn error_class_and_data_list_all_value_types() {
5416        let cond = catch(|| {
5417            crate::error!(
5418                class = "validation_error",
5419                data = [
5420                    ("value", 1.5),
5421                    ("code", 7),
5422                    ("label", "lhs"),
5423                    ("fatal", false),
5424                    ("ints", vec![1, 2]),
5425                    ("reals", vec![0.5_f64]),
5426                    ("flags", vec![true]),
5427                    ("labels", vec!["a".to_string()])
5428                ],
5429                "out of range"
5430            )
5431        });
5432        match cond {
5433            RCondition::Error {
5434                message,
5435                class,
5436                data,
5437                call: super::ConditionCall::Inherit,
5438            } => {
5439                assert_eq!(message, "out of range");
5440                assert_eq!(class, vec!["validation_error"]);
5441                assert_data(
5442                    &data,
5443                    &[
5444                        ("value", RValue::Double(vec![1.5])),
5445                        ("code", RValue::Integer(vec![Some(7)])),
5446                        ("label", RValue::Character(vec![Some("lhs".into())])),
5447                        ("fatal", RValue::Logical(vec![Some(false)])),
5448                        ("ints", RValue::Integer(vec![Some(1), Some(2)])),
5449                        ("reals", RValue::Double(vec![0.5])),
5450                        ("flags", RValue::Logical(vec![Some(true)])),
5451                        ("labels", RValue::Character(vec![Some("a".into())])),
5452                    ],
5453                );
5454            }
5455            other => panic!("wrong variant: {other:?}"),
5456        }
5457    }
5458
5459    #[test]
5460    fn warning_with_class_and_data() {
5461        let cond = catch(|| crate::warning!(class = "trunc", data = ("dropped", 3), "dropped"));
5462        match cond {
5463            RCondition::Warning {
5464                message,
5465                class,
5466                data,
5467                call: super::ConditionCall::Inherit,
5468            } => {
5469                assert_eq!(message, "dropped");
5470                assert_eq!(class, vec!["trunc"]);
5471                assert_data(&data, &[("dropped", RValue::Integer(vec![Some(3)]))]);
5472            }
5473            other => panic!("wrong variant: {other:?}"),
5474        }
5475    }
5476
5477    #[test]
5478    fn message_with_data() {
5479        let cond = catch(|| crate::message!(data = ("step", 2), "step {}", 2));
5480        match cond {
5481            RCondition::Message { message, data, .. } => {
5482                assert_eq!(message, "step 2");
5483                assert_data(&data, &[("step", RValue::Integer(vec![Some(2)]))]);
5484            }
5485            other => panic!("wrong variant: {other:?}"),
5486        }
5487    }
5488
5489    #[test]
5490    fn condition_with_class_and_data() {
5491        let cond =
5492            catch(|| crate::condition!(class = "progress", data = [("n", 10)], "processed {}", 10));
5493        match cond {
5494            RCondition::Condition {
5495                message,
5496                class,
5497                data,
5498                call: super::ConditionCall::Inherit,
5499            } => {
5500                assert_eq!(message, "processed 10");
5501                assert_eq!(class, vec!["progress"]);
5502                assert_data(&data, &[("n", RValue::Integer(vec![Some(10)]))]);
5503            }
5504            other => panic!("wrong variant: {other:?}"),
5505        }
5506    }
5507
5508    #[test]
5509    fn call_none_on_every_condition_macro() {
5510        use super::ConditionCall;
5511
5512        match catch(|| crate::error!(call = none, "plain {}", 1)) {
5513            RCondition::Error {
5514                message,
5515                class,
5516                data,
5517                call,
5518            } => {
5519                assert_eq!(message, "plain 1");
5520                assert!(class.is_empty());
5521                assert!(data.is_none());
5522                assert_eq!(call, ConditionCall::None);
5523            }
5524            other => panic!("wrong variant: {other:?}"),
5525        }
5526        match catch(|| crate::rust_error!(call = none, class = ["member", "family"], "e")) {
5527            RCondition::Error { class, call, .. } => {
5528                assert_eq!(class, vec!["member", "family"]);
5529                assert_eq!(call, ConditionCall::None);
5530            }
5531            other => panic!("wrong variant: {other:?}"),
5532        }
5533        match catch(|| {
5534            crate::warning!(
5535                call = none,
5536                class = "pkg_override",
5537                data = { profile = 3 },
5538                "overrides an earlier row"
5539            )
5540        }) {
5541            RCondition::Warning {
5542                message,
5543                class,
5544                data,
5545                call,
5546            } => {
5547                assert_eq!(message, "overrides an earlier row");
5548                assert_eq!(class, vec!["pkg_override"]);
5549                assert_data(&data, &[("profile", RValue::Integer(vec![Some(3)]))]);
5550                assert_eq!(call, ConditionCall::None);
5551            }
5552            other => panic!("wrong variant: {other:?}"),
5553        }
5554        match catch(|| crate::condition!(call = none, data = ("n", 1), "tick")) {
5555            RCondition::Condition { data, call, .. } => {
5556                assert_data(&data, &[("n", RValue::Integer(vec![Some(1)]))]);
5557                assert_eq!(call, ConditionCall::None);
5558            }
5559            other => panic!("wrong variant: {other:?}"),
5560        }
5561        match catch(|| crate::rust_condition!(call = none, "tick")) {
5562            RCondition::Condition { call, .. } => assert_eq!(call, ConditionCall::None),
5563            other => panic!("wrong variant: {other:?}"),
5564        }
5565        // Without `call = none` the call is inherited.
5566        match catch(|| crate::warning!("sibling")) {
5567            RCondition::Warning { call, .. } => assert_eq!(call, ConditionCall::Inherit),
5568            other => panic!("wrong variant: {other:?}"),
5569        }
5570    }
5571
5572    #[test]
5573    fn rerror_without_call_reaches_the_err_parts() {
5574        use super::{ConditionCall, RConditionData, RError};
5575
5576        let plain = RError::new("m").class("c");
5577        assert_eq!(RConditionData::call(&plain), ConditionCall::Inherit);
5578        let callless = plain.clone().without_call();
5579        assert_eq!(RConditionData::call(&callless), ConditionCall::None);
5580        let parts = crate::__mx_result_err_parts!(callless, "pkg_error", NoShapes);
5581        assert_eq!(parts.call, ConditionCall::None);
5582        assert_eq!(parts.class, vec!["c"]);
5583        let parts = crate::__mx_result_err_parts!(plain, "pkg_error", NoShapes);
5584        assert_eq!(parts.call, ConditionCall::Inherit);
5585    }
5586
5587    #[test]
5588    fn data_list_trailing_comma() {
5589        let cond = catch(|| crate::error!(data = [("a", 1), ("b", 2),], "msg"));
5590        match cond {
5591            RCondition::Error { data, .. } => {
5592                assert_data(
5593                    &data,
5594                    &[
5595                        ("a", RValue::Integer(vec![Some(1)])),
5596                        ("b", RValue::Integer(vec![Some(2)])),
5597                    ],
5598                );
5599            }
5600            other => panic!("wrong variant: {other:?}"),
5601        }
5602    }
5603
5604    // region: keyed builder sugar (ported from #1044/#995)
5605
5606    #[test]
5607    fn keyed_builder_arm_stringifies_idents() {
5608        let cond = catch(|| crate::error!(data = { value = 42, code = 7 }, "boom"));
5609        match cond {
5610            RCondition::Error { data, .. } => {
5611                assert_data(
5612                    &data,
5613                    &[
5614                        ("value", RValue::Integer(vec![Some(42)])),
5615                        ("code", RValue::Integer(vec![Some(7)])),
5616                    ],
5617                );
5618            }
5619            other => panic!("wrong variant: {other:?}"),
5620        }
5621    }
5622
5623    #[test]
5624    fn keyed_builder_arm_trailing_comma_and_mixed_types() {
5625        let cond = catch(|| {
5626            crate::warning!(
5627                class = "trunc",
5628                data = { dropped = 3, ratio = 0.5_f64, tag = "rows", },
5629                "dropped some"
5630            )
5631        });
5632        match cond {
5633            RCondition::Warning { data, class, .. } => {
5634                assert_eq!(class, vec!["trunc"]);
5635                assert_data(
5636                    &data,
5637                    &[
5638                        ("dropped", RValue::Integer(vec![Some(3)])),
5639                        ("ratio", RValue::Double(vec![0.5])),
5640                        ("tag", RValue::Character(vec![Some("rows".into())])),
5641                    ],
5642                );
5643            }
5644            other => panic!("wrong variant: {other:?}"),
5645        }
5646    }
5647
5648    // endregion
5649
5650    // region: NA-aware + wide-int + debug value types via the macro (#995)
5651
5652    #[test]
5653    fn option_scalar_fields_carry_na() {
5654        let cond = catch(|| {
5655            crate::error!(
5656                data = [("present", Some(9_i32)), ("missing", None::<i32>)],
5657                "opts"
5658            )
5659        });
5660        match cond {
5661            RCondition::Error { data, .. } => {
5662                assert_data(
5663                    &data,
5664                    &[
5665                        ("present", RValue::Integer(vec![Some(9)])),
5666                        ("missing", RValue::Integer(vec![None])),
5667                    ],
5668                );
5669            }
5670            other => panic!("wrong variant: {other:?}"),
5671        }
5672    }
5673
5674    #[test]
5675    fn vec_option_field_carries_embedded_na() {
5676        let cond =
5677            catch(|| crate::error!(data = ("codes", vec![Some(1_i32), None, Some(3)]), "vec"));
5678        match cond {
5679            RCondition::Error { data, .. } => {
5680                assert_data(
5681                    &data,
5682                    &[("codes", RValue::Integer(vec![Some(1), None, Some(3)]))],
5683                );
5684            }
5685            other => panic!("wrong variant: {other:?}"),
5686        }
5687    }
5688
5689    #[test]
5690    fn wide_integer_ladder_via_macro() {
5691        // Fits in i32 → integer; beyond → double.
5692        let cond = catch(|| {
5693            crate::error!(
5694                data = [("small", 42_i64), ("big", 5_000_000_000_i64)],
5695                "wide"
5696            )
5697        });
5698        match cond {
5699            RCondition::Error { data, .. } => {
5700                assert_data(
5701                    &data,
5702                    &[
5703                        ("small", RValue::Integer(vec![Some(42)])),
5704                        ("big", RValue::Double(vec![5_000_000_000.0])),
5705                    ],
5706                );
5707            }
5708            other => panic!("wrong variant: {other:?}"),
5709        }
5710    }
5711
5712    #[test]
5713    fn debug_fallback_via_macro() {
5714        let cond = catch(|| crate::error!(data = ("range", RValue::debug(0..=100)), "dbg"));
5715        match cond {
5716            RCondition::Error { data, .. } => {
5717                assert_data(
5718                    &data,
5719                    &[("range", RValue::Character(vec![Some("0..=100".into())]))],
5720                );
5721            }
5722            other => panic!("wrong variant: {other:?}"),
5723        }
5724    }
5725
5726    // endregion
5727}
5728
5729// endregion