Skip to main content

typr_core/processes/transpiling/
mod.rs

1pub mod translatable;
2
3use crate::components::context::config::Environment;
4use crate::components::context::Context;
5use crate::components::error_message::help_data::HelpData;
6use crate::components::language::argument_value::ArgumentValue;
7use crate::components::language::format_backtick;
8use crate::components::language::function_lang::Function;
9use crate::components::language::operators::Op;
10use crate::components::language::set_related_type_if_variable;
11use crate::components::language::var::Var;
12use crate::components::language::Lang;
13use crate::components::language::ModulePosition;
14use crate::components::r#type::argument_type::ArgumentType;
15use crate::components::r#type::array_type::ArrayType;
16use crate::components::r#type::function_type::FunctionType;
17use crate::components::r#type::type_operator::TypeOperator;
18use crate::components::r#type::type_system::TypeSystem;
19use crate::components::r#type::vector_type::VecType;
20use crate::components::r#type::Type;
21use crate::processes::transpiling::translatable::Translatable;
22use crate::processes::type_checking::facets;
23use crate::processes::type_checking::flatten_operator_union;
24use crate::processes::type_checking::resolve_module_member_type;
25use crate::processes::type_checking::type_comparison::reduce_type;
26use crate::processes::type_checking::typing;
27use translatable::RTranslatable;
28
29#[cfg(not(target_arch = "wasm32"))]
30use std::fs::File;
31#[cfg(not(target_arch = "wasm32"))]
32use std::io::Write;
33#[cfg(not(target_arch = "wasm32"))]
34use std::path::PathBuf;
35
36use std::cell::RefCell;
37use std::collections::HashMap;
38
39/// Render a string value as an R double-quoted literal (R's canonical string
40/// form). The value is assumed to be already decoded (see
41/// `parsing::elements::decode_escapes`), so this is the single place that knows
42/// how to escape for the R target: backslashes and double quotes must be
43/// escaped, control characters are emitted as escape sequences.
44pub fn escape_r_string(s: &str) -> String {
45    let escaped = s
46        .replace('\\', "\\\\")
47        .replace('"', "\\\"")
48        .replace('\n', "\\n")
49        .replace('\t', "\\t");
50    format!("\"{}\"", escaped)
51}
52
53/// Raw `typed_vec(...)` R code for an array literal, without the trailing
54/// `|> as.<TypeName>()` annotation. Used by the `Lang::Array` arm (which then
55/// appends its own annotation) and by `Lang::ValidatingCast` over an array
56/// literal, where the cast supplies the one meaningful annotation and the
57/// literal's own would be redundant (or worse, `as.Generic()` when the
58/// literal's inferred type — e.g. `[0, Empty]` for `[]` — has no registered
59/// alias).
60fn array_literal_raw(array: &Lang, cont: &Context) -> String {
61    let typ = array.typing(cont).value;
62    let dimension = ArrayType::try_from(typ)
63        .expect("array literal should have an array type")
64        .get_shape()
65        .map(|sha| format!("c({})", sha))
66        .unwrap_or_else(|| "c(0)".to_string());
67    let lin_array = array
68        .linearize_array()
69        .iter()
70        .map(|lang| lang.to_r(cont).0)
71        .collect::<Vec<_>>()
72        .join(", ");
73    if lin_array.is_empty() {
74        format!("typed_vec(dim = {})", dimension)
75    } else {
76        format!("typed_vec({}, dim = {})", lin_array, dimension)
77    }
78}
79
80// Thread-local storage for generated files (used in WASM mode)
81thread_local! {
82    static GENERATED_FILES: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
83}
84
85// Thread-local stack of roxygen2 `@include` dependencies, scoped per output file.
86//
87// In Project mode, `mod foo;` dependencies must surface as top-level
88// `#' @include foo.R` tags in the *header* of the file that references them — a
89// `#'` comment buried inside a `local({ ... })` block is not attached to any
90// top-level object, so roxygen2 ignores it. Instead of emitting the tag inline,
91// each external module registers its filename into the current frame; the file
92// that owns that frame drains it into its header.
93//
94// The stack mirrors the `to_r` recursion: a new frame is pushed before
95// transpiling the body of a module that writes its own file, and drained when
96// that file is written. The bottom frame collects top-level (`main`) includes.
97thread_local! {
98    static INCLUDE_STACK: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
99}
100
101/// Reset the include stack to a single empty bottom frame (call before a build).
102pub fn reset_include_stack() {
103    INCLUDE_STACK.with(|s| *s.borrow_mut() = vec![Vec::new()]);
104}
105
106/// Push a new frame for the body of a module that writes its own file.
107fn push_include_frame() {
108    INCLUDE_STACK.with(|s| s.borrow_mut().push(Vec::new()));
109}
110
111/// Pop the current frame, returning the includes collected within it.
112fn pop_include_frame() -> Vec<String> {
113    INCLUDE_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
114}
115
116/// Register an `@include` target (e.g. "foo.R") into the current frame.
117fn register_include(file: &str) {
118    INCLUDE_STACK.with(|s| {
119        if let Some(top) = s.borrow_mut().last_mut() {
120            top.push(file.to_string());
121        }
122    });
123}
124
125/// Drain the bottom (main) frame — the top-level includes for `main.R`.
126pub fn take_main_includes() -> Vec<String> {
127    INCLUDE_STACK.with(|s| {
128        let mut stack = s.borrow_mut();
129        match stack.first_mut() {
130            Some(bottom) => std::mem::take(bottom),
131            None => Vec::new(),
132        }
133    })
134}
135
136thread_local! {
137    static IMPORT_FROM_STACK: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
138}
139
140pub fn reset_import_from_stack() {
141    IMPORT_FROM_STACK.with(|s| *s.borrow_mut() = vec![Vec::new()]);
142}
143
144fn push_import_from_frame() {
145    IMPORT_FROM_STACK.with(|s| s.borrow_mut().push(Vec::new()));
146}
147
148fn pop_import_from_frame() -> Vec<String> {
149    IMPORT_FROM_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
150}
151
152fn register_import_from(entry: &str) {
153    IMPORT_FROM_STACK.with(|s| {
154        if let Some(top) = s.borrow_mut().last_mut() {
155            top.push(entry.to_string());
156        }
157    });
158}
159
160pub fn take_main_import_froms() -> Vec<String> {
161    IMPORT_FROM_STACK.with(|s| {
162        let mut stack = s.borrow_mut();
163        match stack.first_mut() {
164            Some(bottom) => std::mem::take(bottom),
165            None => Vec::new(),
166        }
167    })
168}
169
170/// Register a generated file (used for WASM mode to capture file outputs)
171pub fn register_generated_file(path: &str, content: &str) {
172    GENERATED_FILES.with(|files| {
173        files
174            .borrow_mut()
175            .insert(path.to_string(), content.to_string());
176    });
177}
178
179/// Get all generated files
180pub fn get_generated_files() -> HashMap<String, String> {
181    GENERATED_FILES.with(|files| files.borrow().clone())
182}
183
184/// Clear all generated files
185pub fn clear_generated_files() {
186    GENERATED_FILES.with(|files| {
187        files.borrow_mut().clear();
188    });
189}
190
191/// Write a file - in native mode writes to filesystem, in WASM mode stores in memory.
192/// Skips the write when the file already holds the same content, so unchanged
193/// outputs keep a stable mtime across builds.
194#[cfg(not(target_arch = "wasm32"))]
195fn write_output_file(path: &str, content: &str) -> Result<(), String> {
196    use std::fs;
197
198    // Also register in memory for consistency
199    register_generated_file(path, content);
200
201    let path_buf = PathBuf::from(path);
202    if let Ok(existing) = fs::read_to_string(&path_buf) {
203        if existing == content {
204            return Ok(());
205        }
206    }
207    if let Some(parent) = path_buf.parent() {
208        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
209    }
210    let mut file = File::create(&path_buf).map_err(|e| e.to_string())?;
211    file.write_all(content.as_bytes())
212        .map_err(|e| e.to_string())?;
213    Ok(())
214}
215
216#[cfg(target_arch = "wasm32")]
217fn write_output_file(path: &str, content: &str) -> Result<(), String> {
218    register_generated_file(path, content);
219    Ok(())
220}
221
222pub trait ToSome {
223    fn to_some(self) -> Option<Self>
224    where
225        Self: Sized;
226}
227
228impl<T: Sized> ToSome for T {
229    fn to_some(self) -> Option<Self> {
230        Some(self)
231    }
232}
233
234const JS_HEADER: &str = "";
235
236fn to_pattern_match_statement(
237    exp: Lang,
238    branches: &[(Lang, Box<Lang>)],
239    context: &Context,
240) -> String {
241    let match_var = "match_val__";
242    let res = branches
243        .iter()
244        .enumerate()
245        .map(|(id, (pattern, body))| {
246            let (cond, bindings) = pattern_to_condition(pattern, match_var, context);
247            let body_str = body.to_r(context).0;
248            let body_with_bindings = if bindings.is_empty() {
249                body_str
250            } else {
251                format!("{}\n{}", bindings, body_str)
252            };
253            if cond == "TRUE" {
254                // wildcard pattern: always matches
255                if id == 0 {
256                    format!("{{\n{}\n}}", body_with_bindings)
257                } else {
258                    format!("else {{\n{}\n}}", body_with_bindings)
259                }
260            } else if id == 0 {
261                format!("if ({}) {{\n{}\n}}", cond, body_with_bindings)
262            } else {
263                format!("else if ({}) {{\n{}\n}}", cond, body_with_bindings)
264            }
265        })
266        .collect::<Vec<_>>()
267        .join(" ");
268    format!("{{\n{} <- {}\n{}\n}}", match_var, exp.to_r(context).0, res)
269}
270
271/// Map a Type to its corresponding R type-check function name.
272/// Return the TypR lifting function name for the return type of an `@extern` function.
273/// `None` means the raw R value is passed through unchanged (opaque / Any / record types).
274fn extern_lift_fn(typ: &Type) -> Option<&'static str> {
275    match typ {
276        Type::Integer(_, _) => Some("from_int"),
277        Type::Number(_, _) => Some("from_num"),
278        Type::Char(_, _) => Some("from_char"),
279        Type::Boolean(_, _) => Some("from_bool"),
280        // Option<T> return: NULL from external fn becomes .None, any value becomes .Some(value)
281        Type::Alias(name, _, _, _) if name == "Option" => Some("from_nullable"),
282        _ => None,
283    }
284}
285
286fn type_to_r_check(typ: &Type) -> Option<&'static str> {
287    match typ {
288        Type::Integer(_, _) => Some("is.integer"),
289        Type::Boolean(_, _) => Some("is.logical"),
290        Type::Number(_, _) => Some("is.numeric"),
291        Type::Char(_, _) => Some("is.character"),
292        Type::Null(_) => Some("is.null"),
293        _ => None,
294    }
295}
296
297/// R class a value of `typ` is expected to carry at runtime, used by record
298/// validators to check field types via `inherits`. Relies on monomorphisation:
299/// every value already carries its type's class. Returns `None` for types with
300/// no reliable nominal class (generics, functions, unions…), in which case the
301/// field is only checked for presence.
302fn record_field_class(typ: &Type, cont: &Context) -> Option<String> {
303    match typ {
304        Type::Integer(_, _) => Some("integer".to_string()),
305        Type::Number(_, _) => Some("numeric".to_string()),
306        Type::Char(_, _) => Some("character".to_string()),
307        Type::Boolean(_, _) => Some("logical".to_string()),
308        Type::Alias(name, _, _, _) => match cont
309            .aliases()
310            .find(|(var, _)| var.get_name() == *name)
311            .map(|(_, t)| t)
312        {
313            // Record aliases carry their alias name as the S3 class.
314            Some(Type::Record(_, _)) => Some(name.clone()),
315            // Primitive aliases (e.g. `type Meters <- int`) carry the underlying
316            // R class — no constructor adds the alias name as a class.
317            Some(inner) => record_field_class(inner, cont),
318            None => None,
319        },
320        _ => None,
321    }
322}
323
324/// Find the name of a union alias that declares a tag variant called
325/// `tag_name`. Used by the `Lang::Tag` literal to enrich its runtime class
326/// with the union name (canonical representation, see
327/// `validation_variant_d_union.md` §2). Returns `None` for standalone tags
328/// (no declared union).
329fn find_union_for_tag(tag_name: &str, cont: &Context) -> Option<String> {
330    cont.aliases().find_map(|(var, typ)| {
331        let is_union = matches!(
332            typ,
333            Type::Operator(
334                crate::components::r#type::type_operator::TypeOperator::Union,
335                _,
336                _,
337                _
338            )
339        );
340        if !is_union {
341            return None;
342        }
343        let declares_tag = flatten_operator_union(typ)
344            .iter()
345            .any(|m| matches!(m, Type::Tag(n, _, _) if n == tag_name));
346        if declares_tag {
347            Some(var.get_name())
348        } else {
349            None
350        }
351    })
352}
353
354/// Build the structural body-validation block for a tag's payload, shared by
355/// standalone tag aliases (`type Hello <- .Hello(char)`) and union variants.
356/// `name` is the type/variant name used in error messages; `inner_type` is the
357/// declared payload type. An empty payload (`.Nothing`) yields an empty block.
358fn tag_body_validation(name: &str, inner_type: &Type) -> String {
359    match inner_type {
360        Type::Empty(_) => String::new(),
361        Type::Integer(tint, _) => {
362            use crate::components::r#type::tint::Tint;
363            let null_check = format!("\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n  if (!is.integer(x[[\"body\"]])) stop(\"Validation failed for type {name}: body must be int\")");
364            match tint {
365                Tint::Val(i) => format!("{null_check}\n  if (x[[\"body\"]] != {i}L) stop(\"Validation failed for type {name}: body must be literal {i}\")"),
366                Tint::Unknown => null_check,
367            }
368        }
369        Type::Char(tchar, _) => {
370            use crate::components::r#type::tchar::Tchar;
371            let null_check = format!("\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n  if (!is.character(x[[\"body\"]])) stop(\"Validation failed for type {name}: body must be char\")");
372            match tchar {
373                Tchar::Val(s) => format!("{null_check}\n  if (x[[\"body\"]] != '{s}') stop(\"Validation failed for type {name}: body must be literal '{s}'\")"),
374                Tchar::Unknown => null_check,
375            }
376        }
377        Type::Boolean(tbool, _) => {
378            use crate::components::r#type::tbool::Tbool;
379            let null_check = format!("\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n  if (!is.logical(x[[\"body\"]])) stop(\"Validation failed for type {name}: body must be bool\")");
380            match tbool {
381                Tbool::Val(b) => {
382                    let r_val = if *b { "TRUE" } else { "FALSE" };
383                    format!("{null_check}\n  if (x[[\"body\"]] != {r_val}) stop(\"Validation failed for type {name}: body must be literal {r_val}\")")
384                }
385                Tbool::Unknown => null_check,
386            }
387        }
388        Type::Number(tnum, _) => {
389            use crate::components::r#type::tnumber::Tnum;
390            let null_check = format!("\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n  if (!is.numeric(x[[\"body\"]])) stop(\"Validation failed for type {name}: body must be num\")");
391            match tnum {
392                Tnum::Val(v) => format!("{null_check}\n  if (x[[\"body\"]] != {v}) stop(\"Validation failed for type {name}: body must be literal {v}\")"),
393                Tnum::Unknown => null_check,
394            }
395        }
396        Type::Alias(alias_name, _, _, _) => format!(
397            "\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n  validate_{alias_name}(x[[\"body\"]])"
398        ),
399        _ => format!(
400            "\n  if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")"
401        ),
402    }
403}
404
405/// Emit the full constructor/annotator/validator pipeline for a single tag
406/// variant `V` of union `U`, in the canonical representation
407/// (`structure(list("V", body = p), class = c("V", "U", "Tag", "list"))`).
408/// Mirrors the record pipeline (see `Lang::Alias` / `Type::Record`).
409fn tag_variant_pipeline(variant_name: &str, union_name: &str, inner_type: &Type) -> String {
410    let is_empty = matches!(inner_type, Type::Empty(_));
411    // Constructor: build the raw value, then delegate entirely to the
412    // annotator. It neither sets the class nor validates.
413    let constructor = if is_empty {
414        format!(
415            "{variant_name} <- function() {{\n  x <- list(\"{variant_name}\")\n  as.{variant_name}(x)\n}}"
416        )
417    } else {
418        format!(
419            "{variant_name} <- function(x) {{\n  v <- list(\"{variant_name}\", body = x)\n  as.{variant_name}(v)\n}}"
420        )
421    };
422    // Annotator: single entry point. Sets the class idempotently, then runs
423    // the internal validator and the user validator (`validate` S3 generic,
424    // which dispatches to `validate.{variant_name}` then `validate.{union_name}`).
425    let annotator = format!(
426        "as.{variant_name} <- function(x) {{\n  if (!inherits(x, \"{variant_name}\")) class(x) <- c(\"{variant_name}\", \"{union_name}\", \"Tag\", \"list\")\n  x <- validate_{variant_name}(x)\n  x <- validate(x)\n  x\n}}"
427    );
428    // Internal validator: pure structural invariants (tag identity + payload).
429    let body_validation = tag_body_validation(variant_name, inner_type);
430    let validator = format!(
431        "validate_{variant_name} <- function(x) {{\n  if (x[[1]] != '{variant_name}') stop(\"Validation failed for type {variant_name}: expected tag '{variant_name}'\")\n{body_validation}\n  x\n}}"
432    );
433    format!("{constructor}\n{annotator}\n{validator}")
434}
435
436fn pattern_to_condition(pattern: &Lang, match_var: &str, _context: &Context) -> (String, String) {
437    match pattern {
438        // Tag with a binding variable: .Some(a)
439        Lang::Tag {
440            name, value: inner, ..
441        } => {
442            let cond = format!("{}[[1]] == '{}'", match_var, name);
443            match inner.as_ref() {
444                Lang::Variable { name: var_name, .. } => {
445                    let binding = format!("{} <- {}[[\"body\"]]", var_name, match_var);
446                    (cond, binding)
447                }
448                Lang::Empty(_) => (cond, String::new()),
449                _ => (cond, String::new()),
450            }
451        }
452        // Type pattern: x as int
453        Lang::TypePattern {
454            variable_name: var_name,
455            matched_type: typ,
456            ..
457        } => {
458            let check_fn = type_to_r_check(typ).unwrap_or("is.logical");
459            let cond = format!("{}({})", check_fn, match_var);
460            let binding = format!("{} <- {}", var_name, match_var);
461            (cond, binding)
462        }
463        // Tuple pattern: :{a, b, c}
464        Lang::Tuple {
465            value: elements, ..
466        } => {
467            let cond = format!(
468                "inherits({}, 'Tuple') && length({}) == {}",
469                match_var,
470                match_var,
471                elements.len()
472            );
473            let bindings: Vec<String> = elements
474                .iter()
475                .enumerate()
476                .filter_map(|(i, elem)| {
477                    if let Lang::Variable { name: var_name, .. } = elem {
478                        if var_name == "_" {
479                            None
480                        } else {
481                            Some(format!("{} <- {}[[{}]]", var_name, match_var, i + 1))
482                        }
483                    } else {
484                        None
485                    }
486                })
487                .collect();
488            (cond, bindings.join("\n"))
489        }
490        // List/record pattern: :{nom: n, age: a}
491        Lang::List { value: fields, .. } => {
492            let conditions: Vec<String> = fields
493                .iter()
494                .map(|arg_val: &ArgumentValue| {
495                    format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument())
496                })
497                .collect();
498            let cond = if conditions.is_empty() {
499                "is.list(".to_string() + match_var + ")"
500            } else {
501                format!("is.list({}) && {}", match_var, conditions.join(" && "))
502            };
503            let bindings: Vec<String> = fields
504                .iter()
505                .filter_map(|arg_val| {
506                    if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
507                        Some(format!(
508                            "{} <- {}[[\"{}\"]]",
509                            var_name,
510                            match_var,
511                            arg_val.get_argument()
512                        ))
513                    } else {
514                        None
515                    }
516                })
517                .collect();
518            (cond, bindings.join("\n"))
519        }
520        // DataFrame pattern: data__frame(col1 = x, col2 = y)
521        Lang::DataFrame { value: fields, .. } => {
522            let conditions: Vec<String> = fields
523                .iter()
524                .map(|arg_val: &ArgumentValue| {
525                    format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument())
526                })
527                .collect();
528            let cond = if conditions.is_empty() {
529                "is.data.frame(".to_string() + match_var + ")"
530            } else {
531                format!(
532                    "is.data.frame({}) && {}",
533                    match_var,
534                    conditions.join(" && ")
535                )
536            };
537            let bindings: Vec<String> = fields
538                .iter()
539                .filter_map(|arg_val| {
540                    if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
541                        Some(format!(
542                            "{} <- {}[[\"{}\"]]",
543                            var_name,
544                            match_var,
545                            arg_val.get_argument()
546                        ))
547                    } else {
548                        None
549                    }
550                })
551                .collect();
552            (cond, bindings.join("\n"))
553        }
554        // Wildcard: _
555        Lang::Variable { name, .. } if name == "_" => ("TRUE".to_string(), String::new()),
556        // Other variable: bind the whole value
557        Lang::Variable { name, .. } => {
558            let binding = format!("{} <- {}", name, match_var);
559            ("TRUE".to_string(), binding)
560        }
561        _ => ("TRUE".to_string(), String::new()),
562    }
563}
564
565impl RTranslatable<(String, Context)> for Lang {
566    fn to_r(&self, cont: &Context) -> (String, Context) {
567        let result = match self {
568            Lang::Bool { value: b, .. } => {
569                let (typ, _, _) = typing(cont, self).to_tuple();
570                let anotation = cont.get_type_anotation(&typ);
571                (
572                    format!("{} |> {}", b.to_string().to_uppercase(), anotation),
573                    cont.clone(),
574                )
575            }
576            Lang::Number { value: n, .. } => {
577                let (typ, _, _) = typing(cont, self).to_tuple();
578                let anotation = cont.get_type_anotation(&typ);
579                (format!("{} |> {}", n, anotation), cont.clone())
580            }
581            Lang::Integer { value: i, .. } => {
582                let (typ, _, _) = typing(cont, self).to_tuple();
583                let anotation = cont.get_type_anotation(&typ);
584                (format!("{}L |> {}", i, anotation), cont.clone())
585            }
586            Lang::Char { value: s, .. } => {
587                let (typ, _, _) = typing(cont, self).to_tuple();
588                let anotation = cont.get_type_anotation(&typ);
589                (
590                    format!("{} |> {}", escape_r_string(s), anotation),
591                    cont.clone(),
592                )
593            }
594            Lang::Operator {
595                operator: op @ (Op::Dot(_) | Op::Pipe(_)),
596                rhs: e1,
597                lhs: e2,
598                ..
599            } => {
600                // `rhs` is the syntactic-left operand, `lhs` is the
601                // syntactic-right operand (same convention as `Op::Dollar`
602                // and the generic operator case below: `e1.to_r() <op>
603                // e2.to_r()`). For plain field access (`p.x`, `e2` a bare
604                // `Lang::Variable` field name and `e1` not an `Integer`),
605                // that means the receiver `e1` must render first:
606                // `e1[['e2']]`, not `e2[['e1']]`. The `Lang::Integer`
607                // sub-case is the synthetic tuple-destructuring node built
608                // in `parsing/mod.rs` (`rhs: Integer(index), lhs: tmp_var`),
609                // which already has the receiver/index roles swapped
610                // relative to that convention on purpose — left as-is, and
611                // `Op::Pipe` keeps its prior (separate, untouched) ordering
612                // since a bare-variable pipe target (`a |> f`) means
613                // something else entirely (`f(a)`, not field indexing).
614                let is_dot = matches!(op, Op::Dot(_));
615                let e1 = (**e1).clone();
616                let e2 = (**e2).clone();
617                match e2.clone() {
618                    Lang::Variable { .. } => match e1 {
619                        Lang::Integer { .. } => Translatable::from(cont.clone())
620                            .to_r(&e2)
621                            .add("[[")
622                            .to_r(&e1)
623                            .add("]]")
624                            .into(),
625                        _ if is_dot => Translatable::from(cont.clone())
626                            .to_r(&e1)
627                            .add("[['")
628                            .to_r(&e2)
629                            .add("']]")
630                            .into(),
631                        _ => Translatable::from(cont.clone())
632                            .to_r(&e2)
633                            .add("[['")
634                            .to_r(&e1)
635                            .add("']]")
636                            .into(),
637                    },
638                    Lang::List { value: fields, .. } => {
639                        let at = fields[0].clone();
640                        Translatable::from(cont.clone())
641                            .add("within(")
642                            .to_r(&e2)
643                            .add(", { ")
644                            .add(&at.get_argument())
645                            .add(" <- ")
646                            .to_r(&at.get_value())
647                            .add(" })")
648                            .into()
649                    }
650                    Lang::DataFrame { value: fields, .. } => {
651                        let at = fields[0].clone();
652                        Translatable::from(cont.clone())
653                            .add("within(")
654                            .to_r(&e2)
655                            .add(", { ")
656                            .add(&at.get_argument())
657                            .add(" <- ")
658                            .to_r(&at.get_value())
659                            .add(" })")
660                            .into()
661                    }
662                    Lang::FunctionApp {
663                        identifier: var,
664                        arguments: v,
665                        help_data: h,
666                    } => {
667                        let v = [e1].iter().chain(v.iter()).cloned().collect();
668                        Lang::FunctionApp {
669                            identifier: var,
670                            arguments: v,
671                            help_data: h,
672                        }
673                        .to_r(cont)
674                    }
675                    _ => Translatable::from(cont.clone())
676                        .to_r(&e2)
677                        .add("[[")
678                        .add("]]")
679                        .to_r(&e1)
680                        .into(),
681                }
682            }
683            Lang::Operator {
684                operator: Op::Dollar(_),
685                rhs: e1,
686                lhs: e2,
687                ..
688            } => {
689                let e1 = (**e1).clone();
690                let e2 = (**e2).clone();
691                let t1 = typing(cont, &e1).value;
692                let val = match (t1.clone(), e2.clone()) {
693                    (Type::Vec(vtype, _, _, _), Lang::Variable { name, .. })
694                        if vtype.is_array() =>
695                    {
696                        format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name)
697                    }
698                    (Type::Vec(VecType::S3, _, _, _), Lang::Variable { name, .. }) => {
699                        let name_str = name.replace("__", ".");
700                        format!("get({}, '{}')", e1.to_r(cont).0, name_str)
701                    }
702                    (_, Lang::Variable { name, .. }) => format!("{}${}", e1.to_r(cont).0, name),
703                    _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0),
704                };
705                (val, cont.clone())
706            }
707            Lang::Operator {
708                operator: op,
709                rhs: e1,
710                lhs: e2,
711                ..
712            } => {
713                let op_str = format!(" {} ", op);
714                Translatable::from(cont.clone())
715                    .to_r(e1)
716                    .add(&op_str)
717                    .to_r(e2)
718                    .into()
719            }
720            Lang::Scope { body: exps, .. } => Translatable::from(cont.clone())
721                .add("{\n")
722                .join(exps, "\n")
723                .add("\n}")
724                .into(),
725            Lang::Function {
726                parameters: params,
727                body,
728                ..
729            } => {
730                let fn_type = FunctionType::try_from(typing(cont, self).value.clone())
731                    .expect("function expression should have a function type");
732                let return_type = fn_type.get_return_type();
733
734                // Record alias constructors take specific named fields — calling
735                // TypeName(single_value) would fail.  The body already constructs
736                // the correct type (via ConstructorCall or List), so skip the
737                // output conversion for record aliases.
738                let is_record_alias_return = match &return_type {
739                    // `cont.aliases()` only has aliases explicitly `use`d (or
740                    // declared) in this module's own scope. A return type can
741                    // name a record alias that was never imported by name here
742                    // (only its constructor/functions were), resolved instead
743                    // through the whole-program `record_aliases` registry — the
744                    // same fallback `get_matching_alias_signature` relies on.
745                    Type::Alias(alias_name, _, _, _) => cont
746                        .aliases()
747                        .find(|(var, _)| var.get_name() == *alias_name)
748                        .map(|(_, t)| t.clone())
749                        .or_else(|| {
750                            cont.record_aliases
751                                .iter()
752                                .find(|(name, _)| name == alias_name)
753                                .map(|(_, t)| t.clone())
754                        })
755                        .map(|t| matches!(t, Type::Record(_, _)))
756                        .unwrap_or(false),
757                    _ => false,
758                };
759                let output_conversion = if is_record_alias_return {
760                    "".to_string()
761                } else {
762                    cont.get_type_anotation(&return_type)
763                };
764
765                let has_variadic = params.last().map(|p| p.is_variadic()).unwrap_or(false);
766                let list_of_types = params
767                    .iter()
768                    .map(ArgumentType::body_type)
769                    .collect::<Vec<_>>();
770                let sub_context = params
771                    .iter()
772                    .map(|arg_typ| arg_typ.clone().set_type(arg_typ.body_type()).to_var(cont))
773                    .zip(list_of_types.clone())
774                    .fold(cont.clone(), |context: Context, (var, typ)| {
775                        context.clone().push_var_type(var, typ, &context)
776                    });
777                let res = if output_conversion.is_empty() {
778                    "".to_string()
779                } else {
780                    " |> ".to_owned() + &output_conversion
781                };
782                let body_r = body.to_r(&sub_context).0;
783                let final_body_r = if has_variadic {
784                    let vname = params.last().unwrap().get_argument_str();
785                    // The body sees the variadic param as `[#N, T]`, so collect
786                    // the R `...` into a `typed_vec` to match the S3 dispatch the
787                    // stdlib array functions (`sum`, `map`, `length`, …) rely on.
788                    let collector = "typed_vec(..., dim = c(...length()))";
789                    // inject `vname <- typed_vec(...)` after opening `{`
790                    if let Some(rest) = body_r.strip_prefix('{') {
791                        format!("{{\n{} <- {}{}", vname, collector, rest)
792                    } else {
793                        body_r
794                    }
795                } else {
796                    body_r
797                };
798                (
799                    format!(
800                        "(function({}) {}{}) |> {}",
801                        params
802                            .iter()
803                            .map(|x| x.to_r(cont))
804                            .collect::<Vec<_>>()
805                            .join(", "),
806                        final_body_r,
807                        res,
808                        cont.get_type_anotation(&fn_type.into())
809                    ),
810                    cont.clone(),
811                )
812            }
813            Lang::Variable { .. } => {
814                //Here we only keep the variable name, the path and the type
815                let var = Var::from_language(self.clone()).unwrap();
816                let name = if var.contains("__") {
817                    var.replace("__", ".").get_name()
818                } else {
819                    var.display_type(cont).get_name()
820                };
821                (name.to_string(), cont.clone())
822            }
823            Lang::FunctionApp {
824                identifier: exp,
825                arguments: vals,
826                ..
827            } => {
828                let var = Var::try_from(exp.clone()).unwrap();
829
830                let (exp_str, cont1) = exp.to_r(cont);
831                // Callee may be resolved purely structurally (e.g. an interface
832                // method called as a plain function on a rigid-generic/interface
833                // receiver, §5 elimination — `try_constrained_variable_match` in
834                // function_application.rs), with no directly registered
835                // `(name, FunctionType)` entry to look up here. The R call is
836                // still just `name(args...)`: S3 dispatch on the first argument
837                // resolves the concrete implementation at runtime, wherever it's
838                // defined. In that case skip the param-type reduction/annotation
839                // below (it only matters for annotating a variable *argument*
840                // that is itself an overloaded top-level function reference) and
841                // pass the arguments through unchanged.
842                let fn_t_opt = cont1
843                    .get_type_from_variable(&var)
844                    .ok()
845                    .and_then(|t| FunctionType::try_from(t).ok())
846                    .map(|ft| ft.adjust_nb_parameters(vals.len()));
847                let new_vals = match &fn_t_opt {
848                    Some(fn_t) => {
849                        let new_args = fn_t
850                            .get_param_types()
851                            .iter()
852                            .map(|arg| reduce_type(&cont1, arg))
853                            .collect::<Vec<_>>();
854                        vals.iter()
855                            .zip(new_args.iter())
856                            .map(set_related_type_if_variable)
857                            .collect::<Vec<_>>()
858                    }
859                    None => vals.clone(),
860                };
861                let cont1_fallback = cont1.clone();
862                Var::from_language(*exp.clone())
863                    .map(|var| {
864                        let name = var.get_name();
865                        let new_name = if &name[0..1] == "%" {
866                            format!("`{}`", name.replace("__", "."))
867                        } else {
868                            name.replace("__", ".")
869                        };
870                        if cont1.is_extern_fn(&name) {
871                            let r_name = cont1
872                                .get_extern_r_name(&name)
873                                .unwrap_or_else(|| new_name.clone());
874                            let return_type = fn_t_opt
875                                .as_ref()
876                                .map(|ft| ft.get_return_type())
877                                .expect("extern function application identifier should have a function type");
878                            let lift_fn = extern_lift_fn(&return_type);
879                            let (args_vec, current_cont): (Vec<String>, Context) = new_vals
880                                .iter()
881                                .fold((Vec::new(), cont1.clone()), |(mut v, c), val| {
882                                    let (s, c2) = val.to_r(&c);
883                                    v.push(format!("to_native({})", s));
884                                    (v, c2)
885                                });
886                            let args = args_vec.join(", ");
887                            let call = format!("{}({})", r_name, args);
888                            let result = match lift_fn {
889                                Some(f) => format!("{}({})", f, call),
890                                None => call,
891                            };
892                            (result, current_cont)
893                        } else if cont1.is_import_from_fn(&name) {
894                            let r_name = cont1
895                                .get_import_from_r_name(&name)
896                                .unwrap_or_else(|| new_name.clone());
897                            let (args, current_cont) =
898                                Translatable::from(cont1).join(&new_vals, ", ").into();
899                            (format!("{}({})", r_name, args), current_cont)
900                        } else {
901                            let (args, current_cont) =
902                                Translatable::from(cont1).join(&new_vals, ", ").into();
903                            (format!("{}({})", new_name, args), current_cont)
904                        }
905                    })
906                    .unwrap_or_else(|| {
907                        let (args, current_cont) = Translatable::from(cont1_fallback)
908                            .join(&new_vals, ", ")
909                            .into();
910                        (format!("{}({})", exp_str, args), current_cont)
911                    })
912            }
913            Lang::VecFunctionApp {
914                vector_type,
915                identifier: exp,
916                arguments: vals,
917                ..
918            } => {
919                let var = Var::try_from(exp.clone()).unwrap();
920                let name = var.get_name();
921                let str_vals = vals
922                    .iter()
923                    .map(|x| x.to_r(cont).0)
924                    .collect::<Vec<_>>()
925                    .join(", ");
926                if *vector_type == VecType::Vector {
927                    // `Vec[N, T]` values transpile to plain R atomic vectors (see
928                    // vectors.md): R already vectorizes arithmetic/comparison
929                    // operators and ordinary scalar functions over them natively,
930                    // so no `vec_apply` (typed_vec normalization + manual
931                    // recycling, needed for the `[N, T]` / S3-array mechanism) is
932                    // required here — just call the function plainly.
933                    if cont.is_an_untyped_function(&name) {
934                        let name = name.replace("__", ".");
935                        let new_name = if &name[0..1] == "%" {
936                            format!("`{}`", name)
937                        } else {
938                            name.to_string()
939                        };
940                        (format!("{}({})", new_name, str_vals), cont.clone())
941                    } else {
942                        let (exp_str, cont1) = exp.to_r(cont);
943                        // See the comment on the analogous fallback in
944                        // `Lang::FunctionApp` above: a structurally-resolved
945                        // (interface/generic-dispatched) callee has no
946                        // registered signature to look up here.
947                        let new_vals = match cont1
948                            .get_type_from_variable(&var)
949                            .ok()
950                            .and_then(|t| FunctionType::try_from(t).ok())
951                        {
952                            Some(fn_t) => {
953                                let new_args = fn_t
954                                    .get_param_types()
955                                    .iter()
956                                    .map(|arg| reduce_type(&cont1, arg))
957                                    .collect::<Vec<_>>();
958                                vals.iter()
959                                    .zip(new_args.iter())
960                                    .map(set_related_type_if_variable)
961                                    .collect::<Vec<_>>()
962                            }
963                            None => vals.clone(),
964                        };
965                        let (args, current_cont) =
966                            Translatable::from(cont1).join(&new_vals, ", ").into();
967                        Var::from_language(*exp.clone())
968                            .map(|var| {
969                                let name = var.get_name();
970                                let new_name = if &name[0..1] == "%" {
971                                    format!("`{}`", name.replace("__", "."))
972                                } else {
973                                    name.replace("__", ".")
974                                };
975                                (format!("{}({})", new_name, args), current_cont.clone())
976                            })
977                            .unwrap_or((format!("{}({})", exp_str, args), current_cont))
978                    }
979                } else if name == "reduce" {
980                    (format!("vec_reduce({})", str_vals), cont.clone())
981                } else if name == "extend" {
982                    (format!("vec_extend({})", str_vals), cont.clone())
983                } else if cont.is_an_untyped_function(&name) {
984                    let name = name.replace("__", ".");
985                    let new_name = if &name[0..1] == "%" {
986                        format!("`{}`", name)
987                    } else {
988                        name.to_string()
989                    };
990                    let s = format!("vec_apply({}, {})", new_name, str_vals);
991                    (s, cont.clone())
992                } else {
993                    let (exp_str, cont1) = exp.to_r(cont);
994                    // See the comment on the analogous fallback in
995                    // `Lang::FunctionApp` above.
996                    let new_vals = match cont1
997                        .get_type_from_variable(&var)
998                        .ok()
999                        .and_then(|t| FunctionType::try_from(t).ok())
1000                    {
1001                        Some(fn_t) => {
1002                            let new_args = fn_t
1003                                .get_param_types()
1004                                .iter()
1005                                .map(|arg| reduce_type(&cont1, arg))
1006                                .collect::<Vec<_>>();
1007                            vals.iter()
1008                                .zip(new_args.iter())
1009                                .map(set_related_type_if_variable)
1010                                .collect::<Vec<_>>()
1011                        }
1012                        None => vals.clone(),
1013                    };
1014                    let (args, current_cont) =
1015                        Translatable::from(cont1).join(&new_vals, ", ").into();
1016                    Var::from_language(*exp.clone())
1017                        .map(|var| {
1018                            let name = var.get_name();
1019                            let new_name = if &name[0..1] == "%" {
1020                                format!("`{}`", name.replace("__", "."))
1021                            } else {
1022                                name.replace("__", ".")
1023                            };
1024                            (
1025                                format!("vec_apply({}, {})", new_name, args),
1026                                current_cont.clone(),
1027                            )
1028                        })
1029                        .unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont))
1030                }
1031            }
1032            Lang::ArrayIndexing {
1033                identifier: exp,
1034                indexing: val,
1035                ..
1036            } => {
1037                let (exp_str, _) = exp.to_r(cont);
1038                // v[-n] → v[[length(v) + (1 - n)]] (count from end)
1039                let negative_idx = val.get_members_if_array().and_then(|members| {
1040                    if members.len() == 1 {
1041                        if let Lang::Integer { value: i, .. } = &members[0] {
1042                            if *i < 0 {
1043                                Some(*i)
1044                            } else {
1045                                None
1046                            }
1047                        } else {
1048                            None
1049                        }
1050                    } else {
1051                        None
1052                    }
1053                });
1054                let res = if let Some(neg) = negative_idx {
1055                    let offset = 1 + neg; // e.g. -1 → 0, -2 → -1
1056                    if offset == 0 {
1057                        format!("{}[[length({})]]", exp_str, exp_str)
1058                    } else if offset < 0 {
1059                        format!("{}[[length({}) - {}L]]", exp_str, exp_str, -offset)
1060                    } else {
1061                        format!("{}[[length({}) + {}L]]", exp_str, exp_str, offset)
1062                    }
1063                } else {
1064                    let (val_str, _) = val.to_simple_r(cont);
1065                    format!("{}[[{}]]", exp_str, val_str)
1066                };
1067                (res, cont.clone())
1068            }
1069            Lang::GenFunc { name: func, .. } => (
1070                format!("function(x, ...) UseMethod('{}')", func),
1071                cont.clone(),
1072            ),
1073            Lang::Let {
1074                variable: expr,
1075                r#type: ttype,
1076                expression: body,
1077                is_public: _,
1078                is_testable: _,
1079                is_export,
1080                help_data: _,
1081            } => {
1082                let (body_str, new_cont) = body.to_r(cont);
1083                let new_name = format_backtick(expr.clone().to_r(cont).0);
1084
1085                let (r_code, _new_name2) = Function::try_from((**body).clone())
1086                    .map(|_| {
1087                        let related_type = Var::try_from(expr)
1088                            .ok()
1089                            .map(|v| v.get_type())
1090                            .filter(|t| !matches!(t, Type::Empty(_) | Type::UnknownFunction(_)))
1091                            .unwrap_or_else(|| typing(cont, expr).value);
1092                        let method = match cont.get_environment() {
1093                            Environment::Project => format!(
1094                                "#' @method {}\n",
1095                                new_name.replace(".", " ").replace("`", "")
1096                            ),
1097                            _ => "".to_string(),
1098                        };
1099                        match related_type {
1100                            Type::Empty(_) => {
1101                                (format!("{} <- {}", new_name, body_str), new_name.clone())
1102                            }
1103                            // `Type::Any` is the one case `display_type` (which produced
1104                            // `new_name` above) deliberately leaves unsuffixed — see its
1105                            // own `Type::Empty(_) | Type::Any(_) => ""` arm — so `.default`
1106                            // must be appended explicitly here. Bare/kinded generics
1107                            // (`Type::Generic`, `Type::KindedGen`) are NOT special-cased:
1108                            // `display_type` already resolved them to `name.default` via
1109                            // `get_class`'s "default" fallback, so they fall through to
1110                            // the catch-all `_` arm below and use `new_name` as-is.
1111                            Type::Any(_) => (
1112                                format!("{}.default <- {}", new_name, body_str),
1113                                new_name.clone(),
1114                            ),
1115                            _ => {
1116                                let mut code = format!("{}{} <- {}", method, new_name, body_str);
1117                                // Interfaces are satisfied structurally at compile
1118                                // time, but R dispatches nominally on the runtime
1119                                // class vector. Constructed records carry the
1120                                // interface class (see the record-alias arm), but
1121                                // primitives (`integer`, `character`, …) and values
1122                                // built outside TypR constructors never do — so a
1123                                // method whose dispatch parameter is a *pure*
1124                                // interface (interface facet, no record facet) also
1125                                // registers itself as the `.default` fallback.
1126                                // `UseMethod` only reaches `.default` when no more
1127                                // specific method matches, so this never overrides
1128                                // a concrete-type method.
1129                                let suffix = cont.get_class_unquoted(&related_type);
1130                                if suffix != "default"
1131                                    && facets::interface_facet(cont, &related_type).is_some()
1132                                    && facets::record_facet(cont, &related_type).is_none()
1133                                {
1134                                    // `new_name` may be backtick-wrapped (dotted
1135                                    // method names always are) — strip them before
1136                                    // splitting off the suffix, re-wrap on emit.
1137                                    if let Some(base) = new_name
1138                                        .trim_matches('`')
1139                                        .strip_suffix(&format!(".{}", suffix))
1140                                    {
1141                                        let default_method = match cont.get_environment() {
1142                                            Environment::Project => {
1143                                                format!("#' @method {} default\n", base)
1144                                            }
1145                                            _ => "".to_string(),
1146                                        };
1147                                        code = format!(
1148                                            "{}\n{}{} <- {}",
1149                                            code,
1150                                            default_method,
1151                                            format_backtick(format!("{}.default", base)),
1152                                            new_name
1153                                        );
1154                                    }
1155                                }
1156                                (code, new_name.clone())
1157                            }
1158                        }
1159                    })
1160                    .unwrap_or((format!("{} <- {}", new_name, body_str), new_name));
1161                let code = if !ttype.is_empty() {
1162                    let type_annotation = new_cont.get_type_anotation(ttype);
1163                    format!("{} |> {}\n", r_code, type_annotation)
1164                } else {
1165                    r_code + "\n"
1166                };
1167                // RFC-TR-032: @export prepends `#' @export` for R package API
1168                let code = if *is_export {
1169                    format!("#' @export\n{}", code)
1170                } else {
1171                    code
1172                };
1173                (code, new_cont)
1174            }
1175            Lang::Array { .. } => {
1176                let typ = self.typing(cont).value;
1177                let array = array_literal_raw(self, cont);
1178                (
1179                    format!("{} |> {}", array, cont.get_type_anotation(&typ)),
1180                    cont.to_owned(),
1181                )
1182            }
1183            Lang::List {
1184                value: args,
1185                spreads,
1186                ..
1187            } if spreads.is_empty() => {
1188                let (body, current_cont) = Translatable::from(cont.clone())
1189                    .join_arg_val(args, ",\n ")
1190                    .into();
1191                let (typ, _, _) = typing(cont, self).to_tuple();
1192                // For record-alias types use the constructor directly
1193                if let Type::Alias(alias_name, _, _, _) = &typ {
1194                    let is_record = cont
1195                        .aliases()
1196                        .find(|(var, _)| var.get_name() == *alias_name)
1197                        .map(|(_, t)| matches!(t, Type::Record(_, _)))
1198                        .unwrap_or(false);
1199                    if is_record {
1200                        return (format!("{}({})", alias_name, body), current_cont);
1201                    }
1202                }
1203                let anotation = cont.get_type_anotation(&typ);
1204                cont.get_classes(&typ)
1205                    .map(|_| format!("list({}) |> {}", body, anotation))
1206                    .unwrap_or(format!("list({}) |> {}", body, anotation))
1207                    .to_some()
1208                    .map(|s| (s, current_cont))
1209                    .unwrap()
1210            }
1211            // Record literal with one or more `...source` spreads
1212            // (spread_operator2.md): merge spreads sequentially into `base`
1213            // with the runtime `spread()` helper, then apply explicit
1214            // fields as the final override — never via static field
1215            // expansion, so unknown/row-polymorphic fields carried by the
1216            // runtime value of `source` are preserved (§5-§7).
1217            Lang::List {
1218                value: args,
1219                spreads,
1220                ..
1221            } => {
1222                let mut spreads_iter = spreads.iter();
1223                let first = spreads_iter.next().expect("checked non-empty above");
1224                let (mut base, mut current_cont) = first.to_r(cont);
1225                for spread_expr in spreads_iter {
1226                    let (next, next_cont) = spread_expr.to_r(&current_cont);
1227                    base = format!("spread({}, {})", base, next);
1228                    current_cont = next_cont;
1229                }
1230                if args.is_empty() {
1231                    return (base, current_cont);
1232                }
1233                let (overrides, current_cont) = Translatable::from(current_cont)
1234                    .join_arg_val(args, ", ")
1235                    .into();
1236                (
1237                    format!("spread({}, list({}))", base, overrides),
1238                    current_cont,
1239                )
1240            }
1241            Lang::DataFrame { value: args, .. } => {
1242                let (body, current_cont) = Translatable::from(cont.clone())
1243                    .join_arg_val(args, ",\n ")
1244                    .into();
1245                let (typ, _, _) = typing(cont, self).to_tuple();
1246                let anotation = cont.get_type_anotation(&typ);
1247                cont.get_classes(&typ)
1248                    .map(|_| format!("data.frame({}) |> {}", body, anotation))
1249                    .unwrap_or(format!("data.frame({}) |> {}", body, anotation))
1250                    .to_some()
1251                    .map(|s| (s, current_cont))
1252                    .unwrap()
1253            }
1254            Lang::If {
1255                condition: cond,
1256                if_block: exp,
1257                else_block: els,
1258                ..
1259            } if els == &Box::new(Lang::Empty(HelpData::default())) => {
1260                Translatable::from(cont.clone())
1261                    .add("if(")
1262                    .to_r(cond)
1263                    .add(") {\n")
1264                    .to_r(exp)
1265                    .add(" \n}")
1266                    .into()
1267            }
1268            Lang::If {
1269                condition: cond,
1270                if_block: exp,
1271                else_block: els,
1272                help_data: _,
1273            } => Translatable::from(cont.clone())
1274                .add("if(")
1275                .to_r(cond)
1276                .add(") {\n")
1277                .to_r(exp)
1278                .add(" \n} else ")
1279                .to_r(els)
1280                .into(),
1281            Lang::Tuple { value: vals, .. } => {
1282                // Attach the registered TupleN alias class on top of the bare
1283                // 'Tuple' marker — S3 methods on tuple-typed parameters are
1284                // emitted as `name.TupleN`, so the runtime value must carry
1285                // that class for UseMethod dispatch to find them.
1286                let typ = self.typing(cont).value;
1287                let (body, current_cont): (String, Context) = Translatable::from(cont.clone())
1288                    .add("struct(list(")
1289                    .join(vals, ", ")
1290                    .add("), 'Tuple')")
1291                    .into();
1292                (
1293                    format!("{} |> {}", body, cont.get_type_anotation(&typ)),
1294                    current_cont,
1295                )
1296            }
1297            Lang::Assign {
1298                identifier: var,
1299                expression: exp,
1300                ..
1301            } => Translatable::from(cont.clone())
1302                .to_r(var)
1303                .add(" <- ")
1304                .to_r(exp)
1305                .into(),
1306            Lang::Comment { value: txt, .. } => ("#".to_string() + txt, cont.clone()),
1307            Lang::Tag {
1308                name: s, value: t, ..
1309            } => {
1310                let (t_str, new_cont) = t.to_r(cont);
1311                let is_empty = matches!(t.as_ref(), Lang::Empty(_));
1312                // Canonical representation (see validation_variant_d_union.md §2):
1313                // tag identity in position 1, payload under `body`, class enriched
1314                // with the union name (when the tag belongs to a declared union)
1315                // plus `Tag`/`list`. This makes the literal interchangeable with
1316                // the value produced by the variant constructor `V(...)`, so
1317                // `match` and the variant validators apply to both origins.
1318                let class = match find_union_for_tag(s, cont) {
1319                    Some(union_name) => format!("c('{}', '{}', 'Tag', 'list')", s, union_name),
1320                    None => format!("c('{}', 'Tag', 'list')", s),
1321                };
1322                let value = if is_empty {
1323                    format!("structure(list('{}'), class = {})", s, class)
1324                } else {
1325                    format!(
1326                        "structure(list('{}', body = {}), class = {})",
1327                        s, t_str, class
1328                    )
1329                };
1330                (value, new_cont)
1331            }
1332            Lang::Null(_) => ("NULL".to_string(), cont.clone()),
1333            Lang::Empty(_) => ("NA".to_string(), cont.clone()),
1334            Lang::Lines { value: exps, .. } => {
1335                Translatable::from(cont.clone()).join(exps, "\n").into()
1336            }
1337            Lang::Return { value: exp, .. } => Translatable::from(cont.clone())
1338                .add("return ")
1339                .to_r(exp)
1340                .into(),
1341            Lang::Lambda {
1342                parameters: params,
1343                body: bloc,
1344                ..
1345            } => {
1346                let param_names: Vec<String> = params
1347                    .iter()
1348                    .map(|p: &Lang| match p {
1349                        Lang::Variable { name, .. } => name.clone(),
1350                        _ => "x".to_string(),
1351                    })
1352                    .collect();
1353                (
1354                    format!(
1355                        "function({}) {{ {} }}",
1356                        param_names.join(", "),
1357                        bloc.to_r(cont).0
1358                    ),
1359                    cont.clone(),
1360                )
1361            }
1362            Lang::VecBlock { value: bloc, .. } => (bloc.to_string(), cont.clone()),
1363            Lang::Library { value: name, .. } => (format!("library({})", name), cont.clone()),
1364            Lang::Match {
1365                target: exp,
1366                branches,
1367                ..
1368            } => (
1369                to_pattern_match_statement((**exp).clone(), branches, cont),
1370                cont.clone(),
1371            ),
1372            Lang::Exp { value: exp, .. } => (exp.clone(), cont.clone()),
1373            Lang::ForLoop {
1374                identifier: var,
1375                expression: iterator,
1376                body,
1377                ..
1378            } => Translatable::from(cont.clone())
1379                .add("for (")
1380                .to_r_safe(var)
1381                .add(" in ")
1382                .to_r_safe(iterator)
1383                .add(") {\n")
1384                .to_r_safe(body)
1385                .add("\n}")
1386                .into(),
1387            Lang::RFunction {
1388                parameters: vars,
1389                body,
1390                ..
1391            } => Translatable::from(cont.clone())
1392                .add("function (")
1393                .join(vars, ", ")
1394                .add(") \n")
1395                .add(body)
1396                .add("\n")
1397                .into(),
1398            Lang::ExternBlock {
1399                parameters: params,
1400                body,
1401                ..
1402            } => {
1403                let param_names = params
1404                    .iter()
1405                    .map(|p| p.to_r(cont))
1406                    .collect::<Vec<_>>()
1407                    .join(", ");
1408                (
1409                    format!("function({}) {{\n{}\n}}", param_names, body),
1410                    cont.clone(),
1411                )
1412            }
1413            Lang::Signature { .. } => ("".to_string(), cont.clone()),
1414            Lang::TypeConstructor { .. } => ("".to_string(), cont.clone()),
1415            Lang::Alias {
1416                identifier: ident,
1417                target_type: typ,
1418                ..
1419            } => {
1420                let name = Var::from_language(*ident.clone())
1421                    .map(|v| v.get_name())
1422                    .unwrap_or_default();
1423                // An intersection alias (`A & B`, `%T & list {...}`, ...) has
1424                // no fixed runtime shape on any non-record member — TypR
1425                // doesn't monomorphize generics, R has no class for "any
1426                // record", and interface members contribute methods, not
1427                // fields — but every `Record`/list member found anywhere in
1428                // the intersection does have fields worth constructing and
1429                // validating. `facets::record_facet` flattens the whole
1430                // intersection and merges all such members into one field
1431                // set (see its doc comment for why a simple pairwise
1432                // `norm_intersection` reduction isn't enough on its own), so
1433                // substituting that merged record here routes any list-
1434                // bearing intersection into the existing `Type::Record`
1435                // pipeline below (constructor/annotator/validator) instead
1436                // of the unrelated union-alias `Type::Operator` catch-all,
1437                // which doesn't apply here and previously produced no R code
1438                // at all for these shapes, leaving `validate_X`/`as.X`
1439                // referenced elsewhere but never defined.
1440                let typ_for_dispatch: Type = match typ {
1441                    Type::Operator(TypeOperator::Intersection, _, _, h) => {
1442                        facets::record_facet(cont, typ)
1443                            .map(|fields| Type::Record(fields, h.clone()))
1444                            .unwrap_or_else(|| typ.clone())
1445                    }
1446                    _ => typ.clone(),
1447                };
1448                match &typ_for_dispatch {
1449                    Type::Record(fields, _) => {
1450                        let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1451                        sorted_fields.sort_by_key(|f| f.get_argument_str());
1452                        let params = sorted_fields
1453                            .iter()
1454                            .map(|f| f.get_argument_str())
1455                            .collect::<Vec<_>>()
1456                            .join(", ");
1457                        // Each field is only added to `explicit` when the caller
1458                        // actually supplied it (`missing()`, not a NULL default):
1459                        // a record-typed `.spread` (spread_operator3.md) may cover
1460                        // the field instead, and `missing()` never forces the
1461                        // argument promise, so unsupplied fields stay lazy/unevaluated.
1462                        let explicit_lines = sorted_fields
1463                            .iter()
1464                            .map(|f| {
1465                                let n = f.get_argument_str();
1466                                format!("  if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1467                            })
1468                            .collect::<Vec<_>>()
1469                            .join("\n");
1470                        // Constructor: collect the explicitly-supplied fields, merge
1471                        // them over `.spread` (explicit wins, extra spread fields are
1472                        // kept), then delegate entirely to the annotator. It neither
1473                        // adds classes nor validates.
1474                        let constructor = format!(
1475                            "{name} <- function({params}, .spread = NULL) {{\n  explicit <- list()\n{explicit_lines}\n  x <- typr_spread_record(explicit, .spread)\n  as.{name}(x)\n}}"
1476                        );
1477                        // Structural supertypes: any record alias whose fields are a
1478                        // strict subset of this alias's fields. They are included in
1479                        // the S3 class vector so that methods defined on the supertype
1480                        // dispatch correctly to subtype values.
1481                        //
1482                        // Candidates come from the whole-program `record_aliases`
1483                        // registry, not just `cont.aliases()`: the latter is scoped
1484                        // to the current module body, so a supertype declared in a
1485                        // sibling `mod` file (e.g. `Position` while transpiling
1486                        // `Circle` in another file) would otherwise never be found,
1487                        // even though R's S3 classes have no module privacy.
1488                        // Built as an ordered Vec (not a HashMap) and deduplicated by
1489                        // first occurrence, so candidate order — and therefore the
1490                        // final sort below — stays deterministic across runs.
1491                        let mut seen_names: std::collections::HashSet<String> =
1492                            std::collections::HashSet::new();
1493                        let candidates: Vec<(String, Type)> = cont
1494                            .aliases()
1495                            .map(|(var, typ)| (var.get_name(), typ.clone()))
1496                            .chain(cont.record_aliases.iter().cloned())
1497                            .filter(|(other_name, _)| seen_names.insert(other_name.clone()))
1498                            .collect();
1499                        let mut supertype_entries: Vec<(String, usize)> = candidates
1500                            .into_iter()
1501                            .filter_map(|(other_name, typ)| {
1502                                if other_name == name {
1503                                    return None;
1504                                }
1505                                if let Type::Record(other_fields, _) = typ {
1506                                    if fields.is_superset(&other_fields) && other_fields != *fields
1507                                    {
1508                                        Some((other_name, other_fields.len()))
1509                                    } else {
1510                                        None
1511                                    }
1512                                } else {
1513                                    None
1514                                }
1515                            })
1516                            .collect();
1517                        // More-specific supertypes (more fields) first for correct S3
1518                        // dispatch order; ties broken by name for determinism.
1519                        supertype_entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
1520                        // Interfaces-as-classes: any named interface alias — or an
1521                        // intersection alias with an interface facet, e.g.
1522                        // `Combined <- list {...} & interface {...}` — that this
1523                        // record structurally satisfies is also added to the class
1524                        // vector, so functions dispatching on the interface name
1525                        // (`describe.Viewable`) apply to values of this type through
1526                        // normal S3 dispatch. Satisfaction is checked against the
1527                        // final whole-program context (transpiling runs after all
1528                        // typing), so implementing functions declared later in the
1529                        // source are already visible here.
1530                        let self_alias =
1531                            Type::Alias(name.clone(), vec![], false, HelpData::default());
1532                        let mut seen_ifaces: std::collections::HashSet<String> =
1533                            std::collections::HashSet::new();
1534                        let mut interface_entries: Vec<(String, usize)> = cont
1535                            .aliases()
1536                            .filter(|(var, _)| var.get_name() != name)
1537                            .filter(|(_, alias_typ)| !alias_typ.has_generic())
1538                            .filter_map(|(var, alias_typ)| {
1539                                let methods = facets::interface_facet(cont, alias_typ)?;
1540                                (seen_ifaces.insert(var.get_name())
1541                                    && self_alias.is_subtype_raw(alias_typ, cont))
1542                                .then(|| (var.get_name(), methods.len()))
1543                            })
1544                            .collect();
1545                        // More methods = more specific; ties broken by name.
1546                        interface_entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
1547                        // Record supertypes (structural field subsets) first, then
1548                        // interfaces: a record supertype is always at least as
1549                        // specific a match as an interface constraint.
1550                        let all_super_names: Vec<String> = supertype_entries
1551                            .iter()
1552                            .chain(interface_entries.iter())
1553                            .map(|(n, _)| format!("\"{n}\""))
1554                            .collect();
1555                        let supertype_class_str = if all_super_names.is_empty() {
1556                            String::new()
1557                        } else {
1558                            format!(", {}", all_super_names.join(", "))
1559                        };
1560                        // Annotator: the single entry point that adds the class
1561                        // (idempotently), runs the internal validator, then the
1562                        // user validator (`validate` S3 generic, default = identity).
1563                        let annotator = format!(
1564                            "as.{name} <- function(x) {{\n  if (!inherits(x, \"{name}\")) class(x) <- c(\"{name}\"{supertype_class_str}, \"list\")\n  x <- validate_{name}(x)\n  x <- validate(x)\n  x\n}}"
1565                        );
1566                        let fields_quoted = sorted_fields
1567                            .iter()
1568                            .map(|f| format!("\"{}\"", f.get_argument_str()))
1569                            .collect::<Vec<_>>()
1570                            .join(", ");
1571                        // Per-field type invariants, checked by class (`inherits`).
1572                        // Fields whose type has no reliable nominal class are only
1573                        // checked for presence (above).
1574                        let field_checks = sorted_fields
1575                            .iter()
1576                            .filter_map(|f| {
1577                                let n = f.get_argument_str();
1578                                record_field_class(&f.body_type(), cont).map(|cls| {
1579                                    format!(
1580                                        "  if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: field '{n}' must be of class {cls}\")"
1581                                    )
1582                                })
1583                            })
1584                            .collect::<Vec<_>>()
1585                            .join("\n");
1586                        let field_checks_block = if field_checks.is_empty() {
1587                            String::new()
1588                        } else {
1589                            format!("{field_checks}\n")
1590                        };
1591                        // Internal validator: pure structural invariants only.
1592                        // It must not reconstruct the value (that would recurse
1593                        // back through the constructor / annotator).
1594                        let validator = format!(
1595                            "validate_{name} <- function(x) {{\n  required_fields <- c({fields_quoted})\n  missing_fields <- setdiff(required_fields, names(x))\n  if (length(missing_fields) > 0) {{\n    stop(paste0(\"Validation failed for type {name}: missing fields: \", paste(missing_fields, collapse = \", \")))\n  }}\n{field_checks_block}  x\n}}"
1596                        );
1597                        (
1598                            format!("{constructor}\n{annotator}\n{validator}"),
1599                            cont.clone(),
1600                        )
1601                    }
1602                    // Dataframe alias (`type Df <- dataframe[#N]{ ... }` /
1603                    // `df[3]{ ... }`): same constructor/annotator/validator
1604                    // pipeline as a record alias, but columns are assembled
1605                    // into a `data.frame` instead of a `list`, the class
1606                    // chain carries `"data.frame"`, and a concrete size
1607                    // index (`df[3]{...}`) additionally checks `nrow(x)`.
1608                    Type::Vec(VecType::DataFrame, size, fields_type, _)
1609                        if matches!(fields_type.as_ref(), Type::Record(_, _)) =>
1610                    {
1611                        use crate::components::r#type::tint::Tint;
1612                        let fields = match fields_type.as_ref() {
1613                            Type::Record(fields, _) => fields,
1614                            _ => unreachable!(),
1615                        };
1616                        let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1617                        sorted_fields.sort_by_key(|f| f.get_argument_str());
1618                        let params = sorted_fields
1619                            .iter()
1620                            .map(|f| f.get_argument_str())
1621                            .collect::<Vec<_>>()
1622                            .join(", ");
1623                        let explicit_lines = sorted_fields
1624                            .iter()
1625                            .map(|f| {
1626                                let n = f.get_argument_str();
1627                                format!("  if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1628                            })
1629                            .collect::<Vec<_>>()
1630                            .join("\n");
1631                        // Constructor: collect the explicitly-supplied columns,
1632                        // merge them over `.spread`, assemble into a
1633                        // `data.frame`, then delegate to the annotator.
1634                        let constructor = format!(
1635                            "{name} <- function({params}, .spread = NULL) {{\n  explicit <- list()\n{explicit_lines}\n  x <- typr_spread_record(explicit, .spread)\n  as.{name}(do.call(data.frame, c(x, list(stringsAsFactors = FALSE))))\n}}"
1636                        );
1637                        // Annotator: adds the class (idempotently), runs the
1638                        // internal validator, then the user validator.
1639                        let annotator = format!(
1640                            "as.{name} <- function(x) {{\n  if (!inherits(x, \"{name}\")) class(x) <- c(\"{name}\", \"data.frame\", \"list\")\n  x <- validate_{name}(x)\n  x <- validate(x)\n  x\n}}"
1641                        );
1642                        let fields_quoted = sorted_fields
1643                            .iter()
1644                            .map(|f| format!("\"{}\"", f.get_argument_str()))
1645                            .collect::<Vec<_>>()
1646                            .join(", ");
1647                        // Per-column type invariants, checked by class
1648                        // (`inherits`) on the column vector.
1649                        let field_checks = sorted_fields
1650                            .iter()
1651                            .filter_map(|f| {
1652                                let n = f.get_argument_str();
1653                                record_field_class(&f.body_type(), cont).map(|cls| {
1654                                    format!(
1655                                        "  if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: column '{n}' must be of class {cls}\")"
1656                                    )
1657                                })
1658                            })
1659                            .collect::<Vec<_>>()
1660                            .join("\n");
1661                        let field_checks_block = if field_checks.is_empty() {
1662                            String::new()
1663                        } else {
1664                            format!("{field_checks}\n")
1665                        };
1666                        // Row-count check: only emitted when the size index is
1667                        // a concrete literal (`df[3]{...}`); a generic (`#N`)
1668                        // or unconstrained (`df{...}`) size imposes no check.
1669                        let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1670                            format!(
1671                                "  if (nrow(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected {n} rows, got \", nrow(x)))\n"
1672                            )
1673                        } else {
1674                            String::new()
1675                        };
1676                        let validator = format!(
1677                            "validate_{name} <- function(x) {{\n  if (!is.data.frame(x)) stop(\"Validation failed for type {name}: expected a data.frame\")\n  required_fields <- c({fields_quoted})\n  missing_fields <- setdiff(required_fields, names(x))\n  if (length(missing_fields) > 0) {{\n    stop(paste0(\"Validation failed for type {name}: missing columns: \", paste(missing_fields, collapse = \", \")))\n  }}\n{field_checks_block}{size_check}  x\n}}"
1678                        );
1679                        (
1680                            format!("{constructor}\n{annotator}\n{validator}"),
1681                            cont.clone(),
1682                        )
1683                    }
1684                    // Vector alias (`type V <- Vec[#N, T]` / `Vec[3, T]` /
1685                    // `Vec[T]`): no class is added (R already distinguishes
1686                    // atomic vector kinds via `is.*`/implicit class), so the
1687                    // pipeline is the same shape as a primitive alias —
1688                    // constructor delegates straight to the validator — plus
1689                    // an optional length check when the size index is a
1690                    // concrete literal.
1691                    Type::Vec(VecType::Vector, size, elem_type, _) => {
1692                        use crate::components::r#type::tint::Tint;
1693                        let constructor =
1694                            format!("{name} <- function(x) {{\n  validate_{name}(x)\n}}");
1695                        let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1696                            format!(
1697                                "  if (!inherits(x, \"{cls}\")) stop(\"Validation failed for type {name}: expected vector of {cls}\")\n"
1698                            )
1699                        }).unwrap_or_default();
1700                        let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1701                            format!(
1702                                "  if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1703                            )
1704                        } else {
1705                            String::new()
1706                        };
1707                        let validator = format!(
1708                            "validate_{name} <- function(x) {{\n{elem_check}{size_check}  x\n}}"
1709                        );
1710                        (format!("{constructor}\n{validator}"), cont.clone())
1711                    }
1712                    // Array alias (`type A <- [#N, T]` / `[3, T]` / bare
1713                    // brackets, and the explicit `Array[#N, T]` spelling —
1714                    // both `VecType::S3` and `VecType::Array` produce the
1715                    // same runtime shape): the literal/constructor-call
1716                    // transpilation already wraps elements in `typed_vec`
1717                    // (see `Lang::Array`/`Lang::ArrayConstructorCall`), so
1718                    // this alias just needs to register the alias name as a
1719                    // `typed_vec` subclass — same annotator/validator shape
1720                    // as a record alias — so generic functions written
1721                    // against `typed_vec` dispatch on it.
1722                    Type::Vec(VecType::S3, size, elem_type, _)
1723                    | Type::Vec(VecType::Array, size, elem_type, _) => {
1724                        use crate::components::r#type::tint::Tint;
1725                        let constructor = format!(
1726                            "{name} <- function(x) {{\n  if (!inherits(x, \"typed_vec\")) x <- typed_vec(x)\n  as.{name}(x)\n}}"
1727                        );
1728                        let annotator = format!(
1729                            "as.{name} <- function(x) {{\n  if (!inherits(x, \"{name}\")) class(x) <- c(\"{name}\", class(x))\n  x <- validate_{name}(x)\n  x <- validate(x)\n  x\n}}"
1730                        );
1731                        let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1732                            format!(
1733                                "  if (!all(vapply(x$data, inherits, logical(1), \"{cls}\"))) stop(\"Validation failed for type {name}: expected elements of class {cls}\")\n"
1734                            )
1735                        }).unwrap_or_default();
1736                        let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1737                            format!(
1738                                "  if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1739                            )
1740                        } else {
1741                            String::new()
1742                        };
1743                        let validator = format!(
1744                            "validate_{name} <- function(x) {{\n  if (!inherits(x, \"typed_vec\")) stop(\"Validation failed for type {name}: expected typed_vec\")\n{elem_check}{size_check}  x\n}}"
1745                        );
1746                        (
1747                            format!("{constructor}\n{annotator}\n{validator}"),
1748                            cont.clone(),
1749                        )
1750                    }
1751                    Type::Operator(_, _, _, _) => {
1752                        // Union alias: generate the full constructor/annotator/
1753                        // validator pipeline for each variant (see
1754                        // validation_variant_d_union.md).
1755                        let union_name = &name;
1756                        let members = flatten_operator_union(typ);
1757                        // Sort for deterministic output
1758                        let mut members_vec: Vec<Type> = members.into_iter().collect();
1759                        members_vec.sort_by_key(|t| t.pretty2());
1760                        let constructors: Vec<String> = members_vec
1761                            .iter()
1762                            .filter_map(|member| match member {
1763                                Type::Tag(variant_name, inner, _) => Some(tag_variant_pipeline(
1764                                    variant_name,
1765                                    union_name,
1766                                    inner.as_ref(),
1767                                )),
1768                                Type::Alias(alias_name, _, _, _) => {
1769                                    // Look up the record fields for this alias
1770                                    let record_fields = cont
1771                                        .aliases()
1772                                        .find(|(var, _)| var.get_name() == *alias_name)
1773                                        .and_then(|(_, t)| {
1774                                            if let Type::Record(fields, _) = t {
1775                                                Some(fields.clone())
1776                                            } else {
1777                                                None
1778                                            }
1779                                        });
1780                                    if let Some(fields) = record_fields {
1781                                        let mut sorted: Vec<&ArgumentType> =
1782                                            fields.iter().collect();
1783                                        sorted.sort_by_key(|f| f.get_argument_str());
1784                                        let params = sorted
1785                                            .iter()
1786                                            .map(|f| f.get_argument_str())
1787                                            .collect::<Vec<_>>()
1788                                            .join(", ");
1789                                        let field_args = sorted
1790                                            .iter()
1791                                            .map(|f| {
1792                                                let n = f.get_argument_str();
1793                                                format!("{n} = {n}")
1794                                            })
1795                                            .collect::<Vec<_>>()
1796                                            .join(", ");
1797                                        Some(format!(
1798                                            "{alias_name} <- function({params}) {{\n  structure(list({field_args}), class = c(\"{alias_name}\", \"{union_name}\", \"list\"))\n}}"
1799                                        ))
1800                                    } else {
1801                                        None
1802                                    }
1803                                }
1804                                _ => None,
1805                            })
1806                            .collect();
1807                        (constructors.join("\n"), cont.clone())
1808                    }
1809                    Type::Integer(tint, _) => {
1810                        use crate::components::r#type::tint::Tint;
1811                        let validator = match tint {
1812                            Tint::Val(i) => format!(
1813                                "validate_{name} <- function(x) {{\n  if (!is.integer(x)) stop(\"Validation failed for type {name}: expected int\")\n  if (x != {i}L) stop(\"Validation failed for type {name}: expected literal {i}\")\n  x\n}}"
1814                            ),
1815                            Tint::Unknown => format!(
1816                                "validate_{name} <- function(x) {{\n  if (!is.integer(x)) stop(\"Validation failed for type {name}: expected int\")\n  x\n}}"
1817                            ),
1818                        };
1819                        let constructor =
1820                            format!("{name} <- function(x) {{\n  validate_{name}(x)\n}}");
1821                        (format!("{constructor}\n{validator}"), cont.clone())
1822                    }
1823                    Type::Char(tchar, _) => {
1824                        use crate::components::r#type::tchar::Tchar;
1825                        let validator = match tchar {
1826                            Tchar::Val(s) => format!(
1827                                "validate_{name} <- function(x) {{\n  if (!is.character(x)) stop(\"Validation failed for type {name}: expected char\")\n  if (x != '{s}') stop(\"Validation failed for type {name}: expected literal '{s}'\")\n  x\n}}"
1828                            ),
1829                            Tchar::Unknown => format!(
1830                                "validate_{name} <- function(x) {{\n  if (!is.character(x)) stop(\"Validation failed for type {name}: expected char\")\n  x\n}}"
1831                            ),
1832                        };
1833                        let constructor =
1834                            format!("{name} <- function(x) {{\n  validate_{name}(x)\n}}");
1835                        (format!("{constructor}\n{validator}"), cont.clone())
1836                    }
1837                    Type::Boolean(tbool, _) => {
1838                        use crate::components::r#type::tbool::Tbool;
1839                        let validator = match tbool {
1840                            Tbool::Val(b) => {
1841                                let r_val = if *b { "TRUE" } else { "FALSE" };
1842                                format!(
1843                                    "validate_{name} <- function(x) {{\n  if (!is.logical(x)) stop(\"Validation failed for type {name}: expected bool\")\n  if (x != {r_val}) stop(\"Validation failed for type {name}: expected literal {r_val}\")\n  x\n}}"
1844                                )
1845                            }
1846                            Tbool::Unknown => format!(
1847                                "validate_{name} <- function(x) {{\n  if (!is.logical(x)) stop(\"Validation failed for type {name}: expected bool\")\n  x\n}}"
1848                            ),
1849                        };
1850                        let constructor =
1851                            format!("{name} <- function(x) {{\n  validate_{name}(x)\n}}");
1852                        (format!("{constructor}\n{validator}"), cont.clone())
1853                    }
1854                    Type::Number(tnum, _) => {
1855                        use crate::components::r#type::tnumber::Tnum;
1856                        let validator = match tnum {
1857                            Tnum::Val(v) => format!(
1858                                "validate_{name} <- function(x) {{\n  if (!is.numeric(x)) stop(\"Validation failed for type {name}: expected num\")\n  if (x != {v}) stop(\"Validation failed for type {name}: expected literal {v}\")\n  x\n}}"
1859                            ),
1860                            Tnum::Unknown => format!(
1861                                "validate_{name} <- function(x) {{\n  if (!is.numeric(x)) stop(\"Validation failed for type {name}: expected num\")\n  x\n}}"
1862                            ),
1863                        };
1864                        let constructor =
1865                            format!("{name} <- function(x) {{\n  validate_{name}(x)\n}}");
1866                        (format!("{constructor}\n{validator}"), cont.clone())
1867                    }
1868                    Type::Tag(tag_name, inner_type, _) => {
1869                        let body_validation = tag_body_validation(&name, inner_type.as_ref());
1870                        let validator = format!(
1871                            "validate_{name} <- function(x) {{\n  if (x[[1]] != '{tag_name}') stop(\"Validation failed for type {name}: expected tag '{tag_name}'\")\n{body_validation}\n  x\n}}"
1872                        );
1873                        (validator, cont.clone())
1874                    }
1875                    // Alias-to-alias (`type Object <- Circle;`): transparent, so reuse
1876                    // the target's own constructor/validator pipeline under this name
1877                    // instead of generating nothing — otherwise the module export step
1878                    // below (`module$Object <- Object`) would reference a binding that
1879                    // was never created.
1880                    Type::Alias(target_name, ..) => {
1881                        (format!("{name} <- {target_name}"), cont.clone())
1882                    }
1883                    _ => ("".to_string(), cont.clone()),
1884                }
1885            }
1886            Lang::UnionConstructor {
1887                variant_name,
1888                fields,
1889                ..
1890            } => {
1891                if fields.is_empty() {
1892                    (format!("{}()", variant_name), cont.clone())
1893                } else {
1894                    let (body, current_cont) = Translatable::from(cont.clone())
1895                        .join_arg_val(fields, ", ")
1896                        .into();
1897                    (format!("{}({})", variant_name, body), current_cont)
1898                }
1899            }
1900            Lang::KeyValue {
1901                key: k, value: v, ..
1902            } => (format!("{} = {}", k, v.to_r(cont).0), cont.clone()),
1903            Lang::Vector { value: vals, .. } => {
1904                let res = "c(".to_string()
1905                    + &vals
1906                        .iter()
1907                        .map(|x: &Lang| x.to_r(cont).0)
1908                        .collect::<Vec<_>>()
1909                        .join(", ")
1910                    + ")";
1911                (res, cont.to_owned())
1912            }
1913            Lang::Not { value: exp, .. } => (format!("!{}", exp.to_r(cont).0), cont.clone()),
1914            Lang::Sequence { body: vals, .. } => {
1915                let res = if !vals.is_empty() {
1916                    "c(".to_string()
1917                        + &vals
1918                            .iter()
1919                            .map(|x: &Lang| "list(".to_string() + &x.to_r(cont).0 + ")")
1920                            .collect::<Vec<_>>()
1921                            .join(", ")
1922                        + ")"
1923                } else {
1924                    "c(list())".to_string()
1925                };
1926                (res, cont.to_owned())
1927            }
1928            Lang::TestBlock {
1929                value: body,
1930                help_data: h,
1931            } => {
1932                let file_name = h
1933                    .get_file_data()
1934                    .map(|(name, _)| format!("test-{}", name))
1935                    .unwrap_or_else(|| "test-unknown".to_string())
1936                    .replace("TypR/", "")
1937                    .replace(".ty", ".R");
1938
1939                let file_path = format!("tests/testthat/{}", file_name);
1940                let body_str = body.to_r(cont).0;
1941                // RFC-TR-031: bring `@testable` private members of the enclosing
1942                // module into scope (e.g. `sq <- Math$.test_sq`) so the test body
1943                // can call them by their bare names.
1944                let content = if cont.test_preamble.is_empty() {
1945                    body_str
1946                } else {
1947                    format!("{}\n{}", cont.test_preamble.join("\n"), body_str)
1948                };
1949
1950                let _ = write_output_file(&file_path, &content);
1951                ("".to_string(), cont.clone())
1952            }
1953            Lang::JSBlock(exp, _id, _h) => {
1954                let js_cont = Context::default(); //TODO get js context from memory
1955                let res = exp.to_js(&js_cont).0;
1956                (format!("'{}{}'", JS_HEADER, res), cont.clone())
1957            }
1958            Lang::WhileLoop {
1959                condition, body, ..
1960            } => (
1961                format!(
1962                    "while ({}) {{\n{}\n}}",
1963                    condition.to_r(cont).0,
1964                    body.to_r(cont).0
1965                ),
1966                cont.clone(),
1967            ),
1968            Lang::Loop { body, .. } => (
1969                format!("while (TRUE) {{\n{}\n}}", body.to_r(cont).0),
1970                cont.clone(),
1971            ),
1972            Lang::Break(_) => ("break".to_string(), cont.clone()),
1973            Lang::Next(_) => ("next".to_string(), cont.clone()),
1974            Lang::NA(_) => ("NA".to_string(), cont.clone()),
1975            Lang::Module {
1976                name,
1977                body,
1978                module_position: position,
1979                config,
1980                ..
1981            } => {
1982                let name_str = if (name == "main") && (config.environment == Environment::Project) {
1983                    "a_main"
1984                } else {
1985                    name
1986                };
1987
1988                // A module that writes its own roxygen2 file (External in Project)
1989                // gets a dedicated frame so the `@include` deps generated inside its
1990                // body land in *its* header rather than the enclosing file's.
1991                let writes_own_file = matches!(position, ModulePosition::External)
1992                    && config.environment == Environment::Project;
1993                if writes_own_file {
1994                    push_include_frame();
1995                    push_import_from_frame();
1996                }
1997
1998                // Use the inner context cached during type-checking when available.
1999                // Fallback: re-run typing() on the module body (only happens if the
2000                // cached context is missing, e.g. in isolated transpile-only calls).
2001                let mut inner_cont = if let Some(cached) = cont.get_module_inner_context(name) {
2002                    // The cached snapshot was taken while typing *this* module's
2003                    // body, mid-compilation — it can't see whole-program registries
2004                    // (record_aliases, the subtype graph) as they stood after later
2005                    // sibling modules were typed (e.g. `Position` declared in a
2006                    // module processed after this one). `cont`, the ambient context
2007                    // at transpile time, has the final, fully-merged picture, so
2008                    // overlay those registries onto the cheap cached snapshot rather
2009                    // than trusting its stale copies.
2010                    let mut cached = cached.clone();
2011                    cached.record_aliases = cont.record_aliases.clone();
2012                    cached.subtypes = cont.subtypes.clone();
2013                    // Same staleness problem for auto-generated structural
2014                    // aliases (`ArrayN`/`RecordN`/…) hoisted into the outer
2015                    // scope from *other* modules after this one was typed —
2016                    // pull those in too so this module's own class chains
2017                    // (`class(x) <- c(..., "Record1", ...)`) still see them.
2018                    cached.typing_context = cached
2019                        .typing_context
2020                        .clone()
2021                        .hoist_aliases(&cont.typing_context);
2022                    cached
2023                } else {
2024                    let module_expr = if body.len() > 1 {
2025                        Lang::Lines {
2026                            value: body.to_vec(),
2027                            help_data: HelpData::default(),
2028                        }
2029                    } else {
2030                        body.first()
2031                            .cloned()
2032                            .unwrap_or(Lang::Empty(HelpData::default()))
2033                    };
2034                    typing(&cont.clone().set_in_module_body(), &module_expr).context
2035                };
2036
2037                // RFC-TR-031: in a test build, give any `Test { ... }` block in this
2038                // module access to its `@testable` private members by binding their
2039                // bare names to the exposed `M$.test_<name>` aliases at the top of the
2040                // generated test file (see `Lang::TestBlock` below).
2041                if cont.get_test_mode() {
2042                    let preamble: Vec<String> = body
2043                        .iter()
2044                        .filter_map(|lang| match lang {
2045                            Lang::Let {
2046                                variable: var,
2047                                is_testable: true,
2048                                ..
2049                            } => Var::from_language(*var.clone()).map(|v| {
2050                                let raw = v.get_name();
2051                                format!("{} <- {}$`.test_{}`", raw, name_str, raw)
2052                            }),
2053                            _ => None,
2054                        })
2055                        .collect();
2056                    inner_cont = inner_cont.set_test_preamble(preamble);
2057                }
2058
2059                // C1: partition body into file-level imports (mod foo; / External sub-modules)
2060                // and runtime content. Imports are processed first so their side-effects
2061                // (file writes, register_include) happen before the new.env binding, and
2062                // their output is emitted outside the local({}) block.
2063                let (import_langs, runtime_langs): (Vec<_>, Vec<_>) =
2064                    body.iter().partition(|lang| {
2065                        matches!(
2066                            lang,
2067                            Lang::ModuleImport { .. }
2068                                | Lang::ImportFrom { .. }
2069                                | Lang::Module {
2070                                    module_position: ModulePosition::External,
2071                                    ..
2072                                }
2073                        )
2074                    });
2075
2076                let imports_parts: Vec<String> = import_langs
2077                    .iter()
2078                    .map(|lang| lang.to_r(&inner_cont).0)
2079                    .filter(|s| !s.is_empty())
2080                    .collect();
2081                let imports_preamble = if imports_parts.is_empty() {
2082                    String::new()
2083                } else {
2084                    imports_parts.join("\n") + "\n"
2085                };
2086
2087                let body_content = runtime_langs
2088                    .iter()
2089                    .map(|lang| lang.to_r(&inner_cont).0)
2090                    .collect::<Vec<_>>()
2091                    .join("\n");
2092
2093                // Build exports (inside local) and generics (outside local) for @pub/@export members
2094                let mut exports: Vec<String> = Vec::new();
2095                let mut generics: Vec<String> = Vec::new();
2096                let mut generic_exports: Vec<String> = Vec::new();
2097                // RFC-TR-032: @export members also get a top-level #' @export re-export
2098                let mut package_exports: Vec<String> = Vec::new();
2099
2100                for lang in body.iter() {
2101                    if let Lang::Let {
2102                        variable: var,
2103                        is_public: true,
2104                        is_export,
2105                        ..
2106                    } = lang
2107                    {
2108                        if let Some(v) = Var::from_language(*var.clone()) {
2109                            let raw_name = v.get_name();
2110                            // Use `inner_cont`, not `cont`: `body_content` above rendered
2111                            // this same `Let` via `inner_cont` (the re-typed module-body
2112                            // context), so `display_type`'s alias lookup must use the same
2113                            // context here or it can resolve the same structural type to a
2114                            // *different* auto-named alias (e.g. the function gets defined
2115                            // as `animate_move.Record4` but exported/registered as
2116                            // `animate_move.Record1` — a dangling reference at R runtime).
2117                            let typed_name = v.clone().display_type(&inner_cont).get_name();
2118
2119                            // Export the (possibly type-suffixed) member into the module env
2120                            exports.push(format!("{}${} <- {}", name_str, typed_name, typed_name));
2121
2122                            // RFC-TR-032: @export also surfaces as a package-level function
2123                            if *is_export {
2124                                package_exports.push(format!(
2125                                    "#' @export\n{} <- {}${}",
2126                                    raw_name, name_str, typed_name
2127                                ));
2128                            }
2129
2130                            // For typed functions: register as S3 method and create generic
2131                            let var_type = v.get_type();
2132                            if !var_type.is_empty() && typed_name != raw_name {
2133                                let class_name = inner_cont.get_class_unquoted(&var_type);
2134                                exports.push(format!(
2135                                    "registerS3method(\"{}\", \"{}\", {})",
2136                                    raw_name, class_name, typed_name
2137                                ));
2138
2139                                let generic_def = format!(
2140                                    "{} <- function(x, ...) UseMethod(\"{}\")",
2141                                    raw_name, raw_name
2142                                );
2143                                if !generics.contains(&generic_def) {
2144                                    generics.push(generic_def);
2145                                    generic_exports
2146                                        .push(format!("{}${} <- {}", name_str, raw_name, raw_name));
2147                                }
2148
2149                                // The method implementation itself (e.g. `do.Object`) is only
2150                                // bound inside `local({...})`, so it never becomes a top-level
2151                                // binding that load_module.R's dependency-copy step can see.
2152                                // `registerS3method` alone doesn't help here either: it relies
2153                                // on a real package namespace, which a `sys.source`'d module env
2154                                // is not. Re-expose it at top level (outside local) from the
2155                                // module env so UseMethod can find `typed_name` via normal
2156                                // lexical scoping from any file that imports this module.
2157                                generic_exports
2158                                    .push(format!("{} <- {}${}", typed_name, name_str, typed_name));
2159                            }
2160                        }
2161                    }
2162                    // A *private* typed function is also emitted as an S3 method
2163                    // (`snapshot.StoryBoard`), and its call sites transpile to the
2164                    // bare generic (`snapshot(...)`) — so a generic stub must
2165                    // exist for them too. It stays inside `local({...})`: the
2166                    // sibling methods' closures resolve it lexically, and nothing
2167                    // leaks into the module env or the top level.
2168                    if let Lang::Let {
2169                        variable: var,
2170                        is_public: false,
2171                        ..
2172                    } = lang
2173                    {
2174                        if let Some(v) = Var::from_language(*var.clone()) {
2175                            let raw_name = v.get_name();
2176                            let typed_name = v.clone().display_type(&inner_cont).get_name();
2177                            let var_type = v.get_type();
2178                            if !var_type.is_empty() && typed_name != raw_name {
2179                                let class_name = inner_cont.get_class_unquoted(&var_type);
2180                                let generic_def = format!(
2181                                    "{} <- function(x, ...) UseMethod(\"{}\")",
2182                                    raw_name, raw_name
2183                                );
2184                                if !generics.contains(&generic_def)
2185                                    && !exports.contains(&generic_def)
2186                                {
2187                                    exports.push(generic_def);
2188                                }
2189                                exports.push(format!(
2190                                    "registerS3method(\"{}\", \"{}\", {})",
2191                                    raw_name, class_name, typed_name
2192                                ));
2193                            }
2194                        }
2195                    }
2196                    // RFC-TR-032: in a test build, expose `@testable` (and implied-testable
2197                    // `@pub`/`@export`) members as `M$.test_<name>` while keeping them
2198                    // private in the module's regular API.
2199                    if cont.get_test_mode() {
2200                        if let Lang::Let {
2201                            variable: var,
2202                            is_testable: true,
2203                            ..
2204                        } = lang
2205                        {
2206                            if let Some(v) = Var::from_language(*var.clone()) {
2207                                let raw_name = v.get_name();
2208                                // Reference the actual (possibly type-suffixed)
2209                                // binding emitted in the module body. Must use
2210                                // `inner_cont` for the same reason as above.
2211                                let typed_name = v.clone().display_type(&inner_cont).get_name();
2212                                exports.push(format!(
2213                                    "{}$`.test_{}` <- {}",
2214                                    name_str, raw_name, typed_name
2215                                ));
2216                            }
2217                        }
2218                    }
2219                    // Export @pub opaque type constructors into the module environment.
2220                    // Interfaces are compile-time-only structural validators (see CLAUDE.md
2221                    // "Interface Constructors") and generate no R binding at all, so exporting
2222                    // them here would reference an undefined variable (e.g. `object$Animable <-
2223                    // Animable` with `Animable` never assigned).
2224                    if let Lang::Alias {
2225                        identifier: var,
2226                        is_public: true,
2227                        target_type,
2228                        ..
2229                    } = lang
2230                    {
2231                        if !target_type.is_interface() {
2232                            if let Some(v) = Var::from_language(*var.clone()) {
2233                                let alias_name = v.get_name();
2234                                exports
2235                                    .push(format!("{}${} <- {}", name_str, alias_name, alias_name));
2236                            }
2237                        }
2238                    }
2239                }
2240
2241                let exports_str = if exports.is_empty() {
2242                    String::new()
2243                } else {
2244                    "\n".to_string() + &exports.join("\n")
2245                };
2246
2247                let generics_defs_str = if generics.is_empty() {
2248                    String::new()
2249                } else {
2250                    generics.join("\n") + "\n"
2251                };
2252
2253                let generic_exports_str = if generic_exports.is_empty() {
2254                    String::new()
2255                } else {
2256                    "\n".to_string() + &generic_exports.join("\n")
2257                };
2258
2259                let package_exports_str = if package_exports.is_empty() {
2260                    String::new()
2261                } else {
2262                    "\n".to_string() + &package_exports.join("\n")
2263                };
2264
2265                let content = format!(
2266                    "{}{}{} <- new.env(parent = emptyenv())\nlocal({{\n{}{}\n}}){}{}",
2267                    generics_defs_str,
2268                    imports_preamble,
2269                    name_str,
2270                    body_content,
2271                    exports_str,
2272                    generic_exports_str,
2273                    package_exports_str
2274                );
2275
2276                match (position, config.environment) {
2277                    (ModulePosition::Internal, _) => (content, cont.clone()),
2278                    // In WASM mode, inline all external modules instead of writing files
2279                    (ModulePosition::External, Environment::Wasm) => {
2280                        let file_path = format!("{}.R", name_str);
2281                        let _ = write_output_file(&file_path, &content);
2282                        (content, cont.clone())
2283                    }
2284                    (ModulePosition::External, Environment::StandAlone)
2285                    | (ModulePosition::External, Environment::Repl) => {
2286                        let file_path = format!("{}.R", name_str);
2287                        let _ = write_output_file(&file_path, &content);
2288                        (format!("source('{}')", file_path), cont.clone())
2289                    }
2290                    (ModulePosition::External, Environment::Project) => {
2291                        let file_path = format!("R/{}.R", name_str);
2292                        // Drain the deps collected within this module's body and emit
2293                        // them as top-level `@include` tags in this file's header.
2294                        let nested = pop_include_frame();
2295                        let nested_includes = nested
2296                            .iter()
2297                            .map(|f| format!("#' @include {}\n", f))
2298                            .collect::<String>();
2299                        let nested_imports = pop_import_from_frame()
2300                            .iter()
2301                            .map(|e| format!("#' @importFrom {}\n", e))
2302                            .collect::<String>();
2303                        let project_preamble = "#' @include std.R\n#' @include generic_functions.R\n#' @include types.R\n";
2304                        let _ = write_output_file(
2305                            &file_path,
2306                            &format!(
2307                                "{}{}{}{}",
2308                                project_preamble, nested_includes, nested_imports, content
2309                            ),
2310                        );
2311                        // The enclosing file depends on this one: hoist the tag to its
2312                        // header instead of emitting it inline inside a `local({...})`.
2313                        register_include(&format!("{}.R", name_str));
2314                        (String::new(), cont.clone())
2315                    }
2316                }
2317            }
2318            Lang::UseModule {
2319                module_path,
2320                selector,
2321                ..
2322            } => {
2323                use crate::components::language::use_lang::UseSelector;
2324
2325                // Build the R accessor prefix: A::B::C → A$B$C
2326                let r_path = module_path.join("$");
2327
2328                // Resolve the module type from context to enumerate public members for wildcards
2329                let mod_type_opt = (|| {
2330                    let root = cont
2331                        .get_type_from_variable(&Var::from_name(&module_path[0]))
2332                        .ok()?;
2333                    let mut current = root;
2334                    for seg in module_path.iter().skip(1) {
2335                        current = current
2336                            .to_module_type()
2337                            .ok()?
2338                            .get_type_from_name(seg)
2339                            .ok()?;
2340                    }
2341                    current.to_module_type().ok()
2342                })();
2343
2344                let bindings: Vec<String> = match selector {
2345                    UseSelector::Wildcard => mod_type_opt
2346                        .map(|mt| {
2347                            mt.get_public_members()
2348                                .iter()
2349                                .map(|m| {
2350                                    let name = m.get_argument_str();
2351                                    format!("{} <- {}${}", name, r_path, name)
2352                                })
2353                                .collect()
2354                        })
2355                        .unwrap_or_default(),
2356                    UseSelector::Items(items) => items
2357                        .iter()
2358                        .map(|item| {
2359                            let local_name = item.alias.as_deref().unwrap_or(&item.name);
2360                            format!("{} <- {}${}", local_name, r_path, item.name)
2361                        })
2362                        .collect(),
2363                };
2364
2365                (bindings.join("\n"), cont.clone())
2366            }
2367            Lang::ModuleImport { .. } => ("".to_string(), cont.clone()),
2368            Lang::ImportFrom {
2369                package, functions, ..
2370            } => {
2371                let entry = format!("{} {}", package, functions.join(" "));
2372                register_import_from(&entry);
2373                ("".to_string(), cont.clone())
2374            }
2375            // `Self:{ field = expr, ...base }` (generic_constructor.md §5):
2376            // resolve the actual R constructor straight from `base`'s type
2377            // rather than from the literal name "Self" — by the
2378            // type-checking rule `base`'s type is always Self or a subtype
2379            // of it, so this is exactly the constructor that should run.
2380            Lang::ConstructorCall {
2381                type_name,
2382                fields,
2383                spreads,
2384                ..
2385            } if type_name == "Self" => {
2386                let base_typ = spreads
2387                    .first()
2388                    .map(|e| typing(cont, e).value)
2389                    .unwrap_or_else(|| Type::Any(HelpData::default()));
2390                let resolved_name = match &base_typ {
2391                    Type::Alias(alias_name, ..) => cont
2392                        .aliases()
2393                        .find(|(var, _)| var.get_name() == *alias_name)
2394                        .map(|(_, t)| matches!(t, Type::Record(_, _)))
2395                        .unwrap_or(false)
2396                        .then(|| alias_name.clone()),
2397                    _ => None,
2398                };
2399                match (resolved_name, spreads.first()) {
2400                    (Some(name), Some(spread_expr)) => {
2401                        // Same codegen as the plain runtime-spread
2402                        // ConstructorCall path below, with the resolved
2403                        // alias name substituted for "Self".
2404                        let (spread_r, current_cont) = spread_expr.to_r(cont);
2405                        let (body, current_cont) = if fields.is_empty() {
2406                            (format!(".spread = {}", spread_r), current_cont)
2407                        } else {
2408                            let (overrides, next_cont) = Translatable::from(current_cont)
2409                                .join_arg_val(fields, ", ")
2410                                .into();
2411                            (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2412                        };
2413                        (format!("{}({})", name, body), current_cont)
2414                    }
2415                    (None, Some(spread_expr)) => {
2416                        // No nominal constructor to call: fall back to the
2417                        // generic spread()-merge used for plain `{ f=e, ...x }`
2418                        // record literals.
2419                        let (base, current_cont) = spread_expr.to_r(cont);
2420                        if fields.is_empty() {
2421                            (base, current_cont)
2422                        } else {
2423                            let (overrides, current_cont) = Translatable::from(current_cont)
2424                                .join_arg_val(fields, ", ")
2425                                .into();
2426                            (
2427                                format!("spread({}, list({}))", base, overrides),
2428                                current_cont,
2429                            )
2430                        }
2431                    }
2432                    // No spread: ill-typed (type-checking already reports
2433                    // SelfOutsideContext); nothing sensible to emit.
2434                    (_, None) => ("NULL".to_string(), cont.clone()),
2435                }
2436            }
2437            Lang::ConstructorCall {
2438                module_path,
2439                type_name,
2440                fields,
2441                spreads,
2442                ..
2443            } if !spreads.is_empty() => {
2444                // Single runtime `...source` spread (spread_operator3.md): pass the
2445                // explicit fields plus the spread source straight to the generated
2446                // constructor's `.spread` parameter — it merges them at runtime via
2447                // `typr_spread_record` (explicit fields win, extra fields on the
2448                // source are preserved, unlike the old do.call/spread/select).
2449                let spread_expr = spreads.first().expect("checked non-empty above");
2450                let (spread_r, current_cont) = spread_expr.to_r(cont);
2451                let qualified = if module_path.is_empty() {
2452                    type_name.clone()
2453                } else {
2454                    format!("{}${}", module_path.join("$"), type_name)
2455                };
2456                let (body, current_cont) = if fields.is_empty() {
2457                    (format!(".spread = {}", spread_r), current_cont)
2458                } else {
2459                    let (overrides, next_cont) = Translatable::from(current_cont)
2460                        .join_arg_val(fields, ", ")
2461                        .into();
2462                    (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2463                };
2464                (format!("{}({})", qualified, body), current_cont)
2465            }
2466            Lang::ConstructorCall {
2467                module_path,
2468                type_name,
2469                fields,
2470                spread,
2471                help_data: h,
2472                ..
2473            } => {
2474                // With a spread present, statically expand every record field absent
2475                // from `fields` into a `source$field` access (RFC-TR-033 §5: static
2476                // expansion, no runtime merge so the record's R class is preserved).
2477                let all_fields: Vec<ArgumentValue> = match spread {
2478                    Some((spread_path, spread_var, _)) => {
2479                        let resolved_alias = if module_path.is_empty() {
2480                            cont.get_type_from_aliases(&Var::from_name(type_name))
2481                        } else {
2482                            resolve_module_member_type(cont, module_path, type_name)
2483                        };
2484                        let record_fields = resolved_alias.and_then(|t| match t.reduce(cont) {
2485                            Type::Record(fields, _) => Some(fields),
2486                            _ => None,
2487                        });
2488                        let receiver = {
2489                            let qualifier = spread_path.split_first().map(|(first, rest)| {
2490                                rest.iter()
2491                                    .fold(Var::from_name(first).to_language(), |acc, seg| {
2492                                        Lang::Operator {
2493                                            operator: Op::Dollar(h.clone()),
2494                                            rhs: Box::new(acc),
2495                                            lhs: Box::new(Var::from_name(seg).to_language()),
2496                                            help_data: h.clone(),
2497                                        }
2498                                    })
2499                            });
2500                            match qualifier {
2501                                Some(qualifier) => Lang::Operator {
2502                                    operator: Op::Dollar(h.clone()),
2503                                    rhs: Box::new(qualifier),
2504                                    lhs: Box::new(Var::from_name(spread_var).to_language()),
2505                                    help_data: h.clone(),
2506                                },
2507                                None => Var::from_name(spread_var).to_language(),
2508                            }
2509                        };
2510                        let provided: std::collections::HashSet<String> =
2511                            fields.iter().map(|f| f.get_argument()).collect();
2512                        let synthetic = record_fields
2513                            .into_iter()
2514                            .flatten()
2515                            .filter(|rf| !provided.contains(&rf.get_argument_str()))
2516                            .map(|rf| {
2517                                let field_access = Lang::Operator {
2518                                    operator: Op::Dollar(h.clone()),
2519                                    rhs: Box::new(receiver.clone()),
2520                                    lhs: Box::new(
2521                                        Var::from_name(&rf.get_argument_str()).to_language(),
2522                                    ),
2523                                    help_data: h.clone(),
2524                                };
2525                                ArgumentValue(rf.get_argument_str(), field_access)
2526                            });
2527                        fields.iter().cloned().chain(synthetic).collect()
2528                    }
2529                    None => fields.clone(),
2530                };
2531                let (body, current_cont) = Translatable::from(cont.clone())
2532                    .join_arg_val(&all_fields, ", ")
2533                    .into();
2534                let qualified = if module_path.is_empty() {
2535                    type_name.clone()
2536                } else {
2537                    format!("{}${}", module_path.join("$"), type_name)
2538                };
2539                (format!("{}({})", qualified, body), current_cont)
2540            }
2541            Lang::ArrayConstructorCall {
2542                type_name,
2543                elements,
2544                help_data: h,
2545            } => {
2546                let resolved_alias = cont
2547                    .get_type_from_aliases(&Var::from_name(type_name))
2548                    .map(|t| t.reduce(cont));
2549                if let Some(Type::Vec(VecType::Vector, ..)) = resolved_alias {
2550                    // Plain vector alias (`type V <- Vec[#N, T]`): the runtime
2551                    // value is a bare R vector (no `dim`/`typed_vec` wrapper, see
2552                    // the Vec constructor pipeline in `Lang::Alias`), so the
2553                    // elements are simply collected with `c(...)`.
2554                    let inner = elements
2555                        .iter()
2556                        .map(|el| el.to_r(cont).0)
2557                        .collect::<Vec<_>>()
2558                        .join(", ");
2559                    (format!("{}(c({}))", type_name, inner), cont.clone())
2560                } else {
2561                    let temp_array = Lang::Array {
2562                        value: elements.clone(),
2563                        help_data: h.clone(),
2564                    };
2565                    let typ = temp_array.typing(cont).value;
2566                    let dimension = ArrayType::try_from(typ)
2567                        .expect("array constructor call should have an array type")
2568                        .get_shape()
2569                        .map(|sha| format!("c({})", sha))
2570                        .unwrap_or_else(|| "c(0)".to_string());
2571                    let lin_array = temp_array
2572                        .linearize_array()
2573                        .iter()
2574                        .map(|lang| lang.to_r(cont).0)
2575                        .collect::<Vec<_>>()
2576                        .join(", ");
2577                    let inner = if lin_array.is_empty() {
2578                        format!("typed_vec(dim = {})", dimension)
2579                    } else {
2580                        format!("typed_vec({}, dim = {})", lin_array, dimension)
2581                    };
2582                    (format!("{}({})", type_name, inner), cont.clone())
2583                }
2584            }
2585            Lang::Import { .. } | Lang::Test { .. } | Lang::Use { .. } => {
2586                ("".to_string(), cont.clone())
2587            }
2588            Lang::ValidatingCast {
2589                expression,
2590                type_name,
2591                literal_type,
2592                ..
2593            } => {
2594                // An array literal under a cast emits its raw typed_vec: the
2595                // cast supplies the annotation, and the literal's own (based
2596                // on its inferred type, e.g. `[0, Empty]` for `[]`) would be
2597                // a redundant — usually unregistered → `as.Generic()` — cast.
2598                let expr_r = if matches!(expression.as_ref(), Lang::Array { .. }) {
2599                    array_literal_raw(expression, cont)
2600                } else {
2601                    expression.to_r(cont).0
2602                };
2603                match literal_type {
2604                    // Inline structural type (`as! [Any, int]` and friends):
2605                    // call the auto-generated `as.ArrayN`-style cast (see
2606                    // `Context::get_type_anotations`) registered for it at
2607                    // typing time, instead of a named `validate_<name>`.
2608                    Some(t) => (
2609                        format!("{} |> {}", expr_r, cont.get_type_anotation(t)),
2610                        cont.clone(),
2611                    ),
2612                    None => (format!("validate_{}({})", type_name, expr_r), cont.clone()),
2613                }
2614            }
2615            _ => ("".to_string(), cont.clone()),
2616        };
2617
2618        result
2619    }
2620}
2621
2622#[cfg(test)]
2623mod tests {
2624    use crate::components::context::config::{Config, Environment};
2625    use crate::components::context::Context;
2626    use crate::components::error_message::help_data::HelpData;
2627    use crate::components::language::{Lang, ModulePosition};
2628    use crate::processes::transpiling::translatable::RTranslatable;
2629    use crate::utils::fluent_parser::FluentParser;
2630
2631    #[test]
2632    fn test_escape_r_string() {
2633        use super::escape_r_string;
2634        assert_eq!(escape_r_string("hello"), r#""hello""#);
2635        assert_eq!(escape_r_string(r#"say "hi""#), r#""say \"hi\"""#);
2636        assert_eq!(escape_r_string(r"a\b"), r#""a\\b""#);
2637        assert_eq!(escape_r_string("line1\nline2"), r#""line1\nline2""#);
2638    }
2639
2640    /// Interfaces-as-classes + `.default` fallback (hybrid dispatch model).
2641    /// These go through `parse2` + `TypeChecker` (not `FluentParser`) because
2642    /// the interface-satisfaction check runs against the *final* whole-program
2643    /// context — exactly what `TypeChecker::transpile` provides and what
2644    /// per-statement `FluentParser::run()` chains do not.
2645    fn transpile_program(stmts: &[&str]) -> String {
2646        use crate::processes::parsing::parse2;
2647        use crate::processes::type_checking::type_checker::TypeChecker;
2648        let tc = stmts
2649            .iter()
2650            .fold(TypeChecker::new(Context::default()), |tc, s| {
2651                let code = parse2((*s).into()).unwrap();
2652                tc.typing_no_panic(&code)
2653            });
2654        assert!(!tc.has_errors(), "type errors: {:?}", tc.get_errors());
2655        tc.transpile()
2656    }
2657
2658    #[test]
2659    fn test_record_class_chain_includes_satisfied_interface() {
2660        // `view` is declared *after* `Point`: satisfaction must still be
2661        // seen, since transpiling runs on the final context.
2662        let r = transpile_program(&[
2663            "type Viewable <- interface { view: (Self) -> char };",
2664            "type Point <- list { x: int };",
2665            "let view <- fn(p: Point): char { \"pt\" };",
2666            "let describe <- fn(v: Viewable): char { view(v) };",
2667        ]);
2668        assert!(
2669            r.contains("class(x) <- c(\"Point\", \"Viewable\", \"list\")"),
2670            "expected Viewable in Point's class chain, got: {r}"
2671        );
2672    }
2673
2674    #[test]
2675    fn test_pure_interface_param_function_emits_default_fallback() {
2676        // Primitives and foreign values never carry interface classes, so a
2677        // pure-interface method must also register as `.default`.
2678        let r = transpile_program(&[
2679            "type Incrementable <- interface { incr: (Self) -> Self };",
2680            "let incr <- fn(s: int): int { s + 1 };",
2681            "let double_up <- fn(i: Incrementable): Incrementable { i.incr() };",
2682        ]);
2683        assert!(
2684            r.contains("`double_up.default` <- `double_up.Incrementable`"),
2685            "expected .default fallback alias, got: {r}"
2686        );
2687    }
2688
2689    #[test]
2690    fn test_mixed_intersection_alias_tags_satisfier_but_no_default() {
2691        // A record & interface intersection alias: satisfying records get the
2692        // alias in their class chain; no `.default` is emitted since only
2693        // records (which carry the class) can satisfy the record facet.
2694        let r = transpile_program(&[
2695            "type Combined <- list { y: int } & interface { show: (Self) -> char };",
2696            "type Widget <- list { y: int, label: char };",
2697            "let show <- fn(w: Widget): char { \"ws\" };",
2698            "let inspect <- fn(c: Combined): char { show(c) };",
2699        ]);
2700        assert!(
2701            r.contains("class(x) <- c(\"Widget\", \"Combined\", \"list\")"),
2702            "expected Combined in Widget's class chain, got: {r}"
2703        );
2704        assert!(
2705            !r.contains("inspect.default"),
2706            "mixed intersection param must not emit a .default fallback, got: {r}"
2707        );
2708    }
2709
2710    #[test]
2711    fn test_validating_cast_transpiles_to_validate_call() {
2712        let r_code = FluentParser::new()
2713            .push("type Person <- list { name: char, age: int };")
2714            .run()
2715            .check_transpiling("x as! Person");
2716        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2717        assert!(
2718            r_str.contains("validate_Person(x)"),
2719            "expected validate_Person(x), got: {}",
2720            r_str
2721        );
2722    }
2723
2724    #[test]
2725    fn test_validating_cast_type_is_alias() {
2726        let typ = FluentParser::new()
2727            .push("type Person <- list { name: char, age: int };")
2728            .run()
2729            .check_typing("x as! Person");
2730        assert!(
2731            typ.pretty2().contains("Person"),
2732            "expected Alias(Person), got: {}",
2733            typ.pretty2()
2734        );
2735    }
2736
2737    #[test]
2738    fn test_validating_cast_literal_array_type() {
2739        let r_code = FluentParser::new().check_transpiling("c() as! [Any, int]");
2740        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2741        assert!(
2742            r_str.contains("c() |> as.Array0()"),
2743            "expected c() |> as.Array0(), got: {}",
2744            r_str
2745        );
2746    }
2747
2748    #[test]
2749    fn test_validating_cast_literal_vec_and_array_keywords() {
2750        // `Vec[...]` / `Array[...]` prefixes are equivalent to the bare
2751        // `[...]` form for an inline (non-aliased) cast target.
2752        let r_code = FluentParser::new().check_transpiling("c() as! Vec[Any, int]");
2753        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2754        assert!(
2755            r_str.contains("c() |> as.Array0()"),
2756            "expected c() |> as.Array0(), got: {}",
2757            r_str
2758        );
2759    }
2760
2761    #[test]
2762    fn test_validating_cast_array_literal_single_annotation() {
2763        // `[] as! [T]`: the literal's own annotation (its inferred type
2764        // `[0, Empty]` has no registered alias → `as.Generic()`) must be
2765        // suppressed; the cast provides the only annotation.
2766        let r_code = FluentParser::new().check_transpiling("[] as! [Any, int]");
2767        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2768        assert!(
2769            r_str.contains("typed_vec(dim = c(0)) |> as.Array0()"),
2770            "expected raw typed_vec |> as.Array0(), got: {}",
2771            r_str
2772        );
2773        assert!(
2774            !r_str.contains("as.Generic"),
2775            "no as.Generic() should be emitted, got: {}",
2776            r_str
2777        );
2778    }
2779
2780    #[test]
2781    fn test_validating_cast_in_constructor_field_registers_alias() {
2782        // The ArrayN alias registered by an `as!` cast inside a constructor
2783        // call field must survive to transpile time (the ConstructorCall
2784        // typing arm used to drop the field-typing sub-context, so the
2785        // lookup fell back to as.Generic()).
2786        let r_code = FluentParser::new()
2787            .push("type Truc <- list { options: [Option] };")
2788            .run()
2789            .push("let new_truc <- Truc:{ options = [] as! [Option] };")
2790            .run()
2791            .get_r_code();
2792        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2793        assert!(
2794            r_str.contains("typed_vec(dim = c(0)) |> as.Array0()"),
2795            "expected the cast to resolve to as.Array0(), got: {}",
2796            r_str
2797        );
2798        assert!(
2799            !r_str.contains("as.Generic"),
2800            "no as.Generic() should be emitted, got: {}",
2801            r_str
2802        );
2803    }
2804
2805    #[test]
2806    fn test_validating_cast_literal_type_dedup() {
2807        // Two casts to the same structural type reuse the same auto-generated
2808        // alias instead of registering a new one each time.
2809        let r_code = FluentParser::new()
2810            .push("let a <- c() as! [Any, int];")
2811            .run()
2812            .push("let b <- c() as! [Any, int];")
2813            .run()
2814            .get_r_code();
2815        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2816        assert_eq!(
2817            r_str.matches("as.Array0()").count(),
2818            2,
2819            "expected both casts to reuse as.Array0, got: {}",
2820            r_str
2821        );
2822    }
2823
2824    #[test]
2825    fn test_alias_record_generates_validator() {
2826        let r_code =
2827            FluentParser::new().check_transpiling("type Person <- list { name: char, age: int };");
2828        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2829        assert!(
2830            r_str.contains("validate_Person <- function(x)"),
2831            "expected validator function, got: {}",
2832            r_str
2833        );
2834        assert!(
2835            r_str.contains("required_fields"),
2836            "expected field validation, got: {}",
2837            r_str
2838        );
2839    }
2840
2841    #[test]
2842    fn test_record_kinded_generic_param_transpiles_without_panic() {
2843        // Regression for a `get_class` panic ("%T has no class equivalent")
2844        // when a function parameter/alias is typed with a record-kinded
2845        // generic (`%T`), e.g. `type Animator<%T> <- %T & list {...}` plus
2846        // a function spreading a `%T`-typed parameter. `%T` has no fixed
2847        // runtime R class, so it must fall back to the same `Generic`
2848        // convention used for plain `T`, never panic.
2849        let fp = FluentParser::new()
2850            .push("type Animator<%T> <- %T & list { extra: int };")
2851            .run()
2852            .push("let combine <- fn(target: %T): %T { let more <- :{ extra = 1 }; :{ ...target, ...more } };")
2853            .run();
2854        assert_eq!(fp.get_last_log(), "The logs are empty");
2855        let r_code = fp
2856            .get_r_code()
2857            .iter()
2858            .cloned()
2859            .collect::<Vec<_>>()
2860            .join("\n");
2861        assert!(
2862            r_code.contains("spread(target, more)"),
2863            "expected the spread merge to transpile, got:\n{}",
2864            r_code
2865        );
2866    }
2867
2868    #[test]
2869    fn test_generic_intersection_alias_generates_validator() {
2870        // An alias mixing a record-kinded generic with a concrete record
2871        // (`%T & list {...}`) used to produce no R code at all for itself
2872        // (it fell into the union-alias `Type::Operator` catch-all), while
2873        // `as! Animator` elsewhere still emitted a call to `validate_Animator`
2874        // — a dangling reference at actual R runtime. The concrete side's
2875        // fields are the only part with a fixed runtime shape, so the
2876        // constructor/annotator/validator pipeline should be generated from
2877        // them, exactly like a plain `Type::Record` alias.
2878        let r_code = FluentParser::new()
2879            .check_transpiling("type Animator<%T> <- %T & list { animations: int };");
2880        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2881        assert!(
2882            r_str.contains("Animator <- function(animations, .spread = NULL)"),
2883            "expected a constructor for the concrete side's fields, got:\n{}",
2884            r_str
2885        );
2886        assert!(
2887            r_str.contains("as.Animator <- function(x)"),
2888            "expected an annotator, got:\n{}",
2889            r_str
2890        );
2891        assert!(
2892            r_str.contains("validate_Animator <- function(x)")
2893                && r_str.contains("required_fields <- c(\"animations\")"),
2894            "expected a validator checking the concrete field, got:\n{}",
2895            r_str
2896        );
2897    }
2898
2899    #[test]
2900    fn test_multi_record_intersection_alias_merges_all_fields_into_constructor() {
2901        // `Alpha & Beta` where both sides are concrete list/record aliases
2902        // should get a constructor combining every field from every list
2903        // type in the intersection, not just fall through to no R code at
2904        // all (which is what happened before `record_facet` was
2905        // generalized to fold *all* record members together, not just the
2906        // first found).
2907        let r_code = FluentParser::new()
2908            .push("type Alpha <- list { x: int };")
2909            .run()
2910            .push("type Beta <- list { y: char };")
2911            .run()
2912            .push("type Combo <- Alpha & Beta;")
2913            .run()
2914            .get_r_code();
2915        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2916        assert!(
2917            r_str.contains("Combo <- function(x, y, .spread = NULL)"),
2918            "expected a constructor merging fields from both Alpha and Beta, got:\n{}",
2919            r_str
2920        );
2921        assert!(
2922            r_str.contains("as.Combo <- function(x)"),
2923            "expected an annotator, got:\n{}",
2924            r_str
2925        );
2926        assert!(
2927            r_str.contains("validate_Combo <- function(x)")
2928                && r_str.contains("required_fields <- c(\"x\", \"y\")"),
2929            "expected a validator checking both merged fields, got:\n{}",
2930            r_str
2931        );
2932    }
2933
2934    #[test]
2935    fn test_external_module_project_generates_include() {
2936        use super::{reset_include_stack, take_main_includes};
2937        reset_include_stack();
2938        let module = Lang::Module {
2939            name: "MyModule".to_string(),
2940            body: vec![],
2941            module_position: ModulePosition::External,
2942            config: Config::default().set_environment(Environment::Project),
2943            help_data: HelpData::default(),
2944        };
2945        let context = Context::default().set_environment(Environment::Project);
2946        let (r_code, _) = module.to_r(&context);
2947        // The include is no longer emitted inline; it is hoisted to the enclosing
2948        // file's header via the include stack.
2949        assert_eq!(r_code, "", "got: {}", r_code);
2950        let includes = take_main_includes();
2951        assert!(
2952            includes.contains(&"MyModule.R".to_string()),
2953            "expected MyModule.R to be registered, got: {:?}",
2954            includes
2955        );
2956    }
2957
2958    #[test]
2959    fn test_module_transpilation_with_pub() {
2960        let r_code = FluentParser::new()
2961            .push("module Math { let sq <- 2; @pub let pi <- 3; };")
2962            .run()
2963            .get_r_code()
2964            .iter()
2965            .cloned()
2966            .collect::<Vec<_>>()
2967            .join("\n");
2968        assert!(
2969            r_code.contains("Math <- new.env(parent = emptyenv())"),
2970            "missing env init: {}",
2971            r_code
2972        );
2973        assert!(
2974            r_code.contains("local({"),
2975            "missing local block: {}",
2976            r_code
2977        );
2978        assert!(
2979            r_code.contains("Math$pi <- pi"),
2980            "missing public export: {}",
2981            r_code
2982        );
2983        assert!(
2984            !r_code.contains("Math$sq"),
2985            "private member should not be exported: {}",
2986            r_code
2987        );
2988    }
2989
2990    #[test]
2991    fn test_testable_member_hidden_in_normal_build() {
2992        // Without --test, a @testable member stays fully private.
2993        let r_code = FluentParser::new()
2994            .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
2995            .run()
2996            .get_r_code()
2997            .iter()
2998            .cloned()
2999            .collect::<Vec<_>>()
3000            .join("\n");
3001        assert!(
3002            !r_code.contains(".test_sq"),
3003            "testable member must not be exposed in a normal build: {}",
3004            r_code
3005        );
3006    }
3007
3008    #[test]
3009    fn test_testable_member_exposed_in_test_build() {
3010        // With test_mode on, a @testable member is exposed as M$.test_<name>.
3011        let r_code = FluentParser::new()
3012            .set_context(Context::empty().set_test_mode(true))
3013            .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
3014            .run()
3015            .get_r_code()
3016            .iter()
3017            .cloned()
3018            .collect::<Vec<_>>()
3019            .join("\n");
3020        assert!(
3021            r_code.contains("Math$`.test_sq` <- sq"),
3022            "testable member must be exposed in a test build: {}",
3023            r_code
3024        );
3025    }
3026
3027    #[test]
3028    fn test_module_transpilation_no_pub() {
3029        let r_code = FluentParser::new()
3030            .push("module Empty { let x <- 1; };")
3031            .run()
3032            .get_r_code()
3033            .iter()
3034            .cloned()
3035            .collect::<Vec<_>>()
3036            .join("\n");
3037        assert!(
3038            r_code.contains("Empty <- new.env(parent = emptyenv())"),
3039            "missing env init: {}",
3040            r_code
3041        );
3042        assert!(
3043            r_code.contains("local({"),
3044            "missing local block: {}",
3045            r_code
3046        );
3047        assert!(
3048            !r_code.contains("Empty$x"),
3049            "private member should not be exported: {}",
3050            r_code
3051        );
3052    }
3053
3054    #[test]
3055    fn test_module_s3_registration_for_typed_pub_fn() {
3056        let r_code = FluentParser::new()
3057            .push("module Math { @pub let double <- fn(x: Integer): Integer { x }; };")
3058            .run()
3059            .get_r_code()
3060            .iter()
3061            .cloned()
3062            .collect::<Vec<_>>()
3063            .join("\n");
3064        assert!(
3065            r_code.contains("Math <- new.env(parent = emptyenv())"),
3066            "missing env init: {}",
3067            r_code
3068        );
3069        assert!(
3070            r_code.contains("registerS3method(\"double\", \"integer\", double.integer)"),
3071            "missing S3 registration: {}",
3072            r_code
3073        );
3074        assert!(
3075            r_code.contains("double <- function(x, ...) UseMethod(\"double\")"),
3076            "missing generic: {}",
3077            r_code
3078        );
3079        assert!(
3080            r_code.contains("Math$double <- double"),
3081            "missing generic export: {}",
3082            r_code
3083        );
3084    }
3085
3086    #[test]
3087    fn test_module_no_trailing_semicolon() {
3088        let r_code = FluentParser::new()
3089            .push("module Geo { @pub let pi <- 3; }")
3090            .run()
3091            .get_r_code()
3092            .iter()
3093            .cloned()
3094            .collect::<Vec<_>>()
3095            .join("\n");
3096        assert!(
3097            r_code.contains("Geo <- new.env(parent = emptyenv())"),
3098            "missing env init: {}",
3099            r_code
3100        );
3101        assert!(
3102            r_code.contains("Geo$pi <- pi"),
3103            "missing public export: {}",
3104            r_code
3105        );
3106    }
3107
3108    #[test]
3109    fn test_import_module() {
3110        let r_code = FluentParser::new()
3111            .push("module Math { @pub let pi <- 3; }")
3112            .push("import Math")
3113            .run()
3114            .run()
3115            .get_r_code()
3116            .iter()
3117            .cloned()
3118            .collect::<Vec<_>>()
3119            .join("\n");
3120        assert!(
3121            r_code.contains("Math <- new.env(parent = emptyenv())"),
3122            "missing env init: {}",
3123            r_code
3124        );
3125    }
3126
3127    #[test]
3128    fn test_import_module_as_alias() {
3129        let r_code = FluentParser::new()
3130            .push("module Math { @pub let pi <- 3; }")
3131            .push("import Math as Maths")
3132            .run()
3133            .run()
3134            .get_r_code()
3135            .iter()
3136            .cloned()
3137            .collect::<Vec<_>>()
3138            .join("\n");
3139        assert!(
3140            r_code.contains("Math <- new.env(parent = emptyenv())"),
3141            "missing env init: {}",
3142            r_code
3143        );
3144        assert!(
3145            r_code.contains("Maths <- Math") || r_code.contains("`Maths` <- Math"),
3146            "missing alias assignment: {}",
3147            r_code
3148        );
3149    }
3150
3151    #[test]
3152    fn test_use_items_transpiles() {
3153        let r_code = FluentParser::new()
3154            .push("module Math { @pub let pi <- 3; @pub let e <- 2; };")
3155            .push("use Math::{pi, e as euler};")
3156            .run()
3157            .run()
3158            .get_r_code()
3159            .iter()
3160            .cloned()
3161            .collect::<Vec<_>>()
3162            .join("\n");
3163        assert!(
3164            r_code.contains("pi <- Math$pi"),
3165            "missing pi binding: {}",
3166            r_code
3167        );
3168        assert!(
3169            r_code.contains("euler <- Math$e"),
3170            "missing euler binding: {}",
3171            r_code
3172        );
3173    }
3174
3175    #[test]
3176    fn test_use_wildcard_transpiles() {
3177        let r_code = FluentParser::new()
3178            .push("module Math { @pub let pi <- 3; @pub let e <- 2; let secret <- 0; };")
3179            .push("use Math::*;")
3180            .run()
3181            .run()
3182            .get_r_code()
3183            .iter()
3184            .cloned()
3185            .collect::<Vec<_>>()
3186            .join("\n");
3187        assert!(
3188            r_code.contains("pi <- Math$pi"),
3189            "missing pi binding: {}",
3190            r_code
3191        );
3192        assert!(
3193            r_code.contains("e <- Math$e"),
3194            "missing e binding: {}",
3195            r_code
3196        );
3197        assert!(
3198            !r_code.contains("secret <- Math$secret"),
3199            "private member must not be imported: {}",
3200            r_code
3201        );
3202    }
3203
3204    // RFC-TR-032 @export tests
3205
3206    #[test]
3207    fn test_export_at_top_level_prepends_roxygen_tag() {
3208        let r_code = FluentParser::new()
3209            .push("@export let answer <- 42;")
3210            .run()
3211            .get_r_code()
3212            .iter()
3213            .cloned()
3214            .collect::<Vec<_>>()
3215            .join("\n");
3216        assert!(
3217            r_code.contains("#' @export"),
3218            "missing #' @export tag: {}",
3219            r_code
3220        );
3221        assert!(r_code.contains("answer"), "missing assignment: {}", r_code);
3222    }
3223
3224    #[test]
3225    fn test_export_in_module_is_public_and_package_exported() {
3226        let r_code = FluentParser::new()
3227            .push("module Math { @export let norm <- fn(x: int): int { x }; };")
3228            .run()
3229            .get_r_code()
3230            .iter()
3231            .cloned()
3232            .collect::<Vec<_>>()
3233            .join("\n");
3234        assert!(
3235            r_code.contains("Math$norm <- norm"),
3236            "missing module export: {}",
3237            r_code
3238        );
3239        assert!(
3240            r_code.contains("#' @export"),
3241            "missing roxygen export tag: {}",
3242            r_code
3243        );
3244        assert!(
3245            r_code.contains("norm <- Math$norm"),
3246            "missing package-level re-export: {}",
3247            r_code
3248        );
3249    }
3250
3251    #[test]
3252    fn test_export_in_module_test_build_adds_test_alias() {
3253        let r_code = FluentParser::new()
3254            .set_context(Context::empty().set_test_mode(true))
3255            .push("module Math { @export let norm <- fn(x: int): int { x }; };")
3256            .run()
3257            .get_r_code()
3258            .iter()
3259            .cloned()
3260            .collect::<Vec<_>>()
3261            .join("\n");
3262        assert!(
3263            r_code.contains("Math$`.test_norm` <- norm"),
3264            "export member must get .test_ alias in test build: {}",
3265            r_code
3266        );
3267    }
3268
3269    #[test]
3270    fn test_pub_in_module_test_build_adds_test_alias() {
3271        let r_code = FluentParser::new()
3272            .set_context(Context::empty().set_test_mode(true))
3273            .push("module Math { @pub let pi <- 3; };")
3274            .run()
3275            .get_r_code()
3276            .iter()
3277            .cloned()
3278            .collect::<Vec<_>>()
3279            .join("\n");
3280        assert!(
3281            r_code.contains("Math$`.test_pi` <- pi"),
3282            "@pub member must get .test_ alias in test build (RFC-TR-032 §3.2): {}",
3283            r_code
3284        );
3285    }
3286
3287    #[test]
3288    fn test_array_constructor_call_transpilation() {
3289        let r_code = FluentParser::new()
3290            .push("type Bits <- [Any, int];")
3291            .run()
3292            .push("let b <- Bits:[1, 2, 3];")
3293            .run()
3294            .get_r_code()
3295            .iter()
3296            .cloned()
3297            .collect::<Vec<_>>()
3298            .join("\n");
3299        assert!(
3300            r_code.contains("Bits(typed_vec("),
3301            "expected Bits(...) constructor: {}",
3302            r_code
3303        );
3304        assert!(
3305            r_code.contains("dim = c(3)"),
3306            "expected dimension annotation: {}",
3307            r_code
3308        );
3309    }
3310
3311    #[test]
3312    fn test_record_alias_return_no_constructor_pipe() {
3313        // A function returning a record alias must NOT get `|> TypeName()` in
3314        // its body — the constructor takes specific named fields, not a single
3315        // value, so piping the body result through it would fail at runtime.
3316        let r_code = FluentParser::new()
3317            .push("type Point <- list { x: int, y: int };")
3318            .run()
3319            .push("let incr <- fn(p: Point): Point { Point:{x: (p$x+1), y: (p$y+1)} };")
3320            .run()
3321            .get_r_code()
3322            .iter()
3323            .cloned()
3324            .collect::<Vec<_>>()
3325            .join("\n");
3326        // The method definition must not contain `|> Point()` inside the body
3327        assert!(
3328            !r_code.contains("}) |> Point()"),
3329            "record alias output conversion should not be added: {}",
3330            r_code
3331        );
3332        // The method should still be wrapped by as.Generic() or as.FunctionN() for S3 dispatch
3333        assert!(
3334            r_code.contains("|> as.Generic()") || r_code.contains("|> Function"),
3335            "function type annotation should still be applied: {}",
3336            r_code
3337        );
3338    }
3339
3340    #[test]
3341    fn test_alias_int_generates_validator() {
3342        let r_code = FluentParser::new().check_transpiling("type Meters <- int;");
3343        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3344        assert!(
3345            r_str.contains("validate_Meters <- function(x)"),
3346            "expected validator function, got: {}",
3347            r_str
3348        );
3349        assert!(
3350            r_str.contains("is.integer"),
3351            "expected is.integer check, got: {}",
3352            r_str
3353        );
3354    }
3355
3356    #[test]
3357    fn test_alias_char_generates_validator() {
3358        let r_code = FluentParser::new().check_transpiling("type Name <- char;");
3359        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3360        assert!(
3361            r_str.contains("validate_Name <- function(x)"),
3362            "expected validator function, got: {}",
3363            r_str
3364        );
3365        assert!(
3366            r_str.contains("is.character"),
3367            "expected is.character check, got: {}",
3368            r_str
3369        );
3370    }
3371
3372    #[test]
3373    fn test_alias_bool_generates_validator() {
3374        let r_code = FluentParser::new().check_transpiling("type Flag <- bool;");
3375        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3376        assert!(
3377            r_str.contains("validate_Flag <- function(x)"),
3378            "expected validator function, got: {}",
3379            r_str
3380        );
3381        assert!(
3382            r_str.contains("is.logical"),
3383            "expected is.logical check, got: {}",
3384            r_str
3385        );
3386    }
3387
3388    #[test]
3389    fn test_alias_num_generates_validator() {
3390        let r_code = FluentParser::new().check_transpiling("type Real <- num;");
3391        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3392        assert!(
3393            r_str.contains("validate_Real <- function(x)"),
3394            "expected validator function, got: {}",
3395            r_str
3396        );
3397        assert!(
3398            r_str.contains("is.numeric"),
3399            "expected is.numeric check, got: {}",
3400            r_str
3401        );
3402    }
3403
3404    #[test]
3405    fn test_validating_cast_int() {
3406        let r_code = FluentParser::new()
3407            .push("type Meters <- int;")
3408            .run()
3409            .check_transpiling("x as! Meters");
3410        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3411        assert!(
3412            r_str.contains("validate_Meters(x)"),
3413            "expected validate_Meters(x), got: {}",
3414            r_str
3415        );
3416    }
3417
3418    #[test]
3419    fn test_tag_alias_char_generates_validator() {
3420        let r_code = FluentParser::new().check_transpiling("type Hello <- .Hello(char);");
3421        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3422        assert!(
3423            r_str.contains("validate_Hello <- function(x)"),
3424            "expected validator function, got: {}",
3425            r_str
3426        );
3427        assert!(
3428            r_str.contains("x[[1]] != 'Hello'"),
3429            "expected tag name check, got: {}",
3430            r_str
3431        );
3432        assert!(
3433            r_str.contains("x[[\"body\"]]"),
3434            "expected body field check, got: {}",
3435            r_str
3436        );
3437        assert!(
3438            r_str.contains("is.character"),
3439            "expected is.character check on body, got: {}",
3440            r_str
3441        );
3442    }
3443
3444    #[test]
3445    fn test_tag_alias_int_generates_validator() {
3446        let r_code = FluentParser::new().check_transpiling("type Count <- .Count(int);");
3447        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3448        assert!(
3449            r_str.contains("validate_Count <- function(x)"),
3450            "expected validator, got: {}",
3451            r_str
3452        );
3453        assert!(
3454            r_str.contains("x[[1]] != 'Count'"),
3455            "expected tag name check, got: {}",
3456            r_str
3457        );
3458        assert!(
3459            r_str.contains("is.integer"),
3460            "expected is.integer check on body, got: {}",
3461            r_str
3462        );
3463    }
3464
3465    #[test]
3466    fn test_tag_alias_with_alias_body_calls_nested_validator() {
3467        let r_code = FluentParser::new()
3468            .push("type Name <- char;")
3469            .run()
3470            .check_transpiling("type Tagged <- .Tagged(Name);");
3471        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3472        assert!(
3473            r_str.contains("validate_Tagged <- function(x)"),
3474            "expected validator, got: {}",
3475            r_str
3476        );
3477        assert!(
3478            r_str.contains("validate_Name(x[[\"body\"]])"),
3479            "expected nested validator call, got: {}",
3480            r_str
3481        );
3482    }
3483
3484    #[test]
3485    fn test_union_variant_generates_full_pipeline() {
3486        // Each union variant gets the same constructor/annotator/validator
3487        // contract as records (validation_variant_d_union.md §3).
3488        let r_str = FluentParser::new()
3489            .check_transpiling("type Shape <- .Circle(num) | .Nothing;")
3490            .iter()
3491            .cloned()
3492            .collect::<Vec<_>>()
3493            .join("\n");
3494        // Constructor (payload variant) builds the canonical value then delegates.
3495        assert!(
3496            r_str.contains("Circle <- function(x) {")
3497                && r_str.contains("v <- list(\"Circle\", body = x)")
3498                && r_str.contains("as.Circle(v)"),
3499            "expected Circle constructor, got: {r_str}"
3500        );
3501        // Annotator sets the enriched class idempotently and validates.
3502        assert!(
3503            r_str.contains("as.Circle <- function(x) {")
3504                && r_str.contains("class(x) <- c(\"Circle\", \"Shape\", \"Tag\", \"list\")")
3505                && r_str.contains("x <- validate_Circle(x)")
3506                && r_str.contains("x <- validate(x)"),
3507            "expected Circle annotator, got: {r_str}"
3508        );
3509        // Internal validator checks tag identity and payload type.
3510        assert!(
3511            r_str.contains("validate_Circle <- function(x) {")
3512                && r_str.contains("x[[1]] != 'Circle'")
3513                && r_str.contains("is.numeric(x[[\"body\"]])"),
3514            "expected Circle validator, got: {r_str}"
3515        );
3516        // Empty variant has a zero-arg constructor and no body.
3517        assert!(
3518            r_str.contains("Nothing <- function() {") && r_str.contains("x <- list(\"Nothing\")"),
3519            "expected Nothing constructor, got: {r_str}"
3520        );
3521    }
3522
3523    #[test]
3524    fn test_tag_literal_canonical_representation() {
3525        // A `.Circle(..)` literal must produce the same runtime shape as the
3526        // variant constructor: tag in position 1, payload under `body`, class
3527        // enriched with the union name (validation_variant_d_union.md §2).
3528        let r_str = FluentParser::new()
3529            .push("type Shape <- .Circle(num) | .Square(num);")
3530            .run()
3531            .check_transpiling(".Circle(3.14)")
3532            .iter()
3533            .cloned()
3534            .collect::<Vec<_>>()
3535            .join("\n");
3536        assert!(
3537            r_str.contains("structure(list('Circle', body =")
3538                && r_str.contains("class = c('Circle', 'Shape', 'Tag', 'list')"),
3539            "expected canonical tag literal with union class, got: {r_str}"
3540        );
3541    }
3542
3543    #[test]
3544    fn test_tag_literal_without_union_omits_union_class() {
3545        // A tag with no declared union still uses the canonical shape, but the
3546        // class carries no union name.
3547        let r_str = FluentParser::new()
3548            .check_transpiling(".Loose(1)")
3549            .iter()
3550            .cloned()
3551            .collect::<Vec<_>>()
3552            .join("\n");
3553        assert!(
3554            r_str.contains("structure(list('Loose', body =")
3555                && r_str.contains("class = c('Loose', 'Tag', 'list')"),
3556            "expected canonical tag literal without union class, got: {r_str}"
3557        );
3558    }
3559
3560    #[test]
3561    fn test_literal_char_alias_generates_exact_validator() {
3562        let r_code = FluentParser::new().check_transpiling("type Hello <- \"hello\";");
3563        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3564        assert!(
3565            r_str.contains("validate_Hello <- function(x)"),
3566            "expected validator function, got: {}",
3567            r_str
3568        );
3569        assert!(
3570            r_str.contains("x != 'hello'"),
3571            "expected literal equality check, got: {}",
3572            r_str
3573        );
3574    }
3575
3576    #[test]
3577    fn test_literal_int_alias_generates_exact_validator() {
3578        let r_code = FluentParser::new().check_transpiling("type Byte <- 89;");
3579        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3580        assert!(
3581            r_str.contains("validate_Byte <- function(x)"),
3582            "expected validator function, got: {}",
3583            r_str
3584        );
3585        assert!(
3586            r_str.contains("x != 89L"),
3587            "expected literal equality check, got: {}",
3588            r_str
3589        );
3590    }
3591
3592    #[test]
3593    fn test_literal_num_alias_generates_exact_validator() {
3594        let r_code = FluentParser::new().check_transpiling("type Pi <- 3.14;");
3595        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3596        assert!(
3597            r_str.contains("validate_Pi <- function(x)"),
3598            "expected validator function, got: {}",
3599            r_str
3600        );
3601        assert!(
3602            r_str.contains("x != 3.14"),
3603            "expected literal equality check, got: {}",
3604            r_str
3605        );
3606    }
3607
3608    #[test]
3609    fn test_literal_bool_true_alias_generates_exact_validator() {
3610        let r_code = FluentParser::new().check_transpiling("type Yes <- true;");
3611        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3612        assert!(
3613            r_str.contains("validate_Yes <- function(x)"),
3614            "expected validator function, got: {}",
3615            r_str
3616        );
3617        assert!(
3618            r_str.contains("x != TRUE"),
3619            "expected literal TRUE check, got: {}",
3620            r_str
3621        );
3622    }
3623
3624    #[test]
3625    fn test_literal_bool_false_alias_generates_exact_validator() {
3626        let r_code = FluentParser::new().check_transpiling("type No <- false;");
3627        let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3628        assert!(
3629            r_str.contains("validate_No <- function(x)"),
3630            "expected validator function, got: {}",
3631            r_str
3632        );
3633        assert!(
3634            r_str.contains("x != FALSE"),
3635            "expected literal FALSE check, got: {}",
3636            r_str
3637        );
3638    }
3639
3640    #[test]
3641    fn test_record_subtype_includes_supertype_in_s3_class() {
3642        // Person has all fields of Position, so Person <: Position.
3643        // The annotator for Person must include "Position" in its class vector
3644        // so that S3 methods defined on Position dispatch for Person values.
3645        let r_str = FluentParser::new()
3646            .push("type Position <- list{ position: int };")
3647            .run()
3648            .push("type Person <- list{ name: char, age: int, position: int };")
3649            .run()
3650            .get_r_code()
3651            .iter()
3652            .cloned()
3653            .collect::<Vec<_>>()
3654            .join("\n");
3655        assert!(
3656            r_str.contains("class(x) <- c(\"Person\", \"Position\", \"list\")"),
3657            "expected Person's annotator to include Position, got: {r_str}"
3658        );
3659    }
3660
3661    #[test]
3662    fn test_record_without_supertype_keeps_plain_class() {
3663        // Position has no other record supertype, so its class stays c("Position", "list").
3664        let r_str = FluentParser::new()
3665            .check_transpiling("type Position <- list{ position: int };")
3666            .iter()
3667            .cloned()
3668            .collect::<Vec<_>>()
3669            .join("\n");
3670        assert!(
3671            r_str.contains("class(x) <- c(\"Position\", \"list\")"),
3672            "expected Position's annotator with no supertype, got: {r_str}"
3673        );
3674    }
3675
3676    #[test]
3677    fn test_import_from_qualifies_call_site() {
3678        // @importFrom dplyr filter should make filter(a, b) transpile to dplyr::filter(a, b).
3679        let r_str = FluentParser::new()
3680            .push("@importFrom dplyr filter;")
3681            .run()
3682            .push("@filter: (Any, Any) -> Any;")
3683            .run()
3684            .check_transpiling("filter(df, cond)")
3685            .iter()
3686            .cloned()
3687            .collect::<Vec<_>>()
3688            .join("\n");
3689        assert!(
3690            r_str.contains("dplyr::filter("),
3691            "expected dplyr::filter(...), got: {r_str}"
3692        );
3693    }
3694
3695    #[test]
3696    fn test_import_from_multiple_fns() {
3697        // Multiple functions from the same package should each be qualified independently.
3698        let r_str = FluentParser::new()
3699            .push("@importFrom dplyr filter mutate;")
3700            .run()
3701            .push("@mutate: (Any, Any) -> Any;")
3702            .run()
3703            .check_transpiling("mutate(df, z)")
3704            .iter()
3705            .cloned()
3706            .collect::<Vec<_>>()
3707            .join("\n");
3708        assert!(
3709            r_str.contains("dplyr::mutate("),
3710            "expected dplyr::mutate(...), got: {r_str}"
3711        );
3712    }
3713}