Skip to main content

miniextendr_api/
quoted.rs

1//! Unevaluated arguments: the [`Quoted`] and [`Quosure`] parameter markers.
2//!
3//! A generated R wrapper forces every argument it hands to `.Call()`, so a
4//! Rust body normally sees values only. A parameter typed `Quoted` or
5//! `Quosure` is passed unevaluated instead: the body receives the argument's
6//! expression and the environment to evaluate it in, and decides what to do
7//! with them. That is what `subset()`-style methods and APIs taking bare
8//! column names (`f(data, TIME)` as well as `f(data, "TIME")`) need.
9//!
10//! | marker | the wrapper passes | needs | `{{ x }}` / `!!x` |
11//! |---|---|---|---|
12//! | [`Quoted`] | `list(substitute(x), parent.frame())` | base R | no |
13//! | [`Quosure`] | `rlang::enquo(x)` | rlang in `Imports` | yes |
14//!
15//! ```ignore
16//! use miniextendr_api::{miniextendr, Quoted, SEXP};
17//!
18//! /// Rows of `data` where `cond` holds; `cond` sees the columns of `data`,
19//! /// then the caller's variables.
20//! #[miniextendr]
21//! pub fn keep_rows(data: SEXP, cond: Quoted) -> Vec<i32> {
22//!     let keep = cond.eval_in(data);
23//!     // ... read the logical vector
24//! #   vec![]
25//! }
26//! ```
27//!
28//! ```r
29//! threshold <- 3
30//! keep_rows(df, value > threshold)
31//! ```
32//!
33//! The wrapper never forces the argument: a `Quoted` / `Quosure` parameter
34//! takes no R-side checks and none of the per-parameter options (`coerce`,
35//! `match_arg`, `choices`, `default`, `no_na`, `inherits`, ...), which are
36//! compile errors on it. A function taking one runs on R's main thread.
37//! It works on free functions, including standalone S3 methods
38//! (`#[miniextendr(s3(generic = "subset", class = "..."))]`): in a method
39//! reached through `UseMethod()`, `parent.frame()` is the generic's caller, as
40//! in base R's `subset.data.frame()`.
41//!
42//! A missing argument is a conversion error naming the parameter
43//! (`argument "cond" is missing, with no default`), like forcing it in R. Take
44//! `Missing<Quoted>` / `Missing<Quosure>` to make the argument optional.
45//!
46//! Evaluation goes through [`crate::expression::eval_with_handlers`]: the
47//! caller's `withCallingHandlers()` / `suppressWarnings()` see what the
48//! expression signals, and an error, or the exit of a `tryCatch()` handler set
49//! up around the call, unwinds through the Rust frames (running their
50//! destructors) and carries on in R as the original condition.
51//!
52//! Both markers borrow from the `.Call()` arguments, which R keeps rooted for
53//! the call; the lifetime keeps them from outliving it. They are `!Send`.
54//!
55//! A `Quosure` can also be read from a quosure found in an R object
56//! ([`Quosure::from_sexp`]), such as one a wrapper wrote into a call with
57//! `c(time = {{ t }})`, or built from an expression and an environment
58//! ([`Quosure::new`], [`Quosure::args`]). It then borrows the root the caller
59//! holds: the SEXP it was read from, or a [`ProtectScope`].
60
61use std::marker::PhantomData;
62
63use crate::expression::RCall;
64use crate::from_r::charsxp_to_str;
65use crate::gc_protect::{OwnedProtect, ProtectScope};
66use crate::missing::Missing;
67use crate::{PairListExt, SEXP, SEXPTYPE, SexpExt};
68
69/// The name an argument expression stands for: a symbol's name, or the value
70/// of a length-1 character literal. `None` for anything else, and for `NA`.
71fn expr_name<'a>(expr: SEXP) -> Option<&'a str> {
72    match expr.type_of() {
73        // Symbols are never collected; their print names live for the session.
74        SEXPTYPE::SYMSXP => {
75            let name = unsafe { charsxp_to_str(expr.printname()) };
76            (!name.is_empty()).then_some(name)
77        }
78        SEXPTYPE::STRSXP if expr.len() == 1 => {
79            let elt = expr.string_elt(0);
80            // The CHARSXP is held by `expr`, which the caller keeps rooted.
81            (!elt.is_na_string()).then(|| unsafe { charsxp_to_str(elt) })
82        }
83        _ => None,
84    }
85}
86
87// region: Quoted
88
89/// An argument passed unevaluated: its expression and the environment it was
90/// written in, as base R's `substitute(x)` and `parent.frame()` give them.
91///
92/// Take it as a `#[miniextendr]` parameter. The generated wrapper keeps the
93/// parameter as an R formal and passes
94/// `if (missing(x)) quote(expr=) else list(substitute(x), parent.frame())`,
95/// which never forces the argument. See the [module docs](self).
96///
97/// ```ignore
98/// use miniextendr_api::{miniextendr, Quoted, SEXP};
99///
100/// #[miniextendr]
101/// pub fn column_name(col: Quoted) -> Option<String> {
102///     col.as_name().map(str::to_owned)   // column_name(TIME), column_name("TIME")
103/// }
104///
105/// #[miniextendr]
106/// pub fn evaluate(expr: Quoted) -> SEXP {
107///     expr.eval()                          // eval(expr, env)
108/// }
109/// ```
110///
111/// `parent.frame()` is the environment of the function that called the
112/// wrapper, which is where the expression was written when the user calls the
113/// wrapper directly or through `UseMethod()`. A wrapper reached through a
114/// function that forwards its own argument (`g <- function(y) f(y)`) receives
115/// `y` and `g`'s frame, as `subset()` does. To resolve forwarding, `{{ x }}`
116/// and `!!x`, take a [`Quosure`].
117pub struct Quoted<'a> {
118    expr: SEXP,
119    env: SEXP,
120    /// Borrows the `.Call()` argument that roots `expr` and `env`.
121    _arg: PhantomData<&'a SEXP>,
122    /// R objects stay on R's main thread.
123    _not_send: PhantomData<*const ()>,
124}
125
126impl<'a> Quoted<'a> {
127    /// Read the argument a generated wrapper passed for a `Quoted` parameter:
128    /// `None` when the argument is missing (`quote(expr=)`). Generated code
129    /// calls this; user code has no reason to.
130    ///
131    /// # Panics
132    ///
133    /// When `arg` is not the wrapper's `list(<expr>, <environment>)`.
134    ///
135    /// # Safety
136    ///
137    /// R's main thread; `arg` stays rooted for `'a`.
138    #[doc(hidden)]
139    pub unsafe fn from_wrapper_arg(arg: &'a SEXP) -> Option<Self> {
140        let arg = *arg;
141        if arg == SEXP::missing_arg() {
142            return None;
143        }
144        if !(arg.type_of() == SEXPTYPE::VECSXP && arg.len() == 2 &&
            arg.vector_elt(1).is_environment()) {
    {
        ::core::panicking::panic_fmt(format_args!("a `Quoted` argument is passed as `list(substitute(x), parent.frame())`, got {0}",
                arg.type_of().type_name()));
    }
};assert!(
145            arg.type_of() == SEXPTYPE::VECSXP
146                && arg.len() == 2
147                && arg.vector_elt(1).is_environment(),
148            "a `Quoted` argument is passed as `list(substitute(x), parent.frame())`, got {}",
149            arg.type_of().type_name(),
150        );
151        Some(Quoted {
152            expr: arg.vector_elt(0),
153            env: arg.vector_elt(1),
154            _arg: PhantomData,
155            _not_send: PhantomData,
156        })
157    }
158
159    /// [`Self::from_wrapper_arg`] for a `Missing<Quoted>` parameter.
160    ///
161    /// # Safety
162    ///
163    /// As [`Self::from_wrapper_arg`].
164    #[doc(hidden)]
165    pub unsafe fn missing_from_wrapper_arg(arg: &'a SEXP) -> Missing<Self> {
166        match unsafe { Self::from_wrapper_arg(arg) } {
167            Some(quoted) => Missing::Present(quoted),
168            None => Missing::Absent,
169        }
170    }
171
172    /// The expression as written: a symbol, a call, or a constant
173    /// (`substitute(x)`).
174    #[inline]
175    pub fn expr(&self) -> SEXP {
176        self.expr
177    }
178
179    /// The environment the expression was written in (`parent.frame()`).
180    #[inline]
181    pub fn env(&self) -> SEXP {
182        self.env
183    }
184
185    /// The name the argument stands for: `TIME` and `"TIME"` both give
186    /// `"TIME"`. `None` for any other expression (a call, a number, `NA`).
187    #[inline]
188    pub fn as_name(&self) -> Option<&'a str> {
189        expr_name(self.expr)
190    }
191
192    /// Evaluate the expression in its environment, `eval(expr, env)`, keeping
193    /// the caller's condition handlers
194    /// ([`eval_with_handlers`](crate::expression::eval_with_handlers)).
195    ///
196    /// The value is unprotected: protect it before the next allocation.
197    pub fn eval(&self) -> SEXP {
198        // SAFETY: a `Quoted` exists only inside the `#[miniextendr]` call that
199        // received it, on R's main thread; its argument roots both SEXPs.
200        unsafe { crate::expression::eval_with_handlers(self.expr, self.env) }
201    }
202
203    /// Evaluate the expression with `data` as a data mask, like
204    /// `eval(expr, data, env)`: the elements of a list or the columns of a
205    /// data frame are visible by name, ahead of the expression's environment.
206    /// The first of two elements with one name wins, and unnamed elements are
207    /// not visible. An environment is used as is (`env` is not consulted),
208    /// and `NULL` evaluates in the expression's environment.
209    ///
210    /// The value is unprotected: protect it before the next allocation.
211    ///
212    /// # Panics
213    ///
214    /// When `data` is not a list, a data frame, an environment or `NULL`.
215    pub fn eval_in(&self, data: SEXP) -> SEXP {
216        // SAFETY: as in `eval`; `data` is rooted by the caller.
217        unsafe { self.eval_masked(data, crate::expression::eval_with_handlers) }
218    }
219
220    /// [`eval_in`](Self::eval_in) with an R error returned as `Err`, as
221    /// [`try_eval_with_handlers`](crate::expression::try_eval_with_handlers)
222    /// returns it: the caller's handlers still see the expression's warnings
223    /// and messages. The mask and the rules for `data` are `eval_in`'s.
224    ///
225    /// A `subset()` method can explain a column that doesn't exist and leave
226    /// every other error as R raised it
227    /// ([`REvalError::reraise`](crate::expression::REvalError::reraise)):
228    ///
229    /// ```ignore
230    /// let keep = match subset.try_eval_in(table) {
231    ///     Ok(keep) => keep,
232    ///     // `unknown_column` compares the expression's names with the table's.
233    ///     Err(e) if unknown_column(&subset, table) => {
234    ///         rust_error!(class = "pkg_unknown_column", "{e}; the columns are ...")
235    ///     }
236    ///     // SAFETY: inside the `#[miniextendr]` call.
237    ///     Err(e) => unsafe { e.reraise() },
238    /// };
239    /// ```
240    ///
241    /// The value is unprotected: protect it before the next allocation.
242    ///
243    /// # Panics
244    ///
245    /// When `data` is not a list, a data frame, an environment or `NULL`.
246    pub fn try_eval_in(&self, data: SEXP) -> Result<SEXP, crate::expression::REvalError> {
247        // SAFETY: as in `eval`; `data` is rooted by the caller.
248        unsafe { self.eval_masked(data, crate::expression::try_eval_with_handlers) }
249    }
250
251    /// Evaluate the expression with `data` as a mask, as
252    /// [`eval_in`](Self::eval_in) describes, through `eval`.
253    ///
254    /// # Safety
255    ///
256    /// As `eval`'s: R's main thread, inside a miniextendr boundary; `data`
257    /// rooted by the caller.
258    unsafe fn eval_masked<R>(&self, data: SEXP, eval: unsafe fn(SEXP, SEXP) -> R) -> R {
259        unsafe {
260            match data.type_of() {
261                SEXPTYPE::ENVSXP => eval(self.expr, data),
262                SEXPTYPE::NILSXP => eval(self.expr, self.env),
263                SEXPTYPE::VECSXP => {
264                    // Rooted ahead of the evaluation's own protect frame, so an
265                    // R exit leaves the protect stack as this guard expects.
266                    let mask = OwnedProtect::new(data_mask(data, self.env));
267                    eval(self.expr, mask.get())
268                }
269                other => {
    ::core::panicking::panic_fmt(format_args!("`data` must be a list, a data frame, an environment or NULL, not {0}",
            other.type_name()));
}panic!(
270                    "`data` must be a list, a data frame, an environment or NULL, not {}",
271                    other.type_name()
272                ),
273            }
274        }
275    }
276}
277
278/// A new environment enclosed by `enclos` that binds each named element of
279/// the list `data`, as `eval()` builds one for a list `envir`. Bound in
280/// reverse so the first of two same-named elements wins, as in `eval()`.
281///
282/// # Safety
283///
284/// R's main thread, inside a miniextendr boundary; `data` a rooted list,
285/// `enclos` an environment. The result is unprotected.
286unsafe fn data_mask(data: SEXP, enclos: SEXP) -> SEXP {
287    unsafe {
288        let n = data.len();
289        let size = i32::try_from(n).unwrap_or(i32::MAX);
290        let mask = OwnedProtect::new(crate::sys::R_NewEnv(enclos, crate::Rboolean::TRUE, size));
291        let env = crate::expression::REnv::from_sexp(mask.get());
292        let names = data.get_names();
293        if names.type_of() == SEXPTYPE::STRSXP {
294            for i in (0..n).rev() {
295                let i = isize::try_from(i).expect("list index fits in isize");
296                let name = names.string_elt(i);
297                if name.is_na_string() || name.len() == 0 {
298                    continue;
299                }
300                // `names` is held by `data`; the symbol table keeps the symbol.
301                env.define_symbol(SEXP::install_char(name), data.vector_elt(i));
302            }
303        }
304        mask.get()
305    }
306}
307
308// endregion
309
310// region: Quosure
311
312/// Whether `x` is an rlang quosure: a one-sided formula, `~expr`, of class
313/// `quosure`.
314fn is_quosure(x: SEXP) -> bool {
315    x.is_language() && x.inherits_class(c"quosure")
316}
317
318/// The quosure `x` stands for: `x` itself, or, when its expression is a
319/// quosure too (`rlang::new_quosure(<quosure>)`), the innermost one. `None`
320/// when `x` is not a quosure.
321fn innermost_quosure(x: SEXP) -> Option<SEXP> {
322    if !is_quosure(x) {
323        return None;
324    }
325    let mut quo = x;
326    while is_quosure(quo.cdr().car()) {
327        quo = quo.cdr().car();
328    }
329    Some(quo)
330}
331
332/// The function `rlang::new_quosure`, unprotected.
333///
334/// # Panics
335///
336/// When rlang cannot be loaded.
337///
338/// # Safety
339///
340/// R's main thread.
341unsafe fn rlang_new_quosure() -> SEXP {
342    use crate::sys::{R_BaseEnv, Rf_install};
343    unsafe {
344        // `rlang::new_quosure`; `::` takes both names unevaluated.
345        RCall::new("::")
346            .arg(Rf_install(c"rlang".as_ptr()))
347            .arg(Rf_install(c"new_quosure".as_ptr()))
348            .eval(R_BaseEnv)
349            .unwrap_or_else(|e| {
350                {
    ::core::panicking::panic_fmt(format_args!("a `Quosure` of a bare expression is built with `rlang::new_quosure()`, which needs the rlang package: {0}",
            e));
}panic!(
351                    "a `Quosure` of a bare expression is built with `rlang::new_quosure()`, \
352                     which needs the rlang package: {e}"
353                )
354            })
355    }
356}
357
358/// `new_quosure(quote(<expr>), env)`, unprotected.
359///
360/// # Safety
361///
362/// R's main thread; `new_quosure` (from [`rlang_new_quosure`]), `expr` and
363/// `env` rooted, `env` an environment.
364unsafe fn wrap_in_quosure(new_quosure: SEXP, expr: SEXP, env: SEXP) -> SEXP {
365    unsafe {
366        RCall::from_sexp(new_quosure)
367            .quoted_arg(expr)
368            .arg(env)
369            .eval(crate::sys::R_BaseEnv)
370            .unwrap_or_else(|e| {
    ::core::panicking::panic_fmt(format_args!("`rlang::new_quosure()` failed: {0}",
            e));
}panic!("`rlang::new_quosure()` failed: {e}"))
371    }
372}
373
374/// An argument captured as an rlang quosure, `rlang::enquo(x)`: its expression
375/// and environment, with rlang's argument forwarding and injection resolved.
376///
377/// Take it as a `#[miniextendr]` parameter. The generated wrapper keeps the
378/// parameter as an R formal and passes
379/// `if (missing(x)) quote(expr=) else rlang::enquo(x)`, which never forces the
380/// argument. Unlike [`Quoted`], a wrapper reached through
381/// `function(col) f(data, {{ col }})` or called as `f(data, !!sym)` receives
382/// the expression the user wrote, in the user's environment.
383///
384/// **The package must list rlang in `Imports`.** The macro cannot see
385/// `DESCRIPTION`; without rlang the wrapper fails when it is called.
386///
387/// The expression and environment are read from the quosure directly (a
388/// quosure is a one-sided formula, `~expr`, of class `quosure` whose
389/// environment is its `.Environment` attribute), so [`Self::expr`],
390/// [`Self::env`] and [`Self::as_name`] call no R code.
391/// [`Self::eval_tidy`] evaluates through `rlang::eval_tidy()`, which resolves
392/// nested quosures and the `.data` / `.env` pronouns. Hand [`Self::sexp`] to
393/// other quosure-aware R functions (`tidyselect::eval_select()`) with
394/// [`RCall::eval_with_handlers`](crate::expression::RCall::eval_with_handlers).
395///
396/// An argument holding several selections, a role map such as
397/// `cols = c(time = TAD, dose = NA)`, reads entry by entry with
398/// [`Self::args`]: each argument of the call as a quosure of its own. A
399/// quosure found in an R object reads with [`Self::from_sexp`], and
400/// [`Self::new`] makes one from an expression and an environment.
401pub struct Quosure<'a> {
402    quo: SEXP,
403    expr: SEXP,
404    env: SEXP,
405    /// Borrows the root that keeps the quosure alive: the `.Call()` argument,
406    /// the SEXP it was read from, or a [`ProtectScope`].
407    _arg: PhantomData<&'a SEXP>,
408    /// R objects stay on R's main thread.
409    _not_send: PhantomData<*const ()>,
410}
411
412impl<'a> Quosure<'a> {
413    /// Read a quosure's expression and environment.
414    ///
415    /// # Safety
416    ///
417    /// R's main thread; `quo` is a quosure, rooted for `'a`.
418    unsafe fn read(quo: SEXP) -> Self {
419        let expr = quo.cdr().car();
420        let env = quo.get_attr(unsafe { crate::sys::Rf_install(c".Environment".as_ptr()) });
421        Quosure {
422            quo,
423            expr,
424            env,
425            _arg: PhantomData,
426            _not_send: PhantomData,
427        }
428    }
429
430    /// Read the argument a generated wrapper passed for a `Quosure` parameter:
431    /// `None` when the argument is missing (`quote(expr=)`, or a quosure of
432    /// the missing argument, which `{{ x }}` gives for a missing `x`).
433    /// Generated code calls this; user code has no reason to.
434    ///
435    /// # Panics
436    ///
437    /// When `arg` is not a quosure.
438    ///
439    /// # Safety
440    ///
441    /// R's main thread; `arg` stays rooted for `'a`.
442    #[doc(hidden)]
443    pub unsafe fn from_wrapper_arg(arg: &'a SEXP) -> Option<Self> {
444        let quo = *arg;
445        if quo == SEXP::missing_arg() {
446            return None;
447        }
448        if !is_quosure(quo) {
    {
        ::core::panicking::panic_fmt(format_args!("a `Quosure` argument is passed as `rlang::enquo(x)`, got {0}",
                quo.type_of().type_name()));
    }
};assert!(
449            is_quosure(quo),
450            "a `Quosure` argument is passed as `rlang::enquo(x)`, got {}",
451            quo.type_of().type_name(),
452        );
453        let quosure = unsafe { Self::read(quo) };
454        (quosure.expr != SEXP::missing_arg()).then_some(quosure)
455    }
456
457    /// [`Self::from_wrapper_arg`] for a `Missing<Quosure>` parameter.
458    ///
459    /// # Safety
460    ///
461    /// As [`Self::from_wrapper_arg`].
462    #[doc(hidden)]
463    pub unsafe fn missing_from_wrapper_arg(arg: &'a SEXP) -> Missing<Self> {
464        match unsafe { Self::from_wrapper_arg(arg) } {
465            Some(quosure) => Missing::Present(quosure),
466            None => Missing::Absent,
467        }
468    }
469
470    /// Read a quosure the caller holds: `None` when `sexp` is not one.
471    ///
472    /// A quosure whose expression is a quosure too, which
473    /// `rlang::new_quosure(<quosure>)` makes, is looked through: the result is
474    /// the innermost one, which is what the outer one evaluates to and what
475    /// `tidyselect::eval_select()` accepts (it refuses the outer one). A
476    /// quosure of the missing argument is read as it is: [`Self::expr`] is
477    /// [`SEXP::missing_arg()`].
478    ///
479    /// ```ignore
480    /// // `call` is the expression of a `Quosure` argument,
481    /// // `c(time = {{ t }})` written by a forwarding wrapper.
482    /// for (name, arg) in call.args() {
483    ///     if let Some(quosure) = unsafe { Quosure::from_sexp(&arg) } {
484    ///         // the quosure `{{ t }}` left in the call, as it is
485    ///     }
486    /// }
487    /// ```
488    ///
489    /// # Safety
490    ///
491    /// R's main thread, inside a miniextendr boundary (a `#[miniextendr]`
492    /// body, a `with_r_unwind_protect` closure; [`Self::eval_tidy`] needs
493    /// one); `sexp` stays rooted for `'a`.
494    pub unsafe fn from_sexp(sexp: &'a SEXP) -> Option<Self> {
495        innermost_quosure(*sexp).map(|quo| unsafe { Self::read(quo) })
496    }
497
498    /// A quosure of `expr` in `env`, rooted by `scope`.
499    ///
500    /// A bare expression is wrapped with `rlang::new_quosure(expr, env)`. A
501    /// quosure is returned as it is, through [`Self::from_sexp`] (`env` is
502    /// not used): wrapping a quosure again makes a formula that
503    /// `tidyselect::eval_select()` refuses. So an argument read from a call
504    /// can go through `new()` whether or not a wrapper wrote a quosure there.
505    /// The missing argument (an empty argument, `c(a = )`) is wrapped too:
506    /// [`Self::expr`] is then [`SEXP::missing_arg()`].
507    ///
508    /// Each call protects one value in `scope`.
509    ///
510    /// # Panics
511    ///
512    /// When `env` is not an environment, or when `expr` is not a quosure and
513    /// rlang cannot be loaded (a package taking a [`Quosure`] lists rlang in
514    /// `Imports`).
515    ///
516    /// # Safety
517    ///
518    /// As [`Self::from_sexp`]; `expr` and `env` are rooted by the caller for
519    /// the call.
520    pub unsafe fn new(expr: SEXP, env: SEXP, scope: &'a ProtectScope) -> Self {
521        if !env.is_environment() {
    {
        ::core::panicking::panic_fmt(format_args!("`Quosure::new()` takes an environment as `env`, not {0}",
                env.type_of().type_name()));
    }
};assert!(
522            env.is_environment(),
523            "`Quosure::new()` takes an environment as `env`, not {}",
524            env.type_of().type_name(),
525        );
526        let quo = match innermost_quosure(expr) {
527            Some(quo) => quo,
528            // The function's guard drops before `scope` protects the quosure,
529            // so the protect stack unwinds in order; nothing allocates between.
530            None => unsafe {
531                let new_quosure = OwnedProtect::new(rlang_new_quosure());
532                wrap_in_quosure(new_quosure.get(), expr, env)
533            },
534        };
535        unsafe { Self::read(scope.protect_raw(quo)) }
536    }
537
538    /// The arguments of the quosure's call, each as a quosure with its name,
539    /// rooted by `scope`; `None` when the expression is not a call.
540    ///
541    /// `c(time = TAD, dose = NA)` gives `(Some("time"), TAD)` and
542    /// `(Some("dose"), NA)`, each in this quosure's environment. An argument
543    /// a wrapper wrote as a quosure (`c(time = {{ t }})`) comes back as it is,
544    /// in its own environment, as [`Self::new`] returns it; any other is
545    /// wrapped in this quosure's environment with `rlang::new_quosure()`. The
546    /// name is `None` for an unnamed argument; the function (`c`) is not one
547    /// of the arguments.
548    ///
549    /// ```ignore
550    /// #[miniextendr]
551    /// pub fn roles(data: SEXP, cols: Quosure) -> Vec<String> {
552    ///     let scope = unsafe { ProtectScope::new() };
553    ///     let Some(args) = cols.args(&scope) else {
554    ///         panic!("`cols` must be a call, `c(role = column, ...)`");
555    ///     };
556    ///     for (role, quosure) in args {
557    ///         // `quosure.expr()` is `NA` / `NULL` as written; hand the others
558    ///         // to `tidyselect::eval_select()`.
559    ///     }
560    /// #   vec![]
561    /// }
562    /// ```
563    ///
564    /// All the quosures are held in one list, which `scope` protects.
565    ///
566    /// # Panics
567    ///
568    /// As [`Self::new`]: when an argument is not a quosure and rlang cannot
569    /// be loaded.
570    pub fn args<'s>(
571        &'s self,
572        scope: &'s ProtectScope,
573    ) -> Option<Vec<(Option<&'s str>, Quosure<'s>)>> {
574        if !self.expr.is_language() {
575            return None;
576        }
577        let args: Vec<(Option<&'s str>, SEXP)> = self.expr.args().collect();
578        // SAFETY: a `Quosure` exists only on R's main thread, rooted for
579        // `'a`: so are its call and the arguments in it. Each quosure is
580        // stored in `held` as it is made, and `held` is protected by `scope`
581        // before the function's guard, which drops first.
582        unsafe {
583            let held = scope.alloc_vecsxp(args.len()).into_raw();
584            let mut new_quosure: Option<OwnedProtect> = None;
585            for (i, &(_, arg)) in args.iter().enumerate() {
586                let quo = match innermost_quosure(arg) {
587                    Some(quo) => quo,
588                    None => {
589                        let new_quosure = new_quosure
590                            .get_or_insert_with(|| OwnedProtect::new(rlang_new_quosure()));
591                        wrap_in_quosure(new_quosure.get(), arg, self.env)
592                    }
593                };
594                held.set_vector_elt(r_index(i), quo);
595            }
596            drop(new_quosure);
597            Some(
598                args.into_iter()
599                    .enumerate()
600                    .map(|(i, (name, _))| (name, Quosure::read(held.vector_elt(r_index(i)))))
601                    .collect(),
602            )
603        }
604    }
605
606    /// The quosure itself, for R functions that take one
607    /// (`rlang::eval_tidy()`, `tidyselect::eval_select()`).
608    #[inline]
609    pub fn sexp(&self) -> SEXP {
610        self.quo
611    }
612
613    /// The quosure's expression (`rlang::quo_get_expr()`).
614    #[inline]
615    pub fn expr(&self) -> SEXP {
616        self.expr
617    }
618
619    /// The quosure's environment (`rlang::quo_get_env()`): the empty
620    /// environment for a constant.
621    #[inline]
622    pub fn env(&self) -> SEXP {
623        self.env
624    }
625
626    /// The name the argument stands for: `TIME`, `"TIME"`, `{{ col }}` with
627    /// `col = TIME` and `!!rlang::sym("TIME")` all give `"TIME"`. A quosure
628    /// nested in the expression is looked through. `None` for any other
629    /// expression.
630    pub fn as_name(&self) -> Option<&'a str> {
631        let mut expr = self.expr;
632        while is_quosure(expr) {
633            expr = expr.cdr().car();
634        }
635        expr_name(expr)
636    }
637
638    /// Evaluate the quosure with `data` as a data mask,
639    /// `rlang::eval_tidy(quo, data)`: columns of `data` win over variables of
640    /// the same name in the quosure's environment, and `.data$x` / `.env$x`
641    /// pick one side. `data` may be `NULL`.
642    ///
643    /// Evaluated with
644    /// [`eval_with_handlers`](crate::expression::eval_with_handlers), so the
645    /// caller's handlers see what the expression signals.
646    ///
647    /// The value is unprotected: protect it before the next allocation.
648    pub fn eval_tidy(&self, data: SEXP) -> SEXP {
649        // SAFETY: a `Quosure` exists only inside a miniextendr boundary on
650        // R's main thread (the `#[miniextendr]` call that received it, or
651        // the caller of `from_sexp` / `new`); `data` is rooted by the caller.
652        unsafe { self.eval_tidy_with(data, crate::expression::eval_with_handlers) }
653    }
654
655    /// [`eval_tidy`](Self::eval_tidy) with an R error returned as `Err`, as
656    /// [`try_eval_with_handlers`](crate::expression::try_eval_with_handlers)
657    /// returns it: the caller's handlers still see the quosure's warnings and
658    /// messages. A missing rlang is an `Err` too.
659    ///
660    /// The value is unprotected: protect it before the next allocation.
661    pub fn try_eval_tidy(&self, data: SEXP) -> Result<SEXP, crate::expression::REvalError> {
662        // SAFETY: as in `eval_tidy`.
663        unsafe { self.eval_tidy_with(data, crate::expression::try_eval_with_handlers) }
664    }
665
666    /// `rlang::eval_tidy(quo, data)` evaluated by `eval`.
667    ///
668    /// # Safety
669    ///
670    /// As `eval_tidy`'s: R's main thread, inside a miniextendr boundary;
671    /// `data` rooted by the caller.
672    unsafe fn eval_tidy_with<R>(&self, data: SEXP, eval: unsafe fn(SEXP, SEXP) -> R) -> R {
673        use crate::sys::{R_BaseEnv, Rf_install, Rf_lang2, Rf_lang3};
674        unsafe {
675            let scope = ProtectScope::new();
676            // `rlang::eval_tidy(quote(<quo>), quote(<data>))`; a missing rlang raises
677            // R's own error, through the caller's handlers.
678            let fun = scope.protect_raw(Rf_lang3(
679                Rf_install(c"::".as_ptr()),
680                Rf_install(c"rlang".as_ptr()),
681                Rf_install(c"eval_tidy".as_ptr()),
682            ));
683            // Both arguments pass through `quote()`, so neither the quosure
684            // nor a language `data` is evaluated on the way in.
685            let quote = Rf_install(c"quote".as_ptr());
686            let quoted = scope.protect_raw(Rf_lang2(quote, self.quo));
687            let data = scope.protect_raw(Rf_lang2(quote, data));
688            let call = scope.protect_raw(Rf_lang3(fun, quoted, data));
689            eval(call, R_BaseEnv)
690        }
691    }
692}
693
694/// An R vector index.
695fn r_index(i: usize) -> isize {
696    isize::try_from(i).expect("an R vector index fits in isize")
697}
698
699// endregion
700
701#[cfg(test)]
702mod tests {
703    use super::*;
704
705    #[test]
706    fn markers_hold_their_sexps_only() {
707        assert_eq!(
708            std::mem::size_of::<Quoted<'static>>(),
709            2 * std::mem::size_of::<SEXP>()
710        );
711        assert_eq!(
712            std::mem::size_of::<Quosure<'static>>(),
713            3 * std::mem::size_of::<SEXP>()
714        );
715    }
716}