Skip to main content

miniextendr_api/
lazy_dots.rs

1//! The dots left unforced: [`LazyDots`].
2//!
3//! A `&Dots` parameter receives `list(...)`, which the generated wrapper
4//! evaluates before the Rust body runs: every element is forced, and an empty
5//! argument (`f(a = )`, `x[1, , , ]`) stops the call with R's "argument is
6//! missing, with no default". A parameter typed `LazyDots` is R's `...` too,
7//! but the wrapper passes its own frame, `environment()`, in place of
8//! `list(...)`, and nothing is evaluated on the way in. The body counts and
9//! names the dots, sees which are empty, reads what was written, and forces
10//! the elements it wants, each in its own environment.
11//!
12//! ```ignore
13//! use miniextendr_api::{LazyDots, SEXP, miniextendr};
14//!
15//! /// `subset()` for whole rows only: anything in `...` is refused before it
16//! /// is evaluated, so `subset(x, TRUE, select = ID)` reports `select`, not
17//! /// "object 'ID' not found".
18//! #[miniextendr(s3(generic = "subset", class = "my_tbl"))]
19//! pub fn my_tbl_subset(x: SEXP, subset: SEXP, rest: LazyDots) -> Result<SEXP, String> {
20//!     if !rest.is_empty() {
21//!         let names: Vec<_> = rest.names().into_iter().map(|n| n.unwrap_or("")).collect();
22//!         return Err(format!("unsupported arguments: {}", names.join(", ")));
23//!     }
24//!     // ...
25//! #   Ok(x)
26//! }
27//! ```
28//!
29//! | method | R equivalent | forces |
30//! |---|---|---|
31//! | [`len`](LazyDots::len) / [`is_empty`](LazyDots::is_empty) | `...length()` | nothing |
32//! | [`names`](LazyDots::names) | `...names()` | nothing |
33//! | [`is_missing_arg(i)`](LazyDots::is_missing_arg) | element `i` of `substitute(list(...))` is the empty argument | nothing |
34//! | [`expr(i)`](LazyDots::expr) | element `i` of `substitute(list(...))` | nothing |
35//! | [`force(i)`](LazyDots::force) | `..k` (`k = i + 1`) | element `i` |
36//! | [`try_force(i)`](LazyDots::try_force) | `..k`, with an R error returned as `Err` | element `i` |
37//! | [`forward(call)`](LazyDots::forward) | `f(<args>, ...)`, evaluated in the frame | what `f` forces |
38//! | [`try_forward(call)`](LazyDots::try_forward) | the same, with an R error returned as `Err` | what `f` forces |
39//! | [`next_method()`](LazyDots::next_method) | `NextMethod()` | what the next method forces |
40//!
41//! Indices are 0-based, as in [`List::get_index`](crate::List::get_index):
42//! element `i` is R's `..k` with `k = i + 1`. An index past the end panics
43//! before anything is read or forced for it, as slice indexing does.
44//!
45//! # Forwarded dots
46//!
47//! Each element keeps the environment it was written in, also when another
48//! function passed its own dots on (`g <- function(...) f(x, ...)`):
49//! `force(i)` evaluates `..k` in the wrapper's frame, which forces the
50//! element's promise in that environment, and `expr(i)` gives what the caller
51//! of `g` wrote.
52//!
53//! # Passing the dots on
54//!
55//! [`forward`](LazyDots::forward) evaluates an [`RCall`] in the wrapper's
56//! frame with `...` added last (or where [`RCall::dots`] put it), so R hands
57//! the callee each element as the promise it matched: unevaluated, with the
58//! environment it was written in. A data-masking function evaluates them
59//! itself, against its own data, as when R code writes `f(x, ...)`:
60//!
61//! ```ignore
62//! use miniextendr_api::{LazyDots, SEXP, expression::RCall, miniextendr};
63//!
64//! /// `group_by()` on the plain data frame inside the object.
65//! #[miniextendr(s3(generic = "group_by", class = "my_results"))]
66//! pub fn my_results_group_by(data: SEXP, rest: LazyDots) -> Result<SEXP, String> {
67//!     let plain = plain_frame(data); // the data frame inside the object
68//!     // `dplyr::group_by(plain, ...)`; dplyr is only suggested.
69//!     let group_by = unsafe { RCall::namespaced("dplyr", "group_by") }
70//!         .map_err(|e| e.to_string())?;
71//!     Ok(rest.forward(group_by.arg(plain)))
72//! }
73//! ```
74//!
75//! [`next_method`](LazyDots::next_method) evaluates `NextMethod()` in the
76//! frame. The frame is the S3 method's own, so R finds the dispatch it is in
77//! and calls the next method with the method's arguments, the dots passed on
78//! unevaluated.
79//!
80//! # Empty and missing elements
81//!
82//! [`is_missing_arg(i)`](LazyDots::is_missing_arg) is `true` only for a
83//! literally empty slot, the element R stores as the missing argument:
84//! `f(a = )`, the fourth position of `x[1, , , ]`. That is what R 4.6's
85//! `R_GetDotType()` reports as missing.
86//!
87//! An element forwarded from a missing argument is not empty: in
88//! `g <- function(a) f(1, a)` called as `g()`, element 1 is the promise of
89//! `a`, so `is_missing_arg(1)` is `false`. Forcing it raises R's own error,
90//! `argument "a" is missing, with no default`, as `list(...)` would.
91//!
92//! Forcing an empty element raises R's error for it,
93//! `argument "..k" is missing, with no default` (class `missingArgError`
94//! from R 4.6, `simpleError` before), whose call is the generated function's
95//! own call. A body that accepts empty arguments checks `is_missing_arg(i)`
96//! before forcing.
97//!
98//! # Conditions
99//!
100//! [`force`](LazyDots::force) evaluates through
101//! [`eval_with_handlers`]: the caller's
102//! `withCallingHandlers()` / `suppressWarnings()` see what forcing signals,
103//! and an error, or the exit of a `tryCatch()` handler, unwinds through the
104//! Rust frames (running their destructors) and carries on in R as the
105//! original condition. [`try_force`](LazyDots::try_force) evaluates through
106//! [`try_eval_with_handlers`]:
107//! warnings and messages still reach the caller's handlers, and an R error
108//! comes back as `Err(`[`REvalError`]`)` for the body to handle. The calls
109//! behind `len`, `names`, `is_missing_arg` and `expr` evaluate through
110//! `eval_with_handlers` too, never a bare `Rf_eval`.
111//!
112//! # Caching
113//!
114//! - **Forced values are not cached in Rust.** R stores a promise's value in
115//!   the promise, so forcing an element twice evaluates it once and the
116//!   second `force(i)` returns the stored value.
117//! - **The count and the expressions are read once per value.** The first
118//!   call that needs them evaluates `...length()` or `substitute(list(...))`,
119//!   and the value keeps the result, so a loop over the elements copies the
120//!   expressions once.
121//!
122//! # Restrictions
123//!
124//! - Main thread only: the type is `!Send` and borrows the `.Call()`
125//!   argument for the call.
126//! - It takes no per-parameter option (`default`, `coerce`, `match_arg`,
127//!   `no_na`, ...) and no `dots = typed_list!(..)`, which validates a forced
128//!   list. `&LazyDots`, `Option<LazyDots>` and `Missing<LazyDots>` are compile
129//!   errors: the parameter is always present, taken by value.
130//! - A function takes at most one `...`: `&Dots` or `LazyDots`, not both.
131//! - Standalone functions, `s3(...)` functions, and the impl-block methods of
132//!   every class system (env, R6, S3, S4, S7, vctrs) accept it. Trait methods
133//!   and `extern "C-unwind"` functions do not.
134//!
135//! # GC
136//!
137//! The frame is a `.Call()` argument, which R protects for the call, and the
138//! dots are its `...` binding: the promises and the values they cache stay
139//! reachable until the call returns. The list `substitute(list(...))` gives
140//! is rooted by the `LazyDots` value (`R_PreserveObject`) and released when it
141//! drops.
142
143use std::cell::{Cell, OnceCell};
144use std::ffi::{CStr, CString};
145use std::marker::PhantomData;
146
147use crate::expression::{RCall, REvalError, eval_with_handlers, try_eval_with_handlers};
148use crate::from_r::charsxp_to_str;
149use crate::gc_protect::OwnedProtect;
150use crate::sexp_ext::PairListExt;
151use crate::{SEXP, SEXPTYPE, SexpExt};
152
153/// The expressions `substitute(list(...))` gives in the frame, read once.
154struct Exprs {
155    /// Roots the `list(...)` call until the [`LazyDots`] drops; only held.
156    _root: crate::Preserved,
157    /// Its arguments, one per element; rooted by `_root`.
158    elts: Vec<SEXP>,
159}
160
161/// R's `...`, passed unforced: the generated wrapper's own frame, whose `...`
162/// binding holds the dots as R matched them.
163///
164/// Take it as a `#[miniextendr]` parameter, at the position of `...`:
165/// `fn f(x: SEXP, rest: LazyDots)` becomes `f <- function(x, ...)`, and the
166/// wrapper passes `environment()` where a `&Dots` parameter's passes
167/// `list(...)`. See the [module docs](self).
168///
169/// ```ignore
170/// use miniextendr_api::{LazyDots, SEXP, miniextendr};
171///
172/// /// `update()` with a step per named argument; an empty `select = ` runs the
173/// /// step without its argument.
174/// #[miniextendr(s3(generic = "update", class = "my_obj"))]
175/// pub fn my_obj_update(object: SEXP, rest: LazyDots) -> SEXP {
176///     for (i, name) in rest.names().into_iter().enumerate() {
177///         let arg = if rest.is_missing_arg(i) { None } else { Some(rest.force(i)) };
178///         // ... run step `name` with `arg`
179/// #       let _ = (name, arg);
180///     }
181///     object
182/// }
183/// ```
184pub struct LazyDots<'a> {
185    /// The generated wrapper's frame (`environment()`).
186    env: SEXP,
187    /// `...length()`, read once.
188    len: Cell<Option<usize>>,
189    /// `substitute(list(...))`, read once.
190    exprs: OnceCell<Exprs>,
191    /// Borrows the `.Call()` argument that roots the frame.
192    _arg: PhantomData<&'a SEXP>,
193    /// R objects stay on R's main thread.
194    _not_send: PhantomData<*const ()>,
195}
196
197impl<'a> LazyDots<'a> {
198    /// Read the argument a generated wrapper passed for a `LazyDots`
199    /// parameter: its own frame. Generated code calls this; user code has no
200    /// reason to.
201    ///
202    /// # Panics
203    ///
204    /// When `arg` is not an environment.
205    ///
206    /// # Safety
207    ///
208    /// R's main thread; `arg` is the frame of a function with a `...` formal
209    /// and stays rooted for `'a`.
210    #[doc(hidden)]
211    pub unsafe fn from_wrapper_arg(arg: &'a SEXP) -> Self {
212        let env = *arg;
213        if !env.is_environment() {
    {
        ::core::panicking::panic_fmt(format_args!("a `LazyDots` argument is passed as `environment()`, got {0}",
                env.type_of().type_name()));
    }
};assert!(
214            env.is_environment(),
215            "a `LazyDots` argument is passed as `environment()`, got {}",
216            env.type_of().type_name(),
217        );
218        LazyDots {
219            env,
220            len: Cell::new(None),
221            exprs: OnceCell::new(),
222            _arg: PhantomData,
223            _not_send: PhantomData,
224        }
225    }
226
227    /// The number of elements in `...` (`...length()`). Empty elements count.
228    /// Forces nothing.
229    pub fn len(&self) -> usize {
230        if let Some(n) = self.len.get() {
231            return n;
232        }
233        // SAFETY: a `LazyDots` exists only inside the `#[miniextendr]` call
234        // that received it, on R's main thread; the frame is rooted by it.
235        let n = unsafe {
236            let n = OwnedProtect::new(self.call_in_frame(c"...length", None));
237            n.get()
238                .as_integer()
239                .and_then(|n| usize::try_from(n).ok())
240                .expect("`...length()` is a non-negative integer")
241        };
242        self.len.set(Some(n));
243        n
244    }
245
246    /// Whether `...` holds no elements (`...length() == 0`).
247    pub fn is_empty(&self) -> bool {
248        self.len() == 0
249    }
250
251    /// The name of each element, as written (`...names()`): `None` for an
252    /// unnamed one, every entry `None` when nothing is named. A repeated name
253    /// is kept (`f(a = 1, a = 2)` gives `a`, `a`). Forces nothing.
254    ///
255    /// The names are the print names of the argument tags, which R keeps for
256    /// the session.
257    pub fn names(&self) -> Vec<Option<&'a str>> {
258        let n = self.len();
259        // SAFETY: as in `len`. The strings are symbol print names (or the
260        // empty string), which R never collects.
261        unsafe {
262            let names = OwnedProtect::new(self.call_in_frame(c"...names", None));
263            let names = names.get();
264            if names.type_of() != SEXPTYPE::STRSXP {
265                return ::alloc::vec::from_elem(None, n)vec![None; n];
266            }
267            (0..n)
268                .map(|i| {
269                    let elt = names.string_elt(r_index(i));
270                    if elt.is_na_string() {
271                        return None;
272                    }
273                    let name = charsxp_to_str(elt);
274                    (!name.is_empty()).then_some(name)
275                })
276                .collect()
277        }
278    }
279
280    /// Whether element `i` is empty: the slot of `f(a = )` or of the fourth
281    /// position of `x[1, , , ]`, which R stores as the missing argument.
282    /// Forces nothing.
283    ///
284    /// Only a literally empty slot counts. An element forwarded from a
285    /// missing argument (`g <- function(a) f(1, a)` called as `g()`) is the
286    /// promise of `a`, so this is `false`; forcing it raises
287    /// `argument "a" is missing, with no default`.
288    ///
289    /// The first call reads `substitute(list(...))` in the frame; the value
290    /// keeps it, so a loop over the elements reads it once.
291    ///
292    /// # Panics
293    ///
294    /// When `i >= self.len()`.
295    pub fn is_missing_arg(&self, i: usize) -> bool {
296        self.expr(i) == SEXP::missing_arg()
297    }
298
299    /// The expression element `i` was written as, as `substitute(list(...))`
300    /// gives it: a promise's expression (for a forwarded element, what the
301    /// caller of the forwarding function wrote), a constant as it is, and the
302    /// empty symbol (`quote(expr = )`) for an empty element. Forces nothing.
303    ///
304    /// The expression is rooted by this value: it stays valid until the
305    /// `LazyDots` drops. The first call reads `substitute(list(...))` in the
306    /// frame; later calls reuse it.
307    ///
308    /// # Panics
309    ///
310    /// When `i >= self.len()`.
311    pub fn expr(&self, i: usize) -> SEXP {
312        self.check_index(i);
313        self.exprs().elts[i]
314    }
315
316    /// Force element `i` and return its value, as `..k` (`k = i + 1`) gives it
317    /// in the frame: a promise is forced in its own environment, and a
318    /// constant comes back as it is.
319    ///
320    /// Evaluated with
321    /// [`eval_with_handlers`]: the
322    /// caller's handlers see what forcing signals, and an R error unwinds as
323    /// the original condition. An empty element raises
324    /// `argument "..k" is missing, with no default`; check
325    /// [`Self::is_missing_arg`] first to accept one.
326    ///
327    /// R caches a promise's value, so forcing an element again returns the
328    /// stored value without evaluating anything. The value is reachable from
329    /// the frame (the promise, or the `...` binding itself) until the call
330    /// returns.
331    ///
332    /// # Panics
333    ///
334    /// When `i >= self.len()`, before anything is forced.
335    pub fn force(&self, i: usize) -> SEXP {
336        self.check_index(i);
337        // SAFETY: as in `len`; the symbol needs no root.
338        unsafe { eval_with_handlers(dot_symbol(i), self.env) }
339    }
340
341    /// [`Self::force`], but an R error raised while forcing comes back as
342    /// `Err(`[`REvalError`]`)` instead of unwinding.
343    ///
344    /// Evaluated with
345    /// [`try_eval_with_handlers`]:
346    /// warnings and messages still reach the caller's handlers, and any other
347    /// exit (a `tryCatch()` handler's, a restart) unwinds as under
348    /// [`Self::force`]. An empty element is an `Err` with R's
349    /// `argument "..k" is missing, with no default`.
350    ///
351    /// # Panics
352    ///
353    /// When `i >= self.len()`, before anything is forced.
354    pub fn try_force(&self, i: usize) -> Result<SEXP, REvalError> {
355        self.check_index(i);
356        // SAFETY: as in `force`.
357        unsafe { try_eval_with_handlers(dot_symbol(i), self.env) }
358    }
359
360    /// Evaluate `call` in the generated wrapper's frame with the dots passed
361    /// on: `f(<args>, ...)`, so the callee gets each element as the promise R
362    /// matched, unevaluated, with the environment it was written in.
363    /// `...` goes last unless [`RCall::dots`] put it elsewhere. Nothing is
364    /// forced here; the callee forces what it reads, or evaluates the
365    /// expressions against its own data (a data-masking function).
366    ///
367    /// Evaluated with [`eval_with_handlers`], as `f(x, ...)` in the wrapper
368    /// would be: the caller's handlers see the callee's conditions, and an R
369    /// error unwinds through the Rust frames as the callee raised it.
370    ///
371    /// Arguments added with [`RCall::arg_value`] / [`RCall::named_value`] are
372    /// values. A symbol or call added with [`RCall::arg`] is evaluated in the
373    /// frame, where the wrapper's formals are bound, so pass a language
374    /// object with [`RCall::quoted_arg`]. A function named by a symbol
375    /// ([`RCall::new`]) is looked up from the frame, as code in the wrapper
376    /// would look it up: the package's namespace, its imports, then base and
377    /// the search path. Name a function from a package that is only
378    /// suggested with [`RCall::namespaced`].
379    ///
380    /// The value is unprotected.
381    pub fn forward(&self, call: RCall) -> SEXP {
382        let call = with_dots(call);
383        // SAFETY: as in `len`; the frame has a `...` binding.
384        unsafe { call.eval_with_handlers(self.env) }
385    }
386
387    /// [`Self::forward`], but an R error raised by the call comes back as
388    /// `Err(`[`REvalError`]`)` instead of unwinding.
389    ///
390    /// Evaluated with [`try_eval_with_handlers`]: warnings and messages still
391    /// reach the caller's handlers, and any other exit (a `tryCatch()`
392    /// handler's, a restart) unwinds as under [`Self::forward`].
393    pub fn try_forward(&self, call: RCall) -> Result<SEXP, REvalError> {
394        let call = with_dots(call);
395        // SAFETY: as in `forward`.
396        unsafe { call.try_eval_with_handlers(self.env) }
397    }
398
399    /// Call the next S3 method: `NextMethod()`, evaluated in the generated
400    /// wrapper's frame. The frame is the S3 method's own, so R finds the
401    /// dispatch it is in (`.Generic`, `.Class`) and calls the next method
402    /// with the method's arguments as R matched them, the dots passed on
403    /// unevaluated.
404    ///
405    /// Only an S3 method has a dispatch to continue: an `s3(...)` function or
406    /// an S3 (or vctrs) impl-block method. Anywhere else R raises its own
407    /// `NextMethod` error ("generic function not specified"). Conditions
408    /// behave as under [`Self::forward`]. The value is unprotected.
409    pub fn next_method(&self) -> SEXP {
410        // SAFETY: as in `len`.
411        unsafe { self.call_in_frame(c"NextMethod", None) }
412    }
413
414    /// Panic on an index past the end, as slice indexing does.
415    fn check_index(&self, i: usize) {
416        let len = self.len();
417        if !(i < len) {
    {
        ::core::panicking::panic_fmt(format_args!("index out of bounds: `...` has {1} element{0} but the index is {2}",
                if len == 1 { "" } else { "s" }, len, i));
    }
};assert!(
418            i < len,
419            "index out of bounds: `...` has {len} element{} but the index is {i}",
420            if len == 1 { "" } else { "s" },
421        );
422    }
423
424    /// `substitute(list(...))` in the frame, read on first use and rooted
425    /// until the value drops.
426    fn exprs(&self) -> &Exprs {
427        self.exprs.get_or_init(|| {
428            // SAFETY: as in `len`. The list is rooted until the value drops.
429            unsafe {
430                let list_call = OwnedProtect::new(crate::sys::Rf_lang2(
431                    crate::sys::Rf_install(c"list".as_ptr()),
432                    crate::sys::Rf_install(c"...".as_ptr()),
433                ));
434                let list =
435                    crate::Preserved::new(self.call_in_frame(c"substitute", Some(list_call.get())));
436                let mut elts = Vec::with_capacity(self.len());
437                let mut node = list.cdr();
438                while node.type_of() != SEXPTYPE::NILSXP {
439                    elts.push(node.car());
440                    node = node.cdr();
441                }
442                if true {
    {
        match (&elts.len(), &self.len()) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(elts.len(), self.len());
443                Exprs { _root: list, elts }
444            }
445        })
446    }
447
448    /// Evaluate `fun()` or `fun(arg)` in the frame, `fun` the base function of
449    /// that name. The call's head is the function itself, looked up in the
450    /// base environment, so no formal or variable named like it can shadow
451    /// it; `arg` is passed as written (the functions used here either take no
452    /// argument or don't evaluate it).
453    ///
454    /// # Safety
455    ///
456    /// R's main thread, inside the `#[miniextendr]` call; `arg` is rooted.
457    /// The result is unprotected.
458    unsafe fn call_in_frame(&self, fun: &CStr, arg: Option<SEXP>) -> SEXP {
459        unsafe {
460            // A base binding: base keeps the function for the session.
461            let fun =
462                eval_with_handlers(crate::sys::Rf_install(fun.as_ptr()), crate::sys::R_BaseEnv);
463            let call = OwnedProtect::new(match arg {
464                None => crate::sys::Rf_lang1(fun),
465                Some(arg) => crate::sys::Rf_lang2(fun, arg),
466            });
467            eval_with_handlers(call.get(), self.env)
468        }
469    }
470}
471
472impl std::fmt::Debug for LazyDots<'_> {
473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474        f.debug_struct("LazyDots")
475            .field("len", &self.len.get())
476            .finish_non_exhaustive()
477    }
478}
479
480/// `call` with `...` added last, unless it already passes the dots.
481fn with_dots(call: RCall) -> RCall {
482    if call.has_dots() { call } else { call.dots() }
483}
484
485/// The symbol `..k` for 0-based element `i` (`k = i + 1`).
486///
487/// # Safety
488///
489/// R's main thread. Symbols are never collected.
490unsafe fn dot_symbol(i: usize) -> SEXP {
491    let name = CString::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("..{0}", i + 1))
    })format!("..{}", i + 1)).expect("no NUL in `..k`");
492    unsafe { crate::sys::Rf_install(name.as_ptr()) }
493}
494
495/// An R vector index.
496fn r_index(i: usize) -> isize {
497    isize::try_from(i).expect("an R vector index fits in isize")
498}