Skip to main content

miniextendr_macros/
method_return_builder.rs

1//! Shared utilities for handling method return values in R wrapper generation.
2//!
3//! This module provides helpers for generating consistent return value handling
4//! across all class systems (Env, R6, S7, S3, S4).
5
6use crate::miniextendr_impl::ParsedMethod;
7use crate::miniextendr_impl::ReceiverKind;
8
9// region: Shared R error-check helpers
10//
11// All wrappers route through the internal helper `.miniextendr_raise_condition`
12// emitted once at the top of the generated wrappers file (see
13// `miniextendr-api/src/registry.rs` `write_r_wrappers_to_file`). The helper
14// performs the `.val$kind` dispatch and `rust_*` class layering; each wrapper
15// only needs a one-line guard at the call site.
16
17/// Generate the R guard that re-raises a tagged Rust error/condition value.
18///
19/// Expects `.val` to already be assigned (e.g., `.val <- .Call(...)`). Emits a
20/// single line indented by `indent`: when `.val` is a tagged `rust_condition_value`,
21/// hand off to the shared helper and return from the enclosing function.
22///
23/// The helper dispatches on `.val$kind` (see
24/// `miniextendr_api::error_value::kind` for canonical kind strings):
25///
26/// - `error` / `panic` / `result_err` / `none_err` / `conversion` (and any
27///   unknown kind) — `stop()` longjmps with the appropriate `rust_*` class
28///   layering.
29/// - `warning` — `warning()` signals; the wrapper's surrounding `return(...)`
30///   propagates `invisible(NULL)` as the wrapper's result.
31/// - `message` — `message()` signals; same propagation.
32/// - `condition` — `signalCondition()` signals; same propagation.
33pub fn condition_check_lines(indent: &str) -> Vec<String> {
34    condition_check_lines_with_default(indent, "sys.call()")
35}
36
37/// [`condition_check_lines`] with an explicit raise fallback
38/// (`CallAttribution::raise_default`): `environment()` for an S3 method, whose
39/// frame the raise helper turns into the generic's call (#1851).
40pub fn condition_check_lines_with_default(indent: &str, raise_default: &str) -> Vec<String> {
41    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, {1}))",
                                indent, raise_default))
                    })]))vec![format!(
42        "{indent}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, {raise_default}))"
43    )]
44}
45
46/// Generate an inline R error-check block for single-expression contexts (S7, S4).
47///
48/// Returns a multi-line block string: `{ .val <- <call_expr>; if (...) return(...); <inner> }`.
49/// Used where the class system requires a single expression rather than separate lines
50/// (e.g., S7 property definitions, S4 method bodies).
51///
52/// - `call_expr`: The `.Call()` expression to evaluate
53/// - `inner`: The final expression to return after the error check passes
54/// - `indent`: Leading whitespace for the inner lines (e.g., `"    "` for 4-space)
55pub fn condition_check_inline_block(call_expr: &str, inner: &str, indent: &str) -> String {
56    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{\n{0}.val <- {1}\n{0}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, sys.call()))\n{0}{2}\n  }}",
                indent, call_expr, inner))
    })format!(
57        "{{\n{indent}.val <- {call_expr}\n\
58         {indent}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, sys.call()))\n\
59         {indent}{inner}\n  \
60         }}"
61    )
62}
63
64/// Generate a standalone-function R wrapper body.
65///
66/// Returns the full body string: `.val <- <call_expr>; if (...) return(...); <final_return>`.
67/// Used for top-level `#[miniextendr]` functions (not class methods).
68///
69/// - `call_expr`: The `.Call()` expression to evaluate
70/// - `final_return`: The expression to return (typically `".val"` or `"invisible(.val)"`)
71/// - `indent`: Leading whitespace for the body lines (e.g., `"  "` for 2-space)
72pub fn standalone_body(call_expr: &str, final_return: &str, indent: &str) -> String {
73    standalone_body_with_call_default(call_expr, final_return, indent, "sys.call()")
74}
75
76/// [`standalone_body`] with an explicit raise fallback: `sys.call()` for the
77/// wrapper's own frame, `.mx_call` for `call = caller` wrappers, `NULL` under
78/// `call = none` and `environment()` for an S3 method (see
79/// `crate::r_wrapper_builder::CallAttribution::raise_default`).
80pub fn standalone_body_with_call_default(
81    call_expr: &str,
82    final_return: &str,
83    indent: &str,
84    call_default: &str,
85) -> String {
86    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".val <- {0}\n{1}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, {2}))\n{1}{3}",
                call_expr, indent, call_default, final_return))
    })format!(
87        ".val <- {call_expr}\n\
88         {indent}if (inherits(.val, \"rust_condition_value\") && isTRUE(attr(.val, \"__rust_condition__\"))) return(.miniextendr_raise_condition(.val, {call_default}))\n\
89         {indent}{final_return}"
90    )
91}
92// endregion
93
94// region: Return strategy
95
96/// Return handling strategy for class methods.
97///
98/// Determines how the R wrapper function processes and returns the `.Call()` result.
99/// Each class system generator uses this to produce idiomatic R return code.
100#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ReturnStrategy {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ReturnStrategy::ReturnSelf => "ReturnSelf",
                ReturnStrategy::ReturnOtherClass => "ReturnOtherClass",
                ReturnStrategy::ReturnOtherClassList =>
                    "ReturnOtherClassList",
                ReturnStrategy::ChainableMutation => "ChainableMutation",
                ReturnStrategy::Direct => "Direct",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ReturnStrategy { }
#[automatically_derived]
impl ::core::clone::Clone for ReturnStrategy {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ReturnStrategy { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ReturnStrategy { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ReturnStrategy {
    #[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 ReturnStrategy { }Eq)]
101pub enum ReturnStrategy {
102    /// The method returns `Self`. The wrapper wraps the raw pointer result with
103    /// the appropriate class attribute or creates a new class object (e.g.,
104    /// `R6Class$new(.ptr = result)` or `structure(result, class = "...")`).
105    ReturnSelf,
106    /// The method returns a bare type name that may be another registered
107    /// ExternalPtr-backed class. The final decision is deferred to
108    /// `write_r_wrappers_to_file`, where the complete class registry is known.
109    ReturnOtherClass,
110    /// The method returns a *list* of values (`Vec<Class>`, plus its
111    /// `Option<Vec<Class>>` / `Result<Vec<Class>, E>` wrappers; an `Option`'s
112    /// `NULL` passes through the wrap) whose element type may be another registered
113    /// ExternalPtr-backed class (#1284). Deferred to `write_r_wrappers_to_file`
114    /// like [`ReturnOtherClass`](Self::ReturnOtherClass), but through a
115    /// distinct `.__MX_WRAP_LIST_RETURN_*` marker that resolves to a per-element
116    /// `lapply(...)` wrap. The marker prefix must never overlap the scalar
117    /// `.__MX_WRAP_RETURN_` family: the scalar resolver rewrites unrecognized
118    /// markers of its own prefix to the bare expression, so a shared prefix
119    /// would consume list markers before the list resolver ran.
120    ReturnOtherClassList,
121    /// The method is a `&mut self` method returning `()`. The wrapper calls the
122    /// `.Call()` for its side effect and returns the receiver (`self`/`x`) for
123    /// method chaining. The receiver is returned visibly unless the method is
124    /// marked `Invisible<..>` / `invisible` (#1213).
125    ChainableMutation,
126    /// Default strategy: return the `.Call()` result directly without wrapping.
127    Direct,
128}
129
130impl ReturnStrategy {
131    /// Determine the return strategy for a parsed method.
132    ///
133    /// - Methods that return `Self`, `Result<Self, E>`, or `Option<Self>` use
134    ///   `ReturnSelf`. The C wrapper already raised on `Err` (see
135    ///   [`crate::c_wrapper_builder::ReturnHandling::ResultExternalPtr`]) and
136    ///   gives `NULL` for `None` (see
137    ///   [`crate::c_wrapper_builder::ReturnHandling::OptionExternalPtr`]), which
138    ///   passes through; any other `.val` is a bare ExternalPtr — identical in
139    ///   shape to the bare-`Self` case — and gets the same class-wrapping tail.
140    /// - In-place builders (`&mut self -> &mut Self` / `&self -> Self`) and
141    ///   `&mut self -> ()` methods use `ChainableMutation`. Both return the
142    ///   receiver object (`x` / `self`, visibly unless marked invisible) so the
143    ///   call composes under the native pipe (`obj |> set_a(1) |> set_b(2)`);
144    ///   the C wrapper hands back the same ExternalPtr handle (see
145    ///   [`crate::c_wrapper_builder::ReturnHandling::SelfHandle`]).
146    ///
147    /// A visibility marker on the return type (`Invisible<Self>`,
148    /// `Invisible<()>`, ...) is peeled at parse time
149    /// ([`ParsedMethod::sig`] holds the inner type), so it never changes the
150    /// strategy; it only sets [`MethodReturnBuilder::with_invisible`].
151    /// - Bare capitalized return types that are not known primitives/containers
152    ///   use `ReturnOtherClass`; write-time registry lookup wraps registered
153    ///   classes and leaves false positives unchanged. This also covers the
154    ///   explicit-handle spelling `ExternalPtr<Class>` (#1375) — the detector
155    ///   peels through it to the same wrapped name.
156    /// - `Vec<Class>` (and its `Option`/`Result` wrappers; an `Option` return's
157    ///   `NULL` passes through) use `ReturnOtherClassList`; write-time registry lookup wraps
158    ///   registered element classes via `lapply` and leaves false positives
159    ///   unchanged (#1284). This also covers `Vec<ExternalPtr<Class>>` and
160    ///   `Vec<Self>` (#1375) — the latter via `returns_self_list()`, since the
161    ///   scalar detector can't name `Self`;
162    ///   `with_return_class_from_method` substitutes the actual name.
163    /// - All other methods use `Direct`.
164    pub fn for_method(method: &ParsedMethod) -> Self {
165        // In-place builders (`&mut self -> &mut Self` / `&self -> Self`), their
166        // fallible forms (`-> Result<&mut Self, E>` / `Option<&mut Self>`, #1433),
167        // `&mut self -> ()` mutators, and consuming steps (`self -> Self` /
168        // `Result<Self, E>` / `Option<Self>`, #1432: the C wrapper writes the
169        // result back into the same handle) all return the receiver object.
170        let is_self_ref_builder = method.env.is_instance()
171            && (method.returns_self_ref()
172                || method.returns_result_self_ref()
173                || method.returns_option_self_ref());
174        let is_unit_mutator = method.env.is_mut() && method.returns_unit();
175        let returns_self_shaped =
176            method.returns_self() || method.returns_result_self() || method.returns_option_self();
177        let is_consuming_builder = method.env == ReceiverKind::Value && returns_self_shaped;
178        if is_consuming_builder || is_self_ref_builder || is_unit_mutator {
179            ReturnStrategy::ChainableMutation
180        } else if returns_self_shaped {
181            ReturnStrategy::ReturnSelf
182        } else if method.returns_other_class().is_some() {
183            ReturnStrategy::ReturnOtherClass
184        } else if method.returns_other_class_list().is_some() || method.returns_self_list() {
185            ReturnStrategy::ReturnOtherClassList
186        } else {
187            ReturnStrategy::Direct
188        }
189    }
190}
191
192/// Class-specific tail closures, one per [`ReturnStrategy`] variant.
193///
194/// Each closure receives `(indent, class_name)` and produces the tail lines that
195/// run after `.val <- <call_expr>` and the condition check (both emitted by
196/// [`MethodReturnBuilder::build_with_tails`]). The tail therefore always
197/// references `.val` directly.
198///
199/// `class_name` is `""` for [`ReturnStrategy::ChainableMutation`],
200/// [`ReturnStrategy::ReturnOtherClass`],
201/// [`ReturnStrategy::ReturnOtherClassList`], and [`ReturnStrategy::Direct`] —
202/// those tails should not use it.
203#[allow(clippy::type_complexity)]
204struct ReturnTails<'a> {
205    /// Tail for [`ReturnStrategy::ReturnSelf`].
206    ///
207    /// Parameters: `(indent, class_name) -> lines`
208    self_tail: Box<dyn Fn(&str, &str) -> Vec<String> + 'a>,
209    /// Tail for [`ReturnStrategy::ChainableMutation`].
210    ///
211    /// Parameters: `(indent) -> lines`
212    chain_tail: Box<dyn Fn(&str) -> Vec<String> + 'a>,
213    /// Tail for [`ReturnStrategy::Direct`].
214    ///
215    /// Parameters: `(indent) -> lines`
216    direct_tail: Box<dyn Fn(&str) -> Vec<String> + 'a>,
217}
218
219/// Builder for generating R method body lines with appropriate return handling.
220///
221/// Produces lines of R code for a method body, combining the `.Call()` expression
222/// with the return strategy and the tagged-condition error guard. Each class
223/// system has specialized builder methods (`build_r6_body`, `build_s3_body`,
224/// etc.) that produce idiomatic R code for that system.
225pub struct MethodReturnBuilder {
226    /// The `.Call()` expression string (e.g., `".Call(C_Counter__inc, .call = sys.call(), self)"`).
227    call_expr: String,
228    /// How to handle the return value (direct, chaining, or Self wrapping).
229    strategy: ReturnStrategy,
230    /// R class name, required when `strategy` is `ReturnSelf` to construct
231    /// the class wrapper (e.g., `"Counter"` for `Counter$new(.ptr = result)`).
232    class_name: Option<String>,
233    /// Rust return type name, required when `strategy` is `ReturnOtherClass`
234    /// or `ReturnOtherClassList` (the element type in the list case). The
235    /// write-time resolver maps this Rust name to the registered target class
236    /// and constructor syntax, or falls back to the bare value on miss.
237    return_class: Option<String>,
238    /// Explicit constructor selection, before automatic return-shape tails.
239    explicit_wrap: Option<crate::return_wrap::ReturnWrap>,
240    /// The method returns `Option<..>`, so `.val` is `NULL` for `None` (the C
241    /// wrapper converts the whole `Option`, as a function's does): the class
242    /// wrap or receiver passes it through instead of constructing from it.
243    nullable: bool,
244    /// Variable name to return for `ChainableMutation` strategy (e.g., `"self"` for R6,
245    /// `"x"` for S3). Defaults to `"self"` if not set.
246    chain_var: Option<String>,
247    /// Wrap the final expression in `invisible(...)` (#1213). Set from a
248    /// return-type marker (`Invisible<T>`) or the `invisible` method option;
249    /// nothing is invisible by default.
250    invisible: bool,
251    /// Number of leading spaces for each generated line.
252    indent: usize,
253    /// The raise helper's fallback call (`CallAttribution::raise_default`):
254    /// `sys.call()` unless set, `environment()` for an S3 method (#1851).
255    raise_default: String,
256}
257
258impl MethodReturnBuilder {
259    /// Create a new builder with the given .Call expression.
260    pub fn new(call_expr: String) -> Self {
261        Self {
262            call_expr,
263            strategy: ReturnStrategy::Direct,
264            class_name: None,
265            return_class: None,
266            explicit_wrap: None,
267            nullable: false,
268            chain_var: None,
269            invisible: false,
270            indent: 2,
271            raise_default: "sys.call()".to_string(),
272        }
273    }
274
275    /// Set the raise helper's fallback call (see
276    /// `CallAttribution::raise_default`).
277    pub fn with_raise_default(mut self, raise_default: &str) -> Self {
278        self.raise_default = raise_default.to_string();
279        self
280    }
281
282    /// Wrap the method's final expression in `invisible(...)` (marker or
283    /// `invisible` option, see [`ParsedMethod::is_invisible`]).
284    pub fn with_invisible(mut self, invisible: bool) -> Self {
285        self.invisible = invisible;
286        self
287    }
288
289    /// Wrap the last generated line's expression in `invisible(...)` when the
290    /// builder is marked invisible. Every tail ends with the expression the
291    /// wrapper returns, so this is the single place visibility is applied.
292    fn apply_visibility(&self, mut lines: Vec<String>) -> Vec<String> {
293        if self.invisible
294            && let Some(last) = lines.last_mut()
295        {
296            let expr = last.trim_start();
297            let indent = &last[..last.len() - expr.len()];
298            *last = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}invisible({1})", indent, expr))
    })format!("{indent}invisible({expr})");
299        }
300        lines
301    }
302
303    /// Inline-expression counterpart of [`Self::apply_visibility`].
304    fn apply_visibility_expr(&self, expr: String) -> String {
305        if self.invisible {
306            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invisible({0})", expr))
    })format!("invisible({expr})")
307        } else {
308            expr
309        }
310    }
311
312    /// Set the return strategy.
313    pub fn with_strategy(mut self, strategy: ReturnStrategy) -> Self {
314        self.strategy = strategy;
315        self
316    }
317
318    /// Set the class name (for Self returns).
319    pub fn with_class_name(mut self, class_name: String) -> Self {
320        self.class_name = Some(class_name);
321        self
322    }
323
324    /// Set the return class name (for cross-class ExternalPtr returns).
325    pub fn with_return_class(mut self, return_class: String) -> Self {
326        self.return_class = Some(return_class);
327        self
328    }
329
330    /// Attach the method's cross-class return name when this builder uses
331    /// [`ReturnStrategy::ReturnOtherClass`] or
332    /// [`ReturnStrategy::ReturnOtherClassList`].
333    ///
334    /// `self_rust_type` is the enclosing impl block's own Rust type name
335    /// (`type_ident.to_string()`) — every class-system generator already has
336    /// `parsed_impl.type_ident` in scope alongside `class_name`, so this
337    /// threads it through rather than widening `ParsedMethod` to carry it
338    /// (#1375). It's consulted only for the `Vec<Self>` case
339    /// ([`ParsedMethod::returns_self_list`]), where it stands in for the
340    /// missing element name: the write-time resolver keys off the Rust type
341    /// name (`ClassNameEntry::rust_type`), which is *not* necessarily the same
342    /// string as this builder's `class_name` field —
343    /// `#[miniextendr(class = "Override")]` makes the R-facing class name and
344    /// the Rust struct name diverge, and `class_name` here is the former.
345    pub fn with_return_class_from_method(
346        mut self,
347        method: &ParsedMethod,
348        self_rust_type: &str,
349    ) -> Self {
350        if let Some(wrap) = &method.return_wrap {
351            return self.with_explicit_wrap(wrap.clone());
352        }
353        self.nullable = method.returns_option();
354        match self.strategy {
355            ReturnStrategy::ReturnOtherClass => {
356                let return_class = method
357                    .returns_other_class()
358                    .expect("return_class required for ReturnOtherClass strategy");
359                self = self.with_return_class(return_class.to_string());
360            }
361            ReturnStrategy::ReturnOtherClassList => {
362                let return_class = match method.returns_other_class_list() {
363                    Some(ident) => ident.to_string(),
364                    None => {
365                        if !method.returns_self_list() {
    {
        ::core::panicking::panic_fmt(format_args!("return_class required for ReturnOtherClassList strategy"));
    }
};assert!(
366                            method.returns_self_list(),
367                            "return_class required for ReturnOtherClassList strategy"
368                        );
369                        self_rust_type.to_string()
370                    }
371                };
372                self = self.with_return_class(return_class);
373            }
374            _ => {}
375        }
376        self
377    }
378
379    /// Mark `.val` as `NULL` for `None` (an `Option` return; see `nullable`).
380    /// [`with_return_class_from_method`](Self::with_return_class_from_method)
381    /// sets it for inherent methods.
382    pub fn with_nullable(mut self, nullable: bool) -> Self {
383        self.nullable = nullable;
384        self
385    }
386
387    /// Select an explicit class constructor from a marker or wrap attribute.
388    pub fn with_explicit_wrap(mut self, wrap: crate::return_wrap::ReturnWrap) -> Self {
389        self.explicit_wrap = Some(wrap);
390        self
391    }
392
393    /// Set the variable name to return for chaining (default: "self").
394    pub fn with_chain_var(mut self, var: String) -> Self {
395        self.chain_var = Some(var);
396        self
397    }
398
399    /// Set indentation level (number of spaces).
400    pub fn with_indent(mut self, indent: usize) -> Self {
401        self.indent = indent;
402        self
403    }
404
405    fn return_other_class_expr(&self) -> String {
406        let return_class = self
407            .return_class
408            .as_ref()
409            .expect("return_class required for ReturnOtherClass strategy");
410        self.null_passes(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".__MX_WRAP_RETURN_{0}__(.val)",
                return_class))
    })format!(".__MX_WRAP_RETURN_{return_class}__(.val)"))
411    }
412
413    /// Pass a `NULL` `.val` (an `Option` method's `None`) through the
414    /// cross-class wrap `expr`: no constructor takes `NULL`, and `lapply()`
415    /// would turn it into `list()`.
416    fn null_passes(&self, expr: String) -> String {
417        if self.nullable {
418            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (is.null(.val)) NULL else {0}",
                expr))
    })format!("if (is.null(.val)) NULL else {expr}")
419        } else {
420            expr
421        }
422    }
423
424    /// List-shaped sibling of
425    /// [`return_other_class_expr`](Self::return_other_class_expr) (#1284).
426    ///
427    /// The `LIST_` infix keeps the marker family disjoint from the scalar
428    /// `.__MX_WRAP_RETURN_` prefix: the scalar resolver rewrites unrecognized
429    /// markers of its own prefix to the bare expression, so a shared prefix
430    /// would destroy list markers before the list resolver saw them.
431    fn return_other_class_list_expr(&self) -> String {
432        let return_class = self
433            .return_class
434            .as_ref()
435            .expect("return_class required for ReturnOtherClassList strategy");
436        self.null_passes(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".__MX_WRAP_LIST_RETURN_{0}__(.val)",
                return_class))
    })format!(".__MX_WRAP_LIST_RETURN_{return_class}__(.val)"))
437    }
438
439    // region: Core shared build path
440
441    /// Emit:
442    /// ```text
443    /// <indent>.val <- <call_expr>
444    /// <indent>if (inherits(.val, ...) ...) return(...)
445    /// <tail lines — .val is live>
446    /// ```
447    ///
448    /// All paths capture the `.Call()` result, dispatch on the tagged
449    /// condition value if present, and then run the class-specific tail.
450    fn build_with_tails(&self, tails: ReturnTails<'_>) -> Vec<String> {
451        let indent = " ".repeat(self.indent);
452        let class_name = self.class_name.as_deref().unwrap_or("");
453        let call_expr = &self.call_expr;
454
455        let mut lines = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val <- {1}", indent,
                                call_expr))
                    })]))vec![format!("{}.val <- {}", indent, call_expr)];
456        lines.extend(condition_check_lines_with_default(
457            &indent,
458            &self.raise_default,
459        ));
460        if let Some(wrap) = &self.explicit_wrap {
461            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{1}{0}",
                wrap.r_expression(".val", self.class_name.as_deref()),
                indent))
    })format!(
462                "{indent}{}",
463                wrap.r_expression(".val", self.class_name.as_deref())
464            ));
465            return self.apply_visibility(lines);
466        }
467        // An `Option<Self>` / `Option<&mut Self>` return's `None` is `NULL`;
468        // neither the class wrap nor the receiver stands in for it.
469        if self.nullable
470            && #[allow(non_exhaustive_omitted_patterns)] match self.strategy {
    ReturnStrategy::ReturnSelf | ReturnStrategy::ChainableMutation => true,
    _ => false,
}matches!(
471                self.strategy,
472                ReturnStrategy::ReturnSelf | ReturnStrategy::ChainableMutation
473            )
474        {
475            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}if (is.null(.val)) return(NULL)",
                indent))
    })format!("{indent}if (is.null(.val)) return(NULL)"));
476        }
477        match self.strategy {
478            ReturnStrategy::ReturnSelf => {
479                lines.extend((tails.self_tail)(&indent, class_name));
480            }
481            ReturnStrategy::ChainableMutation => {
482                lines.extend((tails.chain_tail)(&indent));
483            }
484            ReturnStrategy::ReturnOtherClass => {
485                lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                self.return_other_class_expr()))
    })format!("{}{}", indent, self.return_other_class_expr()));
486            }
487            ReturnStrategy::ReturnOtherClassList => {
488                lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                self.return_other_class_list_expr()))
    })format!("{}{}", indent, self.return_other_class_list_expr()));
489            }
490            ReturnStrategy::Direct => {
491                lines.extend((tails.direct_tail)(&indent));
492            }
493        }
494        self.apply_visibility(lines)
495    }
496
497    // endregion
498
499    /// Build R code lines for the method body.
500    ///
501    /// Returns a vector of strings, one per line (without trailing newlines).
502    pub fn build(&self) -> Vec<String> {
503        let chain_var = self.chain_var.as_deref().unwrap_or("self").to_owned();
504        self.build_with_tails(ReturnTails {
505            self_tail: Box::new(|indent, class_name| {
506                if !!class_name.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("class_name required for ReturnSelf strategy"));
    }
};assert!(
507                    !class_name.is_empty(),
508                    "class_name required for ReturnSelf strategy"
509                );
510                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}class(.val) <- \"{1}\"",
                                indent, class_name))
                    }),
                ::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![
511                    format!("{}class(.val) <- \"{}\"", indent, class_name),
512                    format!("{}.val", indent),
513                ]
514            }),
515            chain_tail: Box::new(move |indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                                chain_var))
                    })]))vec![format!("{}{}", indent, chain_var)]),
516            direct_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![format!("{}.val", indent)]),
517        })
518    }
519}
520
521/// Specialized builders for different class systems.
522impl MethodReturnBuilder {
523    /// Build R6-style return (chains via `self`; `invisible(self)` only when
524    /// the method is marked invisible, #1213).
525    pub fn build_r6_body(&self) -> Vec<String> {
526        self.build_with_tails(ReturnTails {
527            self_tail: Box::new(|indent, class_name| {
528                if !!class_name.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("class_name required for ReturnSelf strategy"));
    }
};assert!(
529                    !class_name.is_empty(),
530                    "class_name required for ReturnSelf strategy"
531                );
532                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}$new(.ptr = .val)",
                                indent, class_name))
                    })]))vec![format!("{}{}$new(.ptr = .val)", indent, class_name)]
533            }),
534            chain_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}self", indent))
                    })]))vec![format!("{}self", indent)]),
535            direct_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![format!("{}.val", indent)]),
536        })
537    }
538
539    /// Build S3-style return (uses structure() for Self returns).
540    pub fn build_s3_body(&self) -> Vec<String> {
541        let chain_var = self.chain_var.as_deref().unwrap_or("x").to_owned();
542        self.build_with_tails(ReturnTails {
543            self_tail: Box::new(|indent, class_name| {
544                if !!class_name.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("class_name required for ReturnSelf strategy"));
    }
};assert!(
545                    !class_name.is_empty(),
546                    "class_name required for ReturnSelf strategy"
547                );
548                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}structure(.val, class = \"{1}\")",
                                indent, class_name))
                    })]))vec![format!(
549                    "{}structure(.val, class = \"{}\")",
550                    indent, class_name
551                )]
552            }),
553            chain_tail: Box::new(move |indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                                chain_var))
                    })]))vec![format!("{}{}", indent, chain_var)]),
554            direct_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![format!("{}.val", indent)]),
555        })
556    }
557
558    /// Build S7-style method body lines (creates new S7 object with .ptr).
559    ///
560    /// Returns lines suitable for embedding inside an outer `function(...) { ... }`
561    /// block — unlike [`build_s7_inline`](Self::build_s7_inline) which wraps the
562    /// body in its own `{ }` and is intended for callers that emit
563    /// `function(...) <expr>` directly (e.g., S7 `convert` definitions).
564    pub fn build_s7_body(&self) -> Vec<String> {
565        // The chained-mutation tail returns the receiver. The S7 generic method
566        // names its receiver `x`; the per-class fast-path shortcut (#949) names
567        // it `self`. Honour `chain_var` (default `x`) so both reuse this body.
568        let chain_var = self.chain_var.as_deref().unwrap_or("x").to_owned();
569        self.build_with_tails(ReturnTails {
570            self_tail: Box::new(|indent, class_name| {
571                if !!class_name.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("class_name required for ReturnSelf strategy"));
    }
};assert!(
572                    !class_name.is_empty(),
573                    "class_name required for ReturnSelf strategy"
574                );
575                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}(.ptr = .val)",
                                indent, class_name))
                    })]))vec![format!("{}{}(.ptr = .val)", indent, class_name)]
576            }),
577            chain_tail: Box::new(move |indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                                chain_var))
                    })]))vec![format!("{}{}", indent, chain_var)]),
578            direct_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![format!("{}.val", indent)]),
579        })
580    }
581
582    /// Build S4-style method body lines (uses methods::new() to wrap Self returns).
583    ///
584    /// Returns lines suitable for embedding inside an outer
585    /// `function(...) { ... }` block, mirroring [`build_s7_body`](Self::build_s7_body).
586    pub fn build_s4_body(&self) -> Vec<String> {
587        let chain_var = self.chain_var.as_deref().unwrap_or("x");
588        self.build_with_tails(ReturnTails {
589            self_tail: Box::new(|indent, class_name| {
590                if !!class_name.is_empty() {
    {
        ::core::panicking::panic_fmt(format_args!("class_name required for ReturnSelf strategy"));
    }
};assert!(
591                    !class_name.is_empty(),
592                    "class_name required for ReturnSelf strategy"
593                );
594                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}methods::new(\"{1}\", ptr = .val)",
                                indent, class_name))
                    })]))vec![format!(
595                    "{}methods::new(\"{}\", ptr = .val)",
596                    indent, class_name
597                )]
598            }),
599            chain_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}{1}", indent,
                                chain_var))
                    })]))vec![format!("{indent}{chain_var}")]),
600            direct_tail: Box::new(|indent| ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0}.val", indent))
                    })]))vec![format!("{}.val", indent)]),
601        })
602    }
603
604    /// Build S7-style return (creates new S7 object with .ptr).
605    ///
606    /// Returns a multi-line block expression that performs the condition check
607    /// inline (suitable for S7 property definitions / convert methods that
608    /// require a single expression).
609    pub fn build_s7_inline(&self) -> String {
610        let inner = if let Some(wrap) = &self.explicit_wrap {
611            wrap.r_expression(".val", self.class_name.as_deref())
612        } else {
613            match self.strategy {
614                ReturnStrategy::ReturnSelf => {
615                    let class_name = self
616                        .class_name
617                        .as_ref()
618                        .expect("class_name required for ReturnSelf strategy");
619                    self.null_passes(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}(.ptr = .val)", class_name))
    })format!("{}(.ptr = .val)", class_name))
620                }
621                ReturnStrategy::ChainableMutation => self.null_passes("x".to_string()),
622                ReturnStrategy::ReturnOtherClass => self.return_other_class_expr(),
623                ReturnStrategy::ReturnOtherClassList => self.return_other_class_list_expr(),
624                ReturnStrategy::Direct => ".val".to_string(),
625            }
626        };
627        let inner = self.apply_visibility_expr(inner);
628        condition_check_inline_block(&self.call_expr, &inner, "    ")
629    }
630
631    /// Build S4-style return (uses methods::new()).
632    ///
633    /// Returns a multi-line block expression that performs the condition check
634    /// inline.
635    pub fn build_s4_inline(&self) -> String {
636        let inner = if let Some(wrap) = &self.explicit_wrap {
637            wrap.r_expression(".val", self.class_name.as_deref())
638        } else {
639            match self.strategy {
640                ReturnStrategy::ReturnSelf => {
641                    let class_name = self
642                        .class_name
643                        .as_ref()
644                        .expect("class_name required for ReturnSelf strategy");
645                    self.null_passes(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("methods::new(\"{0}\", ptr = .val)",
                class_name))
    })format!("methods::new(\"{}\", ptr = .val)", class_name))
646                }
647                ReturnStrategy::ChainableMutation => self.null_passes("x".to_string()),
648                ReturnStrategy::ReturnOtherClass => self.return_other_class_expr(),
649                ReturnStrategy::ReturnOtherClassList => self.return_other_class_list_expr(),
650                ReturnStrategy::Direct => ".val".to_string(),
651            }
652        };
653        let inner = self.apply_visibility_expr(inner);
654        condition_check_inline_block(&self.call_expr, &inner, "    ")
655    }
656}
657
658#[cfg(test)]
659mod tests;
660// endregion