1pub mod checked_assertions;
2pub mod translatable;
3
4use crate::components::context::config::Environment;
5use crate::components::context::Context;
6use crate::components::error_message::help_data::HelpData;
7use crate::components::language::argument_value::ArgumentValue;
8use crate::components::language::format_backtick;
9use crate::components::language::function_lang::Function;
10use crate::components::language::operators::Op;
11use crate::components::language::set_related_type_if_variable;
12use crate::components::language::var::Var;
13use crate::components::language::Lang;
14use crate::components::language::ModulePosition;
15use crate::components::r#type::argument_type::ArgumentType;
16use crate::components::r#type::array_type::ArrayType;
17use crate::components::r#type::function_type::FunctionType;
18use crate::components::r#type::type_operator::TypeOperator;
19use crate::components::r#type::type_system::TypeSystem;
20use crate::components::r#type::vector_type::VecType;
21use crate::components::r#type::Type;
22use crate::processes::transpiling::translatable::Translatable;
23use crate::processes::type_checking::facets;
24use crate::processes::type_checking::flatten_operator_union;
25use crate::processes::type_checking::resolve_module_member_type;
26use crate::processes::type_checking::type_comparison::reduce_type;
27use crate::processes::type_checking::typing;
28use translatable::RTranslatable;
29
30#[cfg(not(target_arch = "wasm32"))]
31use std::fs::File;
32#[cfg(not(target_arch = "wasm32"))]
33use std::io::Write;
34#[cfg(not(target_arch = "wasm32"))]
35use std::path::PathBuf;
36
37use std::cell::RefCell;
38use std::collections::HashMap;
39
40pub fn escape_r_string(s: &str) -> String {
46 let escaped = s
47 .replace('\\', "\\\\")
48 .replace('"', "\\\"")
49 .replace('\n', "\\n")
50 .replace('\t', "\\t")
51 .replace('\r', "\\r");
52 format!("\"{}\"", escaped)
53}
54
55fn atomic_array_literal(array: &Lang, cont: &Context, elem: &Type) -> 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 let empty = match elem {
75 Type::Integer(_, _) => "integer(0)",
76 Type::Char(_, _) => "character(0)",
77 Type::Boolean(_, _) => "logical(0)",
78 _ => "numeric(0)",
79 };
80 return empty.to_string();
81 }
82 format!("c({})", lin_array)
83}
84
85fn array_literal_raw(array: &Lang, cont: &Context) -> String {
86 let typ = array.typing(cont).value;
87 if let Some(elem) = cont.atomic_array_elem(&typ) {
92 return atomic_array_literal(array, cont, &elem);
93 }
94 let dimension = ArrayType::try_from(typ)
95 .expect("array literal should have an array type")
96 .get_shape()
97 .map(|sha| format!("c({})", sha))
98 .unwrap_or_else(|| "c(0)".to_string());
99 let lin_array = array
100 .linearize_array()
101 .iter()
102 .map(|lang| lang.to_r(cont).0)
103 .collect::<Vec<_>>()
104 .join(", ");
105 if lin_array.is_empty() {
106 format!("typed_vec(dim = {})", dimension)
107 } else {
108 format!("typed_vec({}, dim = {})", lin_array, dimension)
109 }
110}
111
112thread_local! {
114 static GENERATED_FILES: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
115}
116
117thread_local! {
130 static INCLUDE_STACK: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
131}
132
133pub fn reset_include_stack() {
135 INCLUDE_STACK.with(|s| *s.borrow_mut() = vec![Vec::new()]);
136}
137
138fn push_include_frame() {
140 INCLUDE_STACK.with(|s| s.borrow_mut().push(Vec::new()));
141}
142
143fn pop_include_frame() -> Vec<String> {
145 INCLUDE_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
146}
147
148fn register_include(file: &str) {
150 INCLUDE_STACK.with(|s| {
151 if let Some(top) = s.borrow_mut().last_mut() {
152 top.push(file.to_string());
153 }
154 });
155}
156
157pub fn take_main_includes() -> Vec<String> {
159 INCLUDE_STACK.with(|s| {
160 let mut stack = s.borrow_mut();
161 match stack.first_mut() {
162 Some(bottom) => std::mem::take(bottom),
163 None => Vec::new(),
164 }
165 })
166}
167
168thread_local! {
169 static IMPORT_FROM_STACK: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
170}
171
172pub fn reset_import_from_stack() {
173 IMPORT_FROM_STACK.with(|s| *s.borrow_mut() = vec![Vec::new()]);
174}
175
176fn push_import_from_frame() {
177 IMPORT_FROM_STACK.with(|s| s.borrow_mut().push(Vec::new()));
178}
179
180fn pop_import_from_frame() -> Vec<String> {
181 IMPORT_FROM_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
182}
183
184fn register_import_from(entry: &str) {
185 IMPORT_FROM_STACK.with(|s| {
186 if let Some(top) = s.borrow_mut().last_mut() {
187 top.push(entry.to_string());
188 }
189 });
190}
191
192pub fn take_main_import_froms() -> Vec<String> {
193 IMPORT_FROM_STACK.with(|s| {
194 let mut stack = s.borrow_mut();
195 match stack.first_mut() {
196 Some(bottom) => std::mem::take(bottom),
197 None => Vec::new(),
198 }
199 })
200}
201
202pub fn register_generated_file(path: &str, content: &str) {
204 GENERATED_FILES.with(|files| {
205 files.borrow_mut().insert(path.to_string(), content.to_string());
206 });
207}
208
209pub fn get_generated_files() -> HashMap<String, String> {
211 GENERATED_FILES.with(|files| files.borrow().clone())
212}
213
214pub fn clear_generated_files() {
216 GENERATED_FILES.with(|files| {
217 files.borrow_mut().clear();
218 });
219}
220
221#[cfg(not(target_arch = "wasm32"))]
225fn write_output_file(path: &str, content: &str) -> Result<(), String> {
226 use std::fs;
227
228 register_generated_file(path, content);
230
231 let path_buf = PathBuf::from(path);
232 if let Ok(existing) = fs::read_to_string(&path_buf) {
233 if existing == content {
234 return Ok(());
235 }
236 }
237 if let Some(parent) = path_buf.parent() {
238 fs::create_dir_all(parent).map_err(|e| e.to_string())?;
239 }
240 let mut file = File::create(&path_buf).map_err(|e| e.to_string())?;
241 file.write_all(content.as_bytes()).map_err(|e| e.to_string())?;
242 Ok(())
243}
244
245#[cfg(target_arch = "wasm32")]
246fn write_output_file(path: &str, content: &str) -> Result<(), String> {
247 register_generated_file(path, content);
248 Ok(())
249}
250
251pub trait ToSome {
252 fn to_some(self) -> Option<Self>
253 where
254 Self: Sized;
255}
256
257impl<T: Sized> ToSome for T {
258 fn to_some(self) -> Option<Self> {
259 Some(self)
260 }
261}
262
263const JS_HEADER: &str = "";
264
265fn to_pattern_match_statement(exp: Lang, branches: &[(Lang, Box<Lang>)], context: &Context) -> String {
266 let match_var = "match_val__";
267 let res = branches
268 .iter()
269 .enumerate()
270 .map(|(id, (pattern, body))| {
271 let (cond, bindings) = pattern_to_condition(pattern, match_var, context);
272 let body_str = body.to_r(context).0;
273 let body_with_bindings = if bindings.is_empty() {
274 body_str
275 } else {
276 format!("{}\n{}", bindings, body_str)
277 };
278 if cond == "TRUE" {
279 if id == 0 {
281 format!("{{\n{}\n}}", body_with_bindings)
282 } else {
283 format!("else {{\n{}\n}}", body_with_bindings)
284 }
285 } else if id == 0 {
286 format!("if ({}) {{\n{}\n}}", cond, body_with_bindings)
287 } else {
288 format!("else if ({}) {{\n{}\n}}", cond, body_with_bindings)
289 }
290 })
291 .collect::<Vec<_>>()
292 .join(" ");
293 format!("{{\n{} <- {}\n{}\n}}", match_var, exp.to_r(context).0, res)
294}
295
296fn extern_lift_fn(typ: &Type) -> Option<&'static str> {
300 match typ {
301 Type::Integer(_, _) => Some("from_int"),
302 Type::Number(_, _) => Some("from_num"),
303 Type::Char(_, _) => Some("from_char"),
304 Type::Boolean(_, _) => Some("from_bool"),
305 Type::Alias(name, _, _, _) if name == "Option" => Some("from_nullable"),
307 _ => None,
308 }
309}
310
311fn type_to_r_check(typ: &Type) -> Option<&'static str> {
312 match typ {
313 Type::Integer(_, _) => Some("is.integer"),
314 Type::Boolean(_, _) => Some("is.logical"),
315 Type::Number(_, _) => Some("is.numeric"),
316 Type::Char(_, _) => Some("is.character"),
317 Type::Null(_) => Some("is.null"),
318 _ => None,
319 }
320}
321
322fn record_field_class(typ: &Type, cont: &Context) -> Option<String> {
328 match typ {
329 Type::Integer(_, _) => Some("integer".to_string()),
330 Type::Number(_, _) => Some("numeric".to_string()),
331 Type::Char(_, _) => Some("character".to_string()),
332 Type::Boolean(_, _) => Some("logical".to_string()),
333 Type::Alias(name, _, _, _) => match cont.aliases().find(|(var, _)| var.get_name() == *name).map(|(_, t)| t) {
334 Some(Type::Record(_, _)) => Some(name.clone()),
336 Some(inner) => record_field_class(inner, cont),
339 None => None,
340 },
341 _ => None,
342 }
343}
344
345fn find_union_for_tag(tag_name: &str, cont: &Context) -> Option<String> {
351 cont.aliases().find_map(|(var, typ)| {
352 let is_union = matches!(
353 typ,
354 Type::Operator(crate::components::r#type::type_operator::TypeOperator::Union, _, _, _)
355 );
356 if !is_union {
357 return None;
358 }
359 let declares_tag = flatten_operator_union(typ)
360 .iter()
361 .any(|m| matches!(m, Type::Tag(n, _, _) if n == tag_name));
362 if declares_tag {
363 Some(var.get_name())
364 } else {
365 None
366 }
367 })
368}
369
370fn tag_body_validation(name: &str, inner_type: &Type) -> String {
375 match inner_type {
376 Type::Empty(_) => String::new(),
377 Type::Integer(tint, _) => {
378 use crate::components::r#type::tint::Tint;
379 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\")");
380 match tint {
381 Tint::Val(i) => format!("{null_check}\n if (x[[\"body\"]] != {i}L) stop(\"Validation failed for type {name}: body must be literal {i}\")"),
382 Tint::Unknown => null_check,
383 }
384 }
385 Type::Char(tchar, _) => {
386 use crate::components::r#type::tchar::Tchar;
387 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\")");
388 match tchar {
389 Tchar::Val(s) => format!("{null_check}\n if (x[[\"body\"]] != '{s}') stop(\"Validation failed for type {name}: body must be literal '{s}'\")"),
390 Tchar::Unknown => null_check,
391 }
392 }
393 Type::Boolean(tbool, _) => {
394 use crate::components::r#type::tbool::Tbool;
395 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\")");
396 match tbool {
397 Tbool::Val(b) => {
398 let r_val = if *b { "TRUE" } else { "FALSE" };
399 format!("{null_check}\n if (x[[\"body\"]] != {r_val}) stop(\"Validation failed for type {name}: body must be literal {r_val}\")")
400 }
401 Tbool::Unknown => null_check,
402 }
403 }
404 Type::Number(tnum, _) => {
405 use crate::components::r#type::tnumber::Tnum;
406 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\")");
407 match tnum {
408 Tnum::Val(v) => format!("{null_check}\n if (x[[\"body\"]] != {v}) stop(\"Validation failed for type {name}: body must be literal {v}\")"),
409 Tnum::Unknown => null_check,
410 }
411 }
412 Type::Alias(alias_name, _, _, _) => format!(
413 "\n if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n validate_{alias_name}(x[[\"body\"]])"
414 ),
415 _ => format!(
416 "\n if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")"
417 ),
418 }
419}
420
421fn tag_variant_pipeline(variant_name: &str, union_name: &str, inner_type: &Type) -> String {
426 let is_empty = matches!(inner_type, Type::Empty(_));
427 let constructor = if is_empty {
430 format!("{variant_name} <- function() {{\n x <- list(\"{variant_name}\")\n as.{variant_name}(x)\n}}")
431 } else {
432 format!(
433 "{variant_name} <- function(x) {{\n v <- list(\"{variant_name}\", body = x)\n as.{variant_name}(v)\n}}"
434 )
435 };
436 let annotator = format!(
440 "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}}"
441 );
442 let body_validation = tag_body_validation(variant_name, inner_type);
444 let validator = format!(
445 "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}}"
446 );
447 format!("{constructor}\n{annotator}\n{validator}")
448}
449
450fn pattern_to_condition(pattern: &Lang, match_var: &str, _context: &Context) -> (String, String) {
451 match pattern {
452 Lang::Tag { name, value: inner, .. } => {
454 let cond = format!("{}[[1]] == '{}'", match_var, name);
455 match inner.as_ref() {
456 Lang::Variable { name: var_name, .. } => {
457 let binding = format!("{} <- {}[[\"body\"]]", var_name, match_var);
458 (cond, binding)
459 }
460 Lang::Empty(_) => (cond, String::new()),
461 _ => (cond, String::new()),
462 }
463 }
464 Lang::TypePattern {
466 variable_name: var_name,
467 matched_type: typ,
468 ..
469 } => {
470 let check_fn = type_to_r_check(typ).unwrap_or("is.logical");
471 let cond = format!("{}({})", check_fn, match_var);
472 let binding = format!("{} <- {}", var_name, match_var);
473 (cond, binding)
474 }
475 Lang::Tuple { value: elements, .. } => {
477 let cond = format!(
478 "inherits({}, 'Tuple') && length({}) == {}",
479 match_var,
480 match_var,
481 elements.len()
482 );
483 let bindings: Vec<String> = elements
484 .iter()
485 .enumerate()
486 .filter_map(|(i, elem)| {
487 if let Lang::Variable { name: var_name, .. } = elem {
488 if var_name == "_" {
489 None
490 } else {
491 Some(format!("{} <- {}[[{}]]", var_name, match_var, i + 1))
492 }
493 } else {
494 None
495 }
496 })
497 .collect();
498 (cond, bindings.join("\n"))
499 }
500 Lang::List { value: fields, .. } => {
502 let conditions: Vec<String> = fields
503 .iter()
504 .map(|arg_val: &ArgumentValue| format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument()))
505 .collect();
506 let cond = if conditions.is_empty() {
507 "is.list(".to_string() + match_var + ")"
508 } else {
509 format!("is.list({}) && {}", match_var, conditions.join(" && "))
510 };
511 let bindings: Vec<String> = fields
512 .iter()
513 .filter_map(|arg_val| {
514 if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
515 Some(format!(
516 "{} <- {}[[\"{}\"]]",
517 var_name,
518 match_var,
519 arg_val.get_argument()
520 ))
521 } else {
522 None
523 }
524 })
525 .collect();
526 (cond, bindings.join("\n"))
527 }
528 Lang::DataFrame { value: fields, .. } => {
530 let conditions: Vec<String> = fields
531 .iter()
532 .map(|arg_val: &ArgumentValue| format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument()))
533 .collect();
534 let cond = if conditions.is_empty() {
535 "is.data.frame(".to_string() + match_var + ")"
536 } else {
537 format!("is.data.frame({}) && {}", match_var, conditions.join(" && "))
538 };
539 let bindings: Vec<String> = fields
540 .iter()
541 .filter_map(|arg_val| {
542 if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
543 Some(format!(
544 "{} <- {}[[\"{}\"]]",
545 var_name,
546 match_var,
547 arg_val.get_argument()
548 ))
549 } else {
550 None
551 }
552 })
553 .collect();
554 (cond, bindings.join("\n"))
555 }
556 Lang::Variable { name, .. } if name == "_" => ("TRUE".to_string(), String::new()),
558 Lang::Variable { name, .. } => {
560 let binding = format!("{} <- {}", name, match_var);
561 ("TRUE".to_string(), binding)
562 }
563 _ => ("TRUE".to_string(), String::new()),
564 }
565}
566
567impl RTranslatable<(String, Context)> for Lang {
568 fn to_r(&self, cont: &Context) -> (String, Context) {
569 let result = match self {
570 Lang::Bool { value: b, .. } => {
571 let (typ, _, _) = typing(cont, self).to_tuple();
572 let anotation = cont.get_type_anotation(&typ);
573 (
574 format!("{} |> {}", b.to_string().to_uppercase(), anotation),
575 cont.clone(),
576 )
577 }
578 Lang::Number { value: n, .. } => {
579 let (typ, _, _) = typing(cont, self).to_tuple();
580 let anotation = cont.get_type_anotation(&typ);
581 (format!("{} |> {}", n, anotation), cont.clone())
582 }
583 Lang::Integer { value: i, .. } => {
584 let (typ, _, _) = typing(cont, self).to_tuple();
585 let anotation = cont.get_type_anotation(&typ);
586 (format!("{}L |> {}", i, anotation), cont.clone())
587 }
588 Lang::Char { value: s, .. } => {
589 let (typ, _, _) = typing(cont, self).to_tuple();
590 let anotation = cont.get_type_anotation(&typ);
591 (format!("{} |> {}", escape_r_string(s), anotation), cont.clone())
592 }
593 Lang::Operator {
594 operator: op @ (Op::Dot(_) | Op::Pipe(_)),
595 rhs: e1,
596 lhs: e2,
597 ..
598 } => {
599 let is_dot = matches!(op, Op::Dot(_));
614 let e1 = (**e1).clone();
615 let e2 = (**e2).clone();
616 match e2.clone() {
617 Lang::Variable { .. } => match e1 {
618 Lang::Integer { .. } => Translatable::from(cont.clone())
619 .to_r(&e2)
620 .add("[[")
621 .to_r(&e1)
622 .add("]]")
623 .into(),
624 _ if is_dot => Translatable::from(cont.clone())
625 .to_r(&e1)
626 .add("[['")
627 .to_r(&e2)
628 .add("']]")
629 .into(),
630 _ => Translatable::from(cont.clone())
631 .to_r(&e2)
632 .add("[['")
633 .to_r(&e1)
634 .add("']]")
635 .into(),
636 },
637 Lang::List { value: fields, .. } => {
638 let at = fields[0].clone();
639 Translatable::from(cont.clone())
640 .add("within(")
641 .to_r(&e2)
642 .add(", { ")
643 .add(&at.get_argument())
644 .add(" <- ")
645 .to_r(&at.get_value())
646 .add(" })")
647 .into()
648 }
649 Lang::DataFrame { value: fields, .. } => {
650 let at = fields[0].clone();
651 Translatable::from(cont.clone())
652 .add("within(")
653 .to_r(&e2)
654 .add(", { ")
655 .add(&at.get_argument())
656 .add(" <- ")
657 .to_r(&at.get_value())
658 .add(" })")
659 .into()
660 }
661 Lang::FunctionApp {
662 identifier: var,
663 arguments: v,
664 help_data: h,
665 } => {
666 let v = [e1].iter().chain(v.iter()).cloned().collect();
667 Lang::FunctionApp {
668 identifier: var,
669 arguments: v,
670 help_data: h,
671 }
672 .to_r(cont)
673 }
674 _ => Translatable::from(cont.clone())
675 .to_r(&e2)
676 .add("[[")
677 .add("]]")
678 .to_r(&e1)
679 .into(),
680 }
681 }
682 Lang::Operator {
683 operator: Op::Dollar(_),
684 rhs: e1,
685 lhs: e2,
686 ..
687 } => {
688 let e1 = (**e1).clone();
689 let e2 = (**e2).clone();
690 let t1 = typing(cont, &e1).value;
691 let val = match (t1.clone(), e2.clone()) {
692 (Type::Vec(vtype, _, _, _), Lang::Variable { name, .. }) if vtype.is_array() => {
693 format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name)
694 }
695 (Type::Vec(VecType::S3, _, _, _), Lang::Variable { name, .. }) => {
696 let name_str = name.replace("__", ".");
697 format!("get({}, '{}')", e1.to_r(cont).0, name_str)
698 }
699 (_, Lang::Variable { name, .. }) => format!("{}${}", e1.to_r(cont).0, name),
700 _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0),
701 };
702 (val, cont.clone())
703 }
704 Lang::Operator {
705 operator: op,
706 rhs: e1,
707 lhs: e2,
708 ..
709 } => {
710 let op_str = format!(" {} ", op);
711 Translatable::from(cont.clone()).to_r(e1).add(&op_str).to_r(e2).into()
712 }
713 Lang::Scope { body: exps, .. } => Translatable::from(cont.clone())
714 .add("{\n")
715 .join(exps, "\n")
716 .add("\n}")
717 .into(),
718 Lang::Function {
719 parameters: params,
720 body,
721 help_data,
722 ..
723 } => {
724 let fn_type = FunctionType::try_from(typing(cont, self).value.clone())
725 .expect("function expression should have a function type");
726 let return_type = fn_type.get_return_type();
727
728 let is_record_alias_return = match &return_type {
733 Type::Alias(alias_name, _, _, _) => cont
740 .aliases()
741 .find(|(var, _)| var.get_name() == *alias_name)
742 .map(|(_, t)| t.clone())
743 .or_else(|| {
744 cont.record_aliases
745 .iter()
746 .find(|(name, _)| name == alias_name)
747 .map(|(_, t)| t.clone())
748 })
749 .map(|t| matches!(t, Type::Record(_, _)))
750 .unwrap_or(false),
751 _ => false,
752 };
753 let output_conversion = if is_record_alias_return {
754 "".to_string()
755 } else {
756 cont.get_type_anotation(&return_type)
757 };
758
759 let has_variadic = params.last().map(|p| p.is_variadic()).unwrap_or(false);
760 let list_of_types = params.iter().map(ArgumentType::body_type).collect::<Vec<_>>();
761 let sub_context = params
762 .iter()
763 .map(|arg_typ| arg_typ.clone().set_type(arg_typ.body_type()).to_var(cont))
764 .zip(list_of_types.clone())
765 .fold(cont.clone(), |context: Context, (var, typ)| {
766 context.clone().push_var_type(var, typ, &context)
767 });
768 let res = if output_conversion.is_empty() {
769 "".to_string()
770 } else {
771 " |> ".to_owned() + &output_conversion
772 };
773 let body_r = body.to_r(&sub_context).0;
774 let with_variadic_collector = if has_variadic {
775 let vname = params.last().unwrap().get_argument_str();
776 let collector = "typed_vec(..., dim = c(...length()))";
780 if let Some(rest) = body_r.strip_prefix('{') {
782 format!("{{\n{} <- {}{}", vname, collector, rest)
783 } else {
784 body_r
785 }
786 } else {
787 body_r
788 };
789 let checked_prologue: String = params
793 .iter()
794 .filter(|p| !p.is_variadic())
795 .filter_map(|p| {
796 checked_assertions::param_assertion(cont, &p.get_argument_str(), &p.body_type(), help_data)
797 })
798 .collect();
799 let final_body_r = if checked_prologue.is_empty() {
800 with_variadic_collector
801 } else if let Some(rest) = with_variadic_collector.strip_prefix('{') {
802 format!("{{\n{}{}", checked_prologue, rest)
803 } else {
804 with_variadic_collector
805 };
806 let final_body_r =
807 checked_assertions::wrap_checked(cont, final_body_r, &return_type, help_data, "return");
808 (
809 format!(
810 "(function({}) {}{}) |> {}",
811 params.iter().map(|x| x.to_r(cont)).collect::<Vec<_>>().join(", "),
812 final_body_r,
813 res,
814 cont.get_type_anotation(&fn_type.into())
815 ),
816 cont.clone(),
817 )
818 }
819 Lang::Variable { .. } => {
820 let var = Var::from_language(self.clone()).unwrap();
822 let name = if var.contains("__") {
823 var.replace("__", ".").get_name()
824 } else {
825 var.display_type(cont).get_name()
826 };
827 (name.to_string(), cont.clone())
828 }
829 Lang::FunctionApp {
830 identifier: exp,
831 arguments: vals,
832 ..
833 } => {
834 let retyped = typing(cont, self).lang;
844 if matches!(retyped, Lang::VecFunctionApp { .. }) {
845 return retyped.to_r(cont);
846 }
847 let var = Var::try_from(exp.clone()).unwrap();
848
849 let (exp_str, cont1) = exp.to_r(cont);
850 let fn_t_opt = cont1
862 .get_type_from_variable(&var)
863 .ok()
864 .and_then(|t| FunctionType::try_from(t).ok())
865 .map(|ft| ft.adjust_nb_parameters(vals.len()));
866 let new_vals = match &fn_t_opt {
867 Some(fn_t) => {
868 let new_args = fn_t
869 .get_param_types()
870 .iter()
871 .map(|arg| reduce_type(&cont1, arg))
872 .collect::<Vec<_>>();
873 vals.iter()
874 .zip(new_args.iter())
875 .map(set_related_type_if_variable)
876 .collect::<Vec<_>>()
877 }
878 None => vals.clone(),
879 };
880 let cont1_fallback = cont1.clone();
881 Var::from_language(*exp.clone())
882 .map(|var| {
883 let name = var.get_name();
884 let new_name = if &name[0..1] == "%" {
885 format!("`{}`", name.replace("__", "."))
886 } else {
887 name.replace("__", ".")
888 };
889 if cont1.is_extern_fn(&name) {
890 let r_name = cont1.get_extern_r_name(&name).unwrap_or_else(|| new_name.clone());
891 let return_type = fn_t_opt
892 .as_ref()
893 .map(|ft| ft.get_return_type())
894 .expect("extern function application identifier should have a function type");
895 let lift_fn = extern_lift_fn(&return_type);
896 let (args_vec, current_cont): (Vec<String>, Context) =
897 new_vals.iter().fold((Vec::new(), cont1.clone()), |(mut v, c), val| {
898 let (s, c2) = val.to_r(&c);
899 v.push(format!("to_native({})", s));
900 (v, c2)
901 });
902 let args = args_vec.join(", ");
903 let call = format!("{}({})", r_name, args);
904 let result = match lift_fn {
905 Some(f) => format!("{}({})", f, call),
906 None => call,
907 };
908 (result, current_cont)
909 } else if cont1.is_import_from_fn(&name) {
910 let r_name = cont1.get_import_from_r_name(&name).unwrap_or_else(|| new_name.clone());
911 let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
912 (format!("{}({})", r_name, args), current_cont)
913 } else {
914 let forced_name = match var.get_type() {
927 Type::Empty(_) => new_name.clone(),
928 Type::Any(_) => format!("{}.default", new_name),
929 ty => format!("{}.{}", new_name, cont1.get_class_unquoted(&ty)),
930 };
931 let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
932 (format!("{}({})", forced_name, args), current_cont)
933 }
934 })
935 .unwrap_or_else(|| {
936 let (args, current_cont) = Translatable::from(cont1_fallback).join(&new_vals, ", ").into();
937 (format!("{}({})", exp_str, args), current_cont)
938 })
939 }
940 Lang::VecFunctionApp {
941 vector_type,
942 identifier: exp,
943 arguments: vals,
944 ..
945 } => {
946 let var = Var::try_from(exp.clone()).unwrap();
947 let name = var.get_name();
948 let str_vals = vals.iter().map(|x| x.to_r(cont).0).collect::<Vec<_>>().join(", ");
949 let has_atomic_array_arg = vals
955 .iter()
956 .any(|val| cont.atomic_array_elem(&val.typing(cont).value).is_some());
957 if *vector_type == VecType::Vector || has_atomic_array_arg {
958 if cont.is_an_untyped_function(&name)
977 && !cont.is_vectorizable_fn(&name)
978 && !crate::processes::type_checking::vectorizability::is_natively_vectorized_callee(&name)
979 {
980 let (_, cont1) = exp.to_r(cont);
981 let fn_t_opt = cont1
982 .get_type_from_variable(&var)
983 .ok()
984 .and_then(|t| FunctionType::try_from(t).ok());
985 let new_vals = match &fn_t_opt {
986 Some(fn_t) => {
987 let new_args = fn_t
988 .get_param_types()
989 .iter()
990 .map(|arg| reduce_type(&cont1, arg))
991 .collect::<Vec<_>>();
992 vals.iter()
993 .zip(new_args.iter())
994 .map(set_related_type_if_variable)
995 .collect::<Vec<_>>()
996 }
997 None => vals.clone(),
998 };
999 let (arg_strs, current_cont) = new_vals.iter().fold(
1000 (Vec::new(), cont1.clone()),
1001 |(mut acc, c): (Vec<String>, Context), val| {
1002 let (s, c2) = val.to_r(&c);
1003 acc.push(s);
1004 (acc, c2)
1005 },
1006 );
1007 let dotted = name.replace("__", ".");
1008 let new_name = if &dotted[0..1] == "%" {
1009 format!("`{}`", dotted)
1010 } else {
1011 dotted
1012 };
1013 let vec_positions: Vec<usize> = new_vals
1017 .iter()
1018 .enumerate()
1019 .filter(|(_, val)| {
1020 let t = reduce_type(&cont1, &val.typing(&cont1).value);
1021 matches!(&t, Type::Vec(vt, _, _, _) if vt.is_vector())
1022 || cont1.atomic_array_elem(&t).is_some()
1023 })
1024 .map(|(i, _)| i)
1025 .collect();
1026 let fun_value = fn_t_opt
1027 .as_ref()
1028 .map(|fn_t| reduce_type(&cont1, &fn_t.get_return_type()))
1029 .and_then(|ret| match ret {
1030 Type::Boolean(_, _) => Some("logical(1)"),
1031 Type::Integer(_, _) => Some("integer(1)"),
1032 Type::Number(_, _) => Some("numeric(1)"),
1033 Type::Char(_, _) => Some("character(1)"),
1034 _ => None,
1035 });
1036 let code = match vec_positions.as_slice() {
1037 [] => format!("{}({})", new_name, arg_strs.join(", ")),
1038 [i] => {
1039 let inner_args = arg_strs
1040 .iter()
1041 .enumerate()
1042 .map(|(j, s)| if j == *i { ".typr_x".to_string() } else { s.clone() })
1043 .collect::<Vec<_>>()
1044 .join(", ");
1045 let fun = format!("function(.typr_x) {}({})", new_name, inner_args);
1046 match fun_value {
1047 Some(fv) => format!("vapply({}, {}, {}, USE.NAMES = FALSE)", arg_strs[*i], fun, fv),
1048 None => format!("lapply({}, {})", arg_strs[*i], fun),
1049 }
1050 }
1051 many => {
1052 let first = &arg_strs[many[0]];
1053 let inner_args = arg_strs
1054 .iter()
1055 .enumerate()
1056 .map(|(j, s)| {
1057 if many.contains(&j) {
1058 format!("{}[[.typr_i]]", s)
1059 } else {
1060 s.clone()
1061 }
1062 })
1063 .collect::<Vec<_>>()
1064 .join(", ");
1065 let fun = format!("function(.typr_i) {}({})", new_name, inner_args);
1066 match fun_value {
1067 Some(fv) => {
1068 format!("vapply(seq_along({}), {}, {}, USE.NAMES = FALSE)", first, fun, fv)
1069 }
1070 None => format!("lapply(seq_along({}), {})", first, fun),
1071 }
1072 }
1073 };
1074 (code, current_cont)
1075 } else {
1076 let (exp_str, cont1) = exp.to_r(cont);
1077 let new_vals = match cont1
1082 .get_type_from_variable(&var)
1083 .ok()
1084 .and_then(|t| FunctionType::try_from(t).ok())
1085 {
1086 Some(fn_t) => {
1087 let new_args = fn_t
1088 .get_param_types()
1089 .iter()
1090 .map(|arg| reduce_type(&cont1, arg))
1091 .collect::<Vec<_>>();
1092 vals.iter()
1093 .zip(new_args.iter())
1094 .map(set_related_type_if_variable)
1095 .collect::<Vec<_>>()
1096 }
1097 None => vals.clone(),
1098 };
1099 let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
1100 Var::from_language(*exp.clone())
1101 .map(|var| {
1102 let name = var.get_name();
1103 let new_name = if &name[0..1] == "%" {
1104 format!("`{}`", name.replace("__", "."))
1105 } else {
1106 name.replace("__", ".")
1107 };
1108 (format!("{}({})", new_name, args), current_cont.clone())
1109 })
1110 .unwrap_or((format!("{}({})", exp_str, args), current_cont))
1111 }
1112 } else if name == "reduce" {
1113 (format!("vec_reduce({})", str_vals), cont.clone())
1114 } else if name == "extend" {
1115 (format!("vec_extend({})", str_vals), cont.clone())
1116 } else if cont.is_an_untyped_function(&name) {
1117 let name = name.replace("__", ".");
1118 let new_name = if &name[0..1] == "%" {
1119 format!("`{}`", name)
1120 } else {
1121 name.to_string()
1122 };
1123 let s = format!("vec_apply({}, {})", new_name, str_vals);
1124 (s, cont.clone())
1125 } else {
1126 let (exp_str, cont1) = exp.to_r(cont);
1127 let new_vals = match cont1
1130 .get_type_from_variable(&var)
1131 .ok()
1132 .and_then(|t| FunctionType::try_from(t).ok())
1133 {
1134 Some(fn_t) => {
1135 let new_args = fn_t
1136 .get_param_types()
1137 .iter()
1138 .map(|arg| reduce_type(&cont1, arg))
1139 .collect::<Vec<_>>();
1140 vals.iter()
1141 .zip(new_args.iter())
1142 .map(set_related_type_if_variable)
1143 .collect::<Vec<_>>()
1144 }
1145 None => vals.clone(),
1146 };
1147 let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
1148 Var::from_language(*exp.clone())
1149 .map(|var| {
1150 let name = var.get_name();
1151 let new_name = if &name[0..1] == "%" {
1152 format!("`{}`", name.replace("__", "."))
1153 } else {
1154 name.replace("__", ".")
1155 };
1156 (format!("vec_apply({}, {})", new_name, args), current_cont.clone())
1157 })
1158 .unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont))
1159 }
1160 }
1161 Lang::ArrayIndexing {
1162 identifier: exp,
1163 indexing: val,
1164 ..
1165 } => {
1166 let (exp_str, _) = exp.to_r(cont);
1167 let negative_idx = val.get_members_if_array().and_then(|members| {
1169 if members.len() == 1 {
1170 if let Lang::Integer { value: i, .. } = &members[0] {
1171 if *i < 0 {
1172 Some(*i)
1173 } else {
1174 None
1175 }
1176 } else {
1177 None
1178 }
1179 } else {
1180 None
1181 }
1182 });
1183 let res = if let Some(neg) = negative_idx {
1184 let offset = 1 + neg; if offset == 0 {
1186 format!("{}[[length({})]]", exp_str, exp_str)
1187 } else if offset < 0 {
1188 format!("{}[[length({}) - {}L]]", exp_str, exp_str, -offset)
1189 } else {
1190 format!("{}[[length({}) + {}L]]", exp_str, exp_str, offset)
1191 }
1192 } else {
1193 let (val_str, _) = val.to_simple_r(cont);
1194 format!("{}[[{}]]", exp_str, val_str)
1195 };
1196 (res, cont.clone())
1197 }
1198 Lang::GenFunc { name: func, .. } => (format!("function(x, ...) UseMethod('{}')", func), cont.clone()),
1199 Lang::Let {
1200 variable: expr,
1201 r#type: ttype,
1202 expression: body,
1203 is_public: _,
1204 is_testable: _,
1205 is_export,
1206 help_data,
1207 } => {
1208 let (body_str, new_cont) = body.to_r(cont);
1209 let body_str = if ttype.is_empty() {
1216 body_str
1217 } else {
1218 let what = format!("let {}", Var::try_from(expr).map(|v| v.get_name()).unwrap_or_default());
1219 checked_assertions::wrap_checked(&new_cont, body_str, ttype, help_data, &what)
1220 };
1221 let new_name = format_backtick(expr.clone().to_r(cont).0);
1222
1223 let (r_code, _new_name2) = Function::try_from((**body).clone())
1224 .map(|_| {
1225 let related_type = Var::try_from(expr)
1226 .ok()
1227 .map(|v| v.get_type())
1228 .filter(|t| !matches!(t, Type::Empty(_) | Type::UnknownFunction(_)))
1229 .unwrap_or_else(|| typing(cont, expr).value);
1230 let method = match cont.get_environment() {
1231 Environment::Project => format!(
1232 "#' @method {}\n",
1233 new_name.replace(".", " ").replace("`", "")
1234 ),
1235 _ => "".to_string(),
1236 };
1237 match related_type {
1238 Type::Empty(_) => {
1239 (format!("{} <- {}", new_name, body_str), new_name.clone())
1240 }
1241 Type::Any(_) => (
1250 format!(
1251 "{} <- {}",
1252 format_backtick(format!(
1253 "{}.default",
1254 new_name.trim_matches('`')
1255 )),
1256 body_str
1257 ),
1258 new_name.clone(),
1259 ),
1260 _ => {
1261 let mut code = format!("{}{} <- {}", method, new_name, body_str);
1262 let suffix = cont.get_class_unquoted(&related_type);
1286 let is_foreign_dispatch = matches!(&related_type, Type::Alias(name, _, _, _) if cont.resolves_to_foreign_alias(name));
1287 if suffix != "default"
1288 && (is_foreign_dispatch
1289 || (facets::interface_facet(cont, &related_type).is_some()
1290 && facets::record_facet(cont, &related_type).is_none()))
1291 {
1292 if let Some(base) = new_name
1296 .trim_matches('`')
1297 .strip_suffix(&format!(".{}", suffix))
1298 {
1299 let default_method = match cont.get_environment() {
1300 Environment::Project => {
1301 format!("#' @method {} default\n", base)
1302 }
1303 _ => "".to_string(),
1304 };
1305 code = format!(
1306 "{}\n{}{} <- {}",
1307 code,
1308 default_method,
1309 format_backtick(format!("{}.default", base)),
1310 new_name
1311 );
1312 }
1313 }
1314 (code, new_name.clone())
1315 }
1316 }
1317 })
1318 .unwrap_or((format!("{} <- {}", new_name, body_str), new_name));
1319 let code = if !ttype.is_empty() {
1320 let type_annotation = new_cont.get_type_anotation(ttype);
1321 format!("{} |> {}\n", r_code, type_annotation)
1322 } else {
1323 r_code + "\n"
1324 };
1325 let code = if *is_export {
1327 format!("#' @export\n{}", code)
1328 } else {
1329 code
1330 };
1331 (code, new_cont)
1332 }
1333 Lang::Array { .. } => {
1334 let typ = self.typing(cont).value;
1335 let array = array_literal_raw(self, cont);
1336 (
1337 format!("{} |> {}", array, cont.get_type_anotation(&typ)),
1338 cont.to_owned(),
1339 )
1340 }
1341 Lang::List {
1342 value: args, spreads, ..
1343 } if spreads.is_empty() => {
1344 let (body, current_cont) = Translatable::from(cont.clone()).join_arg_val(args, ",\n ").into();
1345 let (typ, _, _) = typing(cont, self).to_tuple();
1346 if let Type::Alias(alias_name, _, _, _) = &typ {
1348 let is_record = cont
1349 .aliases()
1350 .find(|(var, _)| var.get_name() == *alias_name)
1351 .map(|(_, t)| matches!(t, Type::Record(_, _)))
1352 .unwrap_or(false);
1353 if is_record {
1354 return (format!("{}({})", alias_name, body), current_cont);
1355 }
1356 }
1357 let anotation = cont.get_type_anotation(&typ);
1358 cont.get_classes(&typ)
1359 .map(|_| format!("list({}) |> {}", body, anotation))
1360 .unwrap_or(format!("list({}) |> {}", body, anotation))
1361 .to_some()
1362 .map(|s| (s, current_cont))
1363 .unwrap()
1364 }
1365 Lang::List {
1372 value: args, spreads, ..
1373 } => {
1374 let mut spreads_iter = spreads.iter();
1375 let first = spreads_iter.next().expect("checked non-empty above");
1376 let (mut base, mut current_cont) = first.to_r(cont);
1377 for spread_expr in spreads_iter {
1378 let (next, next_cont) = spread_expr.to_r(¤t_cont);
1379 base = format!("spread({}, {})", base, next);
1380 current_cont = next_cont;
1381 }
1382 if args.is_empty() {
1383 return (base, current_cont);
1384 }
1385 let (overrides, current_cont) = Translatable::from(current_cont).join_arg_val(args, ", ").into();
1386 (format!("spread({}, list({}))", base, overrides), current_cont)
1387 }
1388 Lang::DataFrame { value: args, .. } => {
1389 let (body, current_cont) = Translatable::from(cont.clone()).join_arg_val(args, ",\n ").into();
1390 let (typ, _, _) = typing(cont, self).to_tuple();
1391 let anotation = cont.get_type_anotation(&typ);
1392 cont.get_classes(&typ)
1393 .map(|_| format!("data.frame({}) |> {}", body, anotation))
1394 .unwrap_or(format!("data.frame({}) |> {}", body, anotation))
1395 .to_some()
1396 .map(|s| (s, current_cont))
1397 .unwrap()
1398 }
1399 Lang::If {
1400 condition: cond,
1401 if_block: exp,
1402 else_block: els,
1403 ..
1404 } if els == &Box::new(Lang::Empty(HelpData::default())) => Translatable::from(cont.clone())
1405 .add("if(")
1406 .to_r(cond)
1407 .add(") {\n")
1408 .to_r(exp)
1409 .add(" \n}")
1410 .into(),
1411 Lang::If {
1412 condition: cond,
1413 if_block: exp,
1414 else_block: els,
1415 help_data: _,
1416 } => Translatable::from(cont.clone())
1417 .add("if(")
1418 .to_r(cond)
1419 .add(") {\n")
1420 .to_r(exp)
1421 .add(" \n} else ")
1422 .to_r(els)
1423 .into(),
1424 Lang::Tuple { value: vals, .. } => {
1425 let typ = self.typing(cont).value;
1430 let (body, current_cont): (String, Context) = Translatable::from(cont.clone())
1431 .add("struct(list(")
1432 .join(vals, ", ")
1433 .add("), 'Tuple')")
1434 .into();
1435 (format!("{} |> {}", body, cont.get_type_anotation(&typ)), current_cont)
1436 }
1437 Lang::Assign {
1438 identifier: var,
1439 expression: exp,
1440 ..
1441 } => Translatable::from(cont.clone()).to_r(var).add(" <- ").to_r(exp).into(),
1442 Lang::Comment { value: txt, .. } => ("#".to_string() + txt, cont.clone()),
1443 Lang::Tag { name: s, value: t, .. } => {
1444 let (t_str, new_cont) = t.to_r(cont);
1445 let is_empty = matches!(t.as_ref(), Lang::Empty(_));
1446 let class = match find_union_for_tag(s, cont) {
1453 Some(union_name) => format!("c('{}', '{}', 'Tag', 'list')", s, union_name),
1454 None => format!("c('{}', 'Tag', 'list')", s),
1455 };
1456 let value = if is_empty {
1457 format!("structure(list('{}'), class = {})", s, class)
1458 } else {
1459 format!("structure(list('{}', body = {}), class = {})", s, t_str, class)
1460 };
1461 (value, new_cont)
1462 }
1463 Lang::Null(_) => ("NULL".to_string(), cont.clone()),
1464 Lang::Empty(_) => ("NA".to_string(), cont.clone()),
1465 Lang::Lines { value: exps, .. } => Translatable::from(cont.clone()).join(exps, "\n").into(),
1466 Lang::Return { value: exp, .. } => Translatable::from(cont.clone())
1470 .add("return(")
1471 .to_r(exp)
1472 .add(")")
1473 .into(),
1474 Lang::Lambda {
1475 parameters: params,
1476 body: bloc,
1477 ..
1478 } => {
1479 let param_names: Vec<String> = params
1480 .iter()
1481 .map(|p: &Lang| match p {
1482 Lang::Variable { name, .. } => name.clone(),
1483 _ => "x".to_string(),
1484 })
1485 .collect();
1486 (
1487 format!("function({}) {{ {} }}", param_names.join(", "), bloc.to_r(cont).0),
1488 cont.clone(),
1489 )
1490 }
1491 Lang::VecBlock { value: bloc, .. } => (bloc.to_string(), cont.clone()),
1492 Lang::RBlock { value: bloc, .. } => (bloc.to_string(), cont.clone()),
1493 Lang::Library { value: name, .. } => (format!("library({})", name), cont.clone()),
1494 Lang::Match {
1495 target: exp, branches, ..
1496 } => (
1497 to_pattern_match_statement((**exp).clone(), branches, cont),
1498 cont.clone(),
1499 ),
1500 Lang::Exp { value: exp, .. } => (exp.clone(), cont.clone()),
1501 Lang::ForLoop {
1502 identifier: var,
1503 expression: iterator,
1504 body,
1505 ..
1506 } => Translatable::from(cont.clone())
1507 .add("for (")
1508 .to_r_safe(var)
1509 .add(" in ")
1510 .to_r_safe(iterator)
1511 .add(") {\n")
1512 .to_r_safe(body)
1513 .add("\n}")
1514 .into(),
1515 Lang::RFunction {
1516 parameters: vars, body, ..
1517 } => Translatable::from(cont.clone())
1518 .add("function (")
1519 .join(vars, ", ")
1520 .add(") \n")
1521 .add(body)
1522 .add("\n")
1523 .into(),
1524 Lang::ExternBlock {
1525 parameters: params,
1526 body,
1527 ..
1528 } => {
1529 let param_names = params.iter().map(|p| p.to_r(cont)).collect::<Vec<_>>().join(", ");
1530 (format!("function({}) {{\n{}\n}}", param_names, body), cont.clone())
1531 }
1532 Lang::Signature { .. } => ("".to_string(), cont.clone()),
1533 Lang::TypeConstructor { .. } => ("".to_string(), cont.clone()),
1534 Lang::Alias {
1535 identifier: ident,
1536 target_type: typ,
1537 is_export,
1538 ..
1539 } => {
1540 let name = Var::from_language(*ident.clone())
1541 .map(|v| v.get_name())
1542 .unwrap_or_default();
1543 let typ_for_dispatch: Type = match typ {
1561 Type::Operator(TypeOperator::Intersection, _, _, h) => facets::record_facet(cont, typ)
1562 .map(|fields| Type::Record(fields, h.clone()))
1563 .unwrap_or_else(|| typ.clone()),
1564 _ => typ.clone(),
1565 };
1566 let (alias_code, alias_cont) = match &typ_for_dispatch {
1567 Type::Record(fields, _) => {
1568 let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1569 sorted_fields.sort_by_key(|f| f.get_argument_str());
1570 let params = sorted_fields
1571 .iter()
1572 .map(|f| f.get_argument_str())
1573 .collect::<Vec<_>>()
1574 .join(", ");
1575 let explicit_lines = sorted_fields
1581 .iter()
1582 .map(|f| {
1583 let n = f.get_argument_str();
1584 format!(" if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1585 })
1586 .collect::<Vec<_>>()
1587 .join("\n");
1588 let constructor = format!(
1593 "{name} <- function({params}, .spread = NULL) {{\n explicit <- list()\n{explicit_lines}\n x <- typr_spread_record(explicit, .spread)\n as.{name}(x)\n}}"
1594 );
1595 let mut seen_names: std::collections::HashSet<String> = std::collections::HashSet::new();
1610 let candidates: Vec<(String, Type)> = cont
1611 .aliases()
1612 .map(|(var, typ)| (var.get_name(), typ.clone()))
1613 .chain(cont.record_aliases.iter().cloned())
1614 .filter(|(other_name, _)| seen_names.insert(other_name.clone()))
1615 .collect();
1616 let mut supertype_entries: Vec<(String, usize)> = candidates
1617 .into_iter()
1618 .filter_map(|(other_name, typ)| {
1619 if other_name == name {
1620 return None;
1621 }
1622 if let Type::Record(other_fields, _) = typ {
1623 if fields.is_superset(&other_fields) && other_fields != *fields {
1624 Some((other_name, other_fields.len()))
1625 } else {
1626 None
1627 }
1628 } else {
1629 None
1630 }
1631 })
1632 .collect();
1633 supertype_entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
1636 let self_alias = Type::Alias(name.clone(), vec![], false, HelpData::default());
1647 let mut seen_ifaces: std::collections::HashSet<String> = std::collections::HashSet::new();
1648 let mut interface_entries: Vec<(String, usize)> = cont
1649 .aliases()
1650 .filter(|(var, _)| var.get_name() != name)
1651 .filter(|(_, alias_typ)| !alias_typ.has_generic())
1652 .filter_map(|(var, alias_typ)| {
1653 let methods = facets::interface_facet(cont, alias_typ)?;
1654 (seen_ifaces.insert(var.get_name()) && self_alias.is_subtype_raw(alias_typ, cont))
1655 .then(|| (var.get_name(), methods.len()))
1656 })
1657 .collect();
1658 interface_entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
1660 let all_super_names: Vec<String> = supertype_entries
1664 .iter()
1665 .chain(interface_entries.iter())
1666 .map(|(n, _)| format!("\"{n}\""))
1667 .collect();
1668 let supertype_class_str = if all_super_names.is_empty() {
1669 String::new()
1670 } else {
1671 format!(", {}", all_super_names.join(", "))
1672 };
1673 let annotator = format!(
1677 "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}}"
1678 );
1679 let fields_quoted = sorted_fields
1680 .iter()
1681 .map(|f| format!("\"{}\"", f.get_argument_str()))
1682 .collect::<Vec<_>>()
1683 .join(", ");
1684 let field_checks = sorted_fields
1688 .iter()
1689 .filter_map(|f| {
1690 let n = f.get_argument_str();
1691 record_field_class(&f.body_type(), cont).map(|cls| {
1692 format!(
1693 " if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: field '{n}' must be of class {cls}\")"
1694 )
1695 })
1696 })
1697 .collect::<Vec<_>>()
1698 .join("\n");
1699 let field_checks_block = if field_checks.is_empty() {
1700 String::new()
1701 } else {
1702 format!("{field_checks}\n")
1703 };
1704 let validator = format!(
1708 "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}}"
1709 );
1710 (format!("{constructor}\n{annotator}\n{validator}"), cont.clone())
1711 }
1712 Type::Vec(VecType::DataFrame, size, fields_type, _)
1719 if matches!(fields_type.as_ref(), Type::Record(_, _)) =>
1720 {
1721 use crate::components::r#type::tint::Tint;
1722 let fields = match fields_type.as_ref() {
1723 Type::Record(fields, _) => fields,
1724 _ => unreachable!(),
1725 };
1726 let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1727 sorted_fields.sort_by_key(|f| f.get_argument_str());
1728 let params = sorted_fields
1729 .iter()
1730 .map(|f| f.get_argument_str())
1731 .collect::<Vec<_>>()
1732 .join(", ");
1733 let explicit_lines = sorted_fields
1734 .iter()
1735 .map(|f| {
1736 let n = f.get_argument_str();
1737 format!(" if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1738 })
1739 .collect::<Vec<_>>()
1740 .join("\n");
1741 let constructor = format!(
1745 "{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}}"
1746 );
1747 let annotator = format!(
1750 "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}}"
1751 );
1752 let fields_quoted = sorted_fields
1753 .iter()
1754 .map(|f| format!("\"{}\"", f.get_argument_str()))
1755 .collect::<Vec<_>>()
1756 .join(", ");
1757 let field_checks = sorted_fields
1760 .iter()
1761 .filter_map(|f| {
1762 let n = f.get_argument_str();
1763 record_field_class(&f.body_type(), cont).map(|cls| {
1764 format!(
1765 " if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: column '{n}' must be of class {cls}\")"
1766 )
1767 })
1768 })
1769 .collect::<Vec<_>>()
1770 .join("\n");
1771 let field_checks_block = if field_checks.is_empty() {
1772 String::new()
1773 } else {
1774 format!("{field_checks}\n")
1775 };
1776 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1780 format!(
1781 " if (nrow(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected {n} rows, got \", nrow(x)))\n"
1782 )
1783 } else {
1784 String::new()
1785 };
1786 let validator = format!(
1787 "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}}"
1788 );
1789 (format!("{constructor}\n{annotator}\n{validator}"), cont.clone())
1790 }
1791 Type::Vec(VecType::Vector, size, elem_type, _) => {
1799 use crate::components::r#type::tint::Tint;
1800 let constructor = format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1801 let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1802 format!(
1803 " if (!inherits(x, \"{cls}\")) stop(\"Validation failed for type {name}: expected vector of {cls}\")\n"
1804 )
1805 }).unwrap_or_default();
1806 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1807 format!(
1808 " if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1809 )
1810 } else {
1811 String::new()
1812 };
1813 let validator = format!("validate_{name} <- function(x) {{\n{elem_check}{size_check} x\n}}");
1814 (format!("{constructor}\n{validator}"), cont.clone())
1815 }
1816 Type::Vec(VecType::S3, size, elem_type, _) | Type::Vec(VecType::Array, size, elem_type, _)
1834 if cont.atomic_array_elem(typ).is_some() =>
1835 {
1836 use crate::components::r#type::tint::Tint;
1837 let constructor = format!("{name} <- function(x) {{\n as.{name}(x)\n}}");
1838 let annotator = format!(
1839 "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}}"
1840 );
1841 let elem_check = match cont.atomic_array_elem(typ) {
1842 Some(Type::Integer(_, _)) => format!(
1843 " if (!(is.integer(x) || (is.numeric(x) && all(x == trunc(x))))) stop(\"Validation failed for type {name}: expected an integer vector\")\n"
1844 ),
1845 Some(Type::Char(_, _)) => format!(
1846 " if (!is.character(x)) stop(\"Validation failed for type {name}: expected a character vector\")\n"
1847 ),
1848 Some(Type::Boolean(_, _)) => format!(
1849 " if (!is.logical(x)) stop(\"Validation failed for type {name}: expected a logical vector\")\n"
1850 ),
1851 _ => format!(
1852 " if (!is.numeric(x)) stop(\"Validation failed for type {name}: expected a numeric vector\")\n"
1853 ),
1854 };
1855 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1856 format!(
1857 " if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1858 )
1859 } else {
1860 String::new()
1861 };
1862 let validator = format!("validate_{name} <- function(x) {{\n{elem_check}{size_check} x\n}}");
1863 (format!("{constructor}\n{annotator}\n{validator}"), cont.clone())
1864 }
1865 Type::Vec(VecType::S3, size, elem_type, _) | Type::Vec(VecType::Array, size, elem_type, _) => {
1866 use crate::components::r#type::tint::Tint;
1867 let constructor = format!(
1868 "{name} <- function(x) {{\n if (!inherits(x, \"typed_vec\")) x <- typed_vec(x)\n as.{name}(x)\n}}"
1869 );
1870 let annotator = format!(
1871 "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}}"
1872 );
1873 let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1874 format!(
1875 " if (!all(vapply(x$data, inherits, logical(1), \"{cls}\"))) stop(\"Validation failed for type {name}: expected elements of class {cls}\")\n"
1876 )
1877 }).unwrap_or_default();
1878 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1879 format!(
1880 " if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1881 )
1882 } else {
1883 String::new()
1884 };
1885 let validator = format!(
1886 "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}}"
1887 );
1888 (format!("{constructor}\n{annotator}\n{validator}"), cont.clone())
1889 }
1890 Type::Operator(_, _, _, _) => {
1891 let union_name = &name;
1895 let members = flatten_operator_union(typ);
1896 let mut members_vec: Vec<Type> = members.into_iter().collect();
1898 members_vec.sort_by_key(|t| t.pretty2());
1899 let constructors: Vec<String> = members_vec
1900 .iter()
1901 .filter_map(|member| match member {
1902 Type::Tag(variant_name, inner, _) => Some(tag_variant_pipeline(
1903 variant_name,
1904 union_name,
1905 inner.as_ref(),
1906 )),
1907 Type::Alias(alias_name, _, _, _) => {
1908 let record_fields = cont
1910 .aliases()
1911 .find(|(var, _)| var.get_name() == *alias_name)
1912 .and_then(|(_, t)| {
1913 if let Type::Record(fields, _) = t {
1914 Some(fields.clone())
1915 } else {
1916 None
1917 }
1918 });
1919 if let Some(fields) = record_fields {
1920 let mut sorted: Vec<&ArgumentType> =
1921 fields.iter().collect();
1922 sorted.sort_by_key(|f| f.get_argument_str());
1923 let params = sorted
1924 .iter()
1925 .map(|f| f.get_argument_str())
1926 .collect::<Vec<_>>()
1927 .join(", ");
1928 let field_args = sorted
1929 .iter()
1930 .map(|f| {
1931 let n = f.get_argument_str();
1932 format!("{n} = {n}")
1933 })
1934 .collect::<Vec<_>>()
1935 .join(", ");
1936 Some(format!(
1937 "{alias_name} <- function({params}) {{\n structure(list({field_args}), class = c(\"{alias_name}\", \"{union_name}\", \"list\"))\n}}"
1938 ))
1939 } else {
1940 None
1941 }
1942 }
1943 _ => None,
1944 })
1945 .collect();
1946 (constructors.join("\n"), cont.clone())
1947 }
1948 Type::Integer(tint, _) => {
1949 use crate::components::r#type::tint::Tint;
1950 let validator = match tint {
1951 Tint::Val(i) => format!(
1952 "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}}"
1953 ),
1954 Tint::Unknown => format!(
1955 "validate_{name} <- function(x) {{\n if (!is.integer(x)) stop(\"Validation failed for type {name}: expected int\")\n x\n}}"
1956 ),
1957 };
1958 let constructor = format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1959 (format!("{constructor}\n{validator}"), cont.clone())
1960 }
1961 Type::Char(tchar, _) => {
1962 use crate::components::r#type::tchar::Tchar;
1963 let validator = match tchar {
1964 Tchar::Val(s) => format!(
1965 "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}}"
1966 ),
1967 Tchar::Unknown => format!(
1968 "validate_{name} <- function(x) {{\n if (!is.character(x)) stop(\"Validation failed for type {name}: expected char\")\n x\n}}"
1969 ),
1970 };
1971 let constructor = format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1972 (format!("{constructor}\n{validator}"), cont.clone())
1973 }
1974 Type::Boolean(tbool, _) => {
1975 use crate::components::r#type::tbool::Tbool;
1976 let validator = match tbool {
1977 Tbool::Val(b) => {
1978 let r_val = if *b { "TRUE" } else { "FALSE" };
1979 format!(
1980 "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}}"
1981 )
1982 }
1983 Tbool::Unknown => format!(
1984 "validate_{name} <- function(x) {{\n if (!is.logical(x)) stop(\"Validation failed for type {name}: expected bool\")\n x\n}}"
1985 ),
1986 };
1987 let constructor = format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1988 (format!("{constructor}\n{validator}"), cont.clone())
1989 }
1990 Type::Number(tnum, _) => {
1991 use crate::components::r#type::tnumber::Tnum;
1992 let validator = match tnum {
1993 Tnum::Val(v) => format!(
1994 "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}}"
1995 ),
1996 Tnum::Unknown => format!(
1997 "validate_{name} <- function(x) {{\n if (!is.numeric(x)) stop(\"Validation failed for type {name}: expected num\")\n x\n}}"
1998 ),
1999 };
2000 let constructor = format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
2001 (format!("{constructor}\n{validator}"), cont.clone())
2002 }
2003 Type::Tag(tag_name, inner_type, _) => {
2004 let body_validation = tag_body_validation(&name, inner_type.as_ref());
2005 let validator = format!(
2006 "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}}"
2007 );
2008 (validator, cont.clone())
2009 }
2010 Type::Alias(target_name, ..) => (format!("{name} <- {target_name}"), cont.clone()),
2016 _ => ("".to_string(), cont.clone()),
2017 };
2018 let alias_code = if *is_export && !alias_code.is_empty() {
2023 format!("#' @export\n{}", alias_code)
2024 } else {
2025 alias_code
2026 };
2027 (alias_code, alias_cont)
2028 }
2029 Lang::UnionConstructor {
2030 variant_name, fields, ..
2031 } => {
2032 if fields.is_empty() {
2033 (format!("{}()", variant_name), cont.clone())
2034 } else {
2035 let (body, current_cont) = Translatable::from(cont.clone()).join_arg_val(fields, ", ").into();
2036 (format!("{}({})", variant_name, body), current_cont)
2037 }
2038 }
2039 Lang::KeyValue { key: k, value: v, .. } => (format!("{} = {}", k, v.to_r(cont).0), cont.clone()),
2040 Lang::Vector { value: vals, .. } => {
2041 let res = "c(".to_string()
2042 + &vals
2043 .iter()
2044 .map(|x: &Lang| x.to_r(cont).0)
2045 .collect::<Vec<_>>()
2046 .join(", ")
2047 + ")";
2048 (res, cont.to_owned())
2049 }
2050 Lang::Not { value: exp, .. } => (format!("!{}", exp.to_r(cont).0), cont.clone()),
2051 Lang::Sequence { body: vals, .. } => {
2052 let res = if !vals.is_empty() {
2053 "c(".to_string()
2054 + &vals
2055 .iter()
2056 .map(|x: &Lang| "list(".to_string() + &x.to_r(cont).0 + ")")
2057 .collect::<Vec<_>>()
2058 .join(", ")
2059 + ")"
2060 } else {
2061 "c(list())".to_string()
2062 };
2063 (res, cont.to_owned())
2064 }
2065 Lang::TestBlock {
2066 value: body,
2067 help_data: h,
2068 } => {
2069 let file_name = h
2070 .get_file_data()
2071 .map(|(name, _)| format!("test-{}", name))
2072 .unwrap_or_else(|| "test-unknown".to_string())
2073 .replace("TypR/", "")
2074 .replace(".ty", ".R");
2075
2076 let file_path = format!("tests/testthat/{}", file_name);
2077 let body_str = body.to_r(cont).0;
2078 let content = if cont.test_preamble.is_empty() {
2082 body_str
2083 } else {
2084 format!("{}\n{}", cont.test_preamble.join("\n"), body_str)
2085 };
2086
2087 let _ = write_output_file(&file_path, &content);
2088 ("".to_string(), cont.clone())
2089 }
2090 Lang::JSBlock(exp, _id, _h) => {
2091 let js_cont = Context::default(); let res = exp.to_js(&js_cont).0;
2093 (format!("'{}{}'", JS_HEADER, res), cont.clone())
2094 }
2095 Lang::WhileLoop { condition, body, .. } => (
2096 format!("while ({}) {{\n{}\n}}", condition.to_r(cont).0, body.to_r(cont).0),
2097 cont.clone(),
2098 ),
2099 Lang::Loop { body, .. } => (format!("while (TRUE) {{\n{}\n}}", body.to_r(cont).0), cont.clone()),
2100 Lang::Break(_) => ("break".to_string(), cont.clone()),
2101 Lang::Next(_) => ("next".to_string(), cont.clone()),
2102 Lang::NA(_) => ("NA".to_string(), cont.clone()),
2103 Lang::Module {
2104 name,
2105 body,
2106 module_position: position,
2107 config,
2108 ..
2109 } => {
2110 let name_str = if (name == "main") && (config.environment == Environment::Project) {
2111 "a_main"
2112 } else {
2113 name
2114 };
2115
2116 let writes_own_file =
2120 matches!(position, ModulePosition::External) && config.environment == Environment::Project;
2121 if writes_own_file {
2122 push_include_frame();
2123 push_import_from_frame();
2124 }
2125
2126 let mut inner_cont = if let Some(cached) = cont.get_module_inner_context(name) {
2130 let mut cached = cached.clone();
2139 cached.record_aliases = cont.record_aliases.clone();
2140 cached.subtypes = cont.subtypes.clone();
2141 cached.typing_context = cached.typing_context.clone().hoist_aliases(&cont.typing_context);
2147 cached
2148 } else {
2149 let module_expr = if body.len() > 1 {
2150 Lang::Lines {
2151 value: body.to_vec(),
2152 help_data: HelpData::default(),
2153 }
2154 } else {
2155 body.first().cloned().unwrap_or(Lang::Empty(HelpData::default()))
2156 };
2157 typing(&cont.clone().set_in_module_body(), &module_expr).context
2158 };
2159
2160 if cont.get_test_mode() {
2165 let preamble: Vec<String> = body
2166 .iter()
2167 .filter_map(|lang| match lang {
2168 Lang::Let {
2169 variable: var,
2170 is_testable: true,
2171 ..
2172 } => Var::from_language(*var.clone()).map(|v| {
2173 let raw = v.get_name();
2174 format!("{} <- {}$`.test_{}`", raw, name_str, raw)
2175 }),
2176 _ => None,
2177 })
2178 .collect();
2179 inner_cont = inner_cont.set_test_preamble(preamble);
2180 }
2181
2182 let (import_langs, runtime_langs): (Vec<_>, Vec<_>) = body.iter().partition(|lang| {
2187 matches!(
2188 lang,
2189 Lang::ModuleImport { .. }
2190 | Lang::ImportFrom { .. }
2191 | Lang::Module {
2192 module_position: ModulePosition::External,
2193 ..
2194 }
2195 )
2196 });
2197
2198 let imports_parts: Vec<String> = import_langs
2199 .iter()
2200 .map(|lang| lang.to_r(&inner_cont).0)
2201 .filter(|s| !s.is_empty())
2202 .collect();
2203 let imports_preamble = if imports_parts.is_empty() {
2204 String::new()
2205 } else {
2206 imports_parts.join("\n") + "\n"
2207 };
2208
2209 let body_content = runtime_langs
2210 .iter()
2211 .map(|lang| lang.to_r(&inner_cont).0)
2212 .collect::<Vec<_>>()
2213 .join("\n");
2214
2215 let mut exports: Vec<String> = Vec::new();
2217 let mut generics: Vec<String> = Vec::new();
2218 let mut generic_exports: Vec<String> = Vec::new();
2219 let mut package_exports: Vec<String> = Vec::new();
2221
2222 for lang in body.iter() {
2223 if let Lang::Let {
2224 variable: var,
2225 is_public: true,
2226 is_export,
2227 ..
2228 } = lang
2229 {
2230 if let Some(v) = Var::from_language(*var.clone()) {
2231 let raw_name = v.get_name();
2232 let typed_name = v.clone().display_type(&inner_cont).get_name();
2240
2241 exports.push(format!("{}${} <- {}", name_str, typed_name, typed_name));
2243
2244 if *is_export {
2246 package_exports
2247 .push(format!("#' @export\n{} <- {}${}", raw_name, name_str, typed_name));
2248 }
2249
2250 let var_type = v.get_type();
2252 if !var_type.is_empty() && typed_name != raw_name {
2253 let class_name = inner_cont.get_class_unquoted(&var_type);
2254 exports.push(format!(
2255 "registerS3method(\"{}\", \"{}\", {})",
2256 raw_name, class_name, typed_name
2257 ));
2258
2259 let generic_def =
2260 format!("{} <- function(x, ...) UseMethod(\"{}\")", raw_name, raw_name);
2261 if !generics.contains(&generic_def) {
2262 generics.push(generic_def);
2263 generic_exports.push(format!("{}${} <- {}", name_str, raw_name, raw_name));
2264 }
2265
2266 generic_exports.push(format!("{} <- {}${}", typed_name, name_str, typed_name));
2275
2276 let is_foreign_dispatch = matches!(&var_type, Type::Alias(alias_name, _, _, _) if inner_cont.resolves_to_foreign_alias(alias_name));
2286 if class_name != "default"
2287 && (is_foreign_dispatch
2288 || (facets::interface_facet(&inner_cont, &var_type).is_some()
2289 && facets::record_facet(&inner_cont, &var_type).is_none()))
2290 {
2291 exports.push(format!("{}${}.default <- {}.default", name_str, raw_name, raw_name));
2292 generic_exports
2293 .push(format!("{}.default <- {}${}.default", raw_name, name_str, raw_name));
2294 }
2295 }
2296 }
2297 }
2298 if let Lang::Let {
2305 variable: var,
2306 is_public: false,
2307 ..
2308 } = lang
2309 {
2310 if let Some(v) = Var::from_language(*var.clone()) {
2311 let raw_name = v.get_name();
2312 let typed_name = v.clone().display_type(&inner_cont).get_name();
2313 let var_type = v.get_type();
2314 if !var_type.is_empty() && typed_name != raw_name {
2315 let class_name = inner_cont.get_class_unquoted(&var_type);
2316 let generic_def =
2317 format!("{} <- function(x, ...) UseMethod(\"{}\")", raw_name, raw_name);
2318 if !generics.contains(&generic_def) && !exports.contains(&generic_def) {
2319 exports.push(generic_def);
2320 }
2321 exports.push(format!(
2322 "registerS3method(\"{}\", \"{}\", {})",
2323 raw_name, class_name, typed_name
2324 ));
2325 }
2326 }
2327 }
2328 if cont.get_test_mode() {
2332 if let Lang::Let {
2333 variable: var,
2334 is_testable: true,
2335 ..
2336 } = lang
2337 {
2338 if let Some(v) = Var::from_language(*var.clone()) {
2339 let raw_name = v.get_name();
2340 let typed_name = v.clone().display_type(&inner_cont).get_name();
2344 exports.push(format!("{}$`.test_{}` <- {}", name_str, raw_name, typed_name));
2345 }
2346 }
2347 }
2348 if let Lang::Alias {
2354 identifier: var,
2355 is_public: true,
2356 is_export,
2357 target_type,
2358 ..
2359 } = lang
2360 {
2361 if !target_type.is_interface() {
2362 if let Some(v) = Var::from_language(*var.clone()) {
2363 let alias_name = v.get_name();
2364 exports.push(format!("{}${} <- {}", name_str, alias_name, alias_name));
2365
2366 if *is_export {
2370 package_exports
2371 .push(format!("#' @export\n{} <- {}${}", alias_name, name_str, alias_name));
2372 }
2373 }
2374 }
2375 }
2376 }
2377
2378 let exports_str = if exports.is_empty() {
2379 String::new()
2380 } else {
2381 "\n".to_string() + &exports.join("\n")
2382 };
2383
2384 let generics_defs_str = if generics.is_empty() {
2385 String::new()
2386 } else {
2387 generics.join("\n") + "\n"
2388 };
2389
2390 let generic_exports_str = if generic_exports.is_empty() {
2391 String::new()
2392 } else {
2393 "\n".to_string() + &generic_exports.join("\n")
2394 };
2395
2396 let package_exports_str = if package_exports.is_empty() {
2397 String::new()
2398 } else {
2399 "\n".to_string() + &package_exports.join("\n")
2400 };
2401
2402 let content = format!(
2403 "{}{}{} <- new.env(parent = emptyenv())\nlocal({{\n{}{}\n}}){}{}",
2404 generics_defs_str,
2405 imports_preamble,
2406 name_str,
2407 body_content,
2408 exports_str,
2409 generic_exports_str,
2410 package_exports_str
2411 );
2412
2413 match (position, config.environment) {
2414 (ModulePosition::Internal, _) => (content, cont.clone()),
2415 (ModulePosition::External, Environment::Wasm) => {
2417 let file_path = format!("{}.R", name_str);
2418 let _ = write_output_file(&file_path, &content);
2419 (content, cont.clone())
2420 }
2421 (ModulePosition::External, Environment::StandAlone)
2422 | (ModulePosition::External, Environment::Repl) => {
2423 let file_path = format!("{}.R", name_str);
2424 let _ = write_output_file(&file_path, &content);
2425 (format!("source('{}')", file_path), cont.clone())
2426 }
2427 (ModulePosition::External, Environment::Project) => {
2428 let file_path = format!("R/{}.R", name_str);
2429 let nested = pop_include_frame();
2432 let nested_includes = nested
2433 .iter()
2434 .map(|f| format!("#' @include {}\n", f))
2435 .collect::<String>();
2436 let nested_imports = pop_import_from_frame()
2437 .iter()
2438 .map(|e| format!("#' @importFrom {}\n", e))
2439 .collect::<String>();
2440 let project_preamble =
2441 "#' @include std.R\n#' @include generic_functions.R\n#' @include types.R\n";
2442 let _ = write_output_file(
2443 &file_path,
2444 &format!("{}{}{}{}", project_preamble, nested_includes, nested_imports, content),
2445 );
2446 register_include(&format!("{}.R", name_str));
2449 (String::new(), cont.clone())
2450 }
2451 }
2452 }
2453 Lang::UseModule {
2454 module_path, selector, ..
2455 } => {
2456 use crate::components::language::use_lang::UseSelector;
2457
2458 let r_path = module_path.join("$");
2460
2461 let mod_type_opt = (|| {
2463 let root = cont.get_type_from_variable(&Var::from_name(&module_path[0])).ok()?;
2464 let mut current = root;
2465 for seg in module_path.iter().skip(1) {
2466 current = current.to_module_type().ok()?.get_type_from_name(seg).ok()?;
2467 }
2468 current.to_module_type().ok()
2469 })();
2470
2471 let bindings: Vec<String> = match selector {
2472 UseSelector::Wildcard => mod_type_opt
2473 .map(|mt| {
2474 mt.get_public_members()
2475 .iter()
2476 .map(|m| {
2477 let name = m.get_argument_str();
2478 format!("{} <- {}${}", name, r_path, name)
2479 })
2480 .collect()
2481 })
2482 .unwrap_or_default(),
2483 UseSelector::Items(items) => items
2484 .iter()
2485 .map(|item| {
2486 let local_name = item.alias.as_deref().unwrap_or(&item.name);
2487 format!("{} <- {}${}", local_name, r_path, item.name)
2488 })
2489 .collect(),
2490 };
2491
2492 (bindings.join("\n"), cont.clone())
2493 }
2494 Lang::ModuleImport { .. } => ("".to_string(), cont.clone()),
2495 Lang::ImportFrom { package, functions, .. } => {
2496 let entry = format!("{} {}", package, functions.join(" "));
2497 register_import_from(&entry);
2498 ("".to_string(), cont.clone())
2499 }
2500 Lang::ConstructorCall {
2506 type_name,
2507 fields,
2508 spreads,
2509 ..
2510 } if type_name == "Self" => {
2511 let base_typ = spreads
2512 .first()
2513 .map(|e| typing(cont, e).value)
2514 .unwrap_or_else(|| Type::Any(HelpData::default()));
2515 let resolved_name = match &base_typ {
2516 Type::Alias(alias_name, ..) => cont
2517 .aliases()
2518 .find(|(var, _)| var.get_name() == *alias_name)
2519 .map(|(_, t)| matches!(t, Type::Record(_, _)))
2520 .unwrap_or(false)
2521 .then(|| alias_name.clone()),
2522 _ => None,
2523 };
2524 match (resolved_name, spreads.first()) {
2525 (Some(name), Some(spread_expr)) => {
2526 let (spread_r, current_cont) = spread_expr.to_r(cont);
2530 let (body, current_cont) = if fields.is_empty() {
2531 (format!(".spread = {}", spread_r), current_cont)
2532 } else {
2533 let (overrides, next_cont) =
2534 Translatable::from(current_cont).join_arg_val(fields, ", ").into();
2535 (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2536 };
2537 (format!("{}({})", name, body), current_cont)
2538 }
2539 (None, Some(spread_expr)) => {
2540 let (base, current_cont) = spread_expr.to_r(cont);
2544 if fields.is_empty() {
2545 (base, current_cont)
2546 } else {
2547 let (overrides, current_cont) =
2548 Translatable::from(current_cont).join_arg_val(fields, ", ").into();
2549 (format!("spread({}, list({}))", base, overrides), current_cont)
2550 }
2551 }
2552 (_, None) => ("NULL".to_string(), cont.clone()),
2555 }
2556 }
2557 Lang::ConstructorCall {
2558 module_path,
2559 type_name,
2560 fields,
2561 spreads,
2562 ..
2563 } if !spreads.is_empty() => {
2564 let spread_expr = spreads.first().expect("checked non-empty above");
2570 let (spread_r, current_cont) = spread_expr.to_r(cont);
2571 let qualified = if module_path.is_empty() {
2572 type_name.clone()
2573 } else {
2574 format!("{}${}", module_path.join("$"), type_name)
2575 };
2576 let (body, current_cont) = if fields.is_empty() {
2577 (format!(".spread = {}", spread_r), current_cont)
2578 } else {
2579 let (overrides, next_cont) = Translatable::from(current_cont).join_arg_val(fields, ", ").into();
2580 (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2581 };
2582 (format!("{}({})", qualified, body), current_cont)
2583 }
2584 Lang::ConstructorCall {
2585 module_path,
2586 type_name,
2587 fields,
2588 spread,
2589 help_data: h,
2590 ..
2591 } => {
2592 let all_fields: Vec<ArgumentValue> = match spread {
2596 Some((spread_path, spread_var, _)) => {
2597 let resolved_alias = if module_path.is_empty() {
2598 cont.get_type_from_aliases(&Var::from_name(type_name))
2599 } else {
2600 resolve_module_member_type(cont, module_path, type_name)
2601 };
2602 let record_fields = resolved_alias.and_then(|t| match t.reduce(cont) {
2603 Type::Record(fields, _) => Some(fields),
2604 _ => None,
2605 });
2606 let receiver = {
2607 let qualifier = spread_path.split_first().map(|(first, rest)| {
2608 rest.iter()
2609 .fold(Var::from_name(first).to_language(), |acc, seg| Lang::Operator {
2610 operator: Op::Dollar(h.clone()),
2611 rhs: Box::new(acc),
2612 lhs: Box::new(Var::from_name(seg).to_language()),
2613 help_data: h.clone(),
2614 })
2615 });
2616 match qualifier {
2617 Some(qualifier) => Lang::Operator {
2618 operator: Op::Dollar(h.clone()),
2619 rhs: Box::new(qualifier),
2620 lhs: Box::new(Var::from_name(spread_var).to_language()),
2621 help_data: h.clone(),
2622 },
2623 None => Var::from_name(spread_var).to_language(),
2624 }
2625 };
2626 let provided: std::collections::HashSet<String> =
2627 fields.iter().map(|f| f.get_argument()).collect();
2628 let synthetic = record_fields
2629 .into_iter()
2630 .flatten()
2631 .filter(|rf| !provided.contains(&rf.get_argument_str()))
2632 .map(|rf| {
2633 let field_access = Lang::Operator {
2634 operator: Op::Dollar(h.clone()),
2635 rhs: Box::new(receiver.clone()),
2636 lhs: Box::new(Var::from_name(&rf.get_argument_str()).to_language()),
2637 help_data: h.clone(),
2638 };
2639 ArgumentValue(rf.get_argument_str(), field_access)
2640 });
2641 fields.iter().cloned().chain(synthetic).collect()
2642 }
2643 None => fields.clone(),
2644 };
2645 let (body, current_cont) = Translatable::from(cont.clone()).join_arg_val(&all_fields, ", ").into();
2646 let qualified = if module_path.is_empty() {
2647 type_name.clone()
2648 } else {
2649 format!("{}${}", module_path.join("$"), type_name)
2650 };
2651 (format!("{}({})", qualified, body), current_cont)
2652 }
2653 Lang::ArrayConstructorCall {
2654 type_name,
2655 elements,
2656 help_data: h,
2657 } => {
2658 let resolved_alias = cont
2659 .get_type_from_aliases(&Var::from_name(type_name))
2660 .map(|t| t.reduce(cont));
2661 let is_atomic_repr = match &resolved_alias {
2662 Some(Type::Vec(vt, _, _, _)) if vt.is_vector() => true,
2663 Some(t @ Type::Vec(_, _, _, _)) => cont.atomic_array_elem(t).is_some(),
2666 _ => false,
2667 };
2668 if is_atomic_repr {
2669 let inner = elements.iter().map(|el| el.to_r(cont).0).collect::<Vec<_>>().join(", ");
2674 (format!("{}(c({}))", type_name, inner), cont.clone())
2675 } else {
2676 let temp_array = Lang::Array {
2677 value: elements.clone(),
2678 help_data: h.clone(),
2679 };
2680 let typ = temp_array.typing(cont).value;
2681 let dimension = ArrayType::try_from(typ)
2682 .expect("array constructor call should have an array type")
2683 .get_shape()
2684 .map(|sha| format!("c({})", sha))
2685 .unwrap_or_else(|| "c(0)".to_string());
2686 let lin_array = temp_array
2687 .linearize_array()
2688 .iter()
2689 .map(|lang| lang.to_r(cont).0)
2690 .collect::<Vec<_>>()
2691 .join(", ");
2692 let inner = if lin_array.is_empty() {
2693 format!("typed_vec(dim = {})", dimension)
2694 } else {
2695 format!("typed_vec({}, dim = {})", lin_array, dimension)
2696 };
2697 (format!("{}({})", type_name, inner), cont.clone())
2698 }
2699 }
2700 Lang::Import { .. } | Lang::Test { .. } | Lang::Use { .. } => ("".to_string(), cont.clone()),
2701 Lang::PartialApp { .. } => {
2708 let typed = typing(cont, self).lang;
2709 if matches!(typed, Lang::PartialApp { .. }) {
2710 ("".to_string(), cont.clone())
2711 } else {
2712 typed.to_r(cont)
2713 }
2714 }
2715 Lang::ValidatingCast {
2716 expression,
2717 type_name,
2718 literal_type,
2719 ..
2720 } => {
2721 let expr_r = if matches!(expression.as_ref(), Lang::Array { .. }) {
2730 match literal_type.as_ref().and_then(|t| cont.atomic_array_elem(t)) {
2731 Some(elem) => atomic_array_literal(expression, cont, &elem),
2732 None => array_literal_raw(expression, cont),
2733 }
2734 } else {
2735 expression.to_r(cont).0
2736 };
2737 match literal_type {
2738 Some(t) => (format!("{} |> {}", expr_r, cont.get_type_anotation(t)), cont.clone()),
2743 None => (format!("validate_{}({})", type_name, expr_r), cont.clone()),
2744 }
2745 }
2746 _ => ("".to_string(), cont.clone()),
2747 };
2748
2749 let result = if cont.get_checked_mode() {
2756 if let Lang::ConstructorCall { type_name, .. } = self {
2757 let (r_code, r_cont) = result;
2758 let typ = self.typing(cont).value;
2759 let loc = self.get_help_data();
2760 let what = format!("constructor {}", type_name);
2761 let wrapped = checked_assertions::wrap_checked(&r_cont, r_code, &typ, &loc, &what);
2762 (wrapped, r_cont)
2763 } else {
2764 result
2765 }
2766 } else {
2767 result
2768 };
2769
2770 result
2771 }
2772}
2773
2774#[cfg(test)]
2775mod tests {
2776 use crate::components::context::config::{Config, Environment};
2777 use crate::components::context::Context;
2778 use crate::components::error_message::help_data::HelpData;
2779 use crate::components::language::{Lang, ModulePosition};
2780 use crate::processes::transpiling::translatable::RTranslatable;
2781 use crate::utils::fluent_parser::FluentParser;
2782
2783 #[test]
2784 fn test_escape_r_string() {
2785 use super::escape_r_string;
2786 assert_eq!(escape_r_string("hello"), r#""hello""#);
2787 assert_eq!(escape_r_string(r#"say "hi""#), r#""say \"hi\"""#);
2788 assert_eq!(escape_r_string(r"a\b"), r#""a\\b""#);
2789 assert_eq!(escape_r_string("line1\nline2"), r#""line1\nline2""#);
2790 }
2791
2792 fn transpile_program(stmts: &[&str]) -> String {
2798 use crate::processes::parsing::parse2;
2799 use crate::processes::type_checking::type_checker::TypeChecker;
2800 let tc = stmts.iter().fold(TypeChecker::new(Context::default()), |tc, s| {
2801 let code = parse2((*s).into()).unwrap();
2802 tc.typing_no_panic(&code)
2803 });
2804 assert!(!tc.has_errors(), "type errors: {:?}", tc.get_errors());
2805 tc.transpile()
2806 }
2807
2808 #[test]
2809 fn test_record_class_chain_includes_satisfied_interface() {
2810 let r = transpile_program(&[
2813 "type Viewable <- interface { view: (Self) -> char };",
2814 "type Point <- list { x: int };",
2815 "let view <- fn(p: Point): char { \"pt\" };",
2816 "let describe <- fn(v: Viewable): char { view(v) };",
2817 ]);
2818 assert!(
2819 r.contains("class(x) <- c(\"Point\", \"Viewable\", \"list\")"),
2820 "expected Viewable in Point's class chain, got: {r}"
2821 );
2822 }
2823
2824 #[test]
2825 fn test_pure_interface_param_function_emits_default_fallback() {
2826 let r = transpile_program(&[
2829 "type Incrementable <- interface { incr: (Self) -> Self };",
2830 "let incr <- fn(s: int): int { s + 1 };",
2831 "let double_up <- fn(i: Incrementable): Incrementable { i.incr() };",
2832 ]);
2833 assert!(
2834 r.contains("`double_up.default` <- `double_up.Incrementable`"),
2835 "expected .default fallback alias, got: {r}"
2836 );
2837 }
2838
2839 #[test]
2840 fn test_any_first_param_default_name_backticks_wrap_whole_name() {
2841 let r = transpile_program(&["let describe <- fn(x: Any): char { \"any\" };"]);
2847 assert!(
2848 r.contains("`describe.default` <-"),
2849 "expected backticks around the whole `name.default`, got: {r}"
2850 );
2851 assert!(
2852 !r.contains("`describe`.default"),
2853 "backticks must not wrap only the base name, got: {r}"
2854 );
2855 }
2856
2857 #[test]
2858 fn test_generic_function_own_type_has_no_self_cast() {
2859 let r_code = FluentParser::new()
2865 .push("let id <- fn(x: T): T { x };")
2866 .run()
2867 .get_r_code()
2868 .iter()
2869 .cloned()
2870 .collect::<Vec<_>>()
2871 .join("\n");
2872 assert!(
2873 !r_code.contains("as.Function"),
2874 "generic function must not reference an unemitted as.FunctionN self-cast: {}",
2875 r_code
2876 );
2877 }
2878
2879 #[test]
2880 fn test_module_foreign_dispatch_default_exported_outside_local() {
2881 let r_code = FluentParser::new()
2891 .push("type LmModel <- Foreign<Any>;")
2892 .run()
2893 .push("module Reporter { @pub let describe <- fn(x: LmModel): int { 1 }; };")
2894 .run()
2895 .get_r_code()
2896 .iter()
2897 .cloned()
2898 .collect::<Vec<_>>()
2899 .join("\n");
2900 assert!(
2901 r_code.contains("Reporter$describe.default <- describe.default"),
2902 "missing module-env .default export: {}",
2903 r_code
2904 );
2905 assert!(
2906 r_code.contains("describe.default <- Reporter$describe.default"),
2907 "missing top-level .default re-export: {}",
2908 r_code
2909 );
2910 }
2911
2912 #[test]
2913 fn test_mixed_intersection_alias_tags_satisfier_but_no_default() {
2914 let r = transpile_program(&[
2918 "type Combined <- list { y: int } & interface { show: (Self) -> char };",
2919 "type Widget <- list { y: int, label: char };",
2920 "let show <- fn(w: Widget): char { \"ws\" };",
2921 "let inspect <- fn(c: Combined): char { show(c) };",
2922 ]);
2923 assert!(
2924 r.contains("class(x) <- c(\"Widget\", \"Combined\", \"list\")"),
2925 "expected Combined in Widget's class chain, got: {r}"
2926 );
2927 assert!(
2928 !r.contains("inspect.default"),
2929 "mixed intersection param must not emit a .default fallback, got: {r}"
2930 );
2931 }
2932
2933 #[test]
2934 fn test_validating_cast_transpiles_to_validate_call() {
2935 let r_code = FluentParser::new()
2936 .push("type Person <- list { name: char, age: int };")
2937 .run()
2938 .check_transpiling("x as! Person");
2939 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2940 assert!(
2941 r_str.contains("validate_Person(x)"),
2942 "expected validate_Person(x), got: {}",
2943 r_str
2944 );
2945 }
2946
2947 #[test]
2948 fn test_validating_cast_type_is_alias() {
2949 let typ = FluentParser::new()
2950 .push("type Person <- list { name: char, age: int };")
2951 .run()
2952 .check_typing("x as! Person");
2953 assert!(
2954 typ.pretty2().contains("Person"),
2955 "expected Alias(Person), got: {}",
2956 typ.pretty2()
2957 );
2958 }
2959
2960 #[test]
2961 fn test_validating_cast_literal_array_type() {
2962 let r_code = FluentParser::new().check_transpiling("c() as! [Any, int]");
2965 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2966 assert!(
2967 r_str.contains("c() |> identity()"),
2968 "expected c() |> identity(), got: {}",
2969 r_str
2970 );
2971 }
2972
2973 #[test]
2974 fn test_validating_cast_literal_vec_and_array_keywords() {
2975 let r_code = FluentParser::new().check_transpiling("c() as! Vec[Any, int]");
2978 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2979 assert!(
2980 r_str.contains("c() |> identity()"),
2981 "expected c() |> identity(), got: {}",
2982 r_str
2983 );
2984 }
2985
2986 #[test]
2987 fn test_validating_cast_array_literal_single_annotation() {
2988 let r_code = FluentParser::new().check_transpiling("[] as! [Any, int]");
2994 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2995 assert!(
2996 r_str.contains("integer(0) |> identity()"),
2997 "expected integer(0) |> identity(), got: {}",
2998 r_str
2999 );
3000 assert!(
3001 !r_str.contains("as.Generic"),
3002 "no as.Generic() should be emitted, got: {}",
3003 r_str
3004 );
3005 }
3006
3007 #[test]
3008 fn test_validating_cast_in_constructor_field_registers_alias() {
3009 let r_code = FluentParser::new()
3014 .push("type Truc <- list { options: [Option] };")
3015 .run()
3016 .push("let new_truc <- Truc:{ options = [] as! [Option] };")
3017 .run()
3018 .get_r_code();
3019 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3020 assert!(
3021 r_str.contains("typed_vec(dim = c(0)) |> as.Array0()"),
3022 "expected the cast to resolve to as.Array0(), got: {}",
3023 r_str
3024 );
3025 assert!(
3026 !r_str.contains("as.Generic"),
3027 "no as.Generic() should be emitted, got: {}",
3028 r_str
3029 );
3030 }
3031
3032 #[test]
3033 fn test_validating_cast_literal_type_dedup() {
3034 let r_code = FluentParser::new()
3037 .push("let a <- c() as! [Any, int];")
3038 .run()
3039 .push("let b <- c() as! [Any, int];")
3040 .run()
3041 .get_r_code();
3042 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3043 assert_eq!(
3047 r_str.matches("|> identity()").count(),
3048 2,
3049 "expected both casts to emit identity(), got: {}",
3050 r_str
3051 );
3052 }
3053
3054 #[test]
3055 fn test_alias_record_generates_validator() {
3056 let r_code = FluentParser::new().check_transpiling("type Person <- list { name: char, age: int };");
3057 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3058 assert!(
3059 r_str.contains("validate_Person <- function(x)"),
3060 "expected validator function, got: {}",
3061 r_str
3062 );
3063 assert!(
3064 r_str.contains("required_fields"),
3065 "expected field validation, got: {}",
3066 r_str
3067 );
3068 }
3069
3070 #[test]
3071 fn test_record_kinded_generic_param_transpiles_without_panic() {
3072 let fp = FluentParser::new()
3079 .push("type Animator<%T> <- %T & list { extra: int };")
3080 .run()
3081 .push("let combine <- fn(target: %T): %T { let more <- :{ extra = 1 }; :{ ...target, ...more } };")
3082 .run();
3083 assert_eq!(fp.get_last_log(), "The logs are empty");
3084 let r_code = fp.get_r_code().iter().cloned().collect::<Vec<_>>().join("\n");
3085 assert!(
3086 r_code.contains("spread(target, more)"),
3087 "expected the spread merge to transpile, got:\n{}",
3088 r_code
3089 );
3090 }
3091
3092 #[test]
3093 fn test_generic_intersection_alias_generates_validator() {
3094 let r_code = FluentParser::new().check_transpiling("type Animator<%T> <- %T & list { animations: int };");
3103 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3104 assert!(
3105 r_str.contains("Animator <- function(animations, .spread = NULL)"),
3106 "expected a constructor for the concrete side's fields, got:\n{}",
3107 r_str
3108 );
3109 assert!(
3110 r_str.contains("as.Animator <- function(x)"),
3111 "expected an annotator, got:\n{}",
3112 r_str
3113 );
3114 assert!(
3115 r_str.contains("validate_Animator <- function(x)")
3116 && r_str.contains("required_fields <- c(\"animations\")"),
3117 "expected a validator checking the concrete field, got:\n{}",
3118 r_str
3119 );
3120 }
3121
3122 #[test]
3123 fn test_multi_record_intersection_alias_merges_all_fields_into_constructor() {
3124 let r_code = FluentParser::new()
3131 .push("type Alpha <- list { x: int };")
3132 .run()
3133 .push("type Beta <- list { y: char };")
3134 .run()
3135 .push("type Combo <- Alpha & Beta;")
3136 .run()
3137 .get_r_code();
3138 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3139 assert!(
3140 r_str.contains("Combo <- function(x, y, .spread = NULL)"),
3141 "expected a constructor merging fields from both Alpha and Beta, got:\n{}",
3142 r_str
3143 );
3144 assert!(
3145 r_str.contains("as.Combo <- function(x)"),
3146 "expected an annotator, got:\n{}",
3147 r_str
3148 );
3149 assert!(
3150 r_str.contains("validate_Combo <- function(x)") && r_str.contains("required_fields <- c(\"x\", \"y\")"),
3151 "expected a validator checking both merged fields, got:\n{}",
3152 r_str
3153 );
3154 }
3155
3156 #[test]
3157 fn test_external_module_project_generates_include() {
3158 use super::{reset_include_stack, take_main_includes};
3159 reset_include_stack();
3160 let module = Lang::Module {
3161 name: "MyModule".to_string(),
3162 body: vec![],
3163 module_position: ModulePosition::External,
3164 config: Config::default().set_environment(Environment::Project),
3165 help_data: HelpData::default(),
3166 };
3167 let context = Context::default().set_environment(Environment::Project);
3168 let (r_code, _) = module.to_r(&context);
3169 assert_eq!(r_code, "", "got: {}", r_code);
3172 let includes = take_main_includes();
3173 assert!(
3174 includes.contains(&"MyModule.R".to_string()),
3175 "expected MyModule.R to be registered, got: {:?}",
3176 includes
3177 );
3178 }
3179
3180 #[test]
3181 fn test_module_transpilation_with_pub() {
3182 let r_code = FluentParser::new()
3183 .push("module Math { let sq <- 2; @pub let pi <- 3; };")
3184 .run()
3185 .get_r_code()
3186 .iter()
3187 .cloned()
3188 .collect::<Vec<_>>()
3189 .join("\n");
3190 assert!(
3191 r_code.contains("Math <- new.env(parent = emptyenv())"),
3192 "missing env init: {}",
3193 r_code
3194 );
3195 assert!(r_code.contains("local({"), "missing local block: {}", r_code);
3196 assert!(r_code.contains("Math$pi <- pi"), "missing public export: {}", r_code);
3197 assert!(
3198 !r_code.contains("Math$sq"),
3199 "private member should not be exported: {}",
3200 r_code
3201 );
3202 }
3203
3204 #[test]
3205 fn test_partial_app_in_module_transpiles_to_closure() {
3206 let r_code = FluentParser::new()
3212 .push("module M { @pub type Truc <- list { truc: bool, ok: bool }; @pub let new_truc <- \\Truc:{ ok = true }; };")
3213 .run()
3214 .get_r_code()
3215 .iter()
3216 .cloned()
3217 .collect::<Vec<_>>()
3218 .join("\n");
3219 assert!(
3220 r_code.contains("function(truc) Truc("),
3221 "partial app should desugar to a single-hole closure: {}",
3222 r_code
3223 );
3224 assert!(
3225 !r_code.contains("`new_truc` <- \n"),
3226 "partial app must not transpile to an empty RHS: {}",
3227 r_code
3228 );
3229 }
3230
3231 #[test]
3232 fn test_fn_body_lifted_call_is_vapply_not_direct() {
3233 let r_code = FluentParser::new()
3240 .set_context(Context::default())
3241 .push("let clamp <- fn(i: int): int { if (i > 2) { 2 } else { i } };")
3242 .run()
3243 .push("let clamp_all <- fn(v: [#N, int]): [#N, int] { clamp(v) };")
3244 .run()
3245 .get_r_code()
3246 .iter()
3247 .cloned()
3248 .collect::<Vec<_>>()
3249 .join("\n");
3250 assert!(
3251 r_code.contains("vapply(v, function(.typr_x) clamp(.typr_x)"),
3252 "fn-body lifted call over a non-vectorizable callee must vapply: {}",
3253 r_code
3254 );
3255 }
3256
3257 #[test]
3258 fn test_fn_body_lifted_call_over_record_array_alias_uses_vec_apply() {
3259 let r_code = FluentParser::new()
3263 .set_context(Context::default())
3264 .push("type Todo <- list { name: char, done: bool };")
3265 .run()
3266 .push("type TodoList <- [Todo];")
3267 .run()
3268 .push("let check_if_name <- fn(self: Todo, name: char): Todo { self };")
3269 .run()
3270 .push("let check <- fn(self: TodoList, name: char): TodoList { check_if_name(self, name) };")
3271 .run()
3272 .get_r_code()
3273 .iter()
3274 .cloned()
3275 .collect::<Vec<_>>()
3276 .join("\n");
3277 assert!(
3278 r_code.contains("vec_apply(check_if_name, self, name)"),
3279 "fn-body lifted call over a record-array alias must vec_apply: {}",
3280 r_code
3281 );
3282 }
3283
3284 #[test]
3285 fn test_testable_member_hidden_in_normal_build() {
3286 let r_code = FluentParser::new()
3288 .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
3289 .run()
3290 .get_r_code()
3291 .iter()
3292 .cloned()
3293 .collect::<Vec<_>>()
3294 .join("\n");
3295 assert!(
3296 !r_code.contains(".test_sq"),
3297 "testable member must not be exposed in a normal build: {}",
3298 r_code
3299 );
3300 }
3301
3302 #[test]
3303 fn test_testable_member_exposed_in_test_build() {
3304 let r_code = FluentParser::new()
3306 .set_context(Context::empty().set_test_mode(true))
3307 .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
3308 .run()
3309 .get_r_code()
3310 .iter()
3311 .cloned()
3312 .collect::<Vec<_>>()
3313 .join("\n");
3314 assert!(
3315 r_code.contains("Math$`.test_sq` <- sq"),
3316 "testable member must be exposed in a test build: {}",
3317 r_code
3318 );
3319 }
3320
3321 #[test]
3322 fn test_module_transpilation_no_pub() {
3323 let r_code = FluentParser::new()
3324 .push("module Empty { let x <- 1; };")
3325 .run()
3326 .get_r_code()
3327 .iter()
3328 .cloned()
3329 .collect::<Vec<_>>()
3330 .join("\n");
3331 assert!(
3332 r_code.contains("Empty <- new.env(parent = emptyenv())"),
3333 "missing env init: {}",
3334 r_code
3335 );
3336 assert!(r_code.contains("local({"), "missing local block: {}", r_code);
3337 assert!(
3338 !r_code.contains("Empty$x"),
3339 "private member should not be exported: {}",
3340 r_code
3341 );
3342 }
3343
3344 #[test]
3345 fn test_module_s3_registration_for_typed_pub_fn() {
3346 let r_code = FluentParser::new()
3347 .push("module Math { @pub let double <- fn(x: Integer): Integer { x }; };")
3348 .run()
3349 .get_r_code()
3350 .iter()
3351 .cloned()
3352 .collect::<Vec<_>>()
3353 .join("\n");
3354 assert!(
3355 r_code.contains("Math <- new.env(parent = emptyenv())"),
3356 "missing env init: {}",
3357 r_code
3358 );
3359 assert!(
3360 r_code.contains("registerS3method(\"double\", \"integer\", double.integer)"),
3361 "missing S3 registration: {}",
3362 r_code
3363 );
3364 assert!(
3365 r_code.contains("double <- function(x, ...) UseMethod(\"double\")"),
3366 "missing generic: {}",
3367 r_code
3368 );
3369 assert!(
3370 r_code.contains("Math$double <- double"),
3371 "missing generic export: {}",
3372 r_code
3373 );
3374 }
3375
3376 #[test]
3377 fn test_module_no_trailing_semicolon() {
3378 let r_code = FluentParser::new()
3379 .push("module Geo { @pub let pi <- 3; }")
3380 .run()
3381 .get_r_code()
3382 .iter()
3383 .cloned()
3384 .collect::<Vec<_>>()
3385 .join("\n");
3386 assert!(
3387 r_code.contains("Geo <- new.env(parent = emptyenv())"),
3388 "missing env init: {}",
3389 r_code
3390 );
3391 assert!(r_code.contains("Geo$pi <- pi"), "missing public export: {}", r_code);
3392 }
3393
3394 #[test]
3395 fn test_import_module() {
3396 let r_code = FluentParser::new()
3397 .push("module Math { @pub let pi <- 3; }")
3398 .push("import Math")
3399 .run()
3400 .run()
3401 .get_r_code()
3402 .iter()
3403 .cloned()
3404 .collect::<Vec<_>>()
3405 .join("\n");
3406 assert!(
3407 r_code.contains("Math <- new.env(parent = emptyenv())"),
3408 "missing env init: {}",
3409 r_code
3410 );
3411 }
3412
3413 #[test]
3414 fn test_import_module_as_alias() {
3415 let r_code = FluentParser::new()
3416 .push("module Math { @pub let pi <- 3; }")
3417 .push("import Math as Maths")
3418 .run()
3419 .run()
3420 .get_r_code()
3421 .iter()
3422 .cloned()
3423 .collect::<Vec<_>>()
3424 .join("\n");
3425 assert!(
3426 r_code.contains("Math <- new.env(parent = emptyenv())"),
3427 "missing env init: {}",
3428 r_code
3429 );
3430 assert!(
3431 r_code.contains("Maths <- Math") || r_code.contains("`Maths` <- Math"),
3432 "missing alias assignment: {}",
3433 r_code
3434 );
3435 }
3436
3437 #[test]
3438 fn test_use_items_transpiles() {
3439 let r_code = FluentParser::new()
3440 .push("module Math { @pub let pi <- 3; @pub let e <- 2; };")
3441 .push("use Math::{pi, e as euler};")
3442 .run()
3443 .run()
3444 .get_r_code()
3445 .iter()
3446 .cloned()
3447 .collect::<Vec<_>>()
3448 .join("\n");
3449 assert!(r_code.contains("pi <- Math$pi"), "missing pi binding: {}", r_code);
3450 assert!(r_code.contains("euler <- Math$e"), "missing euler binding: {}", r_code);
3451 }
3452
3453 #[test]
3454 fn test_use_wildcard_transpiles() {
3455 let r_code = FluentParser::new()
3456 .push("module Math { @pub let pi <- 3; @pub let e <- 2; let secret <- 0; };")
3457 .push("use Math::*;")
3458 .run()
3459 .run()
3460 .get_r_code()
3461 .iter()
3462 .cloned()
3463 .collect::<Vec<_>>()
3464 .join("\n");
3465 assert!(r_code.contains("pi <- Math$pi"), "missing pi binding: {}", r_code);
3466 assert!(r_code.contains("e <- Math$e"), "missing e binding: {}", r_code);
3467 assert!(
3468 !r_code.contains("secret <- Math$secret"),
3469 "private member must not be imported: {}",
3470 r_code
3471 );
3472 }
3473
3474 #[test]
3477 fn test_export_at_top_level_prepends_roxygen_tag() {
3478 let r_code = FluentParser::new()
3479 .push("@export let answer <- 42;")
3480 .run()
3481 .get_r_code()
3482 .iter()
3483 .cloned()
3484 .collect::<Vec<_>>()
3485 .join("\n");
3486 assert!(r_code.contains("#' @export"), "missing #' @export tag: {}", r_code);
3487 assert!(r_code.contains("answer"), "missing assignment: {}", r_code);
3488 }
3489
3490 #[test]
3491 fn test_export_type_alias_prepends_roxygen_tag_on_constructor() {
3492 let r_code = FluentParser::new()
3493 .push("@export type Point <- list { x: int, y: int };")
3494 .run()
3495 .get_r_code()
3496 .iter()
3497 .cloned()
3498 .collect::<Vec<_>>()
3499 .join("\n");
3500 assert!(r_code.contains("#' @export"), "missing #' @export tag: {}", r_code);
3501 assert!(r_code.contains("Point <- function"), "missing constructor: {}", r_code);
3502 }
3503
3504 #[test]
3505 fn test_pub_type_alias_does_not_get_roxygen_tag() {
3506 let r_code = FluentParser::new()
3507 .push("@pub type Point <- list { x: int, y: int };")
3508 .run()
3509 .get_r_code()
3510 .iter()
3511 .cloned()
3512 .collect::<Vec<_>>()
3513 .join("\n");
3514 assert!(!r_code.contains("#' @export"), "unexpected #' @export tag: {}", r_code);
3515 }
3516
3517 #[test]
3518 fn test_export_in_module_is_public_and_package_exported() {
3519 let r_code = FluentParser::new()
3520 .push("module Math { @export let norm <- fn(x: int): int { x }; };")
3521 .run()
3522 .get_r_code()
3523 .iter()
3524 .cloned()
3525 .collect::<Vec<_>>()
3526 .join("\n");
3527 assert!(
3528 r_code.contains("Math$norm <- norm"),
3529 "missing module export: {}",
3530 r_code
3531 );
3532 assert!(r_code.contains("#' @export"), "missing roxygen export tag: {}", r_code);
3533 assert!(
3534 r_code.contains("norm <- Math$norm"),
3535 "missing package-level re-export: {}",
3536 r_code
3537 );
3538 }
3539
3540 #[test]
3541 fn test_export_type_alias_in_module_is_public_and_package_exported() {
3542 let r_code = FluentParser::new()
3543 .push("module Shapes { @export type Point <- list { x: int, y: int }; };")
3544 .run()
3545 .get_r_code()
3546 .iter()
3547 .cloned()
3548 .collect::<Vec<_>>()
3549 .join("\n");
3550 assert!(
3551 r_code.contains("Shapes$Point <- Point"),
3552 "missing module export: {}",
3553 r_code
3554 );
3555 assert!(r_code.contains("#' @export"), "missing roxygen export tag: {}", r_code);
3556 assert!(
3557 r_code.contains("Point <- Shapes$Point"),
3558 "missing package-level re-export: {}",
3559 r_code
3560 );
3561 }
3562
3563 #[test]
3564 fn test_export_in_module_test_build_adds_test_alias() {
3565 let r_code = FluentParser::new()
3566 .set_context(Context::empty().set_test_mode(true))
3567 .push("module Math { @export let norm <- fn(x: int): int { x }; };")
3568 .run()
3569 .get_r_code()
3570 .iter()
3571 .cloned()
3572 .collect::<Vec<_>>()
3573 .join("\n");
3574 assert!(
3575 r_code.contains("Math$`.test_norm` <- norm"),
3576 "export member must get .test_ alias in test build: {}",
3577 r_code
3578 );
3579 }
3580
3581 #[test]
3582 fn test_pub_in_module_test_build_adds_test_alias() {
3583 let r_code = FluentParser::new()
3584 .set_context(Context::empty().set_test_mode(true))
3585 .push("module Math { @pub let pi <- 3; };")
3586 .run()
3587 .get_r_code()
3588 .iter()
3589 .cloned()
3590 .collect::<Vec<_>>()
3591 .join("\n");
3592 assert!(
3593 r_code.contains("Math$`.test_pi` <- pi"),
3594 "@pub member must get .test_ alias in test build (RFC-TR-032 §3.2): {}",
3595 r_code
3596 );
3597 }
3598
3599 #[test]
3600 fn test_array_constructor_call_transpilation() {
3601 let r_code = FluentParser::new()
3602 .push("type Bits <- [Any, int];")
3603 .run()
3604 .push("let b <- Bits:[1, 2, 3];")
3605 .run()
3606 .get_r_code()
3607 .iter()
3608 .cloned()
3609 .collect::<Vec<_>>()
3610 .join("\n");
3611 assert!(
3615 r_code.contains("Bits(c("),
3616 "expected Bits(c(...)) constructor: {}",
3617 r_code
3618 );
3619 assert!(
3620 !r_code.contains("typed_vec"),
3621 "no typed_vec for an atomic-representation alias: {}",
3622 r_code
3623 );
3624 }
3625
3626 #[test]
3627 fn test_record_alias_return_no_constructor_pipe() {
3628 let r_code = FluentParser::new()
3632 .push("type Point <- list { x: int, y: int };")
3633 .run()
3634 .push("let incr <- fn(p: Point): Point { Point:{x: (p$x+1), y: (p$y+1)} };")
3635 .run()
3636 .get_r_code()
3637 .iter()
3638 .cloned()
3639 .collect::<Vec<_>>()
3640 .join("\n");
3641 assert!(
3643 !r_code.contains("}) |> Point()"),
3644 "record alias output conversion should not be added: {}",
3645 r_code
3646 );
3647 assert!(
3649 r_code.contains("|> as.Generic()") || r_code.contains("|> Function"),
3650 "function type annotation should still be applied: {}",
3651 r_code
3652 );
3653 }
3654
3655 #[test]
3656 fn test_alias_int_generates_validator() {
3657 let r_code = FluentParser::new().check_transpiling("type Meters <- int;");
3658 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3659 assert!(
3660 r_str.contains("validate_Meters <- function(x)"),
3661 "expected validator function, got: {}",
3662 r_str
3663 );
3664 assert!(
3665 r_str.contains("is.integer"),
3666 "expected is.integer check, got: {}",
3667 r_str
3668 );
3669 }
3670
3671 #[test]
3672 fn test_alias_char_generates_validator() {
3673 let r_code = FluentParser::new().check_transpiling("type Name <- char;");
3674 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3675 assert!(
3676 r_str.contains("validate_Name <- function(x)"),
3677 "expected validator function, got: {}",
3678 r_str
3679 );
3680 assert!(
3681 r_str.contains("is.character"),
3682 "expected is.character check, got: {}",
3683 r_str
3684 );
3685 }
3686
3687 #[test]
3688 fn test_alias_bool_generates_validator() {
3689 let r_code = FluentParser::new().check_transpiling("type Flag <- bool;");
3690 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3691 assert!(
3692 r_str.contains("validate_Flag <- function(x)"),
3693 "expected validator function, got: {}",
3694 r_str
3695 );
3696 assert!(
3697 r_str.contains("is.logical"),
3698 "expected is.logical check, got: {}",
3699 r_str
3700 );
3701 }
3702
3703 #[test]
3704 fn test_alias_num_generates_validator() {
3705 let r_code = FluentParser::new().check_transpiling("type Real <- num;");
3706 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3707 assert!(
3708 r_str.contains("validate_Real <- function(x)"),
3709 "expected validator function, got: {}",
3710 r_str
3711 );
3712 assert!(
3713 r_str.contains("is.numeric"),
3714 "expected is.numeric check, got: {}",
3715 r_str
3716 );
3717 }
3718
3719 #[test]
3720 fn test_validating_cast_int() {
3721 let r_code = FluentParser::new()
3722 .push("type Meters <- int;")
3723 .run()
3724 .check_transpiling("x as! Meters");
3725 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3726 assert!(
3727 r_str.contains("validate_Meters(x)"),
3728 "expected validate_Meters(x), got: {}",
3729 r_str
3730 );
3731 }
3732
3733 #[test]
3734 fn test_tag_alias_char_generates_validator() {
3735 let r_code = FluentParser::new().check_transpiling("type Hello <- .Hello(char);");
3736 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3737 assert!(
3738 r_str.contains("validate_Hello <- function(x)"),
3739 "expected validator function, got: {}",
3740 r_str
3741 );
3742 assert!(
3743 r_str.contains("x[[1]] != 'Hello'"),
3744 "expected tag name check, got: {}",
3745 r_str
3746 );
3747 assert!(
3748 r_str.contains("x[[\"body\"]]"),
3749 "expected body field check, got: {}",
3750 r_str
3751 );
3752 assert!(
3753 r_str.contains("is.character"),
3754 "expected is.character check on body, got: {}",
3755 r_str
3756 );
3757 }
3758
3759 #[test]
3760 fn test_tag_alias_int_generates_validator() {
3761 let r_code = FluentParser::new().check_transpiling("type Count <- .Count(int);");
3762 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3763 assert!(
3764 r_str.contains("validate_Count <- function(x)"),
3765 "expected validator, got: {}",
3766 r_str
3767 );
3768 assert!(
3769 r_str.contains("x[[1]] != 'Count'"),
3770 "expected tag name check, got: {}",
3771 r_str
3772 );
3773 assert!(
3774 r_str.contains("is.integer"),
3775 "expected is.integer check on body, got: {}",
3776 r_str
3777 );
3778 }
3779
3780 #[test]
3781 fn test_tag_alias_with_alias_body_calls_nested_validator() {
3782 let r_code = FluentParser::new()
3783 .push("type Name <- char;")
3784 .run()
3785 .check_transpiling("type Tagged <- .Tagged(Name);");
3786 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3787 assert!(
3788 r_str.contains("validate_Tagged <- function(x)"),
3789 "expected validator, got: {}",
3790 r_str
3791 );
3792 assert!(
3793 r_str.contains("validate_Name(x[[\"body\"]])"),
3794 "expected nested validator call, got: {}",
3795 r_str
3796 );
3797 }
3798
3799 #[test]
3800 fn test_union_variant_generates_full_pipeline() {
3801 let r_str = FluentParser::new()
3804 .check_transpiling("type Shape <- .Circle(num) | .Nothing;")
3805 .iter()
3806 .cloned()
3807 .collect::<Vec<_>>()
3808 .join("\n");
3809 assert!(
3811 r_str.contains("Circle <- function(x) {")
3812 && r_str.contains("v <- list(\"Circle\", body = x)")
3813 && r_str.contains("as.Circle(v)"),
3814 "expected Circle constructor, got: {r_str}"
3815 );
3816 assert!(
3818 r_str.contains("as.Circle <- function(x) {")
3819 && r_str.contains("class(x) <- c(\"Circle\", \"Shape\", \"Tag\", \"list\")")
3820 && r_str.contains("x <- validate_Circle(x)")
3821 && r_str.contains("x <- validate(x)"),
3822 "expected Circle annotator, got: {r_str}"
3823 );
3824 assert!(
3826 r_str.contains("validate_Circle <- function(x) {")
3827 && r_str.contains("x[[1]] != 'Circle'")
3828 && r_str.contains("is.numeric(x[[\"body\"]])"),
3829 "expected Circle validator, got: {r_str}"
3830 );
3831 assert!(
3833 r_str.contains("Nothing <- function() {") && r_str.contains("x <- list(\"Nothing\")"),
3834 "expected Nothing constructor, got: {r_str}"
3835 );
3836 }
3837
3838 #[test]
3839 fn test_tag_literal_canonical_representation() {
3840 let r_str = FluentParser::new()
3844 .push("type Shape <- .Circle(num) | .Square(num);")
3845 .run()
3846 .check_transpiling(".Circle(3.14)")
3847 .iter()
3848 .cloned()
3849 .collect::<Vec<_>>()
3850 .join("\n");
3851 assert!(
3852 r_str.contains("structure(list('Circle', body =")
3853 && r_str.contains("class = c('Circle', 'Shape', 'Tag', 'list')"),
3854 "expected canonical tag literal with union class, got: {r_str}"
3855 );
3856 }
3857
3858 #[test]
3859 fn test_tag_literal_without_union_omits_union_class() {
3860 let r_str = FluentParser::new()
3863 .check_transpiling(".Loose(1)")
3864 .iter()
3865 .cloned()
3866 .collect::<Vec<_>>()
3867 .join("\n");
3868 assert!(
3869 r_str.contains("structure(list('Loose', body =") && r_str.contains("class = c('Loose', 'Tag', 'list')"),
3870 "expected canonical tag literal without union class, got: {r_str}"
3871 );
3872 }
3873
3874 #[test]
3875 fn test_literal_char_alias_generates_exact_validator() {
3876 let r_code = FluentParser::new().check_transpiling("type Hello <- \"hello\";");
3877 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3878 assert!(
3879 r_str.contains("validate_Hello <- function(x)"),
3880 "expected validator function, got: {}",
3881 r_str
3882 );
3883 assert!(
3884 r_str.contains("x != 'hello'"),
3885 "expected literal equality check, got: {}",
3886 r_str
3887 );
3888 }
3889
3890 #[test]
3891 fn test_literal_int_alias_generates_exact_validator() {
3892 let r_code = FluentParser::new().check_transpiling("type Byte <- 89;");
3893 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3894 assert!(
3895 r_str.contains("validate_Byte <- function(x)"),
3896 "expected validator function, got: {}",
3897 r_str
3898 );
3899 assert!(
3900 r_str.contains("x != 89L"),
3901 "expected literal equality check, got: {}",
3902 r_str
3903 );
3904 }
3905
3906 #[test]
3907 fn test_literal_num_alias_generates_exact_validator() {
3908 let r_code = FluentParser::new().check_transpiling("type Pi <- 3.14;");
3909 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3910 assert!(
3911 r_str.contains("validate_Pi <- function(x)"),
3912 "expected validator function, got: {}",
3913 r_str
3914 );
3915 assert!(
3916 r_str.contains("x != 3.14"),
3917 "expected literal equality check, got: {}",
3918 r_str
3919 );
3920 }
3921
3922 #[test]
3923 fn test_literal_bool_true_alias_generates_exact_validator() {
3924 let r_code = FluentParser::new().check_transpiling("type Yes <- true;");
3925 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3926 assert!(
3927 r_str.contains("validate_Yes <- function(x)"),
3928 "expected validator function, got: {}",
3929 r_str
3930 );
3931 assert!(
3932 r_str.contains("x != TRUE"),
3933 "expected literal TRUE check, got: {}",
3934 r_str
3935 );
3936 }
3937
3938 #[test]
3939 fn test_literal_bool_false_alias_generates_exact_validator() {
3940 let r_code = FluentParser::new().check_transpiling("type No <- false;");
3941 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3942 assert!(
3943 r_str.contains("validate_No <- function(x)"),
3944 "expected validator function, got: {}",
3945 r_str
3946 );
3947 assert!(
3948 r_str.contains("x != FALSE"),
3949 "expected literal FALSE check, got: {}",
3950 r_str
3951 );
3952 }
3953
3954 #[test]
3955 fn test_record_subtype_includes_supertype_in_s3_class() {
3956 let r_str = FluentParser::new()
3960 .push("type Position <- list{ position: int };")
3961 .run()
3962 .push("type Person <- list{ name: char, age: int, position: int };")
3963 .run()
3964 .get_r_code()
3965 .iter()
3966 .cloned()
3967 .collect::<Vec<_>>()
3968 .join("\n");
3969 assert!(
3970 r_str.contains("class(x) <- c(\"Person\", \"Position\", \"list\")"),
3971 "expected Person's annotator to include Position, got: {r_str}"
3972 );
3973 }
3974
3975 #[test]
3976 fn test_forced_dispatch_call_binds_directly_to_suffixed_method() {
3977 let r_str = FluentParser::new()
3981 .push("type Personne <- list{ name: char };")
3982 .run()
3983 .push("type Animal <- list{ name: char };")
3984 .run()
3985 .push("let greet <- fn(x: Personne, y: Personne): Personne { x };")
3986 .run()
3987 .push("let greet <- fn(x: Animal, y: Animal): Animal { x };")
3988 .run()
3989 .push("let p1 <- Personne:{ name = \"a\" };")
3990 .run()
3991 .push("let p2 <- Personne:{ name = \"b\" };")
3992 .run()
3993 .check_transpiling("greet<Personne>(p1, p2)")
3994 .iter()
3995 .cloned()
3996 .collect::<Vec<_>>()
3997 .join("\n");
3998 assert!(
3999 r_str.contains("greet.Personne(p1, p2)"),
4000 "expected a direct `greet.Personne(...)` call, got: {r_str}"
4001 );
4002 }
4003
4004 #[test]
4005 fn test_unforced_call_stays_plain() {
4006 let r_str = FluentParser::new()
4007 .push("type Personne <- list{ name: char };")
4008 .run()
4009 .push("type Animal <- list{ name: char };")
4010 .run()
4011 .push("let greet <- fn(x: Personne, y: Personne): Personne { x };")
4012 .run()
4013 .push("let greet <- fn(x: Animal, y: Animal): Animal { x };")
4014 .run()
4015 .push("let p1 <- Personne:{ name = \"a\" };")
4016 .run()
4017 .push("let p2 <- Personne:{ name = \"b\" };")
4018 .run()
4019 .check_transpiling("greet(p1, p2)")
4020 .iter()
4021 .cloned()
4022 .collect::<Vec<_>>()
4023 .join("\n");
4024 assert!(
4025 r_str.contains("greet(p1, p2)") && !r_str.contains("greet.Personne(p1, p2)"),
4026 "expected a plain, unsuffixed `greet(...)` call relying on runtime UseMethod dispatch, got: {r_str}"
4027 );
4028 }
4029
4030 #[test]
4031 fn test_forced_dispatch_any_binds_to_default() {
4032 let r_str = FluentParser::new()
4037 .push("type Personne <- list{ name: char };")
4038 .run()
4039 .push("let greet <- fn(x: Personne, y: Personne): Personne { x };")
4040 .run()
4041 .push("let greet <- fn(x: Any, y: Any): Any { x };")
4042 .run()
4043 .push("let p1 <- Personne:{ name = \"a\" };")
4044 .run()
4045 .push("let p2 <- Personne:{ name = \"b\" };")
4046 .run()
4047 .check_transpiling("greet<Any>(p1, p2)")
4048 .iter()
4049 .cloned()
4050 .collect::<Vec<_>>()
4051 .join("\n");
4052 assert!(
4053 r_str.contains("greet.default(p1, p2)"),
4054 "expected a direct `greet.default(...)` call, got: {r_str}"
4055 );
4056 }
4057
4058 #[test]
4059 fn test_record_without_supertype_keeps_plain_class() {
4060 let r_str = FluentParser::new()
4062 .check_transpiling("type Position <- list{ position: int };")
4063 .iter()
4064 .cloned()
4065 .collect::<Vec<_>>()
4066 .join("\n");
4067 assert!(
4068 r_str.contains("class(x) <- c(\"Position\", \"list\")"),
4069 "expected Position's annotator with no supertype, got: {r_str}"
4070 );
4071 }
4072
4073 #[test]
4074 fn test_import_from_qualifies_call_site() {
4075 let r_str = FluentParser::new()
4077 .push("@importFrom dplyr filter;")
4078 .run()
4079 .push("@filter: (Any, Any) -> Any;")
4080 .run()
4081 .check_transpiling("filter(df, cond)")
4082 .iter()
4083 .cloned()
4084 .collect::<Vec<_>>()
4085 .join("\n");
4086 assert!(
4087 r_str.contains("dplyr::filter("),
4088 "expected dplyr::filter(...), got: {r_str}"
4089 );
4090 }
4091
4092 #[test]
4093 fn test_import_from_multiple_fns() {
4094 let r_str = FluentParser::new()
4096 .push("@importFrom dplyr filter mutate;")
4097 .run()
4098 .push("@mutate: (Any, Any) -> Any;")
4099 .run()
4100 .check_transpiling("mutate(df, z)")
4101 .iter()
4102 .cloned()
4103 .collect::<Vec<_>>()
4104 .join("\n");
4105 assert!(
4106 r_str.contains("dplyr::mutate("),
4107 "expected dplyr::mutate(...), got: {r_str}"
4108 );
4109 }
4110}