Skip to main content

r/interpreter/builtins/
rlang_ffi.rs

1// Native Rust implementations of rlang FFI functions.
2// rlang's C code uses `r_abort()` which spins in `while(1)` when Rf_eval
3// returns on error, causing hangs in miniR. By intercepting rlang's `.Call`
4// FFI functions and implementing them in pure Rust, we bypass rlang's C code
5// entirely. This unblocks 83+ CRAN packages that depend on rlang.
6
7use std::collections::hash_map::DefaultHasher;
8use std::hash::{Hash, Hasher};
9
10use crate::interpreter::value::*;
11
12/// Try to dispatch an rlang FFI function by symbol name.
13///
14/// Returns `Some(result)` if the symbol was handled, `None` to fall through
15/// to the native C code path.
16pub fn try_dispatch(
17    name: &str,
18    args: &[RValue],
19    _named: &[(String, RValue)],
20    env: &crate::interpreter::environment::Environment,
21) -> Option<Result<RValue, RError>> {
22    match name {
23        // region: Init functions — register CCallable shims so downstream packages
24        // (purrr, stringr, dplyr) get working function pointers from R_GetCCallable.
25        "ffi_init_r_library" | "ffi_init_rlang" => {
26            register_rlang_ccallables();
27            Some(Ok(RValue::Null))
28        }
29        "ffi_fini_rlang" | "ffi_glue_is_here" => Some(Ok(RValue::Null)),
30
31        // region: Type checking
32        "ffi_is_character" => Some(ffi_is_character(args)),
33        "ffi_is_string" => Some(ffi_is_string(args)),
34        "ffi_is_logical" => Some(ffi_is_logical(args)),
35        "ffi_is_integer" => Some(ffi_is_integer(args)),
36        "ffi_is_double" => Some(ffi_is_double(args)),
37        "ffi_is_complex" => Some(ffi_is_complex(args)),
38        "ffi_is_raw" => Some(ffi_is_raw(args)),
39        "ffi_is_list" => Some(ffi_is_list(args)),
40        "ffi_is_vector" => Some(ffi_is_vector(args)),
41        "ffi_is_atomic" => Some(ffi_is_atomic(args)),
42        "ffi_is_function" => Some(ffi_is_function(args)),
43        "ffi_is_closure" => Some(ffi_is_closure(args)),
44        "ffi_is_primitive" => Some(ffi_is_primitive(args)),
45        "ffi_is_primitive_eager" => Some(ffi_is_primitive_eager(args)),
46        "ffi_is_primitive_lazy" => Some(ffi_is_primitive_lazy(args)),
47        "ffi_is_formula" => Some(ffi_is_formula(args)),
48        "ffi_is_call" => Some(ffi_is_call(args)),
49        "ffi_is_integerish" => Some(ffi_is_integerish(args)),
50        "ffi_is_finite" => Some(ffi_is_finite(args)),
51        "ffi_is_reference" => Some(ffi_is_reference(args)),
52        "ffi_is_weakref" => Some(Ok(r_false())),
53        "ffi_is_splice_box" => Some(Ok(r_false())),
54
55        // region: Utility functions
56        "ffi_length" => Some(ffi_length(args)),
57        "ffi_names" => Some(ffi_names(args)),
58        "ffi_set_names" => Some(ffi_set_names(args)),
59        "ffi_missing_arg" => Some(Ok(RValue::Null)),
60        "ffi_duplicate" => Some(ffi_duplicate(args)),
61        "ffi_symbol" => Some(ffi_symbol(args)),
62        "ffi_compiled_by_gcc" => Some(Ok(r_false())),
63        "ffi_obj_address" => Some(ffi_obj_address(args)),
64        "ffi_hash" => Some(ffi_hash(args)),
65        "ffi_format_error_arg" => Some(ffi_format_error_arg(args)),
66        "ffi_cnd_type" => Some(ffi_cnd_type(args)),
67
68        // region: Environment functions
69        "ffi_env_has" => Some(ffi_env_has(args)),
70        "ffi_env_poke_parent" => Some(ffi_env_poke_parent(args)),
71        "ffi_env_clone" => Some(ffi_env_clone(args)),
72        "ffi_find_var" => Some(ffi_find_var(args)),
73        "ffi_ns_registry_env" => Some(ffi_ns_registry_env()),
74        "ffi_env_binding_types" => Some(ffi_env_binding_types(args)),
75
76        // region: List construction
77        "ffi_list2" | "ffi_dots_list" | "ffi_dots_pairlist" => {
78            // list2(...) / dots_list(...) — grab `...` from the calling environment.
79            // .External2 passes (call, op, args, env) in GNU R; the C code accesses
80            // dots from the env. We do the same by reading `...` from the frame env.
81            let mut elements: Vec<(Option<String>, RValue)> = Vec::new();
82            // First add any explicit positional args (beyond the function name)
83            for v in args {
84                elements.push((None, v.clone()));
85            }
86            // Then expand `...` from the calling environment
87            if let Some(RValue::List(dots)) = env.get("...") {
88                for (opt_name, value) in &dots.values {
89                    elements.push((opt_name.clone(), value.clone()));
90                }
91            }
92            Some(Ok(RValue::List(crate::interpreter::value::RList::new(
93                elements,
94            ))))
95        }
96
97        // region: Promise functions (stubs)
98        "ffi_promise_expr" => Some(Ok(RValue::Null)),
99        "ffi_promise_value" => Some(Ok(RValue::Null)),
100        "ffi_promise_env" => Some(Ok(RValue::Null)),
101
102        // region: Standalone type-check functions (used by lifecycle, vctrs, etc.)
103        "ffi_standalone_is_bool_1.0.7" => Some(ffi_standalone_is_bool(args)),
104        "ffi_standalone_check_number_1.0.7" => Some(ffi_standalone_check_number(args)),
105
106        // Package linked_version checks — return the installed version string
107        // so check_linked_version() succeeds. The C function normally returns the
108        // compiled-in version string. We read it from DESCRIPTION.
109        _ if name.ends_with("_linked_version") => {
110            let pkg_name = name.trim_end_matches("_linked_version");
111            // Try to find the version from cran/<pkg>/DESCRIPTION
112            let version = std::fs::read_to_string(format!("cran/{pkg_name}/DESCRIPTION"))
113                .ok()
114                .and_then(|desc| {
115                    desc.lines()
116                        .find(|l| l.starts_with("Version:"))
117                        .map(|l| l.trim_start_matches("Version:").trim().to_string())
118                })
119                .unwrap_or_default();
120            Some(Ok(RValue::vec(Vector::Character(
121                vec![Some(version)].into(),
122            ))))
123        }
124
125        // Package init functions that pass namespace env to C — intercept as no-ops
126        // since C code can't handle our ENVSXP representation.
127        _ if name.ends_with("_init_library") || name.ends_with("_init_utils") => {
128            tracing::debug!(symbol = name, "intercepted package init — no-op");
129            Some(Ok(RValue::Null))
130        }
131
132        // Catch-all: any ffi_ name we don't handle — return NULL to avoid segfaults
133        // from uninitialized rlang C code. Log for debugging.
134        _ if name.starts_with("ffi_") => {
135            tracing::debug!(symbol = name, "unhandled rlang FFI — returning NULL");
136            Some(Ok(RValue::Null))
137        }
138        _ => None,
139    }
140}
141
142// region: Helpers
143
144fn lgl(v: bool) -> RValue {
145    RValue::vec(Vector::Logical(vec![Some(v)].into()))
146}
147
148fn r_false() -> RValue {
149    lgl(false)
150}
151
152fn r_bool(v: bool) -> RValue {
153    RValue::vec(Vector::Logical(vec![Some(v)].into()))
154}
155
156/// Get the nth arg, returning NULL if missing.
157fn arg(args: &[RValue], i: usize) -> &RValue {
158    args.get(i).unwrap_or(&RValue::Null)
159}
160
161/// Check if an arg is R NULL (meaning "no restriction" in rlang type checks).
162fn is_r_null(v: &RValue) -> bool {
163    v.is_null()
164}
165
166/// Extract an integer scalar from an RValue, or None if NULL.
167fn int_scalar(v: &RValue) -> Option<i64> {
168    if is_r_null(v) {
169        return None;
170    }
171    v.as_vector().and_then(|v| v.as_integer_scalar())
172}
173
174/// Extract a logical scalar from an RValue, or None if NULL.
175fn lgl_scalar(v: &RValue) -> Option<bool> {
176    if is_r_null(v) {
177        return None;
178    }
179    v.as_vector().and_then(|v| v.as_logical_scalar())
180}
181
182/// Check if length matches expected n (None = no restriction).
183fn check_length(actual: usize, expected_n: Option<i64>) -> bool {
184    match expected_n {
185        None => true,
186        Some(n) => {
187            let expected = u64::try_from(n).unwrap_or(0);
188            u64::try_from(actual).unwrap_or(0) == expected
189        }
190    }
191}
192
193/// Extract a character vector from an RValue.
194fn as_char_vec(v: &RValue) -> Option<&[Option<String>]> {
195    match v {
196        RValue::Vector(rv) => match &rv.inner {
197            Vector::Character(c) => Some(c.as_slice()),
198            _ => None,
199        },
200        _ => None,
201    }
202}
203
204// endregion
205
206// region: Type checking implementations
207
208/// ffi_is_character(x, n, missing, empty)
209fn ffi_is_character(args: &[RValue]) -> Result<RValue, RError> {
210    let x = arg(args, 0);
211    let n = int_scalar(arg(args, 1));
212
213    let result = match x.as_vector() {
214        Some(Vector::Character(chars)) => {
215            if !check_length(chars.len(), n) {
216                false
217            } else {
218                let missing_ok = lgl_scalar(arg(args, 2));
219                let empty_ok = lgl_scalar(arg(args, 3));
220                check_character_constraints(chars, missing_ok, empty_ok)
221            }
222        }
223        _ => false,
224    };
225
226    Ok(r_bool(result))
227}
228
229/// Check character-specific constraints: missing (NA) and empty ("") values.
230fn check_character_constraints(
231    chars: &[Option<String>],
232    missing_ok: Option<bool>,
233    empty_ok: Option<bool>,
234) -> bool {
235    // If missing_ok is Some(false), reject NA values
236    if missing_ok == Some(false) && chars.iter().any(|s| s.is_none()) {
237        return false;
238    }
239    // If empty_ok is Some(false), reject empty strings
240    if empty_ok == Some(false) && chars.iter().any(|s| s.as_deref() == Some("")) {
241        return false;
242    }
243    true
244}
245
246/// ffi_is_string(x, string, empty)
247fn ffi_is_string(args: &[RValue]) -> Result<RValue, RError> {
248    let x = arg(args, 0);
249    let string_arg = arg(args, 1);
250    let empty_arg = arg(args, 2);
251
252    let result = match x.as_vector() {
253        Some(Vector::Character(chars)) if chars.len() == 1 => {
254            // Must not be NA
255            match &chars[0] {
256                None => false,
257                Some(s) => {
258                    // If empty is FALSE, reject empty strings
259                    if lgl_scalar(empty_arg) == Some(false) && s.is_empty() {
260                        return Ok(r_false());
261                    }
262                    // If string arg is not NULL, check x matches one of the values
263                    if !is_r_null(string_arg) {
264                        if let Some(allowed) = as_char_vec(string_arg) {
265                            allowed.iter().any(|a| a.as_deref() == Some(s.as_str()))
266                        } else {
267                            false
268                        }
269                    } else {
270                        true
271                    }
272                }
273            }
274        }
275        _ => false,
276    };
277
278    Ok(r_bool(result))
279}
280
281/// ffi_is_logical(x, n)
282fn ffi_is_logical(args: &[RValue]) -> Result<RValue, RError> {
283    let x = arg(args, 0);
284    let n = int_scalar(arg(args, 1));
285
286    let result = match x.as_vector() {
287        Some(Vector::Logical(v)) => check_length(v.len(), n),
288        _ => false,
289    };
290
291    Ok(r_bool(result))
292}
293
294/// ffi_is_integer(x, n)
295fn ffi_is_integer(args: &[RValue]) -> Result<RValue, RError> {
296    let x = arg(args, 0);
297    let n = int_scalar(arg(args, 1));
298
299    let result = match x.as_vector() {
300        Some(Vector::Integer(v)) => check_length(v.len(), n),
301        _ => false,
302    };
303
304    Ok(r_bool(result))
305}
306
307/// ffi_is_double(x, n, finite)
308fn ffi_is_double(args: &[RValue]) -> Result<RValue, RError> {
309    let x = arg(args, 0);
310    let n = int_scalar(arg(args, 1));
311    let finite = lgl_scalar(arg(args, 2));
312
313    let result = match x.as_vector() {
314        Some(Vector::Double(v)) => {
315            if !check_length(v.len(), n) {
316                false
317            } else if finite == Some(true) {
318                v.iter_opt().all(|opt| opt.is_some_and(|f| f.is_finite()))
319            } else {
320                true
321            }
322        }
323        _ => false,
324    };
325
326    Ok(r_bool(result))
327}
328
329/// ffi_is_complex(x, n, finite)
330fn ffi_is_complex(args: &[RValue]) -> Result<RValue, RError> {
331    let x = arg(args, 0);
332    let n = int_scalar(arg(args, 1));
333    let finite = lgl_scalar(arg(args, 2));
334
335    let result = match x.as_vector() {
336        Some(Vector::Complex(v)) => {
337            if !check_length(v.len(), n) {
338                false
339            } else if finite == Some(true) {
340                v.iter()
341                    .all(|opt| opt.is_some_and(|c| c.re.is_finite() && c.im.is_finite()))
342            } else {
343                true
344            }
345        }
346        _ => false,
347    };
348
349    Ok(r_bool(result))
350}
351
352/// ffi_is_raw(x, n)
353fn ffi_is_raw(args: &[RValue]) -> Result<RValue, RError> {
354    let x = arg(args, 0);
355    let n = int_scalar(arg(args, 1));
356
357    let result = match x.as_vector() {
358        Some(Vector::Raw(v)) => check_length(v.len(), n),
359        _ => false,
360    };
361
362    Ok(r_bool(result))
363}
364
365/// ffi_is_list(x, n)
366fn ffi_is_list(args: &[RValue]) -> Result<RValue, RError> {
367    let x = arg(args, 0);
368    let n = int_scalar(arg(args, 1));
369
370    let result = match x {
371        RValue::List(l) => check_length(l.values.len(), n),
372        _ => false,
373    };
374
375    Ok(r_bool(result))
376}
377
378/// ffi_is_vector(x, n) — TRUE for atomic vectors and lists
379fn ffi_is_vector(args: &[RValue]) -> Result<RValue, RError> {
380    let x = arg(args, 0);
381    let n = int_scalar(arg(args, 1));
382
383    let result = match x {
384        RValue::Vector(rv) => check_length(rv.inner.len(), n),
385        RValue::List(l) => check_length(l.values.len(), n),
386        RValue::Null => check_length(0, n),
387        _ => false,
388    };
389
390    Ok(r_bool(result))
391}
392
393/// ffi_is_atomic(x, n) — TRUE for atomic vectors and NULL
394fn ffi_is_atomic(args: &[RValue]) -> Result<RValue, RError> {
395    let x = arg(args, 0);
396    let n = int_scalar(arg(args, 1));
397
398    let result = match x {
399        RValue::Vector(rv) => check_length(rv.inner.len(), n),
400        RValue::Null => check_length(0, n),
401        _ => false,
402    };
403
404    Ok(r_bool(result))
405}
406
407/// ffi_is_function(x)
408fn ffi_is_function(args: &[RValue]) -> Result<RValue, RError> {
409    let x = arg(args, 0);
410    Ok(r_bool(matches!(x, RValue::Function(_))))
411}
412
413/// ffi_is_closure(x)
414fn ffi_is_closure(args: &[RValue]) -> Result<RValue, RError> {
415    let x = arg(args, 0);
416    Ok(r_bool(matches!(
417        x,
418        RValue::Function(RFunction::Closure { .. })
419    )))
420}
421
422/// ffi_is_primitive(x)
423fn ffi_is_primitive(args: &[RValue]) -> Result<RValue, RError> {
424    let x = arg(args, 0);
425    Ok(r_bool(matches!(
426        x,
427        RValue::Function(RFunction::Builtin { .. })
428    )))
429}
430
431/// ffi_is_primitive_eager(x) — in miniR all builtins are eager
432fn ffi_is_primitive_eager(args: &[RValue]) -> Result<RValue, RError> {
433    ffi_is_primitive(args)
434}
435
436/// ffi_is_primitive_lazy(x) — in miniR no builtins are lazy
437fn ffi_is_primitive_lazy(args: &[RValue]) -> Result<RValue, RError> {
438    let _x = arg(args, 0);
439    Ok(r_false())
440}
441
442/// ffi_is_formula(x, n, lhs)
443fn ffi_is_formula(args: &[RValue]) -> Result<RValue, RError> {
444    let x = arg(args, 0);
445    let _n = int_scalar(arg(args, 1));
446    let _lhs = lgl_scalar(arg(args, 2));
447
448    // A formula is a language object with class "formula" or whose expression
449    // is Expr::Formula.
450    let result = match x {
451        RValue::Language(lang) => {
452            // Check class attribute for "formula"
453            let has_formula_class = lang
454                .class()
455                .is_some_and(|c| c.iter().any(|s| s == "formula"));
456            if has_formula_class {
457                true
458            } else {
459                matches!(
460                    lang.inner.as_ref(),
461                    crate::parser::ast::Expr::Formula { .. }
462                )
463            }
464        }
465        _ => false,
466    };
467
468    Ok(r_bool(result))
469}
470
471/// ffi_is_call(x, name, n, ns)
472fn ffi_is_call(args: &[RValue]) -> Result<RValue, RError> {
473    let x = arg(args, 0);
474    let _name = arg(args, 1);
475    let _n = int_scalar(arg(args, 2));
476    let _ns = arg(args, 3);
477
478    // A "call" in R is a language object
479    let result = matches!(x, RValue::Language(_));
480
481    Ok(r_bool(result))
482}
483
484/// ffi_is_integerish(x, n, finite)
485fn ffi_is_integerish(args: &[RValue]) -> Result<RValue, RError> {
486    let x = arg(args, 0);
487    let n = int_scalar(arg(args, 1));
488    let finite = lgl_scalar(arg(args, 2));
489
490    let result = match x.as_vector() {
491        Some(Vector::Integer(v)) => {
492            if !check_length(v.len(), n) {
493                false
494            } else if finite == Some(true) {
495                // All non-NA (integer is always finite if non-NA)
496                v.iter().all(|opt| opt.is_some())
497            } else {
498                true
499            }
500        }
501        Some(Vector::Double(v)) => {
502            if !check_length(v.len(), n) {
503                false
504            } else {
505                // Check all values are whole numbers
506                v.iter_opt().all(|opt| match opt {
507                    None => finite != Some(true), // NA: ok unless finite required
508                    Some(f) => {
509                        if finite == Some(true) && !f.is_finite() {
510                            false
511                        } else if f.is_nan() || f.is_infinite() {
512                            finite != Some(true)
513                        } else {
514                            f == f.trunc()
515                        }
516                    }
517                })
518            }
519        }
520        _ => false,
521    };
522
523    Ok(r_bool(result))
524}
525
526/// ffi_is_finite(x) — all elements are finite
527fn ffi_is_finite(args: &[RValue]) -> Result<RValue, RError> {
528    let x = arg(args, 0);
529
530    let result = match x.as_vector() {
531        Some(Vector::Double(v)) => v.iter_opt().all(|opt| opt.is_some_and(|f| f.is_finite())),
532        Some(Vector::Integer(v)) => v.iter().all(|opt| opt.is_some()),
533        Some(Vector::Logical(v)) => v.iter().all(|opt| opt.is_some()),
534        Some(Vector::Complex(v)) => v
535            .iter()
536            .all(|opt| opt.is_some_and(|c| c.re.is_finite() && c.im.is_finite())),
537        Some(Vector::Character(_) | Vector::Raw(_)) => true,
538        None => matches!(x, RValue::Null),
539    };
540
541    Ok(r_bool(result))
542}
543
544/// ffi_is_reference(x, y) — check if two objects are identical (pointer-equal
545/// for environments, deep-equal for others).
546fn ffi_is_reference(args: &[RValue]) -> Result<RValue, RError> {
547    let x = arg(args, 0);
548    let y = arg(args, 1);
549
550    let result = match (x, y) {
551        (RValue::Environment(a), RValue::Environment(b)) => a.ptr_eq(b),
552        _ => std::ptr::eq(x, y),
553    };
554
555    Ok(r_bool(result))
556}
557
558// endregion
559
560// region: Utility function implementations
561
562/// ffi_length(x) — return length as integer
563fn ffi_length(args: &[RValue]) -> Result<RValue, RError> {
564    let x = arg(args, 0);
565    let len = i64::try_from(x.length()).unwrap_or(i64::MAX);
566    Ok(RValue::vec(Vector::Integer(vec![Some(len)].into())))
567}
568
569/// ffi_names(x) — return names attribute
570fn ffi_names(args: &[RValue]) -> Result<RValue, RError> {
571    let x = arg(args, 0);
572
573    match x {
574        RValue::Vector(rv) => match rv.get_attr("names") {
575            Some(v) => Ok(v.clone()),
576            None => Ok(RValue::Null),
577        },
578        RValue::List(l) => {
579            let names: Vec<Option<String>> =
580                l.values.iter().map(|(name, _)| name.clone()).collect();
581            if names.iter().all(|n| n.is_none()) {
582                Ok(RValue::Null)
583            } else {
584                Ok(RValue::vec(Vector::Character(names.into())))
585            }
586        }
587        _ => Ok(RValue::Null),
588    }
589}
590
591/// ffi_set_names(x, names, transform) — set names on x.
592/// `transform` is ignored (it's a function for name transformation).
593fn ffi_set_names(args: &[RValue]) -> Result<RValue, RError> {
594    let x = arg(args, 0).clone();
595    let names_val = arg(args, 1);
596
597    match x {
598        RValue::Vector(mut rv) => {
599            if names_val.is_null() {
600                if let Some(ref mut attrs) = rv.attrs {
601                    attrs.shift_remove("names");
602                }
603            } else {
604                rv.set_attr("names".to_string(), names_val.clone());
605            }
606            Ok(RValue::Vector(rv))
607        }
608        RValue::List(mut l) => {
609            if let Some(char_names) = as_char_vec(names_val) {
610                for (i, entry) in l.values.iter_mut().enumerate() {
611                    entry.0 = char_names.get(i).cloned().flatten();
612                }
613            } else if names_val.is_null() {
614                for entry in &mut l.values {
615                    entry.0 = None;
616                }
617            }
618            Ok(RValue::List(l))
619        }
620        other => Ok(other),
621    }
622}
623
624/// ffi_duplicate(x, shallow) — deep or shallow copy.
625/// In miniR, RValue::clone() is always a deep copy since we use Rc<RefCell<>>
626/// for environments.
627fn ffi_duplicate(args: &[RValue]) -> Result<RValue, RError> {
628    let x = arg(args, 0);
629    Ok(x.clone())
630}
631
632/// ffi_symbol(x) — create a symbol (language object) from a string.
633fn ffi_symbol(args: &[RValue]) -> Result<RValue, RError> {
634    let x = arg(args, 0);
635    let name = x
636        .as_vector()
637        .and_then(|v| v.as_character_scalar())
638        .unwrap_or_default();
639    Ok(RValue::Language(Language::new(
640        crate::parser::ast::Expr::Symbol(name),
641    )))
642}
643
644/// ffi_obj_address(x) — return address as hex string.
645/// We use a hash of the debug representation since RValues don't have stable addresses.
646fn ffi_obj_address(args: &[RValue]) -> Result<RValue, RError> {
647    let x = arg(args, 0);
648    let mut hasher = DefaultHasher::new();
649    format!("{x:?}").hash(&mut hasher);
650    let addr = format!("0x{:016x}", hasher.finish());
651    Ok(RValue::vec(Vector::Character(vec![Some(addr)].into())))
652}
653
654/// ffi_hash(x) — compute a hash of x.
655fn ffi_hash(args: &[RValue]) -> Result<RValue, RError> {
656    let x = arg(args, 0);
657    let mut hasher = DefaultHasher::new();
658    format!("{x:?}").hash(&mut hasher);
659    let hash_str = format!("{:016x}", hasher.finish());
660    Ok(RValue::vec(Vector::Character(vec![Some(hash_str)].into())))
661}
662
663/// ffi_format_error_arg(x) — format an argument for error messages.
664/// Returns the argument as a backtick-quoted string.
665fn ffi_format_error_arg(args: &[RValue]) -> Result<RValue, RError> {
666    let x = arg(args, 0);
667    let formatted = match x {
668        RValue::Vector(rv) => match &rv.inner {
669            Vector::Character(c) => c
670                .first()
671                .cloned()
672                .flatten()
673                .map(|s| format!("`{s}`"))
674                .unwrap_or_else(|| "``".to_string()),
675            _ => format!("`{x}`"),
676        },
677        RValue::Language(lang) => format!("`{:?}`", lang.inner),
678        _ => format!("`{x}`"),
679    };
680    Ok(RValue::vec(Vector::Character(vec![Some(formatted)].into())))
681}
682
683/// ffi_cnd_type(x) — return the condition type.
684/// Inspects the class attribute to determine if it's an error, warning,
685/// message, or generic condition.
686fn ffi_cnd_type(args: &[RValue]) -> Result<RValue, RError> {
687    let x = arg(args, 0);
688
689    let class = match x {
690        RValue::List(l) => l.class(),
691        RValue::Vector(rv) => rv.class(),
692        _ => None,
693    };
694
695    let cnd_type = match class {
696        Some(classes) => {
697            if classes.iter().any(|c| c == "error") {
698                "error"
699            } else if classes.iter().any(|c| c == "warning") {
700                "warning"
701            } else if classes.iter().any(|c| c == "message") {
702                "message"
703            } else {
704                "condition"
705            }
706        }
707        None => "condition",
708    };
709
710    Ok(RValue::vec(Vector::Character(
711        vec![Some(cnd_type.to_string())].into(),
712    )))
713}
714
715// endregion
716
717// region: Environment function implementations
718
719/// ffi_env_has(env, names, inherit)
720fn ffi_env_has(args: &[RValue]) -> Result<RValue, RError> {
721    let env_val = arg(args, 0);
722    let names_val = arg(args, 1);
723    let inherit = lgl_scalar(arg(args, 2)).unwrap_or(false);
724
725    let env = match env_val {
726        RValue::Environment(e) => e,
727        _ => return Ok(r_false()),
728    };
729
730    let names = match as_char_vec(names_val) {
731        Some(n) => n,
732        None => return Ok(RValue::vec(Vector::Logical(vec![].into()))),
733    };
734
735    let results: Vec<Option<bool>> = names
736        .iter()
737        .map(|name| {
738            let name_str = name.as_deref().unwrap_or("");
739            let found = if inherit {
740                env.get(name_str).is_some()
741            } else {
742                env.has_local(name_str)
743            };
744            Some(found)
745        })
746        .collect();
747
748    let mut rv = RVector::from(Vector::Logical(results.into()));
749    // Set names attribute on result
750    rv.set_attr("names".to_string(), names_val.clone());
751
752    Ok(RValue::Vector(rv))
753}
754
755/// ffi_env_poke_parent(env, parent) — set the parent of an environment.
756fn ffi_env_poke_parent(args: &[RValue]) -> Result<RValue, RError> {
757    let env_val = arg(args, 0);
758    let parent_val = arg(args, 1);
759
760    if let RValue::Environment(env) = env_val {
761        if let RValue::Environment(parent) = parent_val {
762            env.set_parent(Some(parent.clone()));
763        }
764    }
765
766    Ok(RValue::Null)
767}
768
769/// ffi_env_clone(env) — clone an environment (shallow: same parent, copy bindings).
770fn ffi_env_clone(args: &[RValue]) -> Result<RValue, RError> {
771    let env_val = arg(args, 0);
772
773    match env_val {
774        RValue::Environment(env) => {
775            let parent = env.parent();
776            let new_env = match parent {
777                Some(ref p) => crate::interpreter::environment::Environment::new_child(p),
778                None => crate::interpreter::environment::Environment::new_global(),
779            };
780            // Copy all bindings
781            for name in env.ls() {
782                if let Some(val) = env.get(&name) {
783                    new_env.set(name, val);
784                }
785            }
786            Ok(RValue::Environment(new_env))
787        }
788        _ => Ok(RValue::Null),
789    }
790}
791
792/// ffi_find_var(sym, env) — find a variable in an environment.
793fn ffi_find_var(args: &[RValue]) -> Result<RValue, RError> {
794    let sym_val = arg(args, 0);
795    let env_val = arg(args, 1);
796
797    let sym_name = match sym_val {
798        RValue::Vector(rv) => rv.as_character_scalar(),
799        RValue::Language(lang) => match lang.inner.as_ref() {
800            crate::parser::ast::Expr::Symbol(s) => Some(s.clone()),
801            _ => None,
802        },
803        _ => None,
804    };
805
806    let sym_name = match sym_name {
807        Some(s) => s,
808        None => return Ok(RValue::Null),
809    };
810
811    match env_val {
812        RValue::Environment(env) => Ok(env.get(&sym_name).unwrap_or(RValue::Null)),
813        _ => Ok(RValue::Null),
814    }
815}
816
817/// ffi_ns_registry_env() — return the namespace registry (empty environment).
818fn ffi_ns_registry_env() -> Result<RValue, RError> {
819    Ok(RValue::Environment(
820        crate::interpreter::environment::Environment::new_global(),
821    ))
822}
823
824/// ffi_env_binding_types(env, names) — return binding types as an integer vector.
825/// Types: 0 = regular, 1 = active binding, 2 = promise
826fn ffi_env_binding_types(args: &[RValue]) -> Result<RValue, RError> {
827    let env_val = arg(args, 0);
828    let names_val = arg(args, 1);
829
830    let env = match env_val {
831        RValue::Environment(e) => e,
832        _ => return Ok(RValue::vec(Vector::Integer(vec![].into()))),
833    };
834
835    let names = match as_char_vec(names_val) {
836        Some(n) => n,
837        None => return Ok(RValue::vec(Vector::Integer(vec![].into()))),
838    };
839
840    let types: Vec<Option<i64>> = names
841        .iter()
842        .map(|name| {
843            let name_str = name.as_deref().unwrap_or("");
844            if env.get_local_active_binding(name_str).is_some() {
845                Some(1) // active binding
846            } else {
847                Some(0) // regular binding
848            }
849        })
850        .collect();
851
852    let mut rv = RVector::from(Vector::Integer(types.into()));
853    rv.set_attr("names".to_string(), names_val.clone());
854
855    Ok(RValue::Vector(rv))
856}
857
858// endregion
859
860// region: Standalone type-check functions
861
862/// ffi_standalone_is_bool(x, allow_na, allow_null) -> logical
863fn ffi_standalone_is_bool(args: &[RValue]) -> Result<RValue, RError> {
864    let x = args.first().unwrap_or(&RValue::Null);
865    let allow_na = args
866        .get(1)
867        .and_then(|v| v.as_vector()?.as_logical_scalar())
868        .unwrap_or(false);
869    let allow_null = args
870        .get(2)
871        .and_then(|v| v.as_vector()?.as_logical_scalar())
872        .unwrap_or(false);
873
874    if matches!(x, RValue::Null) {
875        return Ok(lgl(allow_null));
876    }
877
878    if let RValue::Vector(rv) = x {
879        if let Vector::Logical(l) = &rv.inner {
880            if l.len() == 1 {
881                return match l[0] {
882                    None => Ok(lgl(allow_na)),
883                    Some(_) => Ok(lgl(true)),
884                };
885            }
886        }
887    }
888
889    Ok(lgl(false))
890}
891
892/// ffi_standalone_check_number(x, allow_decimal, min, max, allow_infinite, allow_na, allow_null) -> integer
893/// Returns 0 for success, positive integer for various failure codes.
894fn ffi_standalone_check_number(args: &[RValue]) -> Result<RValue, RError> {
895    let x = args.first().unwrap_or(&RValue::Null);
896    let allow_decimal = args
897        .get(1)
898        .and_then(|v| v.as_vector()?.as_logical_scalar())
899        .unwrap_or(true);
900    let allow_infinite = args
901        .get(4)
902        .and_then(|v| v.as_vector()?.as_logical_scalar())
903        .unwrap_or(true);
904    let allow_na = args
905        .get(5)
906        .and_then(|v| v.as_vector()?.as_logical_scalar())
907        .unwrap_or(false);
908    let allow_null = args
909        .get(6)
910        .and_then(|v| v.as_vector()?.as_logical_scalar())
911        .unwrap_or(false);
912
913    if matches!(x, RValue::Null) {
914        return Ok(int_val(if allow_null { 0 } else { 1 }));
915    }
916
917    if let RValue::Vector(rv) = x {
918        match &rv.inner {
919            Vector::Integer(i) if i.len() == 1 => {
920                return match i.get_opt(0) {
921                    None => Ok(int_val(if allow_na { 0 } else { 4 })),
922                    Some(_) => Ok(int_val(0)),
923                };
924            }
925            Vector::Double(d) if d.len() == 1 => {
926                return match d.get_opt(0) {
927                    None => Ok(int_val(if allow_na { 0 } else { 4 })),
928                    Some(val) => {
929                        if val.is_infinite() && !allow_infinite {
930                            Ok(int_val(5))
931                        } else if !allow_decimal && val.fract() != 0.0 {
932                            Ok(int_val(2))
933                        } else {
934                            Ok(int_val(0))
935                        }
936                    }
937                };
938            }
939            Vector::Logical(l) if l.len() == 1 && l[0].is_none() => {
940                return Ok(int_val(if allow_na { 0 } else { 4 }));
941            }
942            _ => {}
943        }
944    }
945
946    Ok(int_val(1))
947}
948
949fn int_val(v: i64) -> RValue {
950    RValue::vec(Vector::Integer(vec![Some(v)].into()))
951}
952
953// endregion
954// rlang CCallable shims — Rust implementations of rlang's exported C API.
955// When rlang loads, its C init functions (`ffi_init_r_library`, `ffi_init_rlang`)
956// set up internal state that many CCallable functions depend on. If that init
957// fails or encounters unsupported R internals, the CCallable function pointers
958// registered by `R_init_rlang` point to uninitialized code.
959// This module provides standalone Rust implementations of the most important
960// CCallable functions. After rlang's `.Call(ffi_init_rlang, ns)` runs, we
961// overwrite the CCallable registry entries with these Rust implementations
962// so downstream packages (purrr, stringr, dplyr, etc.) get working functions.
963
964use std::ffi::CStr;
965use std::os::raw::{c_char, c_int, c_void};
966
967use crate::interpreter::native::runtime::{
968    R_NilValue, R_RegisterCCallable, R_alloc, Rf_allocVector, Rf_getAttrib, Rf_inherits, Rf_mkChar,
969    Rf_mkString,
970};
971use crate::interpreter::native::sexp::{self, Sexp};
972
973// region: rlang_obj_type_friendly_full
974
975/// Return a human-friendly type description for an R object.
976///
977/// Signature: `const char* rlang_obj_type_friendly_full(SEXP x, Rboolean value, Rboolean length)`
978///
979/// Returns strings like "a character vector", "a double vector", "NULL", etc.
980/// The `value` param would add the actual value (e.g. `the string "foo"`),
981/// the `length` param would add length info — we simplify both for now.
982///
983/// The returned string is allocated via `R_alloc` so it lives on the vmax
984/// protection stack and is freed automatically.
985extern "C" fn rlang_obj_type_friendly_full(x: Sexp, value: c_int, _length: c_int) -> *const c_char {
986    let desc = if x.is_null() || x == unsafe { R_NilValue } {
987        "NULL"
988    } else {
989        let stype = unsafe { (*x).stype };
990        match stype {
991            sexp::NILSXP => "NULL",
992            sexp::LGLSXP => {
993                let len = unsafe { (*x).length };
994                if value != 0 && len == 1 {
995                    "`TRUE` or `FALSE`"
996                } else {
997                    "a logical vector"
998                }
999            }
1000            sexp::INTSXP => {
1001                if Rf_inherits(x, c"factor".as_ptr()) != 0 {
1002                    "a factor"
1003                } else {
1004                    let len = unsafe { (*x).length };
1005                    if value != 0 && len == 1 {
1006                        "an integer"
1007                    } else {
1008                        "an integer vector"
1009                    }
1010                }
1011            }
1012            sexp::REALSXP => {
1013                let len = unsafe { (*x).length };
1014                if value != 0 && len == 1 {
1015                    "a number"
1016                } else {
1017                    "a double vector"
1018                }
1019            }
1020            sexp::CPLXSXP => "a complex vector",
1021            sexp::STRSXP => {
1022                let len = unsafe { (*x).length };
1023                if value != 0 && len == 1 {
1024                    "a string"
1025                } else {
1026                    "a character vector"
1027                }
1028            }
1029            sexp::RAWSXP => "a raw vector",
1030            sexp::VECSXP => {
1031                if Rf_inherits(x, c"data.frame".as_ptr()) != 0 {
1032                    "a data frame"
1033                } else if Rf_inherits(x, c"tbl_df".as_ptr()) != 0 {
1034                    "a tibble"
1035                } else {
1036                    "a list"
1037                }
1038            }
1039            // LISTSXP = 2 (pairlist)
1040            2 => "a pairlist",
1041            // CLOSXP = 3
1042            3 => "a function",
1043            // ENVSXP = 4
1044            4 => {
1045                if Rf_inherits(x, c"rlang_data_mask".as_ptr()) != 0 {
1046                    "a data mask"
1047                } else {
1048                    "an environment"
1049                }
1050            }
1051            // PROMSXP = 5
1052            5 => "a promise",
1053            // LANGSXP = 6
1054            6 => {
1055                if Rf_inherits(x, c"formula".as_ptr()) != 0 {
1056                    "a formula"
1057                } else {
1058                    "a call"
1059                }
1060            }
1061            // SPECIALSXP = 7
1062            7 => "a primitive function",
1063            // BUILTINSXP = 8
1064            8 => "a primitive function",
1065            sexp::CHARSXP => "an internal string",
1066            // EXPRSXP = 20
1067            20 => "an expression vector",
1068            // EXTPTRSXP = 22
1069            22 => "an external pointer",
1070            // SYMSXP = 1
1071            sexp::SYMSXP => "a symbol",
1072            _ => "an object",
1073        }
1074    };
1075
1076    // Allocate via R_alloc and copy the string
1077    let len = desc.len() + 1;
1078    let buf = R_alloc(len, 1);
1079    if buf.is_null() {
1080        return c"an object".as_ptr();
1081    }
1082    unsafe {
1083        std::ptr::copy_nonoverlapping(desc.as_ptr(), buf as *mut u8, desc.len());
1084        *buf.add(desc.len()) = 0; // null terminator
1085    }
1086    buf
1087}
1088
1089// endregion
1090
1091// region: rlang_format_error_arg
1092
1093/// Format an argument name for error messages.
1094///
1095/// Signature: `const char* rlang_format_error_arg(SEXP arg)`
1096///
1097/// In real rlang, this calls R code `format_arg(x)` which wraps the arg name
1098/// in backticks. We approximate: if the SEXP is a STRSXP of length 1,
1099/// extract the string and wrap it in backticks.
1100extern "C" fn rlang_format_error_arg(arg: Sexp) -> *const c_char {
1101    if arg.is_null() || arg == unsafe { R_NilValue } {
1102        return c"``".as_ptr();
1103    }
1104
1105    // Try to extract a character scalar
1106    let name = unsafe {
1107        if (*arg).stype == sexp::STRSXP && (*arg).length >= 1 && !(*arg).data.is_null() {
1108            let elt = *((*arg).data as *const Sexp);
1109            if !elt.is_null() {
1110                sexp::char_data(elt)
1111            } else {
1112                ""
1113            }
1114        } else if (*arg).stype == sexp::SYMSXP && !(*arg).data.is_null() {
1115            sexp::char_data(arg)
1116        } else {
1117            ""
1118        }
1119    };
1120
1121    // Format as `name`
1122    let formatted = format!("`{name}`");
1123    let len = formatted.len() + 1;
1124    let buf = R_alloc(len, 1);
1125    if buf.is_null() {
1126        return c"``".as_ptr();
1127    }
1128    unsafe {
1129        std::ptr::copy_nonoverlapping(formatted.as_ptr(), buf as *mut u8, formatted.len());
1130        *buf.add(formatted.len()) = 0;
1131    }
1132    buf
1133}
1134
1135// endregion
1136
1137// region: rlang_stop_internal / rlang_stop_internal2
1138
1139/// rlang_stop_internal — abort with an internal error message.
1140///
1141/// Signature: `void rlang_stop_internal(const char* fn, const char* fmt, ...)`
1142///
1143/// In rlang, this is `r_no_return`. It calls `Rf_error` which longjmps.
1144/// Since this is called from C code inside `_minir_call_protected`, the
1145/// longjmp is safe (only crosses C frames).
1146extern "C" fn rlang_stop_internal(func: *const c_char, fmt: *const c_char) {
1147    // We can't handle varargs from Rust, but we can format what we have.
1148    let func_name = if func.is_null() {
1149        "<unknown>"
1150    } else {
1151        unsafe { CStr::from_ptr(func) }
1152            .to_str()
1153            .unwrap_or("<unknown>")
1154    };
1155    let msg = if fmt.is_null() {
1156        "internal error"
1157    } else {
1158        unsafe { CStr::from_ptr(fmt) }
1159            .to_str()
1160            .unwrap_or("internal error")
1161    };
1162
1163    // Build a message and call Rf_error (which longjmps)
1164    let full_msg = format!("Internal error in `{func_name}()`: {msg}\0");
1165    extern "C" {
1166        fn Rf_error(fmt: *const c_char, ...) -> !;
1167    }
1168    unsafe {
1169        Rf_error(c"%s".as_ptr(), full_msg.as_ptr() as *const c_char);
1170    }
1171}
1172
1173/// rlang_stop_internal2 — abort with file/line context.
1174///
1175/// Signature: `void rlang_stop_internal2(const char* file, int line, SEXP call, const char* fmt, ...)`
1176extern "C" fn rlang_stop_internal2(
1177    _file: *const c_char,
1178    _line: c_int,
1179    _call: Sexp,
1180    fmt: *const c_char,
1181) {
1182    let msg = if fmt.is_null() {
1183        "internal error"
1184    } else {
1185        unsafe { CStr::from_ptr(fmt) }
1186            .to_str()
1187            .unwrap_or("internal error")
1188    };
1189    let full_msg = format!("Internal error: {msg}\0");
1190    extern "C" {
1191        fn Rf_error(fmt: *const c_char, ...) -> !;
1192    }
1193    unsafe {
1194        Rf_error(c"%s".as_ptr(), full_msg.as_ptr() as *const c_char);
1195    }
1196}
1197
1198// endregion
1199
1200// region: rlang_is_quosure
1201
1202/// Check if an object is a quosure.
1203///
1204/// Signature: `int rlang_is_quosure(SEXP x)`
1205///
1206/// A quosure is an object inheriting from "quosure".
1207extern "C" fn rlang_is_quosure(x: Sexp) -> c_int {
1208    Rf_inherits(x, c"quosure".as_ptr())
1209}
1210
1211// endregion
1212
1213// region: rlang_str_as_symbol
1214
1215/// Convert a CHARSXP or scalar STRSXP to a symbol (SYMSXP).
1216///
1217/// Signature: `SEXP rlang_str_as_symbol(SEXP x)`
1218extern "C" fn rlang_str_as_symbol(x: Sexp) -> Sexp {
1219    if x.is_null() || x == unsafe { R_NilValue } {
1220        return unsafe { R_NilValue };
1221    }
1222    unsafe {
1223        // If it's a STRSXP, get the first CHARSXP element
1224        let charsxp = if (*x).stype == sexp::STRSXP && (*x).length >= 1 && !(*x).data.is_null() {
1225            *((*x).data as *const Sexp)
1226        } else if (*x).stype == sexp::CHARSXP {
1227            x
1228        } else {
1229            return R_NilValue;
1230        };
1231
1232        if charsxp.is_null() {
1233            return R_NilValue;
1234        }
1235
1236        // Create a symbol SEXP from the character data
1237        let name_ptr = (*charsxp).data as *const c_char;
1238        if name_ptr.is_null() {
1239            return R_NilValue;
1240        }
1241        crate::interpreter::native::runtime::Rf_install(name_ptr)
1242    }
1243}
1244
1245// endregion
1246
1247// region: rlang_names_as_unique
1248
1249/// Make names unique by appending ...1, ...2, etc. for duplicates/NA/empty.
1250///
1251/// Signature: `SEXP rlang_names_as_unique(SEXP names, Rboolean quiet)`
1252///
1253/// Simplified implementation: just return the names as-is. A full implementation
1254/// would deduplicate, but this is enough to unblock dependent packages.
1255extern "C" fn rlang_names_as_unique(names: Sexp, _quiet: c_int) -> Sexp {
1256    if names.is_null() || names == unsafe { R_NilValue } {
1257        return unsafe { R_NilValue };
1258    }
1259    // Return names unchanged for now — downstream code handles missing names OK
1260    names
1261}
1262
1263// endregion
1264
1265// region: rlang_eval_tidy
1266
1267/// Evaluate an expression in a tidy evaluation context.
1268///
1269/// Signature: `SEXP rlang_eval_tidy(SEXP expr, SEXP data, SEXP env)`
1270///
1271/// Simplified: just evaluate the expression in the given environment,
1272/// ignoring the data mask. This is enough for basic purrr/dplyr usage.
1273extern "C" fn rlang_eval_tidy(expr: Sexp, _data: Sexp, env: Sexp) -> Sexp {
1274    // Delegate to Rf_eval — this handles the simple case where there's no data mask
1275    crate::interpreter::native::runtime::Rf_eval(expr, env)
1276}
1277
1278// endregion
1279
1280// region: data mask stubs
1281
1282/// Create a new data mask from bottom and top environments.
1283///
1284/// Signature: `SEXP rlang_new_data_mask_3.0.0(SEXP bottom, SEXP top)`
1285///
1286/// Returns the bottom env as-is — a simplification that works for basic cases.
1287extern "C" fn rlang_new_data_mask(bottom: Sexp, _top: Sexp) -> Sexp {
1288    if bottom.is_null() || bottom == unsafe { R_NilValue } {
1289        return unsafe { R_NilValue };
1290    }
1291    bottom
1292}
1293
1294/// Convert data to a data mask.
1295///
1296/// Signature: `SEXP rlang_as_data_mask(SEXP data)`
1297extern "C" fn rlang_as_data_mask(data: Sexp) -> Sexp {
1298    data
1299}
1300
1301/// Create a data pronoun from an environment.
1302///
1303/// Signature: `SEXP rlang_as_data_pronoun(SEXP env)`
1304extern "C" fn rlang_as_data_pronoun(env: Sexp) -> Sexp {
1305    env
1306}
1307
1308// endregion
1309
1310// region: rlang_env_unbind
1311
1312/// Unbind variables from an environment.
1313///
1314/// Signature: `void rlang_env_unbind(SEXP env, SEXP names)`
1315///
1316/// Stub — unbinding is not critical for package loading.
1317extern "C" fn rlang_env_unbind(_env: Sexp, _names: Sexp) {
1318    // No-op stub
1319}
1320
1321// endregion
1322
1323// region: rlang_as_function
1324
1325/// Coerce to function — if it's already a function, return it.
1326///
1327/// Signature: `SEXP rlang_as_function(SEXP x, const char* arg)`
1328extern "C" fn rlang_as_function(x: Sexp, _arg: *const c_char) -> Sexp {
1329    // If already a function (CLOSXP, BUILTINSXP, SPECIALSXP), return it
1330    if !x.is_null() && x != unsafe { R_NilValue } {
1331        let stype = unsafe { (*x).stype };
1332        if matches!(stype, 3 | 7 | 8) {
1333            return x;
1334        }
1335    }
1336    // Otherwise return as-is — rlang's real impl would convert formulas etc.
1337    x
1338}
1339
1340// endregion
1341
1342// region: quosure stubs
1343
1344/// Get the expression from a quosure.
1345extern "C" fn rlang_quo_get_expr(quo: Sexp) -> Sexp {
1346    // A quosure is a formula with class "quosure" — the expression is the RHS
1347    // For simplicity, return the input or R_NilValue
1348    if quo.is_null() || quo == unsafe { R_NilValue } {
1349        return unsafe { R_NilValue };
1350    }
1351    // Try to get the formula's RHS via the second element of the LANGSXP
1352    unsafe {
1353        if (*quo).stype == 6 {
1354            // LANGSXP — the formula `~expr` has CAR=`~`, CDR->CAR=expr
1355            if !(*quo).data.is_null() {
1356                let pd = (*quo).data as *const sexp::PairlistData;
1357                let cdr = (*pd).cdr;
1358                if !cdr.is_null() && !(*cdr).data.is_null() {
1359                    let pd2 = (*cdr).data as *const sexp::PairlistData;
1360                    return (*pd2).car;
1361                }
1362            }
1363        }
1364        R_NilValue
1365    }
1366}
1367
1368/// Get the environment from a quosure.
1369extern "C" fn rlang_quo_get_env(quo: Sexp) -> Sexp {
1370    // The quosure's env is stored as an attribute ".environment"
1371    if quo.is_null() || quo == unsafe { R_NilValue } {
1372        return unsafe { R_NilValue };
1373    }
1374    // Look for .environment attribute
1375    let env_sym = crate::interpreter::native::runtime::Rf_install(c".environment".as_ptr());
1376    let env = Rf_getAttrib(quo, env_sym);
1377    if env.is_null() || env == unsafe { R_NilValue } {
1378        return unsafe { R_NilValue };
1379    }
1380    env
1381}
1382
1383/// Set the expression on a quosure.
1384extern "C" fn rlang_quo_set_expr(quo: Sexp, _expr: Sexp) -> Sexp {
1385    // Stub — return the quosure unchanged
1386    quo
1387}
1388
1389/// Set the environment on a quosure.
1390extern "C" fn rlang_quo_set_env(quo: Sexp, _env: Sexp) -> Sexp {
1391    // Stub — return the quosure unchanged
1392    quo
1393}
1394
1395/// Create a new quosure.
1396extern "C" fn rlang_new_quosure(_expr: Sexp, _env: Sexp) -> Sexp {
1397    // Stub — return R_NilValue
1398    unsafe { R_NilValue }
1399}
1400
1401// endregion
1402
1403// region: additional stubs
1404
1405/// arg_match — match an argument to allowed values (legacy).
1406extern "C" fn rlang_arg_match(_arg: Sexp, _values: Sexp, _error_arg: Sexp) -> Sexp {
1407    // Return the argument unchanged
1408    _arg
1409}
1410
1411/// arg_match_2 — match an argument to allowed values.
1412extern "C" fn rlang_arg_match_2(
1413    _arg: Sexp,
1414    _values: Sexp,
1415    _error_arg: Sexp,
1416    _error_call: Sexp,
1417) -> Sexp {
1418    _arg
1419}
1420
1421/// is_splice_box — check if object is a splice box.
1422extern "C" fn rlang_is_splice_box(_x: Sexp) -> c_int {
1423    0
1424}
1425
1426/// Encode a character vector as UTF-8.
1427extern "C" fn rlang_obj_encode_utf8(x: Sexp) -> Sexp {
1428    // Our strings are already UTF-8
1429    x
1430}
1431
1432/// Convert a symbol to a character SEXP.
1433extern "C" fn rlang_sym_as_character(sym: Sexp) -> Sexp {
1434    if sym.is_null() || sym == unsafe { R_NilValue } {
1435        return Rf_mkString(c"".as_ptr());
1436    }
1437    unsafe {
1438        if (*sym).stype == sexp::SYMSXP && !(*sym).data.is_null() {
1439            let name_ptr = (*sym).data as *const c_char;
1440            return Rf_mkString(name_ptr);
1441        }
1442    }
1443    Rf_mkString(c"".as_ptr())
1444}
1445
1446/// Convert a symbol to a string SEXP (CHARSXP).
1447extern "C" fn rlang_sym_as_string(sym: Sexp) -> Sexp {
1448    if sym.is_null() || sym == unsafe { R_NilValue } {
1449        return Rf_mkChar(c"".as_ptr());
1450    }
1451    unsafe {
1452        if (*sym).stype == sexp::SYMSXP && !(*sym).data.is_null() {
1453            let name_ptr = (*sym).data as *const c_char;
1454            return Rf_mkChar(name_ptr);
1455        }
1456    }
1457    Rf_mkChar(c"".as_ptr())
1458}
1459
1460/// Unbox a scalar value from a list.
1461extern "C" fn rlang_unbox(x: Sexp) -> Sexp {
1462    // If it's a length-1 list, return the first element
1463    if !x.is_null() && x != unsafe { R_NilValue } {
1464        unsafe {
1465            if (*x).stype == sexp::VECSXP && (*x).length == 1 && !(*x).data.is_null() {
1466                return *((*x).data as *const Sexp);
1467            }
1468        }
1469    }
1470    x
1471}
1472
1473/// Squash a list conditionally.
1474extern "C" fn rlang_squash_if(_x: Sexp, _type: Sexp, _predicate: Sexp) -> Sexp {
1475    unsafe { R_NilValue }
1476}
1477
1478/// Get dots as a list from an environment.
1479extern "C" fn rlang_env_dots_list(_env: Sexp) -> Sexp {
1480    // Return empty list
1481    Rf_allocVector(sexp::VECSXP as c_int, 0)
1482}
1483
1484/// Get dots values from an environment.
1485extern "C" fn rlang_env_dots_values(_env: Sexp) -> Sexp {
1486    Rf_allocVector(sexp::VECSXP as c_int, 0)
1487}
1488
1489/// Print backtrace — no-op.
1490extern "C" fn rlang_print_backtrace() {
1491    // No-op
1492}
1493
1494/// Print environment — no-op.
1495extern "C" fn rlang_env_print(_env: Sexp) {
1496    // No-op
1497}
1498
1499/// xxh3_64bits hash — return 0 as stub.
1500extern "C" fn rlang_xxh3_64bits(_data: *const c_void, _len: usize) -> u64 {
1501    0
1502}
1503
1504// endregion
1505
1506// region: registration
1507
1508/// Register all rlang CCallable functions in the cross-package registry.
1509///
1510/// This overwrites any entries previously registered by rlang's own C code,
1511/// ensuring downstream packages get working Rust implementations instead of
1512/// pointers to uninitialized C code.
1513pub fn register_rlang_ccallables() {
1514    let registrations: &[(&str, *const ())] = &[
1515        (
1516            "rlang_obj_type_friendly_full",
1517            rlang_obj_type_friendly_full as *const (),
1518        ),
1519        (
1520            "rlang_format_error_arg",
1521            rlang_format_error_arg as *const (),
1522        ),
1523        ("rlang_stop_internal", rlang_stop_internal as *const ()),
1524        ("rlang_stop_internal2", rlang_stop_internal2 as *const ()),
1525        ("rlang_is_quosure", rlang_is_quosure as *const ()),
1526        ("rlang_str_as_symbol", rlang_str_as_symbol as *const ()),
1527        ("rlang_names_as_unique", rlang_names_as_unique as *const ()),
1528        ("rlang_eval_tidy", rlang_eval_tidy as *const ()),
1529        (
1530            "rlang_new_data_mask_3.0.0",
1531            rlang_new_data_mask as *const (),
1532        ),
1533        ("rlang_as_data_mask_3.0.0", rlang_as_data_mask as *const ()),
1534        ("rlang_as_data_pronoun", rlang_as_data_pronoun as *const ()),
1535        ("rlang_env_unbind", rlang_env_unbind as *const ()),
1536        ("rlang_as_function", rlang_as_function as *const ()),
1537        ("rlang_quo_get_expr", rlang_quo_get_expr as *const ()),
1538        ("rlang_quo_get_env", rlang_quo_get_env as *const ()),
1539        ("rlang_quo_set_expr", rlang_quo_set_expr as *const ()),
1540        ("rlang_quo_set_env", rlang_quo_set_env as *const ()),
1541        ("rlang_new_quosure", rlang_new_quosure as *const ()),
1542        ("rlang_arg_match", rlang_arg_match as *const ()),
1543        ("rlang_arg_match_2", rlang_arg_match_2 as *const ()),
1544        ("rlang_is_splice_box", rlang_is_splice_box as *const ()),
1545        ("rlang_obj_encode_utf8", rlang_obj_encode_utf8 as *const ()),
1546        (
1547            "rlang_sym_as_character",
1548            rlang_sym_as_character as *const (),
1549        ),
1550        ("rlang_sym_as_string", rlang_sym_as_string as *const ()),
1551        ("rlang_unbox", rlang_unbox as *const ()),
1552        ("rlang_squash_if", rlang_squash_if as *const ()),
1553        ("rlang_env_dots_list", rlang_env_dots_list as *const ()),
1554        ("rlang_env_dots_values", rlang_env_dots_values as *const ()),
1555        ("rlang_as_data_mask", rlang_as_data_mask as *const ()),
1556        ("rlang_new_data_mask", rlang_new_data_mask as *const ()),
1557        ("rlang_print_backtrace", rlang_print_backtrace as *const ()),
1558        ("rlang_env_print", rlang_env_print as *const ()),
1559        ("rlang_xxh3_64bits", rlang_xxh3_64bits as *const ()),
1560    ];
1561
1562    for &(name, fptr) in registrations {
1563        let pkg = std::ffi::CString::new("rlang").expect("CString::new");
1564        let nm = std::ffi::CString::new(name).expect("CString::new");
1565        R_RegisterCCallable(pkg.as_ptr(), nm.as_ptr(), fptr);
1566    }
1567
1568    tracing::debug!("registered {} rlang CCallable shims", registrations.len());
1569}
1570
1571// endregion