1pub mod translatable;
2
3use crate::components::context::config::Environment;
4use crate::components::context::Context;
5use crate::components::error_message::help_data::HelpData;
6use crate::components::language::argument_value::ArgumentValue;
7use crate::components::language::format_backtick;
8use crate::components::language::function_lang::Function;
9use crate::components::language::operators::Op;
10use crate::components::language::set_related_type_if_variable;
11use crate::components::language::var::Var;
12use crate::components::language::Lang;
13use crate::components::language::ModulePosition;
14use crate::components::r#type::argument_type::ArgumentType;
15use crate::components::r#type::array_type::ArrayType;
16use crate::components::r#type::function_type::FunctionType;
17use crate::components::r#type::type_operator::TypeOperator;
18use crate::components::r#type::type_system::TypeSystem;
19use crate::components::r#type::vector_type::VecType;
20use crate::components::r#type::Type;
21use crate::processes::transpiling::translatable::Translatable;
22use crate::processes::type_checking::flatten_operator_union;
23use crate::processes::type_checking::resolve_module_member_type;
24use crate::processes::type_checking::type_comparison::reduce_type;
25use crate::processes::type_checking::typing;
26use translatable::RTranslatable;
27
28#[cfg(not(feature = "wasm"))]
29use std::fs::File;
30#[cfg(not(feature = "wasm"))]
31use std::io::Write;
32#[cfg(not(feature = "wasm"))]
33use std::path::PathBuf;
34
35use std::cell::RefCell;
36use std::collections::HashMap;
37
38pub fn escape_r_string(s: &str) -> String {
44 let escaped = s
45 .replace('\\', "\\\\")
46 .replace('"', "\\\"")
47 .replace('\n', "\\n")
48 .replace('\t', "\\t");
49 format!("\"{}\"", escaped)
50}
51
52thread_local! {
54 static GENERATED_FILES: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
55}
56
57thread_local! {
70 static INCLUDE_STACK: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
71}
72
73pub fn reset_include_stack() {
75 INCLUDE_STACK.with(|s| *s.borrow_mut() = vec![Vec::new()]);
76}
77
78fn push_include_frame() {
80 INCLUDE_STACK.with(|s| s.borrow_mut().push(Vec::new()));
81}
82
83fn pop_include_frame() -> Vec<String> {
85 INCLUDE_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
86}
87
88fn register_include(file: &str) {
90 INCLUDE_STACK.with(|s| {
91 if let Some(top) = s.borrow_mut().last_mut() {
92 top.push(file.to_string());
93 }
94 });
95}
96
97pub fn take_main_includes() -> Vec<String> {
99 INCLUDE_STACK.with(|s| {
100 let mut stack = s.borrow_mut();
101 match stack.first_mut() {
102 Some(bottom) => std::mem::take(bottom),
103 None => Vec::new(),
104 }
105 })
106}
107
108pub fn register_generated_file(path: &str, content: &str) {
110 GENERATED_FILES.with(|files| {
111 files
112 .borrow_mut()
113 .insert(path.to_string(), content.to_string());
114 });
115}
116
117pub fn get_generated_files() -> HashMap<String, String> {
119 GENERATED_FILES.with(|files| files.borrow().clone())
120}
121
122pub fn clear_generated_files() {
124 GENERATED_FILES.with(|files| {
125 files.borrow_mut().clear();
126 });
127}
128
129#[cfg(not(feature = "wasm"))]
131fn write_output_file(path: &str, content: &str) -> Result<(), String> {
132 use std::fs;
133
134 register_generated_file(path, content);
136
137 let path_buf = PathBuf::from(path);
138 if let Some(parent) = path_buf.parent() {
139 fs::create_dir_all(parent).map_err(|e| e.to_string())?;
140 }
141 let mut file = File::create(&path_buf).map_err(|e| e.to_string())?;
142 file.write_all(content.as_bytes())
143 .map_err(|e| e.to_string())?;
144 Ok(())
145}
146
147#[cfg(feature = "wasm")]
148fn write_output_file(path: &str, content: &str) -> Result<(), String> {
149 register_generated_file(path, content);
150 Ok(())
151}
152
153pub trait ToSome {
154 fn to_some(self) -> Option<Self>
155 where
156 Self: Sized;
157}
158
159impl<T: Sized> ToSome for T {
160 fn to_some(self) -> Option<Self> {
161 Some(self)
162 }
163}
164
165trait AndIf {
166 fn and_if<F>(self, condition: F) -> Option<Self>
167 where
168 F: Fn(Self) -> bool,
169 Self: Sized;
170}
171
172impl<T: Clone> AndIf for T {
173 fn and_if<F>(self, condition: F) -> Option<Self>
174 where
175 F: Fn(Self) -> bool,
176 {
177 if condition(self.clone()) {
178 Some(self)
179 } else {
180 None
181 }
182 }
183}
184
185const JS_HEADER: &str = "";
186
187fn to_pattern_match_statement(
188 exp: Lang,
189 branches: &[(Lang, Box<Lang>)],
190 context: &Context,
191) -> String {
192 let match_var = "match_val__";
193 let res = branches
194 .iter()
195 .enumerate()
196 .map(|(id, (pattern, body))| {
197 let (cond, bindings) = pattern_to_condition(pattern, match_var, context);
198 let body_str = body.to_r(context).0;
199 let body_with_bindings = if bindings.is_empty() {
200 body_str
201 } else {
202 format!("{}\n{}", bindings, body_str)
203 };
204 if cond == "TRUE" {
205 if id == 0 {
207 format!("{{\n{}\n}}", body_with_bindings)
208 } else {
209 format!("else {{\n{}\n}}", body_with_bindings)
210 }
211 } else if id == 0 {
212 format!("if ({}) {{\n{}\n}}", cond, body_with_bindings)
213 } else {
214 format!("else if ({}) {{\n{}\n}}", cond, body_with_bindings)
215 }
216 })
217 .collect::<Vec<_>>()
218 .join(" ");
219 format!("{{\n{} <- {}\n{}\n}}", match_var, exp.to_r(context).0, res)
220}
221
222fn type_to_r_check(typ: &Type) -> Option<&'static str> {
224 match typ {
225 Type::Integer(_, _) => Some("is.integer"),
226 Type::Boolean(_, _) => Some("is.logical"),
227 Type::Number(_, _) => Some("is.numeric"),
228 Type::Char(_, _) => Some("is.character"),
229 Type::Null(_) => Some("is.null"),
230 _ => None,
231 }
232}
233
234fn record_field_class(typ: &Type, cont: &Context) -> Option<String> {
240 match typ {
241 Type::Integer(_, _) => Some("integer".to_string()),
242 Type::Number(_, _) => Some("numeric".to_string()),
243 Type::Char(_, _) => Some("character".to_string()),
244 Type::Boolean(_, _) => Some("logical".to_string()),
245 Type::Alias(name, _, _, _) => match cont
246 .aliases()
247 .find(|(var, _)| var.get_name() == *name)
248 .map(|(_, t)| t)
249 {
250 Some(Type::Record(_, _)) => Some(name.clone()),
252 Some(inner) => record_field_class(inner, cont),
255 None => None,
256 },
257 _ => None,
258 }
259}
260
261fn find_union_for_tag(tag_name: &str, cont: &Context) -> Option<String> {
267 cont.aliases().find_map(|(var, typ)| {
268 let is_union = matches!(
269 typ,
270 Type::Operator(
271 crate::components::r#type::type_operator::TypeOperator::Union,
272 _,
273 _,
274 _
275 )
276 );
277 if !is_union {
278 return None;
279 }
280 let declares_tag = flatten_operator_union(typ)
281 .iter()
282 .any(|m| matches!(m, Type::Tag(n, _, _) if n == tag_name));
283 if declares_tag {
284 Some(var.get_name())
285 } else {
286 None
287 }
288 })
289}
290
291fn tag_body_validation(name: &str, inner_type: &Type) -> String {
296 match inner_type {
297 Type::Empty(_) => String::new(),
298 Type::Integer(tint, _) => {
299 use crate::components::r#type::tint::Tint;
300 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\")");
301 match tint {
302 Tint::Val(i) => format!("{null_check}\n if (x[[\"body\"]] != {i}L) stop(\"Validation failed for type {name}: body must be literal {i}\")"),
303 Tint::Unknown => null_check,
304 }
305 }
306 Type::Char(tchar, _) => {
307 use crate::components::r#type::tchar::Tchar;
308 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\")");
309 match tchar {
310 Tchar::Val(s) => format!("{null_check}\n if (x[[\"body\"]] != '{s}') stop(\"Validation failed for type {name}: body must be literal '{s}'\")"),
311 Tchar::Unknown => null_check,
312 }
313 }
314 Type::Boolean(tbool, _) => {
315 use crate::components::r#type::tbool::Tbool;
316 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\")");
317 match tbool {
318 Tbool::Val(b) => {
319 let r_val = if *b { "TRUE" } else { "FALSE" };
320 format!("{null_check}\n if (x[[\"body\"]] != {r_val}) stop(\"Validation failed for type {name}: body must be literal {r_val}\")")
321 }
322 Tbool::Unknown => null_check,
323 }
324 }
325 Type::Number(tnum, _) => {
326 use crate::components::r#type::tnumber::Tnum;
327 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\")");
328 match tnum {
329 Tnum::Val(v) => format!("{null_check}\n if (x[[\"body\"]] != {v}) stop(\"Validation failed for type {name}: body must be literal {v}\")"),
330 Tnum::Unknown => null_check,
331 }
332 }
333 Type::Alias(alias_name, _, _, _) => format!(
334 "\n if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")\n validate_{alias_name}(x[[\"body\"]])"
335 ),
336 _ => format!(
337 "\n if (is.null(x[[\"body\"]])) stop(\"Validation failed for type {name}: missing 'body' field\")"
338 ),
339 }
340}
341
342fn tag_variant_pipeline(variant_name: &str, union_name: &str, inner_type: &Type) -> String {
347 let is_empty = matches!(inner_type, Type::Empty(_));
348 let constructor = if is_empty {
351 format!(
352 "{variant_name} <- function() {{\n x <- list(\"{variant_name}\")\n as.{variant_name}(x)\n}}"
353 )
354 } else {
355 format!(
356 "{variant_name} <- function(x) {{\n v <- list(\"{variant_name}\", body = x)\n as.{variant_name}(v)\n}}"
357 )
358 };
359 let annotator = format!(
363 "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}}"
364 );
365 let body_validation = tag_body_validation(variant_name, inner_type);
367 let validator = format!(
368 "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}}"
369 );
370 format!("{constructor}\n{annotator}\n{validator}")
371}
372
373fn pattern_to_condition(pattern: &Lang, match_var: &str, _context: &Context) -> (String, String) {
374 match pattern {
375 Lang::Tag {
377 name, value: inner, ..
378 } => {
379 let cond = format!("{}[[1]] == '{}'", match_var, name);
380 match inner.as_ref() {
381 Lang::Variable { name: var_name, .. } => {
382 let binding = format!("{} <- {}[[\"body\"]]", var_name, match_var);
383 (cond, binding)
384 }
385 Lang::Empty(_) => (cond, String::new()),
386 _ => (cond, String::new()),
387 }
388 }
389 Lang::TypePattern {
391 variable_name: var_name,
392 matched_type: typ,
393 ..
394 } => {
395 let check_fn = type_to_r_check(typ).unwrap_or("is.logical");
396 let cond = format!("{}({})", check_fn, match_var);
397 let binding = format!("{} <- {}", var_name, match_var);
398 (cond, binding)
399 }
400 Lang::Tuple {
402 value: elements, ..
403 } => {
404 let cond = format!(
405 "inherits({}, 'Tuple') && length({}) == {}",
406 match_var,
407 match_var,
408 elements.len()
409 );
410 let bindings: Vec<String> = elements
411 .iter()
412 .enumerate()
413 .filter_map(|(i, elem)| {
414 if let Lang::Variable { name: var_name, .. } = elem {
415 if var_name == "_" {
416 None
417 } else {
418 Some(format!("{} <- {}[[{}]]", var_name, match_var, i + 1))
419 }
420 } else {
421 None
422 }
423 })
424 .collect();
425 (cond, bindings.join("\n"))
426 }
427 Lang::List { value: fields, .. } => {
429 let conditions: Vec<String> = fields
430 .iter()
431 .map(|arg_val: &ArgumentValue| {
432 format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument())
433 })
434 .collect();
435 let cond = if conditions.is_empty() {
436 "is.list(".to_string() + match_var + ")"
437 } else {
438 format!("is.list({}) && {}", match_var, conditions.join(" && "))
439 };
440 let bindings: Vec<String> = fields
441 .iter()
442 .filter_map(|arg_val| {
443 if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
444 Some(format!(
445 "{} <- {}[[\"{}\"]]",
446 var_name,
447 match_var,
448 arg_val.get_argument()
449 ))
450 } else {
451 None
452 }
453 })
454 .collect();
455 (cond, bindings.join("\n"))
456 }
457 Lang::DataFrame { value: fields, .. } => {
459 let conditions: Vec<String> = fields
460 .iter()
461 .map(|arg_val: &ArgumentValue| {
462 format!("!is.null({}[[\"{}\"]])", match_var, arg_val.get_argument())
463 })
464 .collect();
465 let cond = if conditions.is_empty() {
466 "is.data.frame(".to_string() + match_var + ")"
467 } else {
468 format!(
469 "is.data.frame({}) && {}",
470 match_var,
471 conditions.join(" && ")
472 )
473 };
474 let bindings: Vec<String> = fields
475 .iter()
476 .filter_map(|arg_val| {
477 if let Lang::Variable { name: var_name, .. } = &arg_val.get_value() {
478 Some(format!(
479 "{} <- {}[[\"{}\"]]",
480 var_name,
481 match_var,
482 arg_val.get_argument()
483 ))
484 } else {
485 None
486 }
487 })
488 .collect();
489 (cond, bindings.join("\n"))
490 }
491 Lang::Variable { name, .. } if name == "_" => ("TRUE".to_string(), String::new()),
493 Lang::Variable { name, .. } => {
495 let binding = format!("{} <- {}", name, match_var);
496 ("TRUE".to_string(), binding)
497 }
498 _ => ("TRUE".to_string(), String::new()),
499 }
500}
501
502impl RTranslatable<(String, Context)> for Lang {
503 fn to_r(&self, cont: &Context) -> (String, Context) {
504 let result = match self {
505 Lang::Bool { value: b, .. } => {
506 let (typ, _, _) = typing(cont, self).to_tuple();
507 let anotation = cont.get_type_anotation(&typ);
508 (
509 format!("{} |> {}", b.to_string().to_uppercase(), anotation),
510 cont.clone(),
511 )
512 }
513 Lang::Number { value: n, .. } => {
514 let (typ, _, _) = typing(cont, self).to_tuple();
515 let anotation = cont.get_type_anotation(&typ);
516 (format!("{} |> {}", n, anotation), cont.clone())
517 }
518 Lang::Integer { value: i, .. } => {
519 let (typ, _, _) = typing(cont, self).to_tuple();
520 let anotation = cont.get_type_anotation(&typ);
521 (format!("{}L |> {}", i, anotation), cont.clone())
522 }
523 Lang::Char { value: s, .. } => {
524 let (typ, _, _) = typing(cont, self).to_tuple();
525 let anotation = cont.get_type_anotation(&typ);
526 (
527 format!("{} |> {}", escape_r_string(s), anotation),
528 cont.clone(),
529 )
530 }
531 Lang::Operator {
532 operator: op @ (Op::Dot(_) | Op::Pipe(_)),
533 rhs: e1,
534 lhs: e2,
535 ..
536 } => {
537 let is_dot = matches!(op, Op::Dot(_));
552 let e1 = (**e1).clone();
553 let e2 = (**e2).clone();
554 match e2.clone() {
555 Lang::Variable { .. } => match e1 {
556 Lang::Integer { .. } => Translatable::from(cont.clone())
557 .to_r(&e2)
558 .add("[[")
559 .to_r(&e1)
560 .add("]]")
561 .into(),
562 _ if is_dot => Translatable::from(cont.clone())
563 .to_r(&e1)
564 .add("[['")
565 .to_r(&e2)
566 .add("']]")
567 .into(),
568 _ => Translatable::from(cont.clone())
569 .to_r(&e2)
570 .add("[['")
571 .to_r(&e1)
572 .add("']]")
573 .into(),
574 },
575 Lang::List { value: fields, .. } => {
576 let at = fields[0].clone();
577 Translatable::from(cont.clone())
578 .add("within(")
579 .to_r(&e2)
580 .add(", { ")
581 .add(&at.get_argument())
582 .add(" <- ")
583 .to_r(&at.get_value())
584 .add(" })")
585 .into()
586 }
587 Lang::DataFrame { value: fields, .. } => {
588 let at = fields[0].clone();
589 Translatable::from(cont.clone())
590 .add("within(")
591 .to_r(&e2)
592 .add(", { ")
593 .add(&at.get_argument())
594 .add(" <- ")
595 .to_r(&at.get_value())
596 .add(" })")
597 .into()
598 }
599 Lang::FunctionApp {
600 identifier: var,
601 arguments: v,
602 help_data: h,
603 } => {
604 let v = [e1].iter().chain(v.iter()).cloned().collect();
605 Lang::FunctionApp {
606 identifier: var,
607 arguments: v,
608 help_data: h,
609 }
610 .to_r(cont)
611 }
612 _ => Translatable::from(cont.clone())
613 .to_r(&e2)
614 .add("[[")
615 .add("]]")
616 .to_r(&e1)
617 .into(),
618 }
619 }
620 Lang::Operator {
621 operator: Op::Dollar(_),
622 rhs: e1,
623 lhs: e2,
624 ..
625 } => {
626 let e1 = (**e1).clone();
627 let e2 = (**e2).clone();
628 let t1 = typing(cont, &e1).value;
629 let val = match (t1.clone(), e2.clone()) {
630 (Type::Vec(vtype, _, _, _), Lang::Variable { name, .. })
631 if vtype.is_array() =>
632 {
633 format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name)
634 }
635 (Type::Vec(VecType::S3, _, _, _), Lang::Variable { name, .. }) => {
636 let name_str = name.replace("__", ".");
637 format!("get({}, '{}')", e1.to_r(cont).0, name_str)
638 }
639 (_, Lang::Variable { name, .. }) => format!("{}${}", e1.to_r(cont).0, name),
640 _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0),
641 };
642 (val, cont.clone())
643 }
644 Lang::Operator {
645 operator: op,
646 rhs: e1,
647 lhs: e2,
648 ..
649 } => {
650 let op_str = format!(" {} ", op);
651 Translatable::from(cont.clone())
652 .to_r(e1)
653 .add(&op_str)
654 .to_r(e2)
655 .into()
656 }
657 Lang::Scope { body: exps, .. } => Translatable::from(cont.clone())
658 .add("{\n")
659 .join(exps, "\n")
660 .add("\n}")
661 .into(),
662 Lang::Function {
663 parameters: params,
664 body,
665 ..
666 } => {
667 let fn_type = FunctionType::try_from(typing(cont, self).value.clone())
668 .expect("function expression should have a function type");
669 let return_type = fn_type.get_return_type();
670
671 let is_record_alias_return = match &return_type {
676 Type::Alias(alias_name, _, _, _) => cont
677 .aliases()
678 .find(|(var, _)| var.get_name() == *alias_name)
679 .map(|(_, t)| matches!(t, Type::Record(_, _)))
680 .unwrap_or(false),
681 _ => false,
682 };
683 let output_conversion = if is_record_alias_return {
684 "".to_string()
685 } else {
686 cont.get_type_anotation(&return_type)
687 };
688
689 let has_variadic = params.last().map(|p| p.is_variadic()).unwrap_or(false);
690 let list_of_types = params
691 .iter()
692 .map(ArgumentType::body_type)
693 .collect::<Vec<_>>();
694 let sub_context = params
695 .iter()
696 .map(|arg_typ| arg_typ.clone().set_type(arg_typ.body_type()).to_var(cont))
697 .zip(list_of_types.clone())
698 .fold(cont.clone(), |context: Context, (var, typ)| {
699 context.clone().push_var_type(var, typ, &context)
700 });
701 let res = if output_conversion.is_empty() {
702 "".to_string()
703 } else {
704 " |> ".to_owned() + &output_conversion
705 };
706 let body_r = body.to_r(&sub_context).0;
707 let final_body_r = if has_variadic {
708 let vname = params.last().unwrap().get_argument_str();
709 let collector = "typed_vec(..., dim = c(...length()))";
713 if let Some(rest) = body_r.strip_prefix('{') {
715 format!("{{\n{} <- {}{}", vname, collector, rest)
716 } else {
717 body_r
718 }
719 } else {
720 body_r
721 };
722 (
723 format!(
724 "(function({}) {}{}) |> {}",
725 params
726 .iter()
727 .map(|x| x.to_r(cont))
728 .collect::<Vec<_>>()
729 .join(", "),
730 final_body_r,
731 res,
732 cont.get_type_anotation(&fn_type.into())
733 ),
734 cont.clone(),
735 )
736 }
737 Lang::Variable { .. } => {
738 let var = Var::from_language(self.clone()).unwrap();
740 let name = if var.contains("__") {
741 var.replace("__", ".").get_name()
742 } else {
743 var.display_type(cont).get_name()
744 };
745 (name.to_string(), cont.clone())
746 }
747 Lang::FunctionApp {
748 identifier: exp,
749 arguments: vals,
750 ..
751 } => {
752 let var = Var::try_from(exp.clone()).unwrap();
753
754 let (exp_str, cont1) = exp.to_r(cont);
755 let fn_t = FunctionType::try_from(
756 cont1
757 .get_type_from_variable(&var)
758 .unwrap_or_else(|_| panic!("variable {} don't have a related type", var)),
759 )
760 .map(|ft| ft.adjust_nb_parameters(vals.len()))
761 .expect("function application identifier should have a function type");
762 let new_args = fn_t
763 .get_param_types()
764 .iter()
765 .map(|arg| reduce_type(&cont1, arg))
766 .collect::<Vec<_>>();
767 let new_vals = vals
768 .iter()
769 .zip(new_args.iter())
770 .map(set_related_type_if_variable)
771 .collect::<Vec<_>>();
772 let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
773 Var::from_language(*exp.clone())
774 .map(|var| {
775 let name = var.get_name();
776 let new_name = if &name[0..1] == "%" {
777 format!("`{}`", name.replace("__", "."))
778 } else {
779 name.replace("__", ".")
780 };
781 (format!("{}({})", new_name, args), current_cont.clone())
782 })
783 .unwrap_or((format!("{}({})", exp_str, args), current_cont))
784 }
785 Lang::VecFunctionApp {
786 vector_type,
787 identifier: exp,
788 arguments: vals,
789 ..
790 } => {
791 let var = Var::try_from(exp.clone()).unwrap();
792 let name = var.get_name();
793 let str_vals = vals
794 .iter()
795 .map(|x| x.to_r(cont).0)
796 .collect::<Vec<_>>()
797 .join(", ");
798 if *vector_type == VecType::Vector {
799 if cont.is_an_untyped_function(&name) {
806 let name = name.replace("__", ".");
807 let new_name = if &name[0..1] == "%" {
808 format!("`{}`", name)
809 } else {
810 name.to_string()
811 };
812 (format!("{}({})", new_name, str_vals), cont.clone())
813 } else {
814 let (exp_str, cont1) = exp.to_r(cont);
815 let fn_t = FunctionType::try_from(
816 cont1.get_type_from_variable(&var).unwrap_or_else(|_| {
817 panic!("variable {} don't have a related type", var)
818 }),
819 )
820 .expect(
821 "vector function application identifier should have a function type",
822 );
823 let new_args = fn_t
824 .get_param_types()
825 .iter()
826 .map(|arg| reduce_type(&cont1, arg))
827 .collect::<Vec<_>>();
828 let new_vals = vals
829 .iter()
830 .zip(new_args.iter())
831 .map(set_related_type_if_variable)
832 .collect::<Vec<_>>();
833 let (args, current_cont) =
834 Translatable::from(cont1).join(&new_vals, ", ").into();
835 Var::from_language(*exp.clone())
836 .map(|var| {
837 let name = var.get_name();
838 let new_name = if &name[0..1] == "%" {
839 format!("`{}`", name.replace("__", "."))
840 } else {
841 name.replace("__", ".")
842 };
843 (format!("{}({})", new_name, args), current_cont.clone())
844 })
845 .unwrap_or((format!("{}({})", exp_str, args), current_cont))
846 }
847 } else if name == "reduce" {
848 (format!("vec_reduce({})", str_vals), cont.clone())
849 } else if name == "extend" {
850 (format!("vec_extend({})", str_vals), cont.clone())
851 } else if cont.is_an_untyped_function(&name) {
852 let name = name.replace("__", ".");
853 let new_name = if &name[0..1] == "%" {
854 format!("`{}`", name)
855 } else {
856 name.to_string()
857 };
858 let s = format!("vec_apply({}, {})", new_name, str_vals);
859 (s, cont.clone())
860 } else {
861 let (exp_str, cont1) = exp.to_r(cont);
862 let fn_t = FunctionType::try_from(
863 cont1.get_type_from_variable(&var).unwrap_or_else(|_| {
864 panic!("variable {} don't have a related type", var)
865 }),
866 )
867 .expect("vector function application identifier should have a function type");
868 let new_args = fn_t
869 .get_param_types()
870 .iter()
871 .map(|arg| reduce_type(&cont1, arg))
872 .collect::<Vec<_>>();
873 let new_vals = vals
874 .iter()
875 .zip(new_args.iter())
876 .map(set_related_type_if_variable)
877 .collect::<Vec<_>>();
878 let (args, current_cont) =
879 Translatable::from(cont1).join(&new_vals, ", ").into();
880 Var::from_language(*exp.clone())
881 .map(|var| {
882 let name = var.get_name();
883 let new_name = if &name[0..1] == "%" {
884 format!("`{}`", name.replace("__", "."))
885 } else {
886 name.replace("__", ".")
887 };
888 (
889 format!("vec_apply({}, {})", new_name, args),
890 current_cont.clone(),
891 )
892 })
893 .unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont))
894 }
895 }
896 Lang::ArrayIndexing {
897 identifier: exp,
898 indexing: val,
899 ..
900 } => {
901 let (exp_str, _) = exp.to_r(cont);
902 let negative_idx = val.get_members_if_array().and_then(|members| {
904 if members.len() == 1 {
905 if let Lang::Integer { value: i, .. } = &members[0] {
906 if *i < 0 {
907 Some(*i)
908 } else {
909 None
910 }
911 } else {
912 None
913 }
914 } else {
915 None
916 }
917 });
918 let res = if let Some(neg) = negative_idx {
919 let offset = 1 + neg; if offset == 0 {
921 format!("{}[[length({})]]", exp_str, exp_str)
922 } else if offset < 0 {
923 format!("{}[[length({}) - {}L]]", exp_str, exp_str, -offset)
924 } else {
925 format!("{}[[length({}) + {}L]]", exp_str, exp_str, offset)
926 }
927 } else {
928 let (val_str, _) = val.to_simple_r(cont);
929 format!("{}[[{}]]", exp_str, val_str)
930 };
931 (res, cont.clone())
932 }
933 Lang::GenFunc { name: func, .. } => (
934 format!("function(x, ...) UseMethod('{}')", func),
935 cont.clone(),
936 ),
937 Lang::Let {
938 variable: expr,
939 r#type: ttype,
940 expression: body,
941 is_public: _,
942 is_testable: _,
943 is_export,
944 help_data: _,
945 } => {
946 let (body_str, new_cont) = body.to_r(cont);
947 let new_name = format_backtick(expr.clone().to_r(cont).0);
948
949 let (r_code, _new_name2) = Function::try_from((**body).clone())
950 .map(|_| {
951 let related_type = Var::try_from(expr)
952 .ok()
953 .map(|v| v.get_type())
954 .filter(|t| !matches!(t, Type::Empty(_) | Type::UnknownFunction(_)))
955 .unwrap_or_else(|| typing(cont, expr).value);
956 let method = match cont.get_environment() {
957 Environment::Project => format!(
958 "#' @method {}\n",
959 new_name.replace(".", " ").replace("`", "")
960 ),
961 _ => "".to_string(),
962 };
963 match related_type {
964 Type::Empty(_) => {
965 (format!("{} <- {}", new_name, body_str), new_name.clone())
966 }
967 Type::Any(_) => (
976 format!("{}.default <- {}", new_name, body_str),
977 new_name.clone(),
978 ),
979 _ => (
980 format!("{}{} <- {}", method, new_name, body_str),
981 new_name.clone(),
982 ),
983 }
984 })
985 .unwrap_or((format!("{} <- {}", new_name, body_str), new_name));
986 let code = if !ttype.is_empty() {
987 let type_annotation = new_cont.get_type_anotation(ttype);
988 format!("{} |> {}\n", r_code, type_annotation)
989 } else {
990 r_code + "\n"
991 };
992 let code = if *is_export {
994 format!("#' @export\n{}", code)
995 } else {
996 code
997 };
998 (code, new_cont)
999 }
1000 Lang::Array { .. } => {
1001 let typ = self.typing(cont).value;
1002
1003 let dimension = ArrayType::try_from(typ.clone())
1004 .expect("array literal should have an array type")
1005 .get_shape()
1006 .map(|sha| format!("c({})", sha))
1007 .unwrap_or_else(|| "c(0)".to_string());
1008
1009 let array = &self
1010 .linearize_array()
1011 .iter()
1012 .map(|lang| lang.to_r(cont).0)
1013 .collect::<Vec<_>>()
1014 .join(", ")
1015 .and_if(|lin_array| !lin_array.is_empty())
1016 .map(|lin_array| format!("typed_vec({}, dim = {})", lin_array, dimension))
1017 .unwrap_or_else(|| format!("typed_vec(dim = {})", dimension));
1018
1019 (
1020 format!("{} |> {}", array, cont.get_type_anotation(&typ)),
1021 cont.to_owned(),
1022 )
1023 }
1024 Lang::List {
1025 value: args,
1026 spreads,
1027 ..
1028 } if spreads.is_empty() => {
1029 let (body, current_cont) = Translatable::from(cont.clone())
1030 .join_arg_val(args, ",\n ")
1031 .into();
1032 let (typ, _, _) = typing(cont, self).to_tuple();
1033 if let Type::Alias(alias_name, _, _, _) = &typ {
1035 let is_record = cont
1036 .aliases()
1037 .find(|(var, _)| var.get_name() == *alias_name)
1038 .map(|(_, t)| matches!(t, Type::Record(_, _)))
1039 .unwrap_or(false);
1040 if is_record {
1041 return (format!("{}({})", alias_name, body), current_cont);
1042 }
1043 }
1044 let anotation = cont.get_type_anotation(&typ);
1045 cont.get_classes(&typ)
1046 .map(|_| format!("list({}) |> {}", body, anotation))
1047 .unwrap_or(format!("list({}) |> {}", body, anotation))
1048 .to_some()
1049 .map(|s| (s, current_cont))
1050 .unwrap()
1051 }
1052 Lang::List {
1059 value: args,
1060 spreads,
1061 ..
1062 } => {
1063 let mut spreads_iter = spreads.iter();
1064 let first = spreads_iter.next().expect("checked non-empty above");
1065 let (mut base, mut current_cont) = first.to_r(cont);
1066 for spread_expr in spreads_iter {
1067 let (next, next_cont) = spread_expr.to_r(¤t_cont);
1068 base = format!("spread({}, {})", base, next);
1069 current_cont = next_cont;
1070 }
1071 if args.is_empty() {
1072 return (base, current_cont);
1073 }
1074 let (overrides, current_cont) = Translatable::from(current_cont)
1075 .join_arg_val(args, ", ")
1076 .into();
1077 (
1078 format!("spread({}, list({}))", base, overrides),
1079 current_cont,
1080 )
1081 }
1082 Lang::DataFrame { value: args, .. } => {
1083 let (body, current_cont) = Translatable::from(cont.clone())
1084 .join_arg_val(args, ",\n ")
1085 .into();
1086 let (typ, _, _) = typing(cont, self).to_tuple();
1087 let anotation = cont.get_type_anotation(&typ);
1088 cont.get_classes(&typ)
1089 .map(|_| format!("data.frame({}) |> {}", body, anotation))
1090 .unwrap_or(format!("data.frame({}) |> {}", body, anotation))
1091 .to_some()
1092 .map(|s| (s, current_cont))
1093 .unwrap()
1094 }
1095 Lang::If {
1096 condition: cond,
1097 if_block: exp,
1098 else_block: els,
1099 ..
1100 } if els == &Box::new(Lang::Empty(HelpData::default())) => {
1101 Translatable::from(cont.clone())
1102 .add("if(")
1103 .to_r(cond)
1104 .add(") {\n")
1105 .to_r(exp)
1106 .add(" \n}")
1107 .into()
1108 }
1109 Lang::If {
1110 condition: cond,
1111 if_block: exp,
1112 else_block: els,
1113 help_data: _,
1114 } => Translatable::from(cont.clone())
1115 .add("if(")
1116 .to_r(cond)
1117 .add(") {\n")
1118 .to_r(exp)
1119 .add(" \n} else ")
1120 .to_r(els)
1121 .into(),
1122 Lang::Tuple { value: vals, .. } => Translatable::from(cont.clone())
1123 .add("struct(list(")
1124 .join(vals, ", ")
1125 .add("), 'Tuple')")
1126 .into(),
1127 Lang::Assign {
1128 identifier: var,
1129 expression: exp,
1130 ..
1131 } => Translatable::from(cont.clone())
1132 .to_r(var)
1133 .add(" <- ")
1134 .to_r(exp)
1135 .into(),
1136 Lang::Comment { value: txt, .. } => ("#".to_string() + txt, cont.clone()),
1137 Lang::Tag {
1138 name: s, value: t, ..
1139 } => {
1140 let (t_str, new_cont) = t.to_r(cont);
1141 let is_empty = matches!(t.as_ref(), Lang::Empty(_));
1142 let class = match find_union_for_tag(s, cont) {
1149 Some(union_name) => format!("c('{}', '{}', 'Tag', 'list')", s, union_name),
1150 None => format!("c('{}', 'Tag', 'list')", s),
1151 };
1152 let value = if is_empty {
1153 format!("structure(list('{}'), class = {})", s, class)
1154 } else {
1155 format!(
1156 "structure(list('{}', body = {}), class = {})",
1157 s, t_str, class
1158 )
1159 };
1160 (value, new_cont)
1161 }
1162 Lang::Null(_) => ("NULL".to_string(), cont.clone()),
1163 Lang::Empty(_) => ("NA".to_string(), cont.clone()),
1164 Lang::Lines { value: exps, .. } => {
1165 Translatable::from(cont.clone()).join(exps, "\n").into()
1166 }
1167 Lang::Return { value: exp, .. } => Translatable::from(cont.clone())
1168 .add("return ")
1169 .to_r(exp)
1170 .into(),
1171 Lang::Lambda {
1172 parameters: params,
1173 body: bloc,
1174 ..
1175 } => {
1176 let param_names: Vec<String> = params
1177 .iter()
1178 .map(|p: &Lang| match p {
1179 Lang::Variable { name, .. } => name.clone(),
1180 _ => "x".to_string(),
1181 })
1182 .collect();
1183 (
1184 format!(
1185 "function({}) {{ {} }}",
1186 param_names.join(", "),
1187 bloc.to_r(cont).0
1188 ),
1189 cont.clone(),
1190 )
1191 }
1192 Lang::VecBlock { value: bloc, .. } => (bloc.to_string(), cont.clone()),
1193 Lang::Library { value: name, .. } => (format!("library({})", name), cont.clone()),
1194 Lang::Match {
1195 target: exp,
1196 branches,
1197 ..
1198 } => (
1199 to_pattern_match_statement((**exp).clone(), branches, cont),
1200 cont.clone(),
1201 ),
1202 Lang::Exp { value: exp, .. } => (exp.clone(), cont.clone()),
1203 Lang::ForLoop {
1204 identifier: var,
1205 expression: iterator,
1206 body,
1207 ..
1208 } => Translatable::from(cont.clone())
1209 .add("for (")
1210 .to_r_safe(var)
1211 .add(" in ")
1212 .to_r_safe(iterator)
1213 .add(") {\n")
1214 .to_r_safe(body)
1215 .add("\n}")
1216 .into(),
1217 Lang::RFunction {
1218 parameters: vars,
1219 body,
1220 ..
1221 } => Translatable::from(cont.clone())
1222 .add("function (")
1223 .join(vars, ", ")
1224 .add(") \n")
1225 .add(body)
1226 .add("\n")
1227 .into(),
1228 Lang::Signature { .. } => ("".to_string(), cont.clone()),
1229 Lang::TypeConstructor { .. } => ("".to_string(), cont.clone()),
1230 Lang::Alias {
1231 identifier: ident,
1232 target_type: typ,
1233 ..
1234 } => {
1235 let name = Var::from_language(*ident.clone())
1236 .map(|v| v.get_name())
1237 .unwrap_or_default();
1238 let typ_for_dispatch: Type = match typ {
1252 Type::Operator(TypeOperator::Intersection, t1, t2, _) => {
1253 match (t1.reduce(cont), t2.reduce(cont)) {
1254 (record @ Type::Record(_, _), other) if other.has_generic() => record,
1255 (other, record @ Type::Record(_, _)) if other.has_generic() => record,
1256 _ => typ.clone(),
1257 }
1258 }
1259 _ => typ.clone(),
1260 };
1261 match &typ_for_dispatch {
1262 Type::Record(fields, _) => {
1263 let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1264 sorted_fields.sort_by_key(|f| f.get_argument_str());
1265 let params = sorted_fields
1266 .iter()
1267 .map(|f| f.get_argument_str())
1268 .collect::<Vec<_>>()
1269 .join(", ");
1270 let explicit_lines = sorted_fields
1276 .iter()
1277 .map(|f| {
1278 let n = f.get_argument_str();
1279 format!(" if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1280 })
1281 .collect::<Vec<_>>()
1282 .join("\n");
1283 let constructor = format!(
1288 "{name} <- function({params}, .spread = NULL) {{\n explicit <- list()\n{explicit_lines}\n x <- typr_spread_record(explicit, .spread)\n as.{name}(x)\n}}"
1289 );
1290 let mut seen_names: std::collections::HashSet<String> =
1305 std::collections::HashSet::new();
1306 let candidates: Vec<(String, Type)> = cont
1307 .aliases()
1308 .map(|(var, typ)| (var.get_name(), typ.clone()))
1309 .chain(cont.record_aliases.iter().cloned())
1310 .filter(|(other_name, _)| seen_names.insert(other_name.clone()))
1311 .collect();
1312 let mut supertype_entries: Vec<(String, usize)> = candidates
1313 .into_iter()
1314 .filter_map(|(other_name, typ)| {
1315 if other_name == name {
1316 return None;
1317 }
1318 if let Type::Record(other_fields, _) = typ {
1319 if fields.is_superset(&other_fields) && other_fields != *fields
1320 {
1321 Some((other_name, other_fields.len()))
1322 } else {
1323 None
1324 }
1325 } else {
1326 None
1327 }
1328 })
1329 .collect();
1330 supertype_entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
1333 let supertype_class_str = if supertype_entries.is_empty() {
1334 String::new()
1335 } else {
1336 let names = supertype_entries
1337 .iter()
1338 .map(|(n, _)| format!("\"{n}\""))
1339 .collect::<Vec<_>>()
1340 .join(", ");
1341 format!(", {names}")
1342 };
1343 let annotator = format!(
1347 "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}}"
1348 );
1349 let fields_quoted = sorted_fields
1350 .iter()
1351 .map(|f| format!("\"{}\"", f.get_argument_str()))
1352 .collect::<Vec<_>>()
1353 .join(", ");
1354 let field_checks = sorted_fields
1358 .iter()
1359 .filter_map(|f| {
1360 let n = f.get_argument_str();
1361 record_field_class(&f.body_type(), cont).map(|cls| {
1362 format!(
1363 " if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: field '{n}' must be of class {cls}\")"
1364 )
1365 })
1366 })
1367 .collect::<Vec<_>>()
1368 .join("\n");
1369 let field_checks_block = if field_checks.is_empty() {
1370 String::new()
1371 } else {
1372 format!("{field_checks}\n")
1373 };
1374 let validator = format!(
1378 "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}}"
1379 );
1380 (
1381 format!("{constructor}\n{annotator}\n{validator}"),
1382 cont.clone(),
1383 )
1384 }
1385 Type::Vec(VecType::DataFrame, size, fields_type, _)
1392 if matches!(fields_type.as_ref(), Type::Record(_, _)) =>
1393 {
1394 use crate::components::r#type::tint::Tint;
1395 let fields = match fields_type.as_ref() {
1396 Type::Record(fields, _) => fields,
1397 _ => unreachable!(),
1398 };
1399 let mut sorted_fields: Vec<&ArgumentType> = fields.iter().collect();
1400 sorted_fields.sort_by_key(|f| f.get_argument_str());
1401 let params = sorted_fields
1402 .iter()
1403 .map(|f| f.get_argument_str())
1404 .collect::<Vec<_>>()
1405 .join(", ");
1406 let explicit_lines = sorted_fields
1407 .iter()
1408 .map(|f| {
1409 let n = f.get_argument_str();
1410 format!(" if (!missing({n})) explicit[[\"{n}\"]] <- {n}")
1411 })
1412 .collect::<Vec<_>>()
1413 .join("\n");
1414 let constructor = format!(
1418 "{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}}"
1419 );
1420 let annotator = format!(
1423 "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}}"
1424 );
1425 let fields_quoted = sorted_fields
1426 .iter()
1427 .map(|f| format!("\"{}\"", f.get_argument_str()))
1428 .collect::<Vec<_>>()
1429 .join(", ");
1430 let field_checks = sorted_fields
1433 .iter()
1434 .filter_map(|f| {
1435 let n = f.get_argument_str();
1436 record_field_class(&f.body_type(), cont).map(|cls| {
1437 format!(
1438 " if (!inherits(x[[\"{n}\"]], \"{cls}\")) stop(\"Validation failed for type {name}: column '{n}' must be of class {cls}\")"
1439 )
1440 })
1441 })
1442 .collect::<Vec<_>>()
1443 .join("\n");
1444 let field_checks_block = if field_checks.is_empty() {
1445 String::new()
1446 } else {
1447 format!("{field_checks}\n")
1448 };
1449 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1453 format!(
1454 " if (nrow(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected {n} rows, got \", nrow(x)))\n"
1455 )
1456 } else {
1457 String::new()
1458 };
1459 let validator = format!(
1460 "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}}"
1461 );
1462 (
1463 format!("{constructor}\n{annotator}\n{validator}"),
1464 cont.clone(),
1465 )
1466 }
1467 Type::Vec(VecType::Vector, size, elem_type, _) => {
1475 use crate::components::r#type::tint::Tint;
1476 let constructor =
1477 format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1478 let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1479 format!(
1480 " if (!inherits(x, \"{cls}\")) stop(\"Validation failed for type {name}: expected vector of {cls}\")\n"
1481 )
1482 }).unwrap_or_default();
1483 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1484 format!(
1485 " if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1486 )
1487 } else {
1488 String::new()
1489 };
1490 let validator = format!(
1491 "validate_{name} <- function(x) {{\n{elem_check}{size_check} x\n}}"
1492 );
1493 (format!("{constructor}\n{validator}"), cont.clone())
1494 }
1495 Type::Vec(VecType::S3, size, elem_type, _)
1506 | Type::Vec(VecType::Array, size, elem_type, _) => {
1507 use crate::components::r#type::tint::Tint;
1508 let constructor = format!(
1509 "{name} <- function(x) {{\n if (!inherits(x, \"typed_vec\")) x <- typed_vec(x)\n as.{name}(x)\n}}"
1510 );
1511 let annotator = format!(
1512 "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}}"
1513 );
1514 let elem_check = record_field_class(elem_type.as_ref(), cont).map(|cls| {
1515 format!(
1516 " if (!all(vapply(x$data, inherits, logical(1), \"{cls}\"))) stop(\"Validation failed for type {name}: expected elements of class {cls}\")\n"
1517 )
1518 }).unwrap_or_default();
1519 let size_check = if let Type::Integer(Tint::Val(n), _) = size.as_ref() {
1520 format!(
1521 " if (length(x) != {n}) stop(paste0(\"Validation failed for type {name}: expected length {n}, got \", length(x)))\n"
1522 )
1523 } else {
1524 String::new()
1525 };
1526 let validator = format!(
1527 "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}}"
1528 );
1529 (
1530 format!("{constructor}\n{annotator}\n{validator}"),
1531 cont.clone(),
1532 )
1533 }
1534 Type::Operator(_, _, _, _) => {
1535 let union_name = &name;
1539 let members = flatten_operator_union(typ);
1540 let mut members_vec: Vec<Type> = members.into_iter().collect();
1542 members_vec.sort_by_key(|t| t.pretty2());
1543 let constructors: Vec<String> = members_vec
1544 .iter()
1545 .filter_map(|member| match member {
1546 Type::Tag(variant_name, inner, _) => Some(tag_variant_pipeline(
1547 variant_name,
1548 union_name,
1549 inner.as_ref(),
1550 )),
1551 Type::Alias(alias_name, _, _, _) => {
1552 let record_fields = cont
1554 .aliases()
1555 .find(|(var, _)| var.get_name() == *alias_name)
1556 .and_then(|(_, t)| {
1557 if let Type::Record(fields, _) = t {
1558 Some(fields.clone())
1559 } else {
1560 None
1561 }
1562 });
1563 if let Some(fields) = record_fields {
1564 let mut sorted: Vec<&ArgumentType> =
1565 fields.iter().collect();
1566 sorted.sort_by_key(|f| f.get_argument_str());
1567 let params = sorted
1568 .iter()
1569 .map(|f| f.get_argument_str())
1570 .collect::<Vec<_>>()
1571 .join(", ");
1572 let field_args = sorted
1573 .iter()
1574 .map(|f| {
1575 let n = f.get_argument_str();
1576 format!("{n} = {n}")
1577 })
1578 .collect::<Vec<_>>()
1579 .join(", ");
1580 Some(format!(
1581 "{alias_name} <- function({params}) {{\n structure(list({field_args}), class = c(\"{alias_name}\", \"{union_name}\", \"list\"))\n}}"
1582 ))
1583 } else {
1584 None
1585 }
1586 }
1587 _ => None,
1588 })
1589 .collect();
1590 (constructors.join("\n"), cont.clone())
1591 }
1592 Type::Integer(tint, _) => {
1593 use crate::components::r#type::tint::Tint;
1594 let validator = match tint {
1595 Tint::Val(i) => format!(
1596 "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}}"
1597 ),
1598 Tint::Unknown => format!(
1599 "validate_{name} <- function(x) {{\n if (!is.integer(x)) stop(\"Validation failed for type {name}: expected int\")\n x\n}}"
1600 ),
1601 };
1602 let constructor =
1603 format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1604 (format!("{constructor}\n{validator}"), cont.clone())
1605 }
1606 Type::Char(tchar, _) => {
1607 use crate::components::r#type::tchar::Tchar;
1608 let validator = match tchar {
1609 Tchar::Val(s) => format!(
1610 "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}}"
1611 ),
1612 Tchar::Unknown => format!(
1613 "validate_{name} <- function(x) {{\n if (!is.character(x)) stop(\"Validation failed for type {name}: expected char\")\n x\n}}"
1614 ),
1615 };
1616 let constructor =
1617 format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1618 (format!("{constructor}\n{validator}"), cont.clone())
1619 }
1620 Type::Boolean(tbool, _) => {
1621 use crate::components::r#type::tbool::Tbool;
1622 let validator = match tbool {
1623 Tbool::Val(b) => {
1624 let r_val = if *b { "TRUE" } else { "FALSE" };
1625 format!(
1626 "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}}"
1627 )
1628 }
1629 Tbool::Unknown => format!(
1630 "validate_{name} <- function(x) {{\n if (!is.logical(x)) stop(\"Validation failed for type {name}: expected bool\")\n x\n}}"
1631 ),
1632 };
1633 let constructor =
1634 format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1635 (format!("{constructor}\n{validator}"), cont.clone())
1636 }
1637 Type::Number(tnum, _) => {
1638 use crate::components::r#type::tnumber::Tnum;
1639 let validator = match tnum {
1640 Tnum::Val(v) => format!(
1641 "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}}"
1642 ),
1643 Tnum::Unknown => format!(
1644 "validate_{name} <- function(x) {{\n if (!is.numeric(x)) stop(\"Validation failed for type {name}: expected num\")\n x\n}}"
1645 ),
1646 };
1647 let constructor =
1648 format!("{name} <- function(x) {{\n validate_{name}(x)\n}}");
1649 (format!("{constructor}\n{validator}"), cont.clone())
1650 }
1651 Type::Tag(tag_name, inner_type, _) => {
1652 let body_validation = tag_body_validation(&name, inner_type.as_ref());
1653 let validator = format!(
1654 "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}}"
1655 );
1656 (validator, cont.clone())
1657 }
1658 Type::Alias(target_name, ..) => {
1664 (format!("{name} <- {target_name}"), cont.clone())
1665 }
1666 _ => ("".to_string(), cont.clone()),
1667 }
1668 }
1669 Lang::UnionConstructor {
1670 variant_name,
1671 fields,
1672 ..
1673 } => {
1674 if fields.is_empty() {
1675 (format!("{}()", variant_name), cont.clone())
1676 } else {
1677 let (body, current_cont) = Translatable::from(cont.clone())
1678 .join_arg_val(fields, ", ")
1679 .into();
1680 (format!("{}({})", variant_name, body), current_cont)
1681 }
1682 }
1683 Lang::KeyValue {
1684 key: k, value: v, ..
1685 } => (format!("{} = {}", k, v.to_r(cont).0), cont.clone()),
1686 Lang::Vector { value: vals, .. } => {
1687 let res = "c(".to_string()
1688 + &vals
1689 .iter()
1690 .map(|x: &Lang| x.to_r(cont).0)
1691 .collect::<Vec<_>>()
1692 .join(", ")
1693 + ")";
1694 (res, cont.to_owned())
1695 }
1696 Lang::Not { value: exp, .. } => (format!("!{}", exp.to_r(cont).0), cont.clone()),
1697 Lang::Sequence { body: vals, .. } => {
1698 let res = if !vals.is_empty() {
1699 "c(".to_string()
1700 + &vals
1701 .iter()
1702 .map(|x: &Lang| "list(".to_string() + &x.to_r(cont).0 + ")")
1703 .collect::<Vec<_>>()
1704 .join(", ")
1705 + ")"
1706 } else {
1707 "c(list())".to_string()
1708 };
1709 (res, cont.to_owned())
1710 }
1711 Lang::TestBlock {
1712 value: body,
1713 help_data: h,
1714 } => {
1715 let file_name = h
1716 .get_file_data()
1717 .map(|(name, _)| format!("test-{}", name))
1718 .unwrap_or_else(|| "test-unknown".to_string())
1719 .replace("TypR/", "")
1720 .replace(".ty", ".R");
1721
1722 let file_path = format!("tests/testthat/{}", file_name);
1723 let body_str = body.to_r(cont).0;
1724 let content = if cont.test_preamble.is_empty() {
1728 body_str
1729 } else {
1730 format!("{}\n{}", cont.test_preamble.join("\n"), body_str)
1731 };
1732
1733 let _ = write_output_file(&file_path, &content);
1734 ("".to_string(), cont.clone())
1735 }
1736 Lang::JSBlock(exp, _id, _h) => {
1737 let js_cont = Context::default(); let res = exp.to_js(&js_cont).0;
1739 (format!("'{}{}'", JS_HEADER, res), cont.clone())
1740 }
1741 Lang::WhileLoop {
1742 condition, body, ..
1743 } => (
1744 format!(
1745 "while ({}) {{\n{}\n}}",
1746 condition.to_r(cont).0,
1747 body.to_r(cont).0
1748 ),
1749 cont.clone(),
1750 ),
1751 Lang::Loop { body, .. } => (
1752 format!("while (TRUE) {{\n{}\n}}", body.to_r(cont).0),
1753 cont.clone(),
1754 ),
1755 Lang::Break(_) => ("break".to_string(), cont.clone()),
1756 Lang::Next(_) => ("next".to_string(), cont.clone()),
1757 Lang::NA(_) => ("NA".to_string(), cont.clone()),
1758 Lang::Module {
1759 name,
1760 body,
1761 module_position: position,
1762 config,
1763 ..
1764 } => {
1765 let name_str = if (name == "main") && (config.environment == Environment::Project) {
1766 "a_main"
1767 } else {
1768 name
1769 };
1770
1771 let writes_own_file = matches!(position, ModulePosition::External)
1775 && config.environment == Environment::Project;
1776 if writes_own_file {
1777 push_include_frame();
1778 }
1779
1780 let module_expr = if body.len() > 1 {
1785 Lang::Lines {
1786 value: body.to_vec(),
1787 help_data: HelpData::default(),
1788 }
1789 } else {
1790 body.first()
1791 .cloned()
1792 .unwrap_or(Lang::Empty(HelpData::default()))
1793 };
1794 let mut inner_cont =
1795 typing(&cont.clone().set_in_module_body(), &module_expr).context;
1796
1797 if cont.get_test_mode() {
1802 let preamble: Vec<String> = body
1803 .iter()
1804 .filter_map(|lang| match lang {
1805 Lang::Let {
1806 variable: var,
1807 is_testable: true,
1808 ..
1809 } => Var::from_language(*var.clone()).map(|v| {
1810 let raw = v.get_name();
1811 format!("{} <- {}$`.test_{}`", raw, name_str, raw)
1812 }),
1813 _ => None,
1814 })
1815 .collect();
1816 inner_cont = inner_cont.set_test_preamble(preamble);
1817 }
1818
1819 let (import_langs, runtime_langs): (Vec<_>, Vec<_>) =
1824 body.iter().partition(|lang| {
1825 matches!(
1826 lang,
1827 Lang::ModuleImport { .. }
1828 | Lang::Module {
1829 module_position: ModulePosition::External,
1830 ..
1831 }
1832 )
1833 });
1834
1835 let imports_parts: Vec<String> = import_langs
1836 .iter()
1837 .map(|lang| lang.to_r(&inner_cont).0)
1838 .filter(|s| !s.is_empty())
1839 .collect();
1840 let imports_preamble = if imports_parts.is_empty() {
1841 String::new()
1842 } else {
1843 imports_parts.join("\n") + "\n"
1844 };
1845
1846 let body_content = runtime_langs
1847 .iter()
1848 .map(|lang| lang.to_r(&inner_cont).0)
1849 .collect::<Vec<_>>()
1850 .join("\n");
1851
1852 let mut exports: Vec<String> = Vec::new();
1854 let mut generics: Vec<String> = Vec::new();
1855 let mut generic_exports: Vec<String> = Vec::new();
1856 let mut package_exports: Vec<String> = Vec::new();
1858
1859 for lang in body.iter() {
1860 if let Lang::Let {
1861 variable: var,
1862 is_public: true,
1863 is_export,
1864 ..
1865 } = lang
1866 {
1867 if let Some(v) = Var::from_language(*var.clone()) {
1868 let raw_name = v.get_name();
1869 let typed_name = v.clone().display_type(&inner_cont).get_name();
1877
1878 exports.push(format!("{}${} <- {}", name_str, typed_name, typed_name));
1880
1881 if *is_export {
1883 package_exports.push(format!(
1884 "#' @export\n{} <- {}${}",
1885 raw_name, name_str, typed_name
1886 ));
1887 }
1888
1889 let var_type = v.get_type();
1891 if !var_type.is_empty() && typed_name != raw_name {
1892 let class_name = inner_cont.get_class_unquoted(&var_type);
1893 exports.push(format!(
1894 "registerS3method(\"{}\", \"{}\", {})",
1895 raw_name, class_name, typed_name
1896 ));
1897
1898 let generic_def = format!(
1899 "{} <- function(x, ...) UseMethod(\"{}\")",
1900 raw_name, raw_name
1901 );
1902 if !generics.contains(&generic_def) {
1903 generics.push(generic_def);
1904 generic_exports
1905 .push(format!("{}${} <- {}", name_str, raw_name, raw_name));
1906 }
1907
1908 generic_exports
1917 .push(format!("{} <- {}${}", typed_name, name_str, typed_name));
1918 }
1919 }
1920 }
1921 if cont.get_test_mode() {
1925 if let Lang::Let {
1926 variable: var,
1927 is_testable: true,
1928 ..
1929 } = lang
1930 {
1931 if let Some(v) = Var::from_language(*var.clone()) {
1932 let raw_name = v.get_name();
1933 let typed_name = v.clone().display_type(&inner_cont).get_name();
1937 exports.push(format!(
1938 "{}$`.test_{}` <- {}",
1939 name_str, raw_name, typed_name
1940 ));
1941 }
1942 }
1943 }
1944 if let Lang::Alias {
1946 identifier: var,
1947 is_public: true,
1948 ..
1949 } = lang
1950 {
1951 if let Some(v) = Var::from_language(*var.clone()) {
1952 let alias_name = v.get_name();
1953 exports.push(format!("{}${} <- {}", name_str, alias_name, alias_name));
1954 }
1955 }
1956 }
1957
1958 let exports_str = if exports.is_empty() {
1959 String::new()
1960 } else {
1961 "\n".to_string() + &exports.join("\n")
1962 };
1963
1964 let generics_defs_str = if generics.is_empty() {
1965 String::new()
1966 } else {
1967 generics.join("\n") + "\n"
1968 };
1969
1970 let generic_exports_str = if generic_exports.is_empty() {
1971 String::new()
1972 } else {
1973 "\n".to_string() + &generic_exports.join("\n")
1974 };
1975
1976 let package_exports_str = if package_exports.is_empty() {
1977 String::new()
1978 } else {
1979 "\n".to_string() + &package_exports.join("\n")
1980 };
1981
1982 let content = format!(
1983 "{}{}{} <- new.env(parent = emptyenv())\nlocal({{\n{}{}\n}}){}{}",
1984 generics_defs_str,
1985 imports_preamble,
1986 name_str,
1987 body_content,
1988 exports_str,
1989 generic_exports_str,
1990 package_exports_str
1991 );
1992
1993 match (position, config.environment) {
1994 (ModulePosition::Internal, _) => (content, cont.clone()),
1995 (ModulePosition::External, Environment::Wasm) => {
1997 let file_path = format!("{}.R", name_str);
1998 let _ = write_output_file(&file_path, &content);
1999 (content, cont.clone())
2000 }
2001 (ModulePosition::External, Environment::StandAlone)
2002 | (ModulePosition::External, Environment::Repl) => {
2003 let file_path = format!("{}.R", name_str);
2004 let _ = write_output_file(&file_path, &content);
2005 (format!("source('{}')", file_path), cont.clone())
2006 }
2007 (ModulePosition::External, Environment::Project) => {
2008 let file_path = format!("R/{}.R", name_str);
2009 let nested = pop_include_frame();
2012 let nested_includes = nested
2013 .iter()
2014 .map(|f| format!("#' @include {}\n", f))
2015 .collect::<String>();
2016 let project_preamble = "#' @include std.R\n#' @include generic_functions.R\n#' @include types.R\n";
2017 let _ = write_output_file(
2018 &file_path,
2019 &format!("{}{}{}", project_preamble, nested_includes, content),
2020 );
2021 register_include(&format!("{}.R", name_str));
2024 (String::new(), cont.clone())
2025 }
2026 }
2027 }
2028 Lang::UseModule {
2029 module_path,
2030 selector,
2031 ..
2032 } => {
2033 use crate::components::language::use_lang::UseSelector;
2034
2035 let r_path = module_path.join("$");
2037
2038 let mod_type_opt = (|| {
2040 let root = cont
2041 .get_type_from_variable(&Var::from_name(&module_path[0]))
2042 .ok()?;
2043 let mut current = root;
2044 for seg in module_path.iter().skip(1) {
2045 current = current
2046 .to_module_type()
2047 .ok()?
2048 .get_type_from_name(seg)
2049 .ok()?;
2050 }
2051 current.to_module_type().ok()
2052 })();
2053
2054 let bindings: Vec<String> = match selector {
2055 UseSelector::Wildcard => mod_type_opt
2056 .map(|mt| {
2057 mt.get_public_members()
2058 .iter()
2059 .map(|m| {
2060 let name = m.get_argument_str();
2061 format!("{} <- {}${}", name, r_path, name)
2062 })
2063 .collect()
2064 })
2065 .unwrap_or_default(),
2066 UseSelector::Items(items) => items
2067 .iter()
2068 .map(|item| {
2069 let local_name = item.alias.as_deref().unwrap_or(&item.name);
2070 format!("{} <- {}${}", local_name, r_path, item.name)
2071 })
2072 .collect(),
2073 };
2074
2075 (bindings.join("\n"), cont.clone())
2076 }
2077 Lang::ModuleImport { .. } => ("".to_string(), cont.clone()),
2078 Lang::ConstructorCall {
2084 type_name,
2085 fields,
2086 spreads,
2087 ..
2088 } if type_name == "Self" => {
2089 let base_typ = spreads
2090 .first()
2091 .map(|e| typing(cont, e).value)
2092 .unwrap_or_else(|| Type::Any(HelpData::default()));
2093 let resolved_name = match &base_typ {
2094 Type::Alias(alias_name, ..) => cont
2095 .aliases()
2096 .find(|(var, _)| var.get_name() == *alias_name)
2097 .map(|(_, t)| matches!(t, Type::Record(_, _)))
2098 .unwrap_or(false)
2099 .then(|| alias_name.clone()),
2100 _ => None,
2101 };
2102 match (resolved_name, spreads.first()) {
2103 (Some(name), Some(spread_expr)) => {
2104 let (spread_r, current_cont) = spread_expr.to_r(cont);
2108 let (body, current_cont) = if fields.is_empty() {
2109 (format!(".spread = {}", spread_r), current_cont)
2110 } else {
2111 let (overrides, next_cont) = Translatable::from(current_cont)
2112 .join_arg_val(fields, ", ")
2113 .into();
2114 (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2115 };
2116 (format!("{}({})", name, body), current_cont)
2117 }
2118 (None, Some(spread_expr)) => {
2119 let (base, current_cont) = spread_expr.to_r(cont);
2123 if fields.is_empty() {
2124 (base, current_cont)
2125 } else {
2126 let (overrides, current_cont) = Translatable::from(current_cont)
2127 .join_arg_val(fields, ", ")
2128 .into();
2129 (
2130 format!("spread({}, list({}))", base, overrides),
2131 current_cont,
2132 )
2133 }
2134 }
2135 (_, None) => ("NULL".to_string(), cont.clone()),
2138 }
2139 }
2140 Lang::ConstructorCall {
2141 module_path,
2142 type_name,
2143 fields,
2144 spreads,
2145 ..
2146 } if !spreads.is_empty() => {
2147 let spread_expr = spreads.first().expect("checked non-empty above");
2153 let (spread_r, current_cont) = spread_expr.to_r(cont);
2154 let qualified = if module_path.is_empty() {
2155 type_name.clone()
2156 } else {
2157 format!("{}${}", module_path.join("$"), type_name)
2158 };
2159 let (body, current_cont) = if fields.is_empty() {
2160 (format!(".spread = {}", spread_r), current_cont)
2161 } else {
2162 let (overrides, next_cont) = Translatable::from(current_cont)
2163 .join_arg_val(fields, ", ")
2164 .into();
2165 (format!("{}, .spread = {}", overrides, spread_r), next_cont)
2166 };
2167 (format!("{}({})", qualified, body), current_cont)
2168 }
2169 Lang::ConstructorCall {
2170 module_path,
2171 type_name,
2172 fields,
2173 spread,
2174 help_data: h,
2175 ..
2176 } => {
2177 let all_fields: Vec<ArgumentValue> = match spread {
2181 Some((spread_path, spread_var, _)) => {
2182 let resolved_alias = if module_path.is_empty() {
2183 cont.get_type_from_aliases(&Var::from_name(type_name))
2184 } else {
2185 resolve_module_member_type(cont, module_path, type_name)
2186 };
2187 let record_fields = resolved_alias.and_then(|t| match t.reduce(cont) {
2188 Type::Record(fields, _) => Some(fields),
2189 _ => None,
2190 });
2191 let receiver = {
2192 let qualifier = spread_path.split_first().map(|(first, rest)| {
2193 rest.iter()
2194 .fold(Var::from_name(first).to_language(), |acc, seg| {
2195 Lang::Operator {
2196 operator: Op::Dollar(h.clone()),
2197 rhs: Box::new(acc),
2198 lhs: Box::new(Var::from_name(seg).to_language()),
2199 help_data: h.clone(),
2200 }
2201 })
2202 });
2203 match qualifier {
2204 Some(qualifier) => Lang::Operator {
2205 operator: Op::Dollar(h.clone()),
2206 rhs: Box::new(qualifier),
2207 lhs: Box::new(Var::from_name(spread_var).to_language()),
2208 help_data: h.clone(),
2209 },
2210 None => Var::from_name(spread_var).to_language(),
2211 }
2212 };
2213 let provided: std::collections::HashSet<String> =
2214 fields.iter().map(|f| f.get_argument()).collect();
2215 let synthetic = record_fields
2216 .into_iter()
2217 .flatten()
2218 .filter(|rf| !provided.contains(&rf.get_argument_str()))
2219 .map(|rf| {
2220 let field_access = Lang::Operator {
2221 operator: Op::Dollar(h.clone()),
2222 rhs: Box::new(receiver.clone()),
2223 lhs: Box::new(
2224 Var::from_name(&rf.get_argument_str()).to_language(),
2225 ),
2226 help_data: h.clone(),
2227 };
2228 ArgumentValue(rf.get_argument_str(), field_access)
2229 });
2230 fields.iter().cloned().chain(synthetic).collect()
2231 }
2232 None => fields.clone(),
2233 };
2234 let (body, current_cont) = Translatable::from(cont.clone())
2235 .join_arg_val(&all_fields, ", ")
2236 .into();
2237 let qualified = if module_path.is_empty() {
2238 type_name.clone()
2239 } else {
2240 format!("{}${}", module_path.join("$"), type_name)
2241 };
2242 (format!("{}({})", qualified, body), current_cont)
2243 }
2244 Lang::ArrayConstructorCall {
2245 type_name,
2246 elements,
2247 help_data: h,
2248 } => {
2249 let resolved_alias = cont
2250 .get_type_from_aliases(&Var::from_name(type_name))
2251 .map(|t| t.reduce(cont));
2252 if let Some(Type::Vec(VecType::Vector, ..)) = resolved_alias {
2253 let inner = elements
2258 .iter()
2259 .map(|el| el.to_r(cont).0)
2260 .collect::<Vec<_>>()
2261 .join(", ");
2262 (format!("{}(c({}))", type_name, inner), cont.clone())
2263 } else {
2264 let temp_array = Lang::Array {
2265 value: elements.clone(),
2266 help_data: h.clone(),
2267 };
2268 let typ = temp_array.typing(cont).value;
2269 let dimension = ArrayType::try_from(typ)
2270 .expect("array constructor call should have an array type")
2271 .get_shape()
2272 .map(|sha| format!("c({})", sha))
2273 .unwrap_or_else(|| "c(0)".to_string());
2274 let lin_array = temp_array
2275 .linearize_array()
2276 .iter()
2277 .map(|lang| lang.to_r(cont).0)
2278 .collect::<Vec<_>>()
2279 .join(", ");
2280 let inner = if lin_array.is_empty() {
2281 format!("typed_vec(dim = {})", dimension)
2282 } else {
2283 format!("typed_vec({}, dim = {})", lin_array, dimension)
2284 };
2285 (format!("{}({})", type_name, inner), cont.clone())
2286 }
2287 }
2288 Lang::Import { .. } | Lang::Test { .. } | Lang::Use { .. } => {
2289 ("".to_string(), cont.clone())
2290 }
2291 Lang::ValidatingCast {
2292 expression,
2293 type_name,
2294 literal_type,
2295 ..
2296 } => {
2297 let expr_r = expression.to_r(cont).0;
2298 match literal_type {
2299 Some(t) => (
2304 format!("{} |> {}", expr_r, cont.get_type_anotation(t)),
2305 cont.clone(),
2306 ),
2307 None => (format!("validate_{}({})", type_name, expr_r), cont.clone()),
2308 }
2309 }
2310 _ => ("".to_string(), cont.clone()),
2311 };
2312
2313 result
2314 }
2315}
2316
2317#[cfg(test)]
2318mod tests {
2319 use crate::components::context::config::{Config, Environment};
2320 use crate::components::context::Context;
2321 use crate::components::error_message::help_data::HelpData;
2322 use crate::components::language::{Lang, ModulePosition};
2323 use crate::processes::transpiling::translatable::RTranslatable;
2324 use crate::utils::fluent_parser::FluentParser;
2325
2326 #[test]
2327 fn test_escape_r_string() {
2328 use super::escape_r_string;
2329 assert_eq!(escape_r_string("hello"), r#""hello""#);
2330 assert_eq!(escape_r_string(r#"say "hi""#), r#""say \"hi\"""#);
2331 assert_eq!(escape_r_string(r"a\b"), r#""a\\b""#);
2332 assert_eq!(escape_r_string("line1\nline2"), r#""line1\nline2""#);
2333 }
2334
2335 #[test]
2336 fn test_validating_cast_transpiles_to_validate_call() {
2337 let r_code = FluentParser::new()
2338 .push("type Person <- list { name: char, age: int };")
2339 .run()
2340 .check_transpiling("x as! Person");
2341 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2342 assert!(
2343 r_str.contains("validate_Person(x)"),
2344 "expected validate_Person(x), got: {}",
2345 r_str
2346 );
2347 }
2348
2349 #[test]
2350 fn test_validating_cast_type_is_alias() {
2351 let typ = FluentParser::new()
2352 .push("type Person <- list { name: char, age: int };")
2353 .run()
2354 .check_typing("x as! Person");
2355 assert!(
2356 typ.pretty2().contains("Person"),
2357 "expected Alias(Person), got: {}",
2358 typ.pretty2()
2359 );
2360 }
2361
2362 #[test]
2363 fn test_validating_cast_literal_array_type() {
2364 let r_code = FluentParser::new().check_transpiling("c() as! [Any, int]");
2365 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2366 assert!(
2367 r_str.contains("c() |> as.Array0()"),
2368 "expected c() |> as.Array0(), got: {}",
2369 r_str
2370 );
2371 }
2372
2373 #[test]
2374 fn test_validating_cast_literal_vec_and_array_keywords() {
2375 let r_code = FluentParser::new().check_transpiling("c() as! Vec[Any, int]");
2378 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2379 assert!(
2380 r_str.contains("c() |> as.Array0()"),
2381 "expected c() |> as.Array0(), got: {}",
2382 r_str
2383 );
2384 }
2385
2386 #[test]
2387 fn test_validating_cast_literal_type_dedup() {
2388 let r_code = FluentParser::new()
2391 .push("let a <- c() as! [Any, int];")
2392 .run()
2393 .push("let b <- c() as! [Any, int];")
2394 .run()
2395 .get_r_code();
2396 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2397 assert_eq!(
2398 r_str.matches("as.Array0()").count(),
2399 2,
2400 "expected both casts to reuse as.Array0, got: {}",
2401 r_str
2402 );
2403 }
2404
2405 #[test]
2406 fn test_alias_record_generates_validator() {
2407 let r_code =
2408 FluentParser::new().check_transpiling("type Person <- list { name: char, age: int };");
2409 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2410 assert!(
2411 r_str.contains("validate_Person <- function(x)"),
2412 "expected validator function, got: {}",
2413 r_str
2414 );
2415 assert!(
2416 r_str.contains("required_fields"),
2417 "expected field validation, got: {}",
2418 r_str
2419 );
2420 }
2421
2422 #[test]
2423 fn test_record_kinded_generic_param_transpiles_without_panic() {
2424 let fp = FluentParser::new()
2431 .push("type Animator<%T> <- %T & list { extra: int };")
2432 .run()
2433 .push("let combine <- fn(target: %T): %T { let more <- :{ extra = 1 }; :{ ...target, ...more } };")
2434 .run();
2435 assert_eq!(fp.get_last_log(), "The logs are empty");
2436 let r_code = fp
2437 .get_r_code()
2438 .iter()
2439 .cloned()
2440 .collect::<Vec<_>>()
2441 .join("\n");
2442 assert!(
2443 r_code.contains("spread(target, more)"),
2444 "expected the spread merge to transpile, got:\n{}",
2445 r_code
2446 );
2447 }
2448
2449 #[test]
2450 fn test_generic_intersection_alias_generates_validator() {
2451 let r_code = FluentParser::new()
2460 .check_transpiling("type Animator<%T> <- %T & list { animations: int };");
2461 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2462 assert!(
2463 r_str.contains("Animator <- function(animations, .spread = NULL)"),
2464 "expected a constructor for the concrete side's fields, got:\n{}",
2465 r_str
2466 );
2467 assert!(
2468 r_str.contains("as.Animator <- function(x)"),
2469 "expected an annotator, got:\n{}",
2470 r_str
2471 );
2472 assert!(
2473 r_str.contains("validate_Animator <- function(x)")
2474 && r_str.contains("required_fields <- c(\"animations\")"),
2475 "expected a validator checking the concrete field, got:\n{}",
2476 r_str
2477 );
2478 }
2479
2480 #[test]
2481 fn test_external_module_project_generates_include() {
2482 use super::{reset_include_stack, take_main_includes};
2483 reset_include_stack();
2484 let module = Lang::Module {
2485 name: "MyModule".to_string(),
2486 body: vec![],
2487 module_position: ModulePosition::External,
2488 config: Config::default().set_environment(Environment::Project),
2489 help_data: HelpData::default(),
2490 };
2491 let context = Context::default().set_environment(Environment::Project);
2492 let (r_code, _) = module.to_r(&context);
2493 assert_eq!(r_code, "", "got: {}", r_code);
2496 let includes = take_main_includes();
2497 assert!(
2498 includes.contains(&"MyModule.R".to_string()),
2499 "expected MyModule.R to be registered, got: {:?}",
2500 includes
2501 );
2502 }
2503
2504 #[test]
2505 fn test_module_transpilation_with_pub() {
2506 let r_code = FluentParser::new()
2507 .push("module Math { let sq <- 2; @pub let pi <- 3; };")
2508 .run()
2509 .get_r_code()
2510 .iter()
2511 .cloned()
2512 .collect::<Vec<_>>()
2513 .join("\n");
2514 assert!(
2515 r_code.contains("Math <- new.env(parent = emptyenv())"),
2516 "missing env init: {}",
2517 r_code
2518 );
2519 assert!(
2520 r_code.contains("local({"),
2521 "missing local block: {}",
2522 r_code
2523 );
2524 assert!(
2525 r_code.contains("Math$pi <- pi"),
2526 "missing public export: {}",
2527 r_code
2528 );
2529 assert!(
2530 !r_code.contains("Math$sq"),
2531 "private member should not be exported: {}",
2532 r_code
2533 );
2534 }
2535
2536 #[test]
2537 fn test_testable_member_hidden_in_normal_build() {
2538 let r_code = FluentParser::new()
2540 .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
2541 .run()
2542 .get_r_code()
2543 .iter()
2544 .cloned()
2545 .collect::<Vec<_>>()
2546 .join("\n");
2547 assert!(
2548 !r_code.contains(".test_sq"),
2549 "testable member must not be exposed in a normal build: {}",
2550 r_code
2551 );
2552 }
2553
2554 #[test]
2555 fn test_testable_member_exposed_in_test_build() {
2556 let r_code = FluentParser::new()
2558 .set_context(Context::empty().set_test_mode(true))
2559 .push("module Math { @testable let sq <- fn(x: int): int { x * x }; };")
2560 .run()
2561 .get_r_code()
2562 .iter()
2563 .cloned()
2564 .collect::<Vec<_>>()
2565 .join("\n");
2566 assert!(
2567 r_code.contains("Math$`.test_sq` <- sq"),
2568 "testable member must be exposed in a test build: {}",
2569 r_code
2570 );
2571 }
2572
2573 #[test]
2574 fn test_module_transpilation_no_pub() {
2575 let r_code = FluentParser::new()
2576 .push("module Empty { let x <- 1; };")
2577 .run()
2578 .get_r_code()
2579 .iter()
2580 .cloned()
2581 .collect::<Vec<_>>()
2582 .join("\n");
2583 assert!(
2584 r_code.contains("Empty <- new.env(parent = emptyenv())"),
2585 "missing env init: {}",
2586 r_code
2587 );
2588 assert!(
2589 r_code.contains("local({"),
2590 "missing local block: {}",
2591 r_code
2592 );
2593 assert!(
2594 !r_code.contains("Empty$x"),
2595 "private member should not be exported: {}",
2596 r_code
2597 );
2598 }
2599
2600 #[test]
2601 fn test_module_s3_registration_for_typed_pub_fn() {
2602 let r_code = FluentParser::new()
2603 .push("module Math { @pub let double <- fn(x: Integer): Integer { x }; };")
2604 .run()
2605 .get_r_code()
2606 .iter()
2607 .cloned()
2608 .collect::<Vec<_>>()
2609 .join("\n");
2610 assert!(
2611 r_code.contains("Math <- new.env(parent = emptyenv())"),
2612 "missing env init: {}",
2613 r_code
2614 );
2615 assert!(
2616 r_code.contains("registerS3method(\"double\", \"integer\", double.integer)"),
2617 "missing S3 registration: {}",
2618 r_code
2619 );
2620 assert!(
2621 r_code.contains("double <- function(x, ...) UseMethod(\"double\")"),
2622 "missing generic: {}",
2623 r_code
2624 );
2625 assert!(
2626 r_code.contains("Math$double <- double"),
2627 "missing generic export: {}",
2628 r_code
2629 );
2630 }
2631
2632 #[test]
2633 fn test_module_no_trailing_semicolon() {
2634 let r_code = FluentParser::new()
2635 .push("module Geo { @pub let pi <- 3; }")
2636 .run()
2637 .get_r_code()
2638 .iter()
2639 .cloned()
2640 .collect::<Vec<_>>()
2641 .join("\n");
2642 assert!(
2643 r_code.contains("Geo <- new.env(parent = emptyenv())"),
2644 "missing env init: {}",
2645 r_code
2646 );
2647 assert!(
2648 r_code.contains("Geo$pi <- pi"),
2649 "missing public export: {}",
2650 r_code
2651 );
2652 }
2653
2654 #[test]
2655 fn test_import_module() {
2656 let r_code = FluentParser::new()
2657 .push("module Math { @pub let pi <- 3; }")
2658 .push("import Math")
2659 .run()
2660 .run()
2661 .get_r_code()
2662 .iter()
2663 .cloned()
2664 .collect::<Vec<_>>()
2665 .join("\n");
2666 assert!(
2667 r_code.contains("Math <- new.env(parent = emptyenv())"),
2668 "missing env init: {}",
2669 r_code
2670 );
2671 }
2672
2673 #[test]
2674 fn test_import_module_as_alias() {
2675 let r_code = FluentParser::new()
2676 .push("module Math { @pub let pi <- 3; }")
2677 .push("import Math as Maths")
2678 .run()
2679 .run()
2680 .get_r_code()
2681 .iter()
2682 .cloned()
2683 .collect::<Vec<_>>()
2684 .join("\n");
2685 assert!(
2686 r_code.contains("Math <- new.env(parent = emptyenv())"),
2687 "missing env init: {}",
2688 r_code
2689 );
2690 assert!(
2691 r_code.contains("Maths <- Math") || r_code.contains("`Maths` <- Math"),
2692 "missing alias assignment: {}",
2693 r_code
2694 );
2695 }
2696
2697 #[test]
2698 fn test_use_items_transpiles() {
2699 let r_code = FluentParser::new()
2700 .push("module Math { @pub let pi <- 3; @pub let e <- 2; };")
2701 .push("use Math::{pi, e as euler};")
2702 .run()
2703 .run()
2704 .get_r_code()
2705 .iter()
2706 .cloned()
2707 .collect::<Vec<_>>()
2708 .join("\n");
2709 assert!(
2710 r_code.contains("pi <- Math$pi"),
2711 "missing pi binding: {}",
2712 r_code
2713 );
2714 assert!(
2715 r_code.contains("euler <- Math$e"),
2716 "missing euler binding: {}",
2717 r_code
2718 );
2719 }
2720
2721 #[test]
2722 fn test_use_wildcard_transpiles() {
2723 let r_code = FluentParser::new()
2724 .push("module Math { @pub let pi <- 3; @pub let e <- 2; let secret <- 0; };")
2725 .push("use Math::*;")
2726 .run()
2727 .run()
2728 .get_r_code()
2729 .iter()
2730 .cloned()
2731 .collect::<Vec<_>>()
2732 .join("\n");
2733 assert!(
2734 r_code.contains("pi <- Math$pi"),
2735 "missing pi binding: {}",
2736 r_code
2737 );
2738 assert!(
2739 r_code.contains("e <- Math$e"),
2740 "missing e binding: {}",
2741 r_code
2742 );
2743 assert!(
2744 !r_code.contains("secret <- Math$secret"),
2745 "private member must not be imported: {}",
2746 r_code
2747 );
2748 }
2749
2750 #[test]
2753 fn test_export_at_top_level_prepends_roxygen_tag() {
2754 let r_code = FluentParser::new()
2755 .push("@export let answer <- 42;")
2756 .run()
2757 .get_r_code()
2758 .iter()
2759 .cloned()
2760 .collect::<Vec<_>>()
2761 .join("\n");
2762 assert!(
2763 r_code.contains("#' @export"),
2764 "missing #' @export tag: {}",
2765 r_code
2766 );
2767 assert!(r_code.contains("answer"), "missing assignment: {}", r_code);
2768 }
2769
2770 #[test]
2771 fn test_export_in_module_is_public_and_package_exported() {
2772 let r_code = FluentParser::new()
2773 .push("module Math { @export let norm <- fn(x: int): int { x }; };")
2774 .run()
2775 .get_r_code()
2776 .iter()
2777 .cloned()
2778 .collect::<Vec<_>>()
2779 .join("\n");
2780 assert!(
2781 r_code.contains("Math$norm <- norm"),
2782 "missing module export: {}",
2783 r_code
2784 );
2785 assert!(
2786 r_code.contains("#' @export"),
2787 "missing roxygen export tag: {}",
2788 r_code
2789 );
2790 assert!(
2791 r_code.contains("norm <- Math$norm"),
2792 "missing package-level re-export: {}",
2793 r_code
2794 );
2795 }
2796
2797 #[test]
2798 fn test_export_in_module_test_build_adds_test_alias() {
2799 let r_code = FluentParser::new()
2800 .set_context(Context::empty().set_test_mode(true))
2801 .push("module Math { @export let norm <- fn(x: int): int { x }; };")
2802 .run()
2803 .get_r_code()
2804 .iter()
2805 .cloned()
2806 .collect::<Vec<_>>()
2807 .join("\n");
2808 assert!(
2809 r_code.contains("Math$`.test_norm` <- norm"),
2810 "export member must get .test_ alias in test build: {}",
2811 r_code
2812 );
2813 }
2814
2815 #[test]
2816 fn test_pub_in_module_test_build_adds_test_alias() {
2817 let r_code = FluentParser::new()
2818 .set_context(Context::empty().set_test_mode(true))
2819 .push("module Math { @pub let pi <- 3; };")
2820 .run()
2821 .get_r_code()
2822 .iter()
2823 .cloned()
2824 .collect::<Vec<_>>()
2825 .join("\n");
2826 assert!(
2827 r_code.contains("Math$`.test_pi` <- pi"),
2828 "@pub member must get .test_ alias in test build (RFC-TR-032 §3.2): {}",
2829 r_code
2830 );
2831 }
2832
2833 #[test]
2834 fn test_array_constructor_call_transpilation() {
2835 let r_code = FluentParser::new()
2836 .push("type Bits <- [Any, int];")
2837 .run()
2838 .push("let b <- Bits:[1, 2, 3];")
2839 .run()
2840 .get_r_code()
2841 .iter()
2842 .cloned()
2843 .collect::<Vec<_>>()
2844 .join("\n");
2845 assert!(
2846 r_code.contains("Bits(typed_vec("),
2847 "expected Bits(...) constructor: {}",
2848 r_code
2849 );
2850 assert!(
2851 r_code.contains("dim = c(3)"),
2852 "expected dimension annotation: {}",
2853 r_code
2854 );
2855 }
2856
2857 #[test]
2858 fn test_record_alias_return_no_constructor_pipe() {
2859 let r_code = FluentParser::new()
2863 .push("type Point <- list { x: int, y: int };")
2864 .run()
2865 .push("let incr <- fn(p: Point): Point { Point:{x: (p$x+1), y: (p$y+1)} };")
2866 .run()
2867 .get_r_code()
2868 .iter()
2869 .cloned()
2870 .collect::<Vec<_>>()
2871 .join("\n");
2872 assert!(
2874 !r_code.contains("}) |> Point()"),
2875 "record alias output conversion should not be added: {}",
2876 r_code
2877 );
2878 assert!(
2880 r_code.contains("|> as.Generic()") || r_code.contains("|> Function"),
2881 "function type annotation should still be applied: {}",
2882 r_code
2883 );
2884 }
2885
2886 #[test]
2887 fn test_alias_int_generates_validator() {
2888 let r_code = FluentParser::new().check_transpiling("type Meters <- int;");
2889 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2890 assert!(
2891 r_str.contains("validate_Meters <- function(x)"),
2892 "expected validator function, got: {}",
2893 r_str
2894 );
2895 assert!(
2896 r_str.contains("is.integer"),
2897 "expected is.integer check, got: {}",
2898 r_str
2899 );
2900 }
2901
2902 #[test]
2903 fn test_alias_char_generates_validator() {
2904 let r_code = FluentParser::new().check_transpiling("type Name <- char;");
2905 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2906 assert!(
2907 r_str.contains("validate_Name <- function(x)"),
2908 "expected validator function, got: {}",
2909 r_str
2910 );
2911 assert!(
2912 r_str.contains("is.character"),
2913 "expected is.character check, got: {}",
2914 r_str
2915 );
2916 }
2917
2918 #[test]
2919 fn test_alias_bool_generates_validator() {
2920 let r_code = FluentParser::new().check_transpiling("type Flag <- bool;");
2921 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2922 assert!(
2923 r_str.contains("validate_Flag <- function(x)"),
2924 "expected validator function, got: {}",
2925 r_str
2926 );
2927 assert!(
2928 r_str.contains("is.logical"),
2929 "expected is.logical check, got: {}",
2930 r_str
2931 );
2932 }
2933
2934 #[test]
2935 fn test_alias_num_generates_validator() {
2936 let r_code = FluentParser::new().check_transpiling("type Real <- num;");
2937 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2938 assert!(
2939 r_str.contains("validate_Real <- function(x)"),
2940 "expected validator function, got: {}",
2941 r_str
2942 );
2943 assert!(
2944 r_str.contains("is.numeric"),
2945 "expected is.numeric check, got: {}",
2946 r_str
2947 );
2948 }
2949
2950 #[test]
2951 fn test_validating_cast_int() {
2952 let r_code = FluentParser::new()
2953 .push("type Meters <- int;")
2954 .run()
2955 .check_transpiling("x as! Meters");
2956 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2957 assert!(
2958 r_str.contains("validate_Meters(x)"),
2959 "expected validate_Meters(x), got: {}",
2960 r_str
2961 );
2962 }
2963
2964 #[test]
2965 fn test_tag_alias_char_generates_validator() {
2966 let r_code = FluentParser::new().check_transpiling("type Hello <- .Hello(char);");
2967 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2968 assert!(
2969 r_str.contains("validate_Hello <- function(x)"),
2970 "expected validator function, got: {}",
2971 r_str
2972 );
2973 assert!(
2974 r_str.contains("x[[1]] != 'Hello'"),
2975 "expected tag name check, got: {}",
2976 r_str
2977 );
2978 assert!(
2979 r_str.contains("x[[\"body\"]]"),
2980 "expected body field check, got: {}",
2981 r_str
2982 );
2983 assert!(
2984 r_str.contains("is.character"),
2985 "expected is.character check on body, got: {}",
2986 r_str
2987 );
2988 }
2989
2990 #[test]
2991 fn test_tag_alias_int_generates_validator() {
2992 let r_code = FluentParser::new().check_transpiling("type Count <- .Count(int);");
2993 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
2994 assert!(
2995 r_str.contains("validate_Count <- function(x)"),
2996 "expected validator, got: {}",
2997 r_str
2998 );
2999 assert!(
3000 r_str.contains("x[[1]] != 'Count'"),
3001 "expected tag name check, got: {}",
3002 r_str
3003 );
3004 assert!(
3005 r_str.contains("is.integer"),
3006 "expected is.integer check on body, got: {}",
3007 r_str
3008 );
3009 }
3010
3011 #[test]
3012 fn test_tag_alias_with_alias_body_calls_nested_validator() {
3013 let r_code = FluentParser::new()
3014 .push("type Name <- char;")
3015 .run()
3016 .check_transpiling("type Tagged <- .Tagged(Name);");
3017 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3018 assert!(
3019 r_str.contains("validate_Tagged <- function(x)"),
3020 "expected validator, got: {}",
3021 r_str
3022 );
3023 assert!(
3024 r_str.contains("validate_Name(x[[\"body\"]])"),
3025 "expected nested validator call, got: {}",
3026 r_str
3027 );
3028 }
3029
3030 #[test]
3031 fn test_union_variant_generates_full_pipeline() {
3032 let r_str = FluentParser::new()
3035 .check_transpiling("type Shape <- .Circle(num) | .Nothing;")
3036 .iter()
3037 .cloned()
3038 .collect::<Vec<_>>()
3039 .join("\n");
3040 assert!(
3042 r_str.contains("Circle <- function(x) {")
3043 && r_str.contains("v <- list(\"Circle\", body = x)")
3044 && r_str.contains("as.Circle(v)"),
3045 "expected Circle constructor, got: {r_str}"
3046 );
3047 assert!(
3049 r_str.contains("as.Circle <- function(x) {")
3050 && r_str.contains("class(x) <- c(\"Circle\", \"Shape\", \"Tag\", \"list\")")
3051 && r_str.contains("x <- validate_Circle(x)")
3052 && r_str.contains("x <- validate(x)"),
3053 "expected Circle annotator, got: {r_str}"
3054 );
3055 assert!(
3057 r_str.contains("validate_Circle <- function(x) {")
3058 && r_str.contains("x[[1]] != 'Circle'")
3059 && r_str.contains("is.numeric(x[[\"body\"]])"),
3060 "expected Circle validator, got: {r_str}"
3061 );
3062 assert!(
3064 r_str.contains("Nothing <- function() {") && r_str.contains("x <- list(\"Nothing\")"),
3065 "expected Nothing constructor, got: {r_str}"
3066 );
3067 }
3068
3069 #[test]
3070 fn test_tag_literal_canonical_representation() {
3071 let r_str = FluentParser::new()
3075 .push("type Shape <- .Circle(num) | .Square(num);")
3076 .run()
3077 .check_transpiling(".Circle(3.14)")
3078 .iter()
3079 .cloned()
3080 .collect::<Vec<_>>()
3081 .join("\n");
3082 assert!(
3083 r_str.contains("structure(list('Circle', body =")
3084 && r_str.contains("class = c('Circle', 'Shape', 'Tag', 'list')"),
3085 "expected canonical tag literal with union class, got: {r_str}"
3086 );
3087 }
3088
3089 #[test]
3090 fn test_tag_literal_without_union_omits_union_class() {
3091 let r_str = FluentParser::new()
3094 .check_transpiling(".Loose(1)")
3095 .iter()
3096 .cloned()
3097 .collect::<Vec<_>>()
3098 .join("\n");
3099 assert!(
3100 r_str.contains("structure(list('Loose', body =")
3101 && r_str.contains("class = c('Loose', 'Tag', 'list')"),
3102 "expected canonical tag literal without union class, got: {r_str}"
3103 );
3104 }
3105
3106 #[test]
3107 fn test_literal_char_alias_generates_exact_validator() {
3108 let r_code = FluentParser::new().check_transpiling("type Hello <- \"hello\";");
3109 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3110 assert!(
3111 r_str.contains("validate_Hello <- function(x)"),
3112 "expected validator function, got: {}",
3113 r_str
3114 );
3115 assert!(
3116 r_str.contains("x != 'hello'"),
3117 "expected literal equality check, got: {}",
3118 r_str
3119 );
3120 }
3121
3122 #[test]
3123 fn test_literal_int_alias_generates_exact_validator() {
3124 let r_code = FluentParser::new().check_transpiling("type Byte <- 89;");
3125 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3126 assert!(
3127 r_str.contains("validate_Byte <- function(x)"),
3128 "expected validator function, got: {}",
3129 r_str
3130 );
3131 assert!(
3132 r_str.contains("x != 89L"),
3133 "expected literal equality check, got: {}",
3134 r_str
3135 );
3136 }
3137
3138 #[test]
3139 fn test_literal_num_alias_generates_exact_validator() {
3140 let r_code = FluentParser::new().check_transpiling("type Pi <- 3.14;");
3141 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3142 assert!(
3143 r_str.contains("validate_Pi <- function(x)"),
3144 "expected validator function, got: {}",
3145 r_str
3146 );
3147 assert!(
3148 r_str.contains("x != 3.14"),
3149 "expected literal equality check, got: {}",
3150 r_str
3151 );
3152 }
3153
3154 #[test]
3155 fn test_literal_bool_true_alias_generates_exact_validator() {
3156 let r_code = FluentParser::new().check_transpiling("type Yes <- true;");
3157 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3158 assert!(
3159 r_str.contains("validate_Yes <- function(x)"),
3160 "expected validator function, got: {}",
3161 r_str
3162 );
3163 assert!(
3164 r_str.contains("x != TRUE"),
3165 "expected literal TRUE check, got: {}",
3166 r_str
3167 );
3168 }
3169
3170 #[test]
3171 fn test_literal_bool_false_alias_generates_exact_validator() {
3172 let r_code = FluentParser::new().check_transpiling("type No <- false;");
3173 let r_str = r_code.iter().cloned().collect::<Vec<_>>().join("\n");
3174 assert!(
3175 r_str.contains("validate_No <- function(x)"),
3176 "expected validator function, got: {}",
3177 r_str
3178 );
3179 assert!(
3180 r_str.contains("x != FALSE"),
3181 "expected literal FALSE check, got: {}",
3182 r_str
3183 );
3184 }
3185
3186 #[test]
3187 fn test_record_subtype_includes_supertype_in_s3_class() {
3188 let r_str = FluentParser::new()
3192 .push("type Position <- list{ position: int };")
3193 .run()
3194 .push("type Person <- list{ name: char, age: int, position: int };")
3195 .run()
3196 .get_r_code()
3197 .iter()
3198 .cloned()
3199 .collect::<Vec<_>>()
3200 .join("\n");
3201 assert!(
3202 r_str.contains("class(x) <- c(\"Person\", \"Position\", \"list\")"),
3203 "expected Person's annotator to include Position, got: {r_str}"
3204 );
3205 }
3206
3207 #[test]
3208 fn test_record_without_supertype_keeps_plain_class() {
3209 let r_str = FluentParser::new()
3211 .check_transpiling("type Position <- list{ position: int };")
3212 .iter()
3213 .cloned()
3214 .collect::<Vec<_>>()
3215 .join("\n");
3216 assert!(
3217 r_str.contains("class(x) <- c(\"Position\", \"list\")"),
3218 "expected Position's annotator with no supertype, got: {r_str}"
3219 );
3220 }
3221}