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 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 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 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 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(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}