1use crate::ast::{Program, Statement, WordDef};
7use crate::builtins::builtin_signature;
8use crate::types::{Effect, StackType, Type};
9use crate::unification::{Subst, unify_stacks};
10use std::collections::HashMap;
11
12pub struct TypeChecker {
13 env: HashMap<String, Effect>,
15 fresh_counter: std::cell::Cell<usize>,
17 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
21 expected_quotation_type: std::cell::RefCell<Option<Type>>,
24}
25
26impl TypeChecker {
27 pub fn new() -> Self {
28 TypeChecker {
29 env: HashMap::new(),
30 fresh_counter: std::cell::Cell::new(0),
31 quotation_types: std::cell::RefCell::new(HashMap::new()),
32 expected_quotation_type: std::cell::RefCell::new(None),
33 }
34 }
35
36 pub fn register_external_words(&mut self, words: &[(&str, Option<&Effect>)]) {
41 for (name, effect) in words {
42 if let Some(eff) = effect {
43 self.env.insert(name.to_string(), (*eff).clone());
44 } else {
45 let placeholder = Effect::new(
47 StackType::RowVar("ext_in".to_string()),
48 StackType::RowVar("ext_out".to_string()),
49 );
50 self.env.insert(name.to_string(), placeholder);
51 }
52 }
53 }
54
55 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
61 self.quotation_types.replace(HashMap::new())
62 }
63
64 fn fresh_var(&self, prefix: &str) -> String {
66 let n = self.fresh_counter.get();
67 self.fresh_counter.set(n + 1);
68 format!("{}${}", prefix, n)
69 }
70
71 fn freshen_effect(&self, effect: &Effect) -> Effect {
73 let mut type_map = HashMap::new();
74 let mut row_map = HashMap::new();
75
76 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
77 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
78
79 Effect::new(fresh_inputs, fresh_outputs)
80 }
81
82 fn freshen_stack(
83 &self,
84 stack: &StackType,
85 type_map: &mut HashMap<String, String>,
86 row_map: &mut HashMap<String, String>,
87 ) -> StackType {
88 match stack {
89 StackType::Empty => StackType::Empty,
90 StackType::RowVar(name) => {
91 let fresh_name = row_map
92 .entry(name.clone())
93 .or_insert_with(|| self.fresh_var(name));
94 StackType::RowVar(fresh_name.clone())
95 }
96 StackType::Cons { rest, top } => {
97 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
98 let fresh_top = self.freshen_type(top, type_map, row_map);
99 StackType::Cons {
100 rest: Box::new(fresh_rest),
101 top: fresh_top,
102 }
103 }
104 }
105 }
106
107 fn freshen_type(
108 &self,
109 ty: &Type,
110 type_map: &mut HashMap<String, String>,
111 row_map: &mut HashMap<String, String>,
112 ) -> Type {
113 match ty {
114 Type::Int | Type::Float | Type::Bool | Type::String => ty.clone(),
115 Type::Var(name) => {
116 let fresh_name = type_map
117 .entry(name.clone())
118 .or_insert_with(|| self.fresh_var(name));
119 Type::Var(fresh_name.clone())
120 }
121 Type::Quotation(effect) => {
122 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
123 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
124 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
125 }
126 Type::Closure { effect, captures } => {
127 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
128 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
129 let fresh_captures = captures
130 .iter()
131 .map(|t| self.freshen_type(t, type_map, row_map))
132 .collect();
133 Type::Closure {
134 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
135 captures: fresh_captures,
136 }
137 }
138 }
139 }
140
141 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
143 for word in &program.words {
147 if let Some(effect) = &word.effect {
148 self.env.insert(word.name.clone(), effect.clone());
149 } else {
150 let placeholder = Effect::new(
153 StackType::RowVar("input".to_string()),
154 StackType::RowVar("output".to_string()),
155 );
156 self.env.insert(word.name.clone(), placeholder);
157 }
158 }
159
160 for word in &program.words {
162 self.check_word(word)?;
163 }
164
165 Ok(())
166 }
167
168 fn check_word(&self, word: &WordDef) -> Result<(), String> {
170 if let Some(declared_effect) = &word.effect {
172 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
175 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
176 {
177 *self.expected_quotation_type.borrow_mut() = Some(top_type);
178 }
179
180 let (result_stack, _subst) =
182 self.infer_statements_from(&word.body, &declared_effect.inputs)?;
183
184 *self.expected_quotation_type.borrow_mut() = None;
186
187 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
189 format!(
190 "Word '{}': declared output stack ({:?}) doesn't match inferred ({:?}): {}",
191 word.name, declared_effect.outputs, result_stack, e
192 )
193 })?;
194 } else {
195 self.infer_statements_from(&word.body, &StackType::RowVar("input".to_string()))?;
198 }
199
200 Ok(())
201 }
202
203 fn infer_statements_from(
206 &self,
207 statements: &[Statement],
208 start_stack: &StackType,
209 ) -> Result<(StackType, Subst), String> {
210 let mut current_stack = start_stack.clone();
211 let mut accumulated_subst = Subst::empty();
212
213 for (i, stmt) in statements.iter().enumerate() {
214 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
217 let saved = self.expected_quotation_type.borrow().clone();
219
220 if let Some(Statement::WordCall(next_word)) = statements.get(i + 1) {
222 if let Some(next_effect) = self.lookup_word_effect(next_word) {
224 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
227 && matches!(quot_type, Type::Quotation(_))
228 {
229 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
230 }
231 }
232 }
233 Some(saved)
234 } else {
235 None
236 };
237
238 let (new_stack, subst) = self.infer_statement(stmt, current_stack)?;
239 current_stack = new_stack;
240 accumulated_subst = accumulated_subst.compose(&subst);
241
242 if let Some(saved) = saved_expected_type {
244 *self.expected_quotation_type.borrow_mut() = saved;
245 }
246 }
247
248 Ok((current_stack, accumulated_subst))
249 }
250
251 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
254 let start = StackType::RowVar("input".to_string());
255 let (result, subst) = self.infer_statements_from(statements, &start)?;
256
257 let normalized_start = subst.apply_stack(&start);
260 let normalized_result = subst.apply_stack(&result);
261
262 Ok(Effect::new(normalized_start, normalized_result))
263 }
264
265 fn infer_statement(
268 &self,
269 statement: &Statement,
270 current_stack: StackType,
271 ) -> Result<(StackType, Subst), String> {
272 match statement {
273 Statement::IntLiteral(_) => {
274 Ok((current_stack.push(Type::Int), Subst::empty()))
276 }
277
278 Statement::BoolLiteral(_) => {
279 Ok((current_stack.push(Type::Int), Subst::empty()))
282 }
283
284 Statement::StringLiteral(_) => {
285 Ok((current_stack.push(Type::String), Subst::empty()))
287 }
288
289 Statement::FloatLiteral(_) => {
290 Ok((current_stack.push(Type::Float), Subst::empty()))
292 }
293
294 Statement::WordCall(name) => {
295 let effect = self
297 .lookup_word_effect(name)
298 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
299
300 let fresh_effect = self.freshen_effect(&effect);
302
303 let adjusted_stack = if name == "spawn" {
305 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
306 } else {
307 current_stack
308 };
309
310 self.apply_effect(&fresh_effect, adjusted_stack, name)
312 }
313
314 Statement::If {
315 then_branch,
316 else_branch,
317 } => {
318 let (stack_after_cond, cond_type) =
320 self.pop_type(¤t_stack, "if condition")?;
321
322 let cond_subst = unify_stacks(
324 &StackType::singleton(Type::Int),
325 &StackType::singleton(cond_type),
326 )
327 .map_err(|e| format!("if condition must be Int: {}", e))?;
328
329 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
330
331 let then_effect = self.infer_statements(then_branch)?;
333 let (then_result, _then_subst) =
334 self.apply_effect(&then_effect, stack_after_cond.clone(), "if then")?;
335
336 let (else_result, _else_subst) = if let Some(else_stmts) = else_branch {
338 let else_effect = self.infer_statements(else_stmts)?;
339 self.apply_effect(&else_effect, stack_after_cond, "if else")?
340 } else {
341 (stack_after_cond, Subst::empty())
342 };
343
344 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
346 format!(
347 "if/else branches have incompatible stack effects:\n\
348 \x20 then branch produces: {}\n\
349 \x20 else branch produces: {}\n\
350 \x20 Both branches of an if/else must produce the same stack shape.\n\
351 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
352 \x20 Error: {}",
353 then_result, else_result, e
354 )
355 })?;
356
357 let result = branch_subst.apply_stack(&then_result);
359
360 let total_subst = cond_subst.compose(&branch_subst);
362 Ok((result, total_subst))
363 }
364
365 Statement::Quotation { id, body } => {
366 let body_effect = self.infer_statements(body)?;
383
384 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
386
387 self.quotation_types
389 .borrow_mut()
390 .insert(*id, quot_type.clone());
391
392 let result_stack = match "_type {
394 Type::Quotation(_) => {
395 current_stack.push(quot_type)
397 }
398 Type::Closure { captures, .. } => {
399 let mut stack = current_stack.clone();
401 for _ in 0..captures.len() {
402 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
403 stack = new_stack;
404 }
405 stack.push(quot_type)
406 }
407 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
408 };
409
410 Ok((result_stack, Subst::empty()))
411 }
412 }
413 }
414
415 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
417 if let Some(effect) = builtin_signature(name) {
419 return Some(effect);
420 }
421
422 self.env.get(name).cloned()
424 }
425
426 fn apply_effect(
431 &self,
432 effect: &Effect,
433 current_stack: StackType,
434 operation: &str,
435 ) -> Result<(StackType, Subst), String> {
436 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
438 format!(
439 "{}: stack type mismatch. Expected {:?}, got {:?}: {}",
440 operation, effect.inputs, current_stack, e
441 )
442 })?;
443
444 let result_stack = subst.apply_stack(&effect.outputs);
446
447 Ok((result_stack, subst))
448 }
449
450 fn adjust_stack_for_spawn(
455 &self,
456 current_stack: StackType,
457 spawn_effect: &Effect,
458 ) -> Result<StackType, String> {
459 let expected_quot_type = match &spawn_effect.inputs {
462 StackType::Cons { top, rest: _ } => {
463 if !matches!(top, Type::Quotation(_)) {
464 return Ok(current_stack); }
466 top
467 }
468 _ => return Ok(current_stack),
469 };
470
471 let (rest_stack, actual_type) = match ¤t_stack {
473 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
474 _ => return Ok(current_stack), };
476
477 if let Type::Quotation(actual_effect) = actual_type {
479 if !matches!(actual_effect.inputs, StackType::Empty) {
481 let expected_effect = match expected_quot_type {
483 Type::Quotation(eff) => eff.as_ref(),
484 _ => return Ok(current_stack),
485 };
486
487 let captures = self.calculate_captures(actual_effect, expected_effect)?;
489
490 let closure_type = Type::Closure {
492 effect: Box::new(expected_effect.clone()),
493 captures: captures.clone(),
494 };
495
496 let mut adjusted_stack = rest_stack;
499 for _ in &captures {
500 adjusted_stack = match adjusted_stack {
501 StackType::Cons { rest, .. } => rest.as_ref().clone(),
502 _ => {
503 return Err(format!(
504 "spawn: not enough values on stack to capture. Need {} values",
505 captures.len()
506 ));
507 }
508 };
509 }
510
511 return Ok(adjusted_stack.push(closure_type));
513 }
514 }
515
516 Ok(current_stack)
517 }
518
519 fn analyze_captures(
545 &self,
546 body_effect: &Effect,
547 _current_stack: &StackType,
548 ) -> Result<Type, String> {
549 let expected = self.expected_quotation_type.borrow().clone();
551
552 match expected {
553 Some(Type::Closure { effect, .. }) => {
554 let captures = self.calculate_captures(body_effect, &effect)?;
556 Ok(Type::Closure { effect, captures })
557 }
558 Some(Type::Quotation(expected_effect)) => {
559 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
565 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
566
567 if expected_is_empty && body_needs_inputs {
568 let captures = self.calculate_captures(body_effect, &expected_effect)?;
571 Ok(Type::Closure {
572 effect: expected_effect,
573 captures,
574 })
575 } else {
576 Ok(Type::Quotation(expected_effect))
578 }
579 }
580 _ => {
581 Ok(Type::Quotation(Box::new(body_effect.clone())))
583 }
584 }
585 }
586
587 fn calculate_captures(
610 &self,
611 body_effect: &Effect,
612 call_effect: &Effect,
613 ) -> Result<Vec<Type>, String> {
614 let body_inputs = self.extract_concrete_types(&body_effect.inputs);
616 let call_inputs = self.extract_concrete_types(&call_effect.inputs);
617
618 if call_inputs.len() > body_inputs.len() {
620 return Err(format!(
621 "Closure signature error: call site provides {} values but body only needs {}",
622 call_inputs.len(),
623 body_inputs.len()
624 ));
625 }
626
627 let capture_count = body_inputs.len() - call_inputs.len();
629
630 Ok(body_inputs[0..capture_count].to_vec())
634 }
635
636 fn extract_concrete_types(&self, stack: &StackType) -> Vec<Type> {
644 let mut types = Vec::new();
645 let mut current = stack.clone();
646
647 while let Some((rest, top)) = current.pop() {
649 types.push(top);
650 current = rest;
651 }
652
653 types.reverse();
655 types
656 }
657
658 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
660 match stack {
661 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
662 StackType::Empty => Err(format!(
663 "{}: stack underflow - expected value on stack but stack is empty",
664 context
665 )),
666 StackType::RowVar(_) => {
667 Err(format!(
671 "{}: cannot pop from polymorphic stack without more type information",
672 context
673 ))
674 }
675 }
676 }
677}
678
679impl Default for TypeChecker {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
690 fn test_simple_literal() {
691 let program = Program {
692 includes: vec![],
693 words: vec![WordDef {
694 name: "test".to_string(),
695 effect: Some(Effect::new(
696 StackType::Empty,
697 StackType::singleton(Type::Int),
698 )),
699 body: vec![Statement::IntLiteral(42)],
700 source: None,
701 }],
702 };
703
704 let mut checker = TypeChecker::new();
705 assert!(checker.check_program(&program).is_ok());
706 }
707
708 #[test]
709 fn test_simple_operation() {
710 let program = Program {
712 includes: vec![],
713 words: vec![WordDef {
714 name: "test".to_string(),
715 effect: Some(Effect::new(
716 StackType::Empty.push(Type::Int).push(Type::Int),
717 StackType::singleton(Type::Int),
718 )),
719 body: vec![Statement::WordCall("add".to_string())],
720 source: None,
721 }],
722 };
723
724 let mut checker = TypeChecker::new();
725 assert!(checker.check_program(&program).is_ok());
726 }
727
728 #[test]
729 fn test_type_mismatch() {
730 let program = Program {
732 includes: vec![],
733 words: vec![WordDef {
734 name: "test".to_string(),
735 effect: Some(Effect::new(
736 StackType::singleton(Type::String),
737 StackType::Empty,
738 )),
739 body: vec![
740 Statement::IntLiteral(42), Statement::WordCall("write_line".to_string()),
742 ],
743 source: None,
744 }],
745 };
746
747 let mut checker = TypeChecker::new();
748 let result = checker.check_program(&program);
749 assert!(result.is_err());
750 assert!(result.unwrap_err().contains("Type mismatch"));
751 }
752
753 #[test]
754 fn test_polymorphic_dup() {
755 let program = Program {
757 includes: vec![],
758 words: vec![WordDef {
759 name: "my-dup".to_string(),
760 effect: Some(Effect::new(
761 StackType::singleton(Type::Int),
762 StackType::Empty.push(Type::Int).push(Type::Int),
763 )),
764 body: vec![Statement::WordCall("dup".to_string())],
765 source: None,
766 }],
767 };
768
769 let mut checker = TypeChecker::new();
770 assert!(checker.check_program(&program).is_ok());
771 }
772
773 #[test]
774 fn test_conditional_branches() {
775 let program = Program {
778 includes: vec![],
779 words: vec![WordDef {
780 name: "test".to_string(),
781 effect: Some(Effect::new(
782 StackType::Empty.push(Type::Int).push(Type::Int),
783 StackType::singleton(Type::String),
784 )),
785 body: vec![
786 Statement::WordCall(">".to_string()),
787 Statement::If {
788 then_branch: vec![Statement::StringLiteral("greater".to_string())],
789 else_branch: Some(vec![Statement::StringLiteral(
790 "not greater".to_string(),
791 )]),
792 },
793 ],
794 source: None,
795 }],
796 };
797
798 let mut checker = TypeChecker::new();
799 assert!(checker.check_program(&program).is_ok());
800 }
801
802 #[test]
803 fn test_mismatched_branches() {
804 let program = Program {
807 includes: vec![],
808 words: vec![WordDef {
809 name: "test".to_string(),
810 effect: None,
811 body: vec![
812 Statement::IntLiteral(1),
813 Statement::If {
814 then_branch: vec![Statement::IntLiteral(42)],
815 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
816 },
817 ],
818 source: None,
819 }],
820 };
821
822 let mut checker = TypeChecker::new();
823 let result = checker.check_program(&program);
824 assert!(result.is_err());
825 assert!(result.unwrap_err().contains("incompatible"));
826 }
827
828 #[test]
829 fn test_user_defined_word_call() {
830 let program = Program {
833 includes: vec![],
834 words: vec![
835 WordDef {
836 name: "helper".to_string(),
837 effect: Some(Effect::new(
838 StackType::singleton(Type::Int),
839 StackType::singleton(Type::String),
840 )),
841 body: vec![Statement::WordCall("int->string".to_string())],
842 source: None,
843 },
844 WordDef {
845 name: "main".to_string(),
846 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
847 body: vec![
848 Statement::IntLiteral(42),
849 Statement::WordCall("helper".to_string()),
850 Statement::WordCall("write_line".to_string()),
851 ],
852 source: None,
853 },
854 ],
855 };
856
857 let mut checker = TypeChecker::new();
858 assert!(checker.check_program(&program).is_ok());
859 }
860
861 #[test]
862 fn test_arithmetic_chain() {
863 let program = Program {
866 includes: vec![],
867 words: vec![WordDef {
868 name: "test".to_string(),
869 effect: Some(Effect::new(
870 StackType::Empty
871 .push(Type::Int)
872 .push(Type::Int)
873 .push(Type::Int),
874 StackType::singleton(Type::Int),
875 )),
876 body: vec![
877 Statement::WordCall("add".to_string()),
878 Statement::WordCall("multiply".to_string()),
879 ],
880 source: None,
881 }],
882 };
883
884 let mut checker = TypeChecker::new();
885 assert!(checker.check_program(&program).is_ok());
886 }
887
888 #[test]
889 fn test_write_line_type_error() {
890 let program = Program {
892 includes: vec![],
893 words: vec![WordDef {
894 name: "test".to_string(),
895 effect: Some(Effect::new(
896 StackType::singleton(Type::Int),
897 StackType::Empty,
898 )),
899 body: vec![Statement::WordCall("write_line".to_string())],
900 source: None,
901 }],
902 };
903
904 let mut checker = TypeChecker::new();
905 let result = checker.check_program(&program);
906 assert!(result.is_err());
907 assert!(result.unwrap_err().contains("Type mismatch"));
908 }
909
910 #[test]
911 fn test_stack_underflow_drop() {
912 let program = Program {
914 includes: vec![],
915 words: vec![WordDef {
916 name: "test".to_string(),
917 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
918 body: vec![Statement::WordCall("drop".to_string())],
919 source: None,
920 }],
921 };
922
923 let mut checker = TypeChecker::new();
924 let result = checker.check_program(&program);
925 assert!(result.is_err());
926 assert!(result.unwrap_err().contains("mismatch"));
927 }
928
929 #[test]
930 fn test_stack_underflow_add() {
931 let program = Program {
933 includes: vec![],
934 words: vec![WordDef {
935 name: "test".to_string(),
936 effect: Some(Effect::new(
937 StackType::singleton(Type::Int),
938 StackType::singleton(Type::Int),
939 )),
940 body: vec![Statement::WordCall("add".to_string())],
941 source: None,
942 }],
943 };
944
945 let mut checker = TypeChecker::new();
946 let result = checker.check_program(&program);
947 assert!(result.is_err());
948 assert!(result.unwrap_err().contains("mismatch"));
949 }
950
951 #[test]
952 fn test_csp_operations() {
953 let program = Program {
959 includes: vec![],
960 words: vec![WordDef {
961 name: "test".to_string(),
962 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
963 body: vec![
964 Statement::WordCall("make-channel".to_string()),
965 Statement::IntLiteral(42),
966 Statement::WordCall("swap".to_string()),
967 Statement::WordCall("send".to_string()),
968 ],
969 source: None,
970 }],
971 };
972
973 let mut checker = TypeChecker::new();
974 assert!(checker.check_program(&program).is_ok());
975 }
976
977 #[test]
978 fn test_complex_stack_shuffling() {
979 let program = Program {
982 includes: vec![],
983 words: vec![WordDef {
984 name: "test".to_string(),
985 effect: Some(Effect::new(
986 StackType::Empty
987 .push(Type::Int)
988 .push(Type::Int)
989 .push(Type::Int),
990 StackType::singleton(Type::Int),
991 )),
992 body: vec![
993 Statement::WordCall("rot".to_string()),
994 Statement::WordCall("add".to_string()),
995 Statement::WordCall("add".to_string()),
996 ],
997 source: None,
998 }],
999 };
1000
1001 let mut checker = TypeChecker::new();
1002 assert!(checker.check_program(&program).is_ok());
1003 }
1004
1005 #[test]
1006 fn test_empty_program() {
1007 let program = Program {
1009 includes: vec![],
1010 words: vec![],
1011 };
1012
1013 let mut checker = TypeChecker::new();
1014 assert!(checker.check_program(&program).is_ok());
1015 }
1016
1017 #[test]
1018 fn test_word_without_effect_declaration() {
1019 let program = Program {
1021 includes: vec![],
1022 words: vec![WordDef {
1023 name: "helper".to_string(),
1024 effect: None,
1025 body: vec![Statement::IntLiteral(42)],
1026 source: None,
1027 }],
1028 };
1029
1030 let mut checker = TypeChecker::new();
1031 assert!(checker.check_program(&program).is_ok());
1032 }
1033
1034 #[test]
1035 fn test_nested_conditionals() {
1036 let program = Program {
1045 includes: vec![],
1046 words: vec![WordDef {
1047 name: "test".to_string(),
1048 effect: Some(Effect::new(
1049 StackType::Empty
1050 .push(Type::Int)
1051 .push(Type::Int)
1052 .push(Type::Int)
1053 .push(Type::Int),
1054 StackType::singleton(Type::String),
1055 )),
1056 body: vec![
1057 Statement::WordCall(">".to_string()),
1058 Statement::If {
1059 then_branch: vec![
1060 Statement::WordCall(">".to_string()),
1061 Statement::If {
1062 then_branch: vec![Statement::StringLiteral(
1063 "both true".to_string(),
1064 )],
1065 else_branch: Some(vec![Statement::StringLiteral(
1066 "first true".to_string(),
1067 )]),
1068 },
1069 ],
1070 else_branch: Some(vec![
1071 Statement::WordCall("drop".to_string()),
1072 Statement::WordCall("drop".to_string()),
1073 Statement::StringLiteral("first false".to_string()),
1074 ]),
1075 },
1076 ],
1077 source: None,
1078 }],
1079 };
1080
1081 let mut checker = TypeChecker::new();
1082 match checker.check_program(&program) {
1083 Ok(_) => {}
1084 Err(e) => panic!("Type check failed: {}", e),
1085 }
1086 }
1087
1088 #[test]
1089 fn test_conditional_without_else() {
1090 let program = Program {
1094 includes: vec![],
1095 words: vec![WordDef {
1096 name: "test".to_string(),
1097 effect: Some(Effect::new(
1098 StackType::Empty.push(Type::Int).push(Type::Int),
1099 StackType::singleton(Type::Int),
1100 )),
1101 body: vec![
1102 Statement::WordCall(">".to_string()),
1103 Statement::If {
1104 then_branch: vec![Statement::IntLiteral(100)],
1105 else_branch: None, },
1107 ],
1108 source: None,
1109 }],
1110 };
1111
1112 let mut checker = TypeChecker::new();
1113 let result = checker.check_program(&program);
1114 assert!(result.is_err());
1116 }
1117
1118 #[test]
1119 fn test_multiple_word_chain() {
1120 let program = Program {
1124 includes: vec![],
1125 words: vec![
1126 WordDef {
1127 name: "helper1".to_string(),
1128 effect: Some(Effect::new(
1129 StackType::singleton(Type::Int),
1130 StackType::singleton(Type::String),
1131 )),
1132 body: vec![Statement::WordCall("int->string".to_string())],
1133 source: None,
1134 },
1135 WordDef {
1136 name: "helper2".to_string(),
1137 effect: Some(Effect::new(
1138 StackType::singleton(Type::String),
1139 StackType::Empty,
1140 )),
1141 body: vec![Statement::WordCall("write_line".to_string())],
1142 source: None,
1143 },
1144 WordDef {
1145 name: "main".to_string(),
1146 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1147 body: vec![
1148 Statement::IntLiteral(42),
1149 Statement::WordCall("helper1".to_string()),
1150 Statement::WordCall("helper2".to_string()),
1151 ],
1152 source: None,
1153 },
1154 ],
1155 };
1156
1157 let mut checker = TypeChecker::new();
1158 assert!(checker.check_program(&program).is_ok());
1159 }
1160
1161 #[test]
1162 fn test_all_stack_ops() {
1163 let program = Program {
1166 includes: vec![],
1167 words: vec![WordDef {
1168 name: "test".to_string(),
1169 effect: Some(Effect::new(
1170 StackType::Empty
1171 .push(Type::Int)
1172 .push(Type::Int)
1173 .push(Type::Int),
1174 StackType::Empty
1175 .push(Type::Int)
1176 .push(Type::Int)
1177 .push(Type::Int)
1178 .push(Type::Int),
1179 )),
1180 body: vec![
1181 Statement::WordCall("over".to_string()),
1182 Statement::WordCall("nip".to_string()),
1183 Statement::WordCall("tuck".to_string()),
1184 ],
1185 source: None,
1186 }],
1187 };
1188
1189 let mut checker = TypeChecker::new();
1190 assert!(checker.check_program(&program).is_ok());
1191 }
1192
1193 #[test]
1194 fn test_mixed_types_complex() {
1195 let program = Program {
1204 includes: vec![],
1205 words: vec![WordDef {
1206 name: "test".to_string(),
1207 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1208 body: vec![
1209 Statement::IntLiteral(42),
1210 Statement::WordCall("int->string".to_string()),
1211 Statement::IntLiteral(100),
1212 Statement::IntLiteral(200),
1213 Statement::WordCall(">".to_string()),
1214 Statement::If {
1215 then_branch: vec![Statement::WordCall("write_line".to_string())],
1216 else_branch: Some(vec![Statement::WordCall("write_line".to_string())]),
1217 },
1218 ],
1219 source: None,
1220 }],
1221 };
1222
1223 let mut checker = TypeChecker::new();
1224 assert!(checker.check_program(&program).is_ok());
1225 }
1226
1227 #[test]
1228 fn test_string_literal() {
1229 let program = Program {
1231 includes: vec![],
1232 words: vec![WordDef {
1233 name: "test".to_string(),
1234 effect: Some(Effect::new(
1235 StackType::Empty,
1236 StackType::singleton(Type::String),
1237 )),
1238 body: vec![Statement::StringLiteral("hello".to_string())],
1239 source: None,
1240 }],
1241 };
1242
1243 let mut checker = TypeChecker::new();
1244 assert!(checker.check_program(&program).is_ok());
1245 }
1246
1247 #[test]
1248 fn test_bool_literal() {
1249 let program = Program {
1252 includes: vec![],
1253 words: vec![WordDef {
1254 name: "test".to_string(),
1255 effect: Some(Effect::new(
1256 StackType::Empty,
1257 StackType::singleton(Type::Int),
1258 )),
1259 body: vec![Statement::BoolLiteral(true)],
1260 source: None,
1261 }],
1262 };
1263
1264 let mut checker = TypeChecker::new();
1265 assert!(checker.check_program(&program).is_ok());
1266 }
1267
1268 #[test]
1269 fn test_type_error_in_nested_conditional() {
1270 let program = Program {
1277 includes: vec![],
1278 words: vec![WordDef {
1279 name: "test".to_string(),
1280 effect: None,
1281 body: vec![
1282 Statement::IntLiteral(10),
1283 Statement::IntLiteral(20),
1284 Statement::WordCall(">".to_string()),
1285 Statement::If {
1286 then_branch: vec![
1287 Statement::IntLiteral(42),
1288 Statement::WordCall("write_line".to_string()),
1289 ],
1290 else_branch: Some(vec![
1291 Statement::StringLiteral("ok".to_string()),
1292 Statement::WordCall("write_line".to_string()),
1293 ]),
1294 },
1295 ],
1296 source: None,
1297 }],
1298 };
1299
1300 let mut checker = TypeChecker::new();
1301 let result = checker.check_program(&program);
1302 assert!(result.is_err());
1303 assert!(result.unwrap_err().contains("Type mismatch"));
1304 }
1305
1306 #[test]
1307 fn test_read_line_operation() {
1308 let program = Program {
1310 includes: vec![],
1311 words: vec![WordDef {
1312 name: "test".to_string(),
1313 effect: Some(Effect::new(
1314 StackType::Empty,
1315 StackType::singleton(Type::String),
1316 )),
1317 body: vec![Statement::WordCall("read_line".to_string())],
1318 source: None,
1319 }],
1320 };
1321
1322 let mut checker = TypeChecker::new();
1323 assert!(checker.check_program(&program).is_ok());
1324 }
1325
1326 #[test]
1327 fn test_comparison_operations() {
1328 let program = Program {
1333 includes: vec![],
1334 words: vec![WordDef {
1335 name: "test".to_string(),
1336 effect: Some(Effect::new(
1337 StackType::Empty.push(Type::Int).push(Type::Int),
1338 StackType::singleton(Type::Int),
1339 )),
1340 body: vec![Statement::WordCall("<=".to_string())],
1341 source: None,
1342 }],
1343 };
1344
1345 let mut checker = TypeChecker::new();
1346 assert!(checker.check_program(&program).is_ok());
1347 }
1348
1349 #[test]
1350 fn test_recursive_word_definitions() {
1351 let program = Program {
1357 includes: vec![],
1358 words: vec![
1359 WordDef {
1360 name: "is-even".to_string(),
1361 effect: Some(Effect::new(
1362 StackType::singleton(Type::Int),
1363 StackType::singleton(Type::Int),
1364 )),
1365 body: vec![
1366 Statement::WordCall("dup".to_string()),
1367 Statement::IntLiteral(0),
1368 Statement::WordCall("=".to_string()),
1369 Statement::If {
1370 then_branch: vec![
1371 Statement::WordCall("drop".to_string()),
1372 Statement::IntLiteral(1),
1373 ],
1374 else_branch: Some(vec![
1375 Statement::IntLiteral(1),
1376 Statement::WordCall("subtract".to_string()),
1377 Statement::WordCall("is-odd".to_string()),
1378 ]),
1379 },
1380 ],
1381 source: None,
1382 },
1383 WordDef {
1384 name: "is-odd".to_string(),
1385 effect: Some(Effect::new(
1386 StackType::singleton(Type::Int),
1387 StackType::singleton(Type::Int),
1388 )),
1389 body: vec![
1390 Statement::WordCall("dup".to_string()),
1391 Statement::IntLiteral(0),
1392 Statement::WordCall("=".to_string()),
1393 Statement::If {
1394 then_branch: vec![
1395 Statement::WordCall("drop".to_string()),
1396 Statement::IntLiteral(0),
1397 ],
1398 else_branch: Some(vec![
1399 Statement::IntLiteral(1),
1400 Statement::WordCall("subtract".to_string()),
1401 Statement::WordCall("is-even".to_string()),
1402 ]),
1403 },
1404 ],
1405 source: None,
1406 },
1407 ],
1408 };
1409
1410 let mut checker = TypeChecker::new();
1411 assert!(checker.check_program(&program).is_ok());
1412 }
1413
1414 #[test]
1415 fn test_word_calling_word_with_row_polymorphism() {
1416 let program = Program {
1421 includes: vec![],
1422 words: vec![
1423 WordDef {
1424 name: "apply-twice".to_string(),
1425 effect: Some(Effect::new(
1426 StackType::singleton(Type::Int),
1427 StackType::singleton(Type::Int),
1428 )),
1429 body: vec![
1430 Statement::WordCall("dup".to_string()),
1431 Statement::WordCall("add".to_string()),
1432 ],
1433 source: None,
1434 },
1435 WordDef {
1436 name: "quad".to_string(),
1437 effect: Some(Effect::new(
1438 StackType::singleton(Type::Int),
1439 StackType::singleton(Type::Int),
1440 )),
1441 body: vec![
1442 Statement::WordCall("apply-twice".to_string()),
1443 Statement::WordCall("apply-twice".to_string()),
1444 ],
1445 source: None,
1446 },
1447 ],
1448 };
1449
1450 let mut checker = TypeChecker::new();
1451 assert!(checker.check_program(&program).is_ok());
1452 }
1453
1454 #[test]
1455 fn test_deep_stack_types() {
1456 let mut stack_type = StackType::Empty;
1460 for _ in 0..10 {
1461 stack_type = stack_type.push(Type::Int);
1462 }
1463
1464 let program = Program {
1465 includes: vec![],
1466 words: vec![WordDef {
1467 name: "test".to_string(),
1468 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
1469 body: vec![
1470 Statement::WordCall("add".to_string()),
1471 Statement::WordCall("add".to_string()),
1472 Statement::WordCall("add".to_string()),
1473 Statement::WordCall("add".to_string()),
1474 Statement::WordCall("add".to_string()),
1475 Statement::WordCall("add".to_string()),
1476 Statement::WordCall("add".to_string()),
1477 Statement::WordCall("add".to_string()),
1478 Statement::WordCall("add".to_string()),
1479 ],
1480 source: None,
1481 }],
1482 };
1483
1484 let mut checker = TypeChecker::new();
1485 assert!(checker.check_program(&program).is_ok());
1486 }
1487
1488 #[test]
1489 fn test_simple_quotation() {
1490 let program = Program {
1494 includes: vec![],
1495 words: vec![WordDef {
1496 name: "test".to_string(),
1497 effect: Some(Effect::new(
1498 StackType::Empty,
1499 StackType::singleton(Type::Quotation(Box::new(Effect::new(
1500 StackType::RowVar("input".to_string()).push(Type::Int),
1501 StackType::RowVar("input".to_string()).push(Type::Int),
1502 )))),
1503 )),
1504 body: vec![Statement::Quotation {
1505 id: 0,
1506 body: vec![
1507 Statement::IntLiteral(1),
1508 Statement::WordCall("add".to_string()),
1509 ],
1510 }],
1511 source: None,
1512 }],
1513 };
1514
1515 let mut checker = TypeChecker::new();
1516 match checker.check_program(&program) {
1517 Ok(_) => {}
1518 Err(e) => panic!("Type check failed: {}", e),
1519 }
1520 }
1521
1522 #[test]
1523 fn test_empty_quotation() {
1524 let program = Program {
1528 includes: vec![],
1529 words: vec![WordDef {
1530 name: "test".to_string(),
1531 effect: Some(Effect::new(
1532 StackType::Empty,
1533 StackType::singleton(Type::Quotation(Box::new(Effect::new(
1534 StackType::RowVar("input".to_string()),
1535 StackType::RowVar("input".to_string()),
1536 )))),
1537 )),
1538 body: vec![Statement::Quotation {
1539 id: 1,
1540 body: vec![],
1541 }],
1542 source: None,
1543 }],
1544 };
1545
1546 let mut checker = TypeChecker::new();
1547 assert!(checker.check_program(&program).is_ok());
1548 }
1549
1550 #[test]
1577 fn test_nested_quotation() {
1578 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
1582 StackType::RowVar("input".to_string()).push(Type::Int),
1583 StackType::RowVar("input".to_string()).push(Type::Int),
1584 )));
1585
1586 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
1587 StackType::RowVar("input".to_string()),
1588 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
1589 )));
1590
1591 let program = Program {
1592 includes: vec![],
1593 words: vec![WordDef {
1594 name: "test".to_string(),
1595 effect: Some(Effect::new(
1596 StackType::Empty,
1597 StackType::singleton(outer_quot_type),
1598 )),
1599 body: vec![Statement::Quotation {
1600 id: 2,
1601 body: vec![Statement::Quotation {
1602 id: 3,
1603 body: vec![
1604 Statement::IntLiteral(1),
1605 Statement::WordCall("add".to_string()),
1606 ],
1607 }],
1608 }],
1609 source: None,
1610 }],
1611 };
1612
1613 let mut checker = TypeChecker::new();
1614 assert!(checker.check_program(&program).is_ok());
1615 }
1616}