1use crate::ast::{Program, Statement, WordDef};
7use crate::builtins::builtin_signature;
8use crate::capture_analysis::calculate_captures;
9use crate::types::{
10 Effect, SideEffect, StackType, Type, UnionTypeInfo, VariantFieldInfo, VariantInfo,
11};
12use crate::unification::{Subst, unify_stacks, unify_types};
13use std::collections::HashMap;
14
15pub struct TypeChecker {
16 env: HashMap<String, Effect>,
18 unions: HashMap<String, UnionTypeInfo>,
21 fresh_counter: std::cell::Cell<usize>,
23 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
27 expected_quotation_type: std::cell::RefCell<Option<Type>>,
30 current_word: std::cell::RefCell<Option<String>>,
33 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
37}
38
39impl TypeChecker {
40 pub fn new() -> Self {
41 TypeChecker {
42 env: HashMap::new(),
43 unions: HashMap::new(),
44 fresh_counter: std::cell::Cell::new(0),
45 quotation_types: std::cell::RefCell::new(HashMap::new()),
46 expected_quotation_type: std::cell::RefCell::new(None),
47 current_word: std::cell::RefCell::new(None),
48 statement_top_types: std::cell::RefCell::new(HashMap::new()),
49 }
50 }
51
52 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
54 self.unions.get(name)
55 }
56
57 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
59 &self.unions
60 }
61
62 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
66 for (union_name, union_info) in &self.unions {
67 for variant in &union_info.variants {
68 if variant.name == variant_name {
69 return Some((union_name.as_str(), variant));
70 }
71 }
72 }
73 None
74 }
75
76 pub fn register_external_words(&mut self, words: &[(&str, &Effect)]) {
80 for (name, effect) in words {
81 self.env.insert(name.to_string(), (*effect).clone());
82 }
83 }
84
85 pub fn register_external_unions(&mut self, union_names: &[&str]) {
91 for name in union_names {
92 self.unions.insert(
95 name.to_string(),
96 UnionTypeInfo {
97 name: name.to_string(),
98 variants: vec![],
99 },
100 );
101 }
102 }
103
104 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
110 self.quotation_types.replace(HashMap::new())
111 }
112
113 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
116 self.statement_top_types.replace(HashMap::new())
117 }
118
119 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
122 match stack {
123 StackType::Cons { top, .. } => match top {
124 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
125 _ => None,
126 },
127 _ => None,
128 }
129 }
130
131 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
133 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
134 self.statement_top_types
135 .borrow_mut()
136 .insert((word_name.to_string(), stmt_index), top_type);
137 }
138 }
139
140 fn fresh_var(&self, prefix: &str) -> String {
142 let n = self.fresh_counter.get();
143 self.fresh_counter.set(n + 1);
144 format!("{}${}", prefix, n)
145 }
146
147 fn freshen_effect(&self, effect: &Effect) -> Effect {
149 let mut type_map = HashMap::new();
150 let mut row_map = HashMap::new();
151
152 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
153 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
154
155 let fresh_effects = effect
157 .effects
158 .iter()
159 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
160 .collect();
161
162 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
163 }
164
165 fn freshen_side_effect(
166 &self,
167 effect: &SideEffect,
168 type_map: &mut HashMap<String, String>,
169 row_map: &mut HashMap<String, String>,
170 ) -> SideEffect {
171 match effect {
172 SideEffect::Yield(ty) => {
173 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
174 }
175 }
176 }
177
178 fn freshen_stack(
179 &self,
180 stack: &StackType,
181 type_map: &mut HashMap<String, String>,
182 row_map: &mut HashMap<String, String>,
183 ) -> StackType {
184 match stack {
185 StackType::Empty => StackType::Empty,
186 StackType::RowVar(name) => {
187 let fresh_name = row_map
188 .entry(name.clone())
189 .or_insert_with(|| self.fresh_var(name));
190 StackType::RowVar(fresh_name.clone())
191 }
192 StackType::Cons { rest, top } => {
193 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
194 let fresh_top = self.freshen_type(top, type_map, row_map);
195 StackType::Cons {
196 rest: Box::new(fresh_rest),
197 top: fresh_top,
198 }
199 }
200 }
201 }
202
203 fn freshen_type(
204 &self,
205 ty: &Type,
206 type_map: &mut HashMap<String, String>,
207 row_map: &mut HashMap<String, String>,
208 ) -> Type {
209 match ty {
210 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
211 ty.clone()
212 }
213 Type::Var(name) => {
214 let fresh_name = type_map
215 .entry(name.clone())
216 .or_insert_with(|| self.fresh_var(name));
217 Type::Var(fresh_name.clone())
218 }
219 Type::Quotation(effect) => {
220 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
221 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
222 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
223 }
224 Type::Closure { effect, captures } => {
225 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
226 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
227 let fresh_captures = captures
228 .iter()
229 .map(|t| self.freshen_type(t, type_map, row_map))
230 .collect();
231 Type::Closure {
232 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
233 captures: fresh_captures,
234 }
235 }
236 Type::Union(name) => Type::Union(name.clone()),
238 }
239 }
240
241 fn parse_type_name(&self, name: &str) -> Type {
245 match name {
246 "Int" => Type::Int,
247 "Float" => Type::Float,
248 "Bool" => Type::Bool,
249 "String" => Type::String,
250 "Channel" => Type::Channel,
251 other => Type::Union(other.to_string()),
253 }
254 }
255
256 fn is_valid_type_name(&self, name: &str) -> bool {
261 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
262 || self.unions.contains_key(name)
263 }
264
265 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
269 for union_def in &program.unions {
270 for variant in &union_def.variants {
271 for field in &variant.fields {
272 if !self.is_valid_type_name(&field.type_name) {
273 return Err(format!(
274 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
275 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
276 field.type_name, field.name, variant.name, union_def.name
277 ));
278 }
279 }
280 }
281 }
282 Ok(())
283 }
284
285 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
287 for union_def in &program.unions {
289 let variants = union_def
290 .variants
291 .iter()
292 .map(|v| VariantInfo {
293 name: v.name.clone(),
294 fields: v
295 .fields
296 .iter()
297 .map(|f| VariantFieldInfo {
298 name: f.name.clone(),
299 field_type: self.parse_type_name(&f.type_name),
300 })
301 .collect(),
302 })
303 .collect();
304
305 self.unions.insert(
306 union_def.name.clone(),
307 UnionTypeInfo {
308 name: union_def.name.clone(),
309 variants,
310 },
311 );
312 }
313
314 self.validate_union_field_types(program)?;
316
317 for word in &program.words {
320 if let Some(effect) = &word.effect {
321 self.env.insert(word.name.clone(), effect.clone());
322 } else {
323 return Err(format!(
324 "Word '{}' is missing a stack effect declaration.\n\
325 All words must declare their stack effect, e.g.: : {} ( -- ) ... ;",
326 word.name, word.name
327 ));
328 }
329 }
330
331 for word in &program.words {
333 self.check_word(word)?;
334 }
335
336 Ok(())
337 }
338
339 fn check_word(&self, word: &WordDef) -> Result<(), String> {
341 *self.current_word.borrow_mut() = Some(word.name.clone());
343
344 let declared_effect = word.effect.as_ref().expect("word must have effect");
346
347 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
350 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
351 {
352 *self.expected_quotation_type.borrow_mut() = Some(top_type);
353 }
354
355 let (result_stack, _subst, inferred_effects) =
357 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
358
359 *self.expected_quotation_type.borrow_mut() = None;
361
362 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
364 format!(
365 "Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
366 word.name, declared_effect.outputs, result_stack, e
367 )
368 })?;
369
370 for inferred in &inferred_effects {
374 if !self.effect_matches_any(inferred, &declared_effect.effects) {
375 return Err(format!(
376 "Word '{}': body produces effect '{}' but no matching effect is declared.\n\
377 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
378 word.name, inferred
379 ));
380 }
381 }
382
383 for declared in &declared_effect.effects {
386 if !self.effect_matches_any(declared, &inferred_effects) {
387 return Err(format!(
388 "Word '{}': declares effect '{}' but body doesn't produce it.\n\
389 Hint: Remove the effect declaration or ensure the body uses yield.",
390 word.name, declared
391 ));
392 }
393 }
394
395 *self.current_word.borrow_mut() = None;
397
398 Ok(())
399 }
400
401 fn infer_statements_from(
408 &self,
409 statements: &[Statement],
410 start_stack: &StackType,
411 capture_stmt_types: bool,
412 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
413 let mut current_stack = start_stack.clone();
414 let mut accumulated_subst = Subst::empty();
415 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
416 let mut skip_next = false;
417
418 for (i, stmt) in statements.iter().enumerate() {
419 if skip_next {
421 skip_next = false;
422 continue;
423 }
424
425 if let Statement::IntLiteral(n) = stmt
428 && let Some(Statement::WordCall {
429 name: next_word, ..
430 }) = statements.get(i + 1)
431 {
432 if next_word == "pick" {
433 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
434 current_stack = new_stack;
435 accumulated_subst = accumulated_subst.compose(&subst);
436 skip_next = true; continue;
438 } else if next_word == "roll" {
439 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
440 current_stack = new_stack;
441 accumulated_subst = accumulated_subst.compose(&subst);
442 skip_next = true; continue;
444 }
445 }
446
447 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
450 let saved = self.expected_quotation_type.borrow().clone();
452
453 if let Some(Statement::WordCall {
455 name: next_word, ..
456 }) = statements.get(i + 1)
457 {
458 if let Some(next_effect) = self.lookup_word_effect(next_word) {
460 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
463 && matches!(quot_type, Type::Quotation(_))
464 {
465 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
466 }
467 }
468 }
469 Some(saved)
470 } else {
471 None
472 };
473
474 if capture_stmt_types && let Some(word_name) = self.current_word.borrow().as_ref() {
478 self.capture_statement_type(word_name, i, ¤t_stack);
479 }
480
481 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
482 current_stack = new_stack;
483 accumulated_subst = accumulated_subst.compose(&subst);
484
485 for effect in effects {
487 if !accumulated_effects.contains(&effect) {
488 accumulated_effects.push(effect);
489 }
490 }
491
492 if let Some(saved) = saved_expected_type {
494 *self.expected_quotation_type.borrow_mut() = saved;
495 }
496 }
497
498 Ok((current_stack, accumulated_subst, accumulated_effects))
499 }
500
501 fn handle_literal_pick(
512 &self,
513 n: i64,
514 current_stack: StackType,
515 ) -> Result<(StackType, Subst), String> {
516 if n < 0 {
517 return Err(format!("pick: index must be non-negative, got {}", n));
518 }
519
520 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
522
523 Ok((current_stack.push(type_at_n), Subst::empty()))
525 }
526
527 fn handle_literal_roll(
538 &self,
539 n: i64,
540 current_stack: StackType,
541 ) -> Result<(StackType, Subst), String> {
542 if n < 0 {
543 return Err(format!("roll: index must be non-negative, got {}", n));
544 }
545
546 self.rotate_type_to_top(current_stack, n as usize)
551 }
552
553 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
555 let mut current = stack;
556 let mut pos = 0;
557
558 loop {
559 match current {
560 StackType::Cons { rest, top } => {
561 if pos == n {
562 return Ok(top.clone());
563 }
564 pos += 1;
565 current = rest;
566 }
567 StackType::RowVar(name) => {
568 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
578 return Ok(fresh_type);
579 }
580 StackType::Empty => {
581 return Err(format!(
582 "{}: stack underflow - position {} requested but stack has only {} concrete items",
583 op, n, pos
584 ));
585 }
586 }
587 }
588 }
589
590 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
592 if n == 0 {
593 return Ok((stack, Subst::empty()));
595 }
596
597 let mut types_above: Vec<Type> = Vec::new();
599 let mut current = stack;
600 let mut pos = 0;
601
602 loop {
604 match current {
605 StackType::Cons { rest, top } => {
606 if pos == n {
607 let mut result = *rest;
610 for ty in types_above.into_iter().rev() {
612 result = result.push(ty);
613 }
614 result = result.push(top);
616 return Ok((result, Subst::empty()));
617 }
618 types_above.push(top);
619 pos += 1;
620 current = *rest;
621 }
622 StackType::RowVar(name) => {
623 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
636
637 let mut result = StackType::RowVar(name.clone());
639 for ty in types_above.into_iter().rev() {
640 result = result.push(ty);
641 }
642 result = result.push(fresh_type);
643 return Ok((result, Subst::empty()));
644 }
645 StackType::Empty => {
646 return Err(format!(
647 "roll: stack underflow - position {} requested but stack has only {} items",
648 n, pos
649 ));
650 }
651 }
652 }
653 }
654
655 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
659 let start = StackType::RowVar("input".to_string());
660 let (result, subst, effects) = self.infer_statements_from(statements, &start, false)?;
662
663 let normalized_start = subst.apply_stack(&start);
666 let normalized_result = subst.apply_stack(&result);
667
668 Ok(Effect::with_effects(
669 normalized_start,
670 normalized_result,
671 effects,
672 ))
673 }
674
675 fn infer_match(
677 &self,
678 arms: &[crate::ast::MatchArm],
679 current_stack: StackType,
680 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
681 if arms.is_empty() {
682 return Err("match expression must have at least one arm".to_string());
683 }
684
685 let (stack_after_match, _matched_type) =
687 self.pop_type(¤t_stack, "match expression")?;
688
689 let mut arm_results: Vec<StackType> = Vec::new();
691 let mut combined_subst = Subst::empty();
692 let mut merged_effects: Vec<SideEffect> = Vec::new();
693
694 for arm in arms {
695 let variant_name = match &arm.pattern {
697 crate::ast::Pattern::Variant(name) => name.as_str(),
698 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
699 };
700
701 let (_union_name, variant_info) = self
703 .find_variant(variant_name)
704 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
705
706 let arm_stack = self.push_variant_fields(
708 &stack_after_match,
709 &arm.pattern,
710 variant_info,
711 variant_name,
712 )?;
713
714 let (arm_result, arm_subst, arm_effects) =
717 self.infer_statements_from(&arm.body, &arm_stack, false)?;
718
719 combined_subst = combined_subst.compose(&arm_subst);
720 arm_results.push(arm_result);
721
722 for effect in arm_effects {
724 if !merged_effects.contains(&effect) {
725 merged_effects.push(effect);
726 }
727 }
728 }
729
730 let mut final_result = arm_results[0].clone();
732 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
733 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
734 format!(
735 "match arms have incompatible stack effects:\n\
736 \x20 arm 0 produces: {}\n\
737 \x20 arm {} produces: {}\n\
738 \x20 All match arms must produce the same stack shape.\n\
739 \x20 Error: {}",
740 final_result, i, arm_result, e
741 )
742 })?;
743 combined_subst = combined_subst.compose(&arm_subst);
744 final_result = arm_subst.apply_stack(&final_result);
745 }
746
747 Ok((final_result, combined_subst, merged_effects))
748 }
749
750 fn push_variant_fields(
752 &self,
753 stack: &StackType,
754 pattern: &crate::ast::Pattern,
755 variant_info: &VariantInfo,
756 variant_name: &str,
757 ) -> Result<StackType, String> {
758 let mut arm_stack = stack.clone();
759 match pattern {
760 crate::ast::Pattern::Variant(_) => {
761 for field in &variant_info.fields {
763 arm_stack = arm_stack.push(field.field_type.clone());
764 }
765 }
766 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
767 for binding in bindings {
769 let field = variant_info
770 .fields
771 .iter()
772 .find(|f| &f.name == binding)
773 .ok_or_else(|| {
774 let available: Vec<_> = variant_info
775 .fields
776 .iter()
777 .map(|f| f.name.as_str())
778 .collect();
779 format!(
780 "Unknown field '{}' in pattern for variant '{}'.\n\
781 Available fields: {}",
782 binding,
783 variant_name,
784 available.join(", ")
785 )
786 })?;
787 arm_stack = arm_stack.push(field.field_type.clone());
788 }
789 }
790 }
791 Ok(arm_stack)
792 }
793
794 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
810 if let Some(current_word) = self.current_word.borrow().as_ref()
811 && let Some(Statement::WordCall { name, .. }) = statements.last()
812 {
813 return name == current_word;
814 }
815 false
816 }
817
818 fn infer_if(
820 &self,
821 then_branch: &[Statement],
822 else_branch: &Option<Vec<Statement>>,
823 current_stack: StackType,
824 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
825 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
827
828 let cond_subst = unify_stacks(
830 &StackType::singleton(Type::Bool),
831 &StackType::singleton(cond_type),
832 )
833 .map_err(|e| format!("if condition must be Bool: {}", e))?;
834
835 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
836
837 let then_diverges = self.is_divergent_branch(then_branch);
839 let else_diverges = else_branch
840 .as_ref()
841 .map(|stmts| self.is_divergent_branch(stmts))
842 .unwrap_or(false);
843
844 let (then_result, then_subst, then_effects) =
847 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
848
849 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
851 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
852 } else {
853 (stack_after_cond.clone(), Subst::empty(), vec![])
854 };
855
856 let mut merged_effects = then_effects;
858 for effect in else_effects {
859 if !merged_effects.contains(&effect) {
860 merged_effects.push(effect);
861 }
862 }
863
864 let (result, branch_subst) = if then_diverges && !else_diverges {
870 (else_result, Subst::empty())
872 } else if else_diverges && !then_diverges {
873 (then_result, Subst::empty())
875 } else {
876 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
878 format!(
879 "if/else branches have incompatible stack effects:\n\
880 \x20 then branch produces: {}\n\
881 \x20 else branch produces: {}\n\
882 \x20 Both branches of an if/else must produce the same stack shape.\n\
883 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
884 \x20 Error: {}",
885 then_result, else_result, e
886 )
887 })?;
888 (branch_subst.apply_stack(&then_result), branch_subst)
889 };
890
891 let total_subst = cond_subst
893 .compose(&then_subst)
894 .compose(&else_subst)
895 .compose(&branch_subst);
896 Ok((result, total_subst, merged_effects))
897 }
898
899 fn infer_quotation(
902 &self,
903 id: usize,
904 body: &[Statement],
905 current_stack: StackType,
906 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
907 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
910 *self.expected_quotation_type.borrow_mut() = None;
911
912 let body_effect = self.infer_statements(body)?;
914
915 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
917
918 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
920
921 self.quotation_types
923 .borrow_mut()
924 .insert(id, quot_type.clone());
925
926 let result_stack = match "_type {
928 Type::Quotation(_) => {
929 current_stack.push(quot_type)
931 }
932 Type::Closure { captures, .. } => {
933 let mut stack = current_stack.clone();
935 for _ in 0..captures.len() {
936 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
937 stack = new_stack;
938 }
939 stack.push(quot_type)
940 }
941 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
942 };
943
944 Ok((result_stack, Subst::empty(), vec![]))
948 }
949
950 fn infer_word_call(
952 &self,
953 name: &str,
954 current_stack: StackType,
955 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
956 if name == "call" {
959 return self.infer_call(current_stack);
960 }
961
962 let effect = self
964 .lookup_word_effect(name)
965 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
966
967 let fresh_effect = self.freshen_effect(&effect);
969
970 let adjusted_stack = if name == "strand.spawn" {
972 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
973 } else {
974 current_stack
975 };
976
977 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name)?;
979
980 let propagated_effects = fresh_effect.effects.clone();
984
985 Ok((result_stack, subst, propagated_effects))
986 }
987
988 fn infer_call(
994 &self,
995 current_stack: StackType,
996 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
997 let (remaining_stack, quot_type) = current_stack
999 .clone()
1000 .pop()
1001 .ok_or_else(|| "call: stack underflow - expected quotation on stack".to_string())?;
1002
1003 let quot_effect = match "_type {
1005 Type::Quotation(effect) => (**effect).clone(),
1006 Type::Closure { effect, .. } => (**effect).clone(),
1007 Type::Var(_) => {
1008 let effect = self
1011 .lookup_word_effect("call")
1012 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1013 let fresh_effect = self.freshen_effect(&effect);
1014 let (result_stack, subst) =
1015 self.apply_effect(&fresh_effect, current_stack, "call")?;
1016 return Ok((result_stack, subst, vec![]));
1017 }
1018 _ => {
1019 return Err(format!(
1020 "call: expected quotation or closure on stack, got {}",
1021 quot_type
1022 ));
1023 }
1024 };
1025
1026 if quot_effect.has_yield() {
1028 return Err("Cannot call quotation with Yield effect directly.\n\
1029 Quotations that yield values must be wrapped with `strand.weave`.\n\
1030 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1031 .to_string());
1032 }
1033
1034 let fresh_effect = self.freshen_effect("_effect);
1036
1037 let (result_stack, subst) = self.apply_effect(&fresh_effect, remaining_stack, "call")?;
1039
1040 let propagated_effects = fresh_effect.effects.clone();
1042
1043 Ok((result_stack, subst, propagated_effects))
1044 }
1045
1046 fn infer_statement(
1049 &self,
1050 statement: &Statement,
1051 current_stack: StackType,
1052 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1053 match statement {
1054 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1055 Statement::BoolLiteral(_) => {
1056 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1057 }
1058 Statement::StringLiteral(_) => {
1059 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1060 }
1061 Statement::FloatLiteral(_) => {
1062 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1063 }
1064 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1065 Statement::Match { arms } => self.infer_match(arms, current_stack),
1066 Statement::WordCall { name, .. } => self.infer_word_call(name, current_stack),
1067 Statement::If {
1068 then_branch,
1069 else_branch,
1070 } => self.infer_if(then_branch, else_branch, current_stack),
1071 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1072 }
1073 }
1074
1075 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1077 if let Some(effect) = builtin_signature(name) {
1079 return Some(effect);
1080 }
1081
1082 self.env.get(name).cloned()
1084 }
1085
1086 fn apply_effect(
1091 &self,
1092 effect: &Effect,
1093 current_stack: StackType,
1094 operation: &str,
1095 ) -> Result<(StackType, Subst), String> {
1096 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1104 let stack_concrete = Self::count_concrete_types(¤t_stack);
1105
1106 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1107 let is_rigid = row_var_name == "rest";
1120
1121 if is_rigid && effect_concrete > stack_concrete {
1122 let word_name = self.current_word.borrow().clone().unwrap_or_default();
1123 return Err(format!(
1124 "In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1125 word_name, operation, effect_concrete, stack_concrete
1126 ));
1127 }
1128 }
1129
1130 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1132 format!(
1133 "{}: stack type mismatch. Expected {}, got {}: {}",
1134 operation, effect.inputs, current_stack, e
1135 )
1136 })?;
1137
1138 let result_stack = subst.apply_stack(&effect.outputs);
1140
1141 Ok((result_stack, subst))
1142 }
1143
1144 fn count_concrete_types(stack: &StackType) -> usize {
1146 let mut count = 0;
1147 let mut current = stack;
1148 while let StackType::Cons { rest, top: _ } = current {
1149 count += 1;
1150 current = rest;
1151 }
1152 count
1153 }
1154
1155 fn get_row_var_base(stack: &StackType) -> Option<String> {
1157 let mut current = stack;
1158 while let StackType::Cons { rest, top: _ } = current {
1159 current = rest;
1160 }
1161 match current {
1162 StackType::RowVar(name) => Some(name.clone()),
1163 _ => None,
1164 }
1165 }
1166
1167 fn adjust_stack_for_spawn(
1172 &self,
1173 current_stack: StackType,
1174 spawn_effect: &Effect,
1175 ) -> Result<StackType, String> {
1176 let expected_quot_type = match &spawn_effect.inputs {
1179 StackType::Cons { top, rest: _ } => {
1180 if !matches!(top, Type::Quotation(_)) {
1181 return Ok(current_stack); }
1183 top
1184 }
1185 _ => return Ok(current_stack),
1186 };
1187
1188 let (rest_stack, actual_type) = match ¤t_stack {
1190 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1191 _ => return Ok(current_stack), };
1193
1194 if let Type::Quotation(actual_effect) = actual_type {
1196 if !matches!(actual_effect.inputs, StackType::Empty) {
1198 let expected_effect = match expected_quot_type {
1200 Type::Quotation(eff) => eff.as_ref(),
1201 _ => return Ok(current_stack),
1202 };
1203
1204 let captures = calculate_captures(actual_effect, expected_effect)?;
1206
1207 let closure_type = Type::Closure {
1209 effect: Box::new(expected_effect.clone()),
1210 captures: captures.clone(),
1211 };
1212
1213 let mut adjusted_stack = rest_stack;
1216 for _ in &captures {
1217 adjusted_stack = match adjusted_stack {
1218 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1219 _ => {
1220 return Err(format!(
1221 "strand.spawn: not enough values on stack to capture. Need {} values",
1222 captures.len()
1223 ));
1224 }
1225 };
1226 }
1227
1228 return Ok(adjusted_stack.push(closure_type));
1230 }
1231 }
1232
1233 Ok(current_stack)
1234 }
1235
1236 fn analyze_captures(
1262 &self,
1263 body_effect: &Effect,
1264 _current_stack: &StackType,
1265 ) -> Result<Type, String> {
1266 let expected = self.expected_quotation_type.borrow().clone();
1268
1269 match expected {
1270 Some(Type::Closure { effect, .. }) => {
1271 let captures = calculate_captures(body_effect, &effect)?;
1273 Ok(Type::Closure { effect, captures })
1274 }
1275 Some(Type::Quotation(expected_effect)) => {
1276 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1282 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1283
1284 if expected_is_empty && body_needs_inputs {
1285 let captures = calculate_captures(body_effect, &expected_effect)?;
1288 Ok(Type::Closure {
1289 effect: expected_effect,
1290 captures,
1291 })
1292 } else {
1293 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1299 let expected_quot = Type::Quotation(expected_effect.clone());
1300 unify_types(&body_quot, &expected_quot).map_err(|e| {
1301 format!(
1302 "quotation effect mismatch: expected {}, got {}: {}",
1303 expected_effect, body_effect, e
1304 )
1305 })?;
1306
1307 Ok(Type::Quotation(expected_effect))
1309 }
1310 }
1311 _ => {
1312 Ok(Type::Quotation(Box::new(body_effect.clone())))
1314 }
1315 }
1316 }
1317
1318 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1322 declared.iter().any(|decl| match (inferred, decl) {
1323 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1324 })
1325 }
1326
1327 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1329 match stack {
1330 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1331 StackType::Empty => Err(format!(
1332 "{}: stack underflow - expected value on stack but stack is empty",
1333 context
1334 )),
1335 StackType::RowVar(_) => {
1336 Err(format!(
1340 "{}: cannot pop from polymorphic stack without more type information",
1341 context
1342 ))
1343 }
1344 }
1345 }
1346}
1347
1348impl Default for TypeChecker {
1349 fn default() -> Self {
1350 Self::new()
1351 }
1352}
1353
1354#[cfg(test)]
1355mod tests {
1356 use super::*;
1357
1358 #[test]
1359 fn test_simple_literal() {
1360 let program = Program {
1361 includes: vec![],
1362 unions: vec![],
1363 words: vec![WordDef {
1364 name: "test".to_string(),
1365 effect: Some(Effect::new(
1366 StackType::Empty,
1367 StackType::singleton(Type::Int),
1368 )),
1369 body: vec![Statement::IntLiteral(42)],
1370 source: None,
1371 }],
1372 };
1373
1374 let mut checker = TypeChecker::new();
1375 assert!(checker.check_program(&program).is_ok());
1376 }
1377
1378 #[test]
1379 fn test_simple_operation() {
1380 let program = Program {
1382 includes: vec![],
1383 unions: vec![],
1384 words: vec![WordDef {
1385 name: "test".to_string(),
1386 effect: Some(Effect::new(
1387 StackType::Empty.push(Type::Int).push(Type::Int),
1388 StackType::singleton(Type::Int),
1389 )),
1390 body: vec![Statement::WordCall {
1391 name: "i.add".to_string(),
1392 span: None,
1393 }],
1394 source: None,
1395 }],
1396 };
1397
1398 let mut checker = TypeChecker::new();
1399 assert!(checker.check_program(&program).is_ok());
1400 }
1401
1402 #[test]
1403 fn test_type_mismatch() {
1404 let program = Program {
1406 includes: vec![],
1407 unions: vec![],
1408 words: vec![WordDef {
1409 name: "test".to_string(),
1410 effect: Some(Effect::new(
1411 StackType::singleton(Type::String),
1412 StackType::Empty,
1413 )),
1414 body: vec![
1415 Statement::IntLiteral(42), Statement::WordCall {
1417 name: "io.write-line".to_string(),
1418 span: None,
1419 },
1420 ],
1421 source: None,
1422 }],
1423 };
1424
1425 let mut checker = TypeChecker::new();
1426 let result = checker.check_program(&program);
1427 assert!(result.is_err());
1428 assert!(result.unwrap_err().contains("Type mismatch"));
1429 }
1430
1431 #[test]
1432 fn test_polymorphic_dup() {
1433 let program = Program {
1435 includes: vec![],
1436 unions: vec![],
1437 words: vec![WordDef {
1438 name: "my-dup".to_string(),
1439 effect: Some(Effect::new(
1440 StackType::singleton(Type::Int),
1441 StackType::Empty.push(Type::Int).push(Type::Int),
1442 )),
1443 body: vec![Statement::WordCall {
1444 name: "dup".to_string(),
1445 span: None,
1446 }],
1447 source: None,
1448 }],
1449 };
1450
1451 let mut checker = TypeChecker::new();
1452 assert!(checker.check_program(&program).is_ok());
1453 }
1454
1455 #[test]
1456 fn test_conditional_branches() {
1457 let program = Program {
1460 includes: vec![],
1461 unions: vec![],
1462 words: vec![WordDef {
1463 name: "test".to_string(),
1464 effect: Some(Effect::new(
1465 StackType::Empty.push(Type::Int).push(Type::Int),
1466 StackType::singleton(Type::String),
1467 )),
1468 body: vec![
1469 Statement::WordCall {
1470 name: "i.>".to_string(),
1471 span: None,
1472 },
1473 Statement::If {
1474 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1475 else_branch: Some(vec![Statement::StringLiteral(
1476 "not greater".to_string(),
1477 )]),
1478 },
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_mismatched_branches() {
1490 let program = Program {
1493 includes: vec![],
1494 unions: vec![],
1495 words: vec![WordDef {
1496 name: "test".to_string(),
1497 effect: Some(Effect::new(
1498 StackType::Empty,
1499 StackType::singleton(Type::Int),
1500 )),
1501 body: vec![
1502 Statement::BoolLiteral(true),
1503 Statement::If {
1504 then_branch: vec![Statement::IntLiteral(42)],
1505 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1506 },
1507 ],
1508 source: None,
1509 }],
1510 };
1511
1512 let mut checker = TypeChecker::new();
1513 let result = checker.check_program(&program);
1514 assert!(result.is_err());
1515 assert!(result.unwrap_err().contains("incompatible"));
1516 }
1517
1518 #[test]
1519 fn test_user_defined_word_call() {
1520 let program = Program {
1523 includes: vec![],
1524 unions: vec![],
1525 words: vec![
1526 WordDef {
1527 name: "helper".to_string(),
1528 effect: Some(Effect::new(
1529 StackType::singleton(Type::Int),
1530 StackType::singleton(Type::String),
1531 )),
1532 body: vec![Statement::WordCall {
1533 name: "int->string".to_string(),
1534 span: None,
1535 }],
1536 source: None,
1537 },
1538 WordDef {
1539 name: "main".to_string(),
1540 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1541 body: vec![
1542 Statement::IntLiteral(42),
1543 Statement::WordCall {
1544 name: "helper".to_string(),
1545 span: None,
1546 },
1547 Statement::WordCall {
1548 name: "io.write-line".to_string(),
1549 span: None,
1550 },
1551 ],
1552 source: None,
1553 },
1554 ],
1555 };
1556
1557 let mut checker = TypeChecker::new();
1558 assert!(checker.check_program(&program).is_ok());
1559 }
1560
1561 #[test]
1562 fn test_arithmetic_chain() {
1563 let program = Program {
1566 includes: vec![],
1567 unions: vec![],
1568 words: vec![WordDef {
1569 name: "test".to_string(),
1570 effect: Some(Effect::new(
1571 StackType::Empty
1572 .push(Type::Int)
1573 .push(Type::Int)
1574 .push(Type::Int),
1575 StackType::singleton(Type::Int),
1576 )),
1577 body: vec![
1578 Statement::WordCall {
1579 name: "i.add".to_string(),
1580 span: None,
1581 },
1582 Statement::WordCall {
1583 name: "i.multiply".to_string(),
1584 span: None,
1585 },
1586 ],
1587 source: None,
1588 }],
1589 };
1590
1591 let mut checker = TypeChecker::new();
1592 assert!(checker.check_program(&program).is_ok());
1593 }
1594
1595 #[test]
1596 fn test_write_line_type_error() {
1597 let program = Program {
1599 includes: vec![],
1600 unions: vec![],
1601 words: vec![WordDef {
1602 name: "test".to_string(),
1603 effect: Some(Effect::new(
1604 StackType::singleton(Type::Int),
1605 StackType::Empty,
1606 )),
1607 body: vec![Statement::WordCall {
1608 name: "io.write-line".to_string(),
1609 span: None,
1610 }],
1611 source: None,
1612 }],
1613 };
1614
1615 let mut checker = TypeChecker::new();
1616 let result = checker.check_program(&program);
1617 assert!(result.is_err());
1618 assert!(result.unwrap_err().contains("Type mismatch"));
1619 }
1620
1621 #[test]
1622 fn test_stack_underflow_drop() {
1623 let program = Program {
1625 includes: vec![],
1626 unions: vec![],
1627 words: vec![WordDef {
1628 name: "test".to_string(),
1629 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1630 body: vec![Statement::WordCall {
1631 name: "drop".to_string(),
1632 span: None,
1633 }],
1634 source: None,
1635 }],
1636 };
1637
1638 let mut checker = TypeChecker::new();
1639 let result = checker.check_program(&program);
1640 assert!(result.is_err());
1641 assert!(result.unwrap_err().contains("mismatch"));
1642 }
1643
1644 #[test]
1645 fn test_stack_underflow_add() {
1646 let program = Program {
1648 includes: vec![],
1649 unions: vec![],
1650 words: vec![WordDef {
1651 name: "test".to_string(),
1652 effect: Some(Effect::new(
1653 StackType::singleton(Type::Int),
1654 StackType::singleton(Type::Int),
1655 )),
1656 body: vec![Statement::WordCall {
1657 name: "i.add".to_string(),
1658 span: None,
1659 }],
1660 source: None,
1661 }],
1662 };
1663
1664 let mut checker = TypeChecker::new();
1665 let result = checker.check_program(&program);
1666 assert!(result.is_err());
1667 assert!(result.unwrap_err().contains("mismatch"));
1668 }
1669
1670 #[test]
1673 fn test_stack_underflow_rot_issue_169() {
1674 let program = Program {
1678 includes: vec![],
1679 unions: vec![],
1680 words: vec![WordDef {
1681 name: "test".to_string(),
1682 effect: Some(Effect::new(
1683 StackType::RowVar("rest".to_string()),
1684 StackType::RowVar("rest".to_string()),
1685 )),
1686 body: vec![
1687 Statement::IntLiteral(3),
1688 Statement::IntLiteral(4),
1689 Statement::WordCall {
1690 name: "rot".to_string(),
1691 span: None,
1692 },
1693 ],
1694 source: None,
1695 }],
1696 };
1697
1698 let mut checker = TypeChecker::new();
1699 let result = checker.check_program(&program);
1700 assert!(result.is_err(), "rot with 2 values should fail");
1701 let err = result.unwrap_err();
1702 assert!(
1703 err.contains("stack underflow") || err.contains("requires 3"),
1704 "Error should mention underflow: {}",
1705 err
1706 );
1707 }
1708
1709 #[test]
1710 fn test_csp_operations() {
1711 let program = Program {
1718 includes: vec![],
1719 unions: vec![],
1720 words: vec![WordDef {
1721 name: "test".to_string(),
1722 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1723 body: vec![
1724 Statement::WordCall {
1725 name: "chan.make".to_string(),
1726 span: None,
1727 },
1728 Statement::IntLiteral(42),
1729 Statement::WordCall {
1730 name: "swap".to_string(),
1731 span: None,
1732 },
1733 Statement::WordCall {
1734 name: "chan.send".to_string(),
1735 span: None,
1736 },
1737 Statement::WordCall {
1738 name: "drop".to_string(),
1739 span: None,
1740 },
1741 ],
1742 source: None,
1743 }],
1744 };
1745
1746 let mut checker = TypeChecker::new();
1747 assert!(checker.check_program(&program).is_ok());
1748 }
1749
1750 #[test]
1751 fn test_complex_stack_shuffling() {
1752 let program = Program {
1755 includes: vec![],
1756 unions: vec![],
1757 words: vec![WordDef {
1758 name: "test".to_string(),
1759 effect: Some(Effect::new(
1760 StackType::Empty
1761 .push(Type::Int)
1762 .push(Type::Int)
1763 .push(Type::Int),
1764 StackType::singleton(Type::Int),
1765 )),
1766 body: vec![
1767 Statement::WordCall {
1768 name: "rot".to_string(),
1769 span: None,
1770 },
1771 Statement::WordCall {
1772 name: "i.add".to_string(),
1773 span: None,
1774 },
1775 Statement::WordCall {
1776 name: "i.add".to_string(),
1777 span: None,
1778 },
1779 ],
1780 source: None,
1781 }],
1782 };
1783
1784 let mut checker = TypeChecker::new();
1785 assert!(checker.check_program(&program).is_ok());
1786 }
1787
1788 #[test]
1789 fn test_empty_program() {
1790 let program = Program {
1792 includes: vec![],
1793 unions: vec![],
1794 words: vec![],
1795 };
1796
1797 let mut checker = TypeChecker::new();
1798 assert!(checker.check_program(&program).is_ok());
1799 }
1800
1801 #[test]
1802 fn test_word_without_effect_declaration() {
1803 let program = Program {
1805 includes: vec![],
1806 unions: vec![],
1807 words: vec![WordDef {
1808 name: "helper".to_string(),
1809 effect: None,
1810 body: vec![Statement::IntLiteral(42)],
1811 source: None,
1812 }],
1813 };
1814
1815 let mut checker = TypeChecker::new();
1816 let result = checker.check_program(&program);
1817 assert!(result.is_err());
1818 assert!(
1819 result
1820 .unwrap_err()
1821 .contains("missing a stack effect declaration")
1822 );
1823 }
1824
1825 #[test]
1826 fn test_nested_conditionals() {
1827 let program = Program {
1836 includes: vec![],
1837 unions: vec![],
1838 words: vec![WordDef {
1839 name: "test".to_string(),
1840 effect: Some(Effect::new(
1841 StackType::Empty
1842 .push(Type::Int)
1843 .push(Type::Int)
1844 .push(Type::Int)
1845 .push(Type::Int),
1846 StackType::singleton(Type::String),
1847 )),
1848 body: vec![
1849 Statement::WordCall {
1850 name: "i.>".to_string(),
1851 span: None,
1852 },
1853 Statement::If {
1854 then_branch: vec![
1855 Statement::WordCall {
1856 name: "i.>".to_string(),
1857 span: None,
1858 },
1859 Statement::If {
1860 then_branch: vec![Statement::StringLiteral(
1861 "both true".to_string(),
1862 )],
1863 else_branch: Some(vec![Statement::StringLiteral(
1864 "first true".to_string(),
1865 )]),
1866 },
1867 ],
1868 else_branch: Some(vec![
1869 Statement::WordCall {
1870 name: "drop".to_string(),
1871 span: None,
1872 },
1873 Statement::WordCall {
1874 name: "drop".to_string(),
1875 span: None,
1876 },
1877 Statement::StringLiteral("first false".to_string()),
1878 ]),
1879 },
1880 ],
1881 source: None,
1882 }],
1883 };
1884
1885 let mut checker = TypeChecker::new();
1886 match checker.check_program(&program) {
1887 Ok(_) => {}
1888 Err(e) => panic!("Type check failed: {}", e),
1889 }
1890 }
1891
1892 #[test]
1893 fn test_conditional_without_else() {
1894 let program = Program {
1898 includes: vec![],
1899 unions: vec![],
1900 words: vec![WordDef {
1901 name: "test".to_string(),
1902 effect: Some(Effect::new(
1903 StackType::Empty.push(Type::Int).push(Type::Int),
1904 StackType::singleton(Type::Int),
1905 )),
1906 body: vec![
1907 Statement::WordCall {
1908 name: "i.>".to_string(),
1909 span: None,
1910 },
1911 Statement::If {
1912 then_branch: vec![Statement::IntLiteral(100)],
1913 else_branch: None, },
1915 ],
1916 source: None,
1917 }],
1918 };
1919
1920 let mut checker = TypeChecker::new();
1921 let result = checker.check_program(&program);
1922 assert!(result.is_err());
1924 }
1925
1926 #[test]
1927 fn test_multiple_word_chain() {
1928 let program = Program {
1932 includes: vec![],
1933 unions: vec![],
1934 words: vec![
1935 WordDef {
1936 name: "helper1".to_string(),
1937 effect: Some(Effect::new(
1938 StackType::singleton(Type::Int),
1939 StackType::singleton(Type::String),
1940 )),
1941 body: vec![Statement::WordCall {
1942 name: "int->string".to_string(),
1943 span: None,
1944 }],
1945 source: None,
1946 },
1947 WordDef {
1948 name: "helper2".to_string(),
1949 effect: Some(Effect::new(
1950 StackType::singleton(Type::String),
1951 StackType::Empty,
1952 )),
1953 body: vec![Statement::WordCall {
1954 name: "io.write-line".to_string(),
1955 span: None,
1956 }],
1957 source: None,
1958 },
1959 WordDef {
1960 name: "main".to_string(),
1961 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1962 body: vec![
1963 Statement::IntLiteral(42),
1964 Statement::WordCall {
1965 name: "helper1".to_string(),
1966 span: None,
1967 },
1968 Statement::WordCall {
1969 name: "helper2".to_string(),
1970 span: None,
1971 },
1972 ],
1973 source: None,
1974 },
1975 ],
1976 };
1977
1978 let mut checker = TypeChecker::new();
1979 assert!(checker.check_program(&program).is_ok());
1980 }
1981
1982 #[test]
1983 fn test_all_stack_ops() {
1984 let program = Program {
1987 includes: vec![],
1988 unions: vec![],
1989 words: vec![WordDef {
1990 name: "test".to_string(),
1991 effect: Some(Effect::new(
1992 StackType::Empty
1993 .push(Type::Int)
1994 .push(Type::Int)
1995 .push(Type::Int),
1996 StackType::Empty
1997 .push(Type::Int)
1998 .push(Type::Int)
1999 .push(Type::Int)
2000 .push(Type::Int),
2001 )),
2002 body: vec![
2003 Statement::WordCall {
2004 name: "over".to_string(),
2005 span: None,
2006 },
2007 Statement::WordCall {
2008 name: "nip".to_string(),
2009 span: None,
2010 },
2011 Statement::WordCall {
2012 name: "tuck".to_string(),
2013 span: None,
2014 },
2015 ],
2016 source: None,
2017 }],
2018 };
2019
2020 let mut checker = TypeChecker::new();
2021 assert!(checker.check_program(&program).is_ok());
2022 }
2023
2024 #[test]
2025 fn test_mixed_types_complex() {
2026 let program = Program {
2035 includes: vec![],
2036 unions: vec![],
2037 words: vec![WordDef {
2038 name: "test".to_string(),
2039 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2040 body: vec![
2041 Statement::IntLiteral(42),
2042 Statement::WordCall {
2043 name: "int->string".to_string(),
2044 span: None,
2045 },
2046 Statement::IntLiteral(100),
2047 Statement::IntLiteral(200),
2048 Statement::WordCall {
2049 name: "i.>".to_string(),
2050 span: None,
2051 },
2052 Statement::If {
2053 then_branch: vec![Statement::WordCall {
2054 name: "io.write-line".to_string(),
2055 span: None,
2056 }],
2057 else_branch: Some(vec![Statement::WordCall {
2058 name: "io.write-line".to_string(),
2059 span: None,
2060 }]),
2061 },
2062 ],
2063 source: None,
2064 }],
2065 };
2066
2067 let mut checker = TypeChecker::new();
2068 assert!(checker.check_program(&program).is_ok());
2069 }
2070
2071 #[test]
2072 fn test_string_literal() {
2073 let program = Program {
2075 includes: vec![],
2076 unions: vec![],
2077 words: vec![WordDef {
2078 name: "test".to_string(),
2079 effect: Some(Effect::new(
2080 StackType::Empty,
2081 StackType::singleton(Type::String),
2082 )),
2083 body: vec![Statement::StringLiteral("hello".to_string())],
2084 source: None,
2085 }],
2086 };
2087
2088 let mut checker = TypeChecker::new();
2089 assert!(checker.check_program(&program).is_ok());
2090 }
2091
2092 #[test]
2093 fn test_bool_literal() {
2094 let program = Program {
2097 includes: vec![],
2098 unions: vec![],
2099 words: vec![WordDef {
2100 name: "test".to_string(),
2101 effect: Some(Effect::new(
2102 StackType::Empty,
2103 StackType::singleton(Type::Bool),
2104 )),
2105 body: vec![Statement::BoolLiteral(true)],
2106 source: None,
2107 }],
2108 };
2109
2110 let mut checker = TypeChecker::new();
2111 assert!(checker.check_program(&program).is_ok());
2112 }
2113
2114 #[test]
2115 fn test_type_error_in_nested_conditional() {
2116 let program = Program {
2123 includes: vec![],
2124 unions: vec![],
2125 words: vec![WordDef {
2126 name: "test".to_string(),
2127 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2128 body: vec![
2129 Statement::IntLiteral(10),
2130 Statement::IntLiteral(20),
2131 Statement::WordCall {
2132 name: "i.>".to_string(),
2133 span: None,
2134 },
2135 Statement::If {
2136 then_branch: vec![
2137 Statement::IntLiteral(42),
2138 Statement::WordCall {
2139 name: "io.write-line".to_string(),
2140 span: None,
2141 },
2142 ],
2143 else_branch: Some(vec![
2144 Statement::StringLiteral("ok".to_string()),
2145 Statement::WordCall {
2146 name: "io.write-line".to_string(),
2147 span: None,
2148 },
2149 ]),
2150 },
2151 ],
2152 source: None,
2153 }],
2154 };
2155
2156 let mut checker = TypeChecker::new();
2157 let result = checker.check_program(&program);
2158 assert!(result.is_err());
2159 assert!(result.unwrap_err().contains("Type mismatch"));
2160 }
2161
2162 #[test]
2163 fn test_read_line_operation() {
2164 let program = Program {
2167 includes: vec![],
2168 unions: vec![],
2169 words: vec![WordDef {
2170 name: "test".to_string(),
2171 effect: Some(Effect::new(
2172 StackType::Empty,
2173 StackType::from_vec(vec![Type::String, Type::Bool]),
2174 )),
2175 body: vec![Statement::WordCall {
2176 name: "io.read-line".to_string(),
2177 span: None,
2178 }],
2179 source: None,
2180 }],
2181 };
2182
2183 let mut checker = TypeChecker::new();
2184 assert!(checker.check_program(&program).is_ok());
2185 }
2186
2187 #[test]
2188 fn test_comparison_operations() {
2189 let program = Program {
2194 includes: vec![],
2195 unions: vec![],
2196 words: vec![WordDef {
2197 name: "test".to_string(),
2198 effect: Some(Effect::new(
2199 StackType::Empty.push(Type::Int).push(Type::Int),
2200 StackType::singleton(Type::Bool),
2201 )),
2202 body: vec![Statement::WordCall {
2203 name: "i.<=".to_string(),
2204 span: None,
2205 }],
2206 source: None,
2207 }],
2208 };
2209
2210 let mut checker = TypeChecker::new();
2211 assert!(checker.check_program(&program).is_ok());
2212 }
2213
2214 #[test]
2215 fn test_recursive_word_definitions() {
2216 let program = Program {
2222 includes: vec![],
2223 unions: vec![],
2224 words: vec![
2225 WordDef {
2226 name: "is-even".to_string(),
2227 effect: Some(Effect::new(
2228 StackType::singleton(Type::Int),
2229 StackType::singleton(Type::Int),
2230 )),
2231 body: vec![
2232 Statement::WordCall {
2233 name: "dup".to_string(),
2234 span: None,
2235 },
2236 Statement::IntLiteral(0),
2237 Statement::WordCall {
2238 name: "i.=".to_string(),
2239 span: None,
2240 },
2241 Statement::If {
2242 then_branch: vec![
2243 Statement::WordCall {
2244 name: "drop".to_string(),
2245 span: None,
2246 },
2247 Statement::IntLiteral(1),
2248 ],
2249 else_branch: Some(vec![
2250 Statement::IntLiteral(1),
2251 Statement::WordCall {
2252 name: "i.subtract".to_string(),
2253 span: None,
2254 },
2255 Statement::WordCall {
2256 name: "is-odd".to_string(),
2257 span: None,
2258 },
2259 ]),
2260 },
2261 ],
2262 source: None,
2263 },
2264 WordDef {
2265 name: "is-odd".to_string(),
2266 effect: Some(Effect::new(
2267 StackType::singleton(Type::Int),
2268 StackType::singleton(Type::Int),
2269 )),
2270 body: vec![
2271 Statement::WordCall {
2272 name: "dup".to_string(),
2273 span: None,
2274 },
2275 Statement::IntLiteral(0),
2276 Statement::WordCall {
2277 name: "i.=".to_string(),
2278 span: None,
2279 },
2280 Statement::If {
2281 then_branch: vec![
2282 Statement::WordCall {
2283 name: "drop".to_string(),
2284 span: None,
2285 },
2286 Statement::IntLiteral(0),
2287 ],
2288 else_branch: Some(vec![
2289 Statement::IntLiteral(1),
2290 Statement::WordCall {
2291 name: "i.subtract".to_string(),
2292 span: None,
2293 },
2294 Statement::WordCall {
2295 name: "is-even".to_string(),
2296 span: None,
2297 },
2298 ]),
2299 },
2300 ],
2301 source: None,
2302 },
2303 ],
2304 };
2305
2306 let mut checker = TypeChecker::new();
2307 assert!(checker.check_program(&program).is_ok());
2308 }
2309
2310 #[test]
2311 fn test_word_calling_word_with_row_polymorphism() {
2312 let program = Program {
2317 includes: vec![],
2318 unions: vec![],
2319 words: vec![
2320 WordDef {
2321 name: "apply-twice".to_string(),
2322 effect: Some(Effect::new(
2323 StackType::singleton(Type::Int),
2324 StackType::singleton(Type::Int),
2325 )),
2326 body: vec![
2327 Statement::WordCall {
2328 name: "dup".to_string(),
2329 span: None,
2330 },
2331 Statement::WordCall {
2332 name: "i.add".to_string(),
2333 span: None,
2334 },
2335 ],
2336 source: None,
2337 },
2338 WordDef {
2339 name: "quad".to_string(),
2340 effect: Some(Effect::new(
2341 StackType::singleton(Type::Int),
2342 StackType::singleton(Type::Int),
2343 )),
2344 body: vec![
2345 Statement::WordCall {
2346 name: "apply-twice".to_string(),
2347 span: None,
2348 },
2349 Statement::WordCall {
2350 name: "apply-twice".to_string(),
2351 span: None,
2352 },
2353 ],
2354 source: None,
2355 },
2356 ],
2357 };
2358
2359 let mut checker = TypeChecker::new();
2360 assert!(checker.check_program(&program).is_ok());
2361 }
2362
2363 #[test]
2364 fn test_deep_stack_types() {
2365 let mut stack_type = StackType::Empty;
2369 for _ in 0..10 {
2370 stack_type = stack_type.push(Type::Int);
2371 }
2372
2373 let program = Program {
2374 includes: vec![],
2375 unions: vec![],
2376 words: vec![WordDef {
2377 name: "test".to_string(),
2378 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2379 body: vec![
2380 Statement::WordCall {
2381 name: "i.add".to_string(),
2382 span: None,
2383 },
2384 Statement::WordCall {
2385 name: "i.add".to_string(),
2386 span: None,
2387 },
2388 Statement::WordCall {
2389 name: "i.add".to_string(),
2390 span: None,
2391 },
2392 Statement::WordCall {
2393 name: "i.add".to_string(),
2394 span: None,
2395 },
2396 Statement::WordCall {
2397 name: "i.add".to_string(),
2398 span: None,
2399 },
2400 Statement::WordCall {
2401 name: "i.add".to_string(),
2402 span: None,
2403 },
2404 Statement::WordCall {
2405 name: "i.add".to_string(),
2406 span: None,
2407 },
2408 Statement::WordCall {
2409 name: "i.add".to_string(),
2410 span: None,
2411 },
2412 Statement::WordCall {
2413 name: "i.add".to_string(),
2414 span: None,
2415 },
2416 ],
2417 source: None,
2418 }],
2419 };
2420
2421 let mut checker = TypeChecker::new();
2422 assert!(checker.check_program(&program).is_ok());
2423 }
2424
2425 #[test]
2426 fn test_simple_quotation() {
2427 let program = Program {
2431 includes: vec![],
2432 unions: vec![],
2433 words: vec![WordDef {
2434 name: "test".to_string(),
2435 effect: Some(Effect::new(
2436 StackType::Empty,
2437 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2438 StackType::RowVar("input".to_string()).push(Type::Int),
2439 StackType::RowVar("input".to_string()).push(Type::Int),
2440 )))),
2441 )),
2442 body: vec![Statement::Quotation {
2443 span: None,
2444 id: 0,
2445 body: vec![
2446 Statement::IntLiteral(1),
2447 Statement::WordCall {
2448 name: "i.add".to_string(),
2449 span: None,
2450 },
2451 ],
2452 }],
2453 source: None,
2454 }],
2455 };
2456
2457 let mut checker = TypeChecker::new();
2458 match checker.check_program(&program) {
2459 Ok(_) => {}
2460 Err(e) => panic!("Type check failed: {}", e),
2461 }
2462 }
2463
2464 #[test]
2465 fn test_empty_quotation() {
2466 let program = Program {
2470 includes: vec![],
2471 unions: vec![],
2472 words: vec![WordDef {
2473 name: "test".to_string(),
2474 effect: Some(Effect::new(
2475 StackType::Empty,
2476 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2477 StackType::RowVar("input".to_string()),
2478 StackType::RowVar("input".to_string()),
2479 )))),
2480 )),
2481 body: vec![Statement::Quotation {
2482 span: None,
2483 id: 1,
2484 body: vec![],
2485 }],
2486 source: None,
2487 }],
2488 };
2489
2490 let mut checker = TypeChecker::new();
2491 assert!(checker.check_program(&program).is_ok());
2492 }
2493
2494 #[test]
2521 fn test_nested_quotation() {
2522 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2526 StackType::RowVar("input".to_string()).push(Type::Int),
2527 StackType::RowVar("input".to_string()).push(Type::Int),
2528 )));
2529
2530 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2531 StackType::RowVar("input".to_string()),
2532 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2533 )));
2534
2535 let program = Program {
2536 includes: vec![],
2537 unions: vec![],
2538 words: vec![WordDef {
2539 name: "test".to_string(),
2540 effect: Some(Effect::new(
2541 StackType::Empty,
2542 StackType::singleton(outer_quot_type),
2543 )),
2544 body: vec![Statement::Quotation {
2545 span: None,
2546 id: 2,
2547 body: vec![Statement::Quotation {
2548 span: None,
2549 id: 3,
2550 body: vec![
2551 Statement::IntLiteral(1),
2552 Statement::WordCall {
2553 name: "i.add".to_string(),
2554 span: None,
2555 },
2556 ],
2557 }],
2558 }],
2559 source: None,
2560 }],
2561 };
2562
2563 let mut checker = TypeChecker::new();
2564 assert!(checker.check_program(&program).is_ok());
2565 }
2566
2567 #[test]
2568 fn test_invalid_field_type_error() {
2569 use crate::ast::{UnionDef, UnionField, UnionVariant};
2570
2571 let program = Program {
2572 includes: vec![],
2573 unions: vec![UnionDef {
2574 name: "Message".to_string(),
2575 variants: vec![UnionVariant {
2576 name: "Get".to_string(),
2577 fields: vec![UnionField {
2578 name: "chan".to_string(),
2579 type_name: "InvalidType".to_string(),
2580 }],
2581 source: None,
2582 }],
2583 source: None,
2584 }],
2585 words: vec![],
2586 };
2587
2588 let mut checker = TypeChecker::new();
2589 let result = checker.check_program(&program);
2590 assert!(result.is_err());
2591 let err = result.unwrap_err();
2592 assert!(err.contains("Unknown type 'InvalidType'"));
2593 assert!(err.contains("chan"));
2594 assert!(err.contains("Get"));
2595 assert!(err.contains("Message"));
2596 }
2597
2598 #[test]
2599 fn test_roll_inside_conditional_with_concrete_stack() {
2600 let program = Program {
2608 includes: vec![],
2609 unions: vec![],
2610 words: vec![WordDef {
2611 name: "test".to_string(),
2612 effect: Some(Effect::new(
2613 StackType::Empty
2614 .push(Type::Int)
2615 .push(Type::Int)
2616 .push(Type::Int)
2617 .push(Type::Int),
2618 StackType::Empty
2619 .push(Type::Int)
2620 .push(Type::Int)
2621 .push(Type::Int)
2622 .push(Type::Int),
2623 )),
2624 body: vec![
2625 Statement::WordCall {
2626 name: "dup".to_string(),
2627 span: None,
2628 },
2629 Statement::IntLiteral(0),
2630 Statement::WordCall {
2631 name: "i.>".to_string(),
2632 span: None,
2633 },
2634 Statement::If {
2635 then_branch: vec![
2636 Statement::IntLiteral(3),
2637 Statement::WordCall {
2638 name: "roll".to_string(),
2639 span: None,
2640 },
2641 ],
2642 else_branch: Some(vec![
2643 Statement::WordCall {
2644 name: "rot".to_string(),
2645 span: None,
2646 },
2647 Statement::WordCall {
2648 name: "rot".to_string(),
2649 span: None,
2650 },
2651 ]),
2652 },
2653 ],
2654 source: None,
2655 }],
2656 };
2657
2658 let mut checker = TypeChecker::new();
2659 match checker.check_program(&program) {
2661 Ok(_) => {}
2662 Err(e) => panic!("Type check failed: {}", e),
2663 }
2664 }
2665
2666 #[test]
2667 fn test_roll_inside_match_arm_with_concrete_stack() {
2668 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2671
2672 let union_def = UnionDef {
2674 name: "Result".to_string(),
2675 variants: vec![
2676 UnionVariant {
2677 name: "Ok".to_string(),
2678 fields: vec![],
2679 source: None,
2680 },
2681 UnionVariant {
2682 name: "Err".to_string(),
2683 fields: vec![],
2684 source: None,
2685 },
2686 ],
2687 source: None,
2688 };
2689
2690 let program = Program {
2696 includes: vec![],
2697 unions: vec![union_def],
2698 words: vec![WordDef {
2699 name: "test".to_string(),
2700 effect: Some(Effect::new(
2701 StackType::Empty
2702 .push(Type::Int)
2703 .push(Type::Int)
2704 .push(Type::Int)
2705 .push(Type::Int)
2706 .push(Type::Union("Result".to_string())),
2707 StackType::Empty
2708 .push(Type::Int)
2709 .push(Type::Int)
2710 .push(Type::Int)
2711 .push(Type::Int),
2712 )),
2713 body: vec![Statement::Match {
2714 arms: vec![
2715 MatchArm {
2716 pattern: Pattern::Variant("Ok".to_string()),
2717 body: vec![
2718 Statement::IntLiteral(3),
2719 Statement::WordCall {
2720 name: "roll".to_string(),
2721 span: None,
2722 },
2723 ],
2724 },
2725 MatchArm {
2726 pattern: Pattern::Variant("Err".to_string()),
2727 body: vec![
2728 Statement::WordCall {
2729 name: "rot".to_string(),
2730 span: None,
2731 },
2732 Statement::WordCall {
2733 name: "rot".to_string(),
2734 span: None,
2735 },
2736 ],
2737 },
2738 ],
2739 }],
2740 source: None,
2741 }],
2742 };
2743
2744 let mut checker = TypeChecker::new();
2745 match checker.check_program(&program) {
2746 Ok(_) => {}
2747 Err(e) => panic!("Type check failed: {}", e),
2748 }
2749 }
2750
2751 #[test]
2752 fn test_roll_with_row_polymorphic_input() {
2753 let program = Program {
2757 includes: vec![],
2758 unions: vec![],
2759 words: vec![WordDef {
2760 name: "test".to_string(),
2761 effect: Some(Effect::new(
2762 StackType::Empty
2763 .push(Type::Var("T".to_string()))
2764 .push(Type::Var("U".to_string()))
2765 .push(Type::Var("V".to_string()))
2766 .push(Type::Var("W".to_string())),
2767 StackType::Empty
2768 .push(Type::Var("U".to_string()))
2769 .push(Type::Var("V".to_string()))
2770 .push(Type::Var("W".to_string()))
2771 .push(Type::Var("T".to_string())),
2772 )),
2773 body: vec![
2774 Statement::IntLiteral(3),
2775 Statement::WordCall {
2776 name: "roll".to_string(),
2777 span: None,
2778 },
2779 ],
2780 source: None,
2781 }],
2782 };
2783
2784 let mut checker = TypeChecker::new();
2785 let result = checker.check_program(&program);
2786 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
2787 }
2788
2789 #[test]
2790 fn test_pick_with_row_polymorphic_input() {
2791 let program = Program {
2795 includes: vec![],
2796 unions: vec![],
2797 words: vec![WordDef {
2798 name: "test".to_string(),
2799 effect: Some(Effect::new(
2800 StackType::Empty
2801 .push(Type::Var("T".to_string()))
2802 .push(Type::Var("U".to_string()))
2803 .push(Type::Var("V".to_string())),
2804 StackType::Empty
2805 .push(Type::Var("T".to_string()))
2806 .push(Type::Var("U".to_string()))
2807 .push(Type::Var("V".to_string()))
2808 .push(Type::Var("T".to_string())),
2809 )),
2810 body: vec![
2811 Statement::IntLiteral(2),
2812 Statement::WordCall {
2813 name: "pick".to_string(),
2814 span: None,
2815 },
2816 ],
2817 source: None,
2818 }],
2819 };
2820
2821 let mut checker = TypeChecker::new();
2822 assert!(checker.check_program(&program).is_ok());
2823 }
2824
2825 #[test]
2826 fn test_valid_union_reference_in_field() {
2827 use crate::ast::{UnionDef, UnionField, UnionVariant};
2828
2829 let program = Program {
2830 includes: vec![],
2831 unions: vec![
2832 UnionDef {
2833 name: "Inner".to_string(),
2834 variants: vec![UnionVariant {
2835 name: "Val".to_string(),
2836 fields: vec![UnionField {
2837 name: "x".to_string(),
2838 type_name: "Int".to_string(),
2839 }],
2840 source: None,
2841 }],
2842 source: None,
2843 },
2844 UnionDef {
2845 name: "Outer".to_string(),
2846 variants: vec![UnionVariant {
2847 name: "Wrap".to_string(),
2848 fields: vec![UnionField {
2849 name: "inner".to_string(),
2850 type_name: "Inner".to_string(), }],
2852 source: None,
2853 }],
2854 source: None,
2855 },
2856 ],
2857 words: vec![],
2858 };
2859
2860 let mut checker = TypeChecker::new();
2861 assert!(
2862 checker.check_program(&program).is_ok(),
2863 "Union reference in field should be valid"
2864 );
2865 }
2866
2867 #[test]
2868 fn test_divergent_recursive_tail_call() {
2869 let program = Program {
2891 includes: vec![],
2892 unions: vec![],
2893 words: vec![WordDef {
2894 name: "store-loop".to_string(),
2895 effect: Some(Effect::new(
2896 StackType::singleton(Type::Channel), StackType::Empty,
2898 )),
2899 body: vec![
2900 Statement::WordCall {
2902 name: "dup".to_string(),
2903 span: None,
2904 },
2905 Statement::WordCall {
2907 name: "chan.receive".to_string(),
2908 span: None,
2909 },
2910 Statement::WordCall {
2912 name: "not".to_string(),
2913 span: None,
2914 },
2915 Statement::If {
2917 then_branch: vec![
2918 Statement::WordCall {
2920 name: "drop".to_string(),
2921 span: None,
2922 },
2923 Statement::WordCall {
2925 name: "store-loop".to_string(), span: None,
2927 },
2928 ],
2929 else_branch: None, },
2931 Statement::WordCall {
2933 name: "drop".to_string(),
2934 span: None,
2935 },
2936 Statement::WordCall {
2937 name: "drop".to_string(),
2938 span: None,
2939 },
2940 ],
2941 source: None,
2942 }],
2943 };
2944
2945 let mut checker = TypeChecker::new();
2946 let result = checker.check_program(&program);
2947 assert!(
2948 result.is_ok(),
2949 "Divergent recursive tail call should be accepted: {:?}",
2950 result.err()
2951 );
2952 }
2953
2954 #[test]
2955 fn test_divergent_else_branch() {
2956 let program = Program {
2969 includes: vec![],
2970 unions: vec![],
2971 words: vec![WordDef {
2972 name: "process-loop".to_string(),
2973 effect: Some(Effect::new(
2974 StackType::singleton(Type::Channel), StackType::Empty,
2976 )),
2977 body: vec![
2978 Statement::WordCall {
2979 name: "dup".to_string(),
2980 span: None,
2981 },
2982 Statement::WordCall {
2983 name: "chan.receive".to_string(),
2984 span: None,
2985 },
2986 Statement::If {
2987 then_branch: vec![
2988 Statement::WordCall {
2990 name: "drop".to_string(),
2991 span: None,
2992 },
2993 Statement::WordCall {
2994 name: "drop".to_string(),
2995 span: None,
2996 },
2997 ],
2998 else_branch: Some(vec![
2999 Statement::WordCall {
3001 name: "drop".to_string(),
3002 span: None,
3003 },
3004 Statement::WordCall {
3005 name: "process-loop".to_string(), span: None,
3007 },
3008 ]),
3009 },
3010 ],
3011 source: None,
3012 }],
3013 };
3014
3015 let mut checker = TypeChecker::new();
3016 let result = checker.check_program(&program);
3017 assert!(
3018 result.is_ok(),
3019 "Divergent else branch should be accepted: {:?}",
3020 result.err()
3021 );
3022 }
3023
3024 #[test]
3025 fn test_non_tail_call_recursion_not_divergent() {
3026 let program = Program {
3043 includes: vec![],
3044 unions: vec![],
3045 words: vec![WordDef {
3046 name: "bad-loop".to_string(),
3047 effect: Some(Effect::new(
3048 StackType::singleton(Type::Int),
3049 StackType::singleton(Type::Int),
3050 )),
3051 body: vec![
3052 Statement::WordCall {
3053 name: "dup".to_string(),
3054 span: None,
3055 },
3056 Statement::IntLiteral(0),
3057 Statement::WordCall {
3058 name: "i.>".to_string(),
3059 span: None,
3060 },
3061 Statement::If {
3062 then_branch: vec![
3063 Statement::IntLiteral(1),
3064 Statement::WordCall {
3065 name: "i.subtract".to_string(),
3066 span: None,
3067 },
3068 Statement::WordCall {
3069 name: "bad-loop".to_string(), span: None,
3071 },
3072 Statement::IntLiteral(1),
3073 Statement::WordCall {
3074 name: "i.add".to_string(), span: None,
3076 },
3077 ],
3078 else_branch: None,
3079 },
3080 ],
3081 source: None,
3082 }],
3083 };
3084
3085 let mut checker = TypeChecker::new();
3086 let result = checker.check_program(&program);
3091 assert!(
3092 result.is_ok(),
3093 "Non-tail recursion should type check normally: {:?}",
3094 result.err()
3095 );
3096 }
3097
3098 #[test]
3099 fn test_call_yield_quotation_error() {
3100 let program = Program {
3104 includes: vec![],
3105 unions: vec![],
3106 words: vec![WordDef {
3107 name: "bad".to_string(),
3108 effect: Some(Effect::new(
3109 StackType::singleton(Type::Var("Ctx".to_string())),
3110 StackType::singleton(Type::Var("Ctx".to_string())),
3111 )),
3112 body: vec![
3113 Statement::IntLiteral(42),
3115 Statement::Quotation {
3116 span: None,
3117 id: 0,
3118 body: vec![Statement::WordCall {
3119 name: "yield".to_string(),
3120 span: None,
3121 }],
3122 },
3123 Statement::WordCall {
3124 name: "call".to_string(),
3125 span: None,
3126 },
3127 ],
3128 source: None,
3129 }],
3130 };
3131
3132 let mut checker = TypeChecker::new();
3133 let result = checker.check_program(&program);
3134 assert!(
3135 result.is_err(),
3136 "Calling yield quotation directly should fail"
3137 );
3138 let err = result.unwrap_err();
3139 assert!(
3140 err.contains("Yield") || err.contains("strand.weave"),
3141 "Error should mention Yield or strand.weave: {}",
3142 err
3143 );
3144 }
3145
3146 #[test]
3147 fn test_strand_weave_yield_quotation_ok() {
3148 let program = Program {
3151 includes: vec![],
3152 unions: vec![],
3153 words: vec![WordDef {
3154 name: "good".to_string(),
3155 effect: Some(Effect::new(
3156 StackType::Empty,
3157 StackType::Empty
3158 .push(Type::Int)
3159 .push(Type::Var("Handle".to_string())),
3160 )),
3161 body: vec![
3162 Statement::IntLiteral(42),
3163 Statement::Quotation {
3164 span: None,
3165 id: 0,
3166 body: vec![Statement::WordCall {
3167 name: "yield".to_string(),
3168 span: None,
3169 }],
3170 },
3171 Statement::WordCall {
3172 name: "strand.weave".to_string(),
3173 span: None,
3174 },
3175 ],
3176 source: None,
3177 }],
3178 };
3179
3180 let mut checker = TypeChecker::new();
3181 let result = checker.check_program(&program);
3182 assert!(
3183 result.is_ok(),
3184 "strand.weave on yield quotation should pass: {:?}",
3185 result.err()
3186 );
3187 }
3188
3189 #[test]
3190 fn test_call_pure_quotation_ok() {
3191 let program = Program {
3194 includes: vec![],
3195 unions: vec![],
3196 words: vec![WordDef {
3197 name: "ok".to_string(),
3198 effect: Some(Effect::new(
3199 StackType::singleton(Type::Int),
3200 StackType::singleton(Type::Int),
3201 )),
3202 body: vec![
3203 Statement::Quotation {
3204 span: None,
3205 id: 0,
3206 body: vec![
3207 Statement::IntLiteral(1),
3208 Statement::WordCall {
3209 name: "i.add".to_string(),
3210 span: None,
3211 },
3212 ],
3213 },
3214 Statement::WordCall {
3215 name: "call".to_string(),
3216 span: None,
3217 },
3218 ],
3219 source: None,
3220 }],
3221 };
3222
3223 let mut checker = TypeChecker::new();
3224 let result = checker.check_program(&program);
3225 assert!(
3226 result.is_ok(),
3227 "Calling pure quotation should pass: {:?}",
3228 result.err()
3229 );
3230 }
3231
3232 #[test]
3238 fn test_pollution_extra_push() {
3239 let program = Program {
3243 includes: vec![],
3244 unions: vec![],
3245 words: vec![WordDef {
3246 name: "test".to_string(),
3247 effect: Some(Effect::new(
3248 StackType::singleton(Type::Int),
3249 StackType::singleton(Type::Int),
3250 )),
3251 body: vec![Statement::IntLiteral(42)],
3252 source: None,
3253 }],
3254 };
3255
3256 let mut checker = TypeChecker::new();
3257 let result = checker.check_program(&program);
3258 assert!(
3259 result.is_err(),
3260 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3261 );
3262 }
3263
3264 #[test]
3265 fn test_pollution_extra_dup() {
3266 let program = Program {
3269 includes: vec![],
3270 unions: vec![],
3271 words: vec![WordDef {
3272 name: "test".to_string(),
3273 effect: Some(Effect::new(
3274 StackType::singleton(Type::Int),
3275 StackType::singleton(Type::Int),
3276 )),
3277 body: vec![Statement::WordCall {
3278 name: "dup".to_string(),
3279 span: None,
3280 }],
3281 source: None,
3282 }],
3283 };
3284
3285 let mut checker = TypeChecker::new();
3286 let result = checker.check_program(&program);
3287 assert!(
3288 result.is_err(),
3289 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3290 );
3291 }
3292
3293 #[test]
3294 fn test_pollution_consumes_extra() {
3295 let program = Program {
3298 includes: vec![],
3299 unions: vec![],
3300 words: vec![WordDef {
3301 name: "test".to_string(),
3302 effect: Some(Effect::new(
3303 StackType::singleton(Type::Int),
3304 StackType::singleton(Type::Int),
3305 )),
3306 body: vec![
3307 Statement::WordCall {
3308 name: "drop".to_string(),
3309 span: None,
3310 },
3311 Statement::WordCall {
3312 name: "drop".to_string(),
3313 span: None,
3314 },
3315 Statement::IntLiteral(42),
3316 ],
3317 source: None,
3318 }],
3319 };
3320
3321 let mut checker = TypeChecker::new();
3322 let result = checker.check_program(&program);
3323 assert!(
3324 result.is_err(),
3325 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3326 );
3327 }
3328
3329 #[test]
3330 fn test_pollution_in_then_branch() {
3331 let program = Program {
3335 includes: vec![],
3336 unions: vec![],
3337 words: vec![WordDef {
3338 name: "test".to_string(),
3339 effect: Some(Effect::new(
3340 StackType::singleton(Type::Bool),
3341 StackType::singleton(Type::Int),
3342 )),
3343 body: vec![Statement::If {
3344 then_branch: vec![
3345 Statement::IntLiteral(1),
3346 Statement::IntLiteral(2), ],
3348 else_branch: Some(vec![Statement::IntLiteral(3)]),
3349 }],
3350 source: None,
3351 }],
3352 };
3353
3354 let mut checker = TypeChecker::new();
3355 let result = checker.check_program(&program);
3356 assert!(
3357 result.is_err(),
3358 "Should reject: then branch pushes 2 values, else pushes 1"
3359 );
3360 }
3361
3362 #[test]
3363 fn test_pollution_in_else_branch() {
3364 let program = Program {
3368 includes: vec![],
3369 unions: vec![],
3370 words: vec![WordDef {
3371 name: "test".to_string(),
3372 effect: Some(Effect::new(
3373 StackType::singleton(Type::Bool),
3374 StackType::singleton(Type::Int),
3375 )),
3376 body: vec![Statement::If {
3377 then_branch: vec![Statement::IntLiteral(1)],
3378 else_branch: Some(vec![
3379 Statement::IntLiteral(2),
3380 Statement::IntLiteral(3), ]),
3382 }],
3383 source: None,
3384 }],
3385 };
3386
3387 let mut checker = TypeChecker::new();
3388 let result = checker.check_program(&program);
3389 assert!(
3390 result.is_err(),
3391 "Should reject: then branch pushes 1 value, else pushes 2"
3392 );
3393 }
3394
3395 #[test]
3396 fn test_pollution_both_branches_extra() {
3397 let program = Program {
3401 includes: vec![],
3402 unions: vec![],
3403 words: vec![WordDef {
3404 name: "test".to_string(),
3405 effect: Some(Effect::new(
3406 StackType::singleton(Type::Bool),
3407 StackType::singleton(Type::Int),
3408 )),
3409 body: vec![Statement::If {
3410 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3411 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3412 }],
3413 source: None,
3414 }],
3415 };
3416
3417 let mut checker = TypeChecker::new();
3418 let result = checker.check_program(&program);
3419 assert!(
3420 result.is_err(),
3421 "Should reject: both branches push 2 values, but declared output is 1"
3422 );
3423 }
3424
3425 #[test]
3426 fn test_pollution_branch_consumes_extra() {
3427 let program = Program {
3431 includes: vec![],
3432 unions: vec![],
3433 words: vec![WordDef {
3434 name: "test".to_string(),
3435 effect: Some(Effect::new(
3436 StackType::Empty.push(Type::Bool).push(Type::Int),
3437 StackType::singleton(Type::Int),
3438 )),
3439 body: vec![Statement::If {
3440 then_branch: vec![
3441 Statement::WordCall {
3442 name: "drop".to_string(),
3443 span: None,
3444 },
3445 Statement::WordCall {
3446 name: "drop".to_string(),
3447 span: None,
3448 },
3449 Statement::IntLiteral(1),
3450 ],
3451 else_branch: Some(vec![]),
3452 }],
3453 source: None,
3454 }],
3455 };
3456
3457 let mut checker = TypeChecker::new();
3458 let result = checker.check_program(&program);
3459 assert!(
3460 result.is_err(),
3461 "Should reject: then branch consumes Bool (should only have Int after if)"
3462 );
3463 }
3464
3465 #[test]
3466 fn test_pollution_quotation_wrong_arity_output() {
3467 let program = Program {
3471 includes: vec![],
3472 unions: vec![],
3473 words: vec![WordDef {
3474 name: "test".to_string(),
3475 effect: Some(Effect::new(
3476 StackType::singleton(Type::Int),
3477 StackType::singleton(Type::Int),
3478 )),
3479 body: vec![
3480 Statement::Quotation {
3481 span: None,
3482 id: 0,
3483 body: vec![Statement::WordCall {
3484 name: "dup".to_string(),
3485 span: None,
3486 }],
3487 },
3488 Statement::WordCall {
3489 name: "call".to_string(),
3490 span: None,
3491 },
3492 ],
3493 source: None,
3494 }],
3495 };
3496
3497 let mut checker = TypeChecker::new();
3498 let result = checker.check_program(&program);
3499 assert!(
3500 result.is_err(),
3501 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3502 );
3503 }
3504
3505 #[test]
3506 fn test_pollution_quotation_wrong_arity_input() {
3507 let program = Program {
3511 includes: vec![],
3512 unions: vec![],
3513 words: vec![WordDef {
3514 name: "test".to_string(),
3515 effect: Some(Effect::new(
3516 StackType::singleton(Type::Int),
3517 StackType::singleton(Type::Int),
3518 )),
3519 body: vec![
3520 Statement::Quotation {
3521 span: None,
3522 id: 0,
3523 body: vec![
3524 Statement::WordCall {
3525 name: "drop".to_string(),
3526 span: None,
3527 },
3528 Statement::WordCall {
3529 name: "drop".to_string(),
3530 span: None,
3531 },
3532 Statement::IntLiteral(42),
3533 ],
3534 },
3535 Statement::WordCall {
3536 name: "call".to_string(),
3537 span: None,
3538 },
3539 ],
3540 source: None,
3541 }],
3542 };
3543
3544 let mut checker = TypeChecker::new();
3545 let result = checker.check_program(&program);
3546 assert!(
3547 result.is_err(),
3548 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3549 );
3550 }
3551
3552 #[test]
3553 fn test_missing_effect_provides_helpful_error() {
3554 let program = Program {
3557 includes: vec![],
3558 unions: vec![],
3559 words: vec![WordDef {
3560 name: "myword".to_string(),
3561 effect: None, body: vec![Statement::IntLiteral(42)],
3563 source: None,
3564 }],
3565 };
3566
3567 let mut checker = TypeChecker::new();
3568 let result = checker.check_program(&program);
3569 assert!(result.is_err());
3570 let err = result.unwrap_err();
3571 assert!(err.contains("myword"), "Error should mention word name");
3572 assert!(
3573 err.contains("stack effect"),
3574 "Error should mention stack effect"
3575 );
3576 }
3577
3578 #[test]
3579 fn test_valid_effect_exact_match() {
3580 let program = Program {
3583 includes: vec![],
3584 unions: vec![],
3585 words: vec![WordDef {
3586 name: "test".to_string(),
3587 effect: Some(Effect::new(
3588 StackType::Empty.push(Type::Int).push(Type::Int),
3589 StackType::singleton(Type::Int),
3590 )),
3591 body: vec![Statement::WordCall {
3592 name: "i.add".to_string(),
3593 span: None,
3594 }],
3595 source: None,
3596 }],
3597 };
3598
3599 let mut checker = TypeChecker::new();
3600 let result = checker.check_program(&program);
3601 assert!(result.is_ok(), "Should accept: effect matches exactly");
3602 }
3603
3604 #[test]
3605 fn test_valid_polymorphic_passthrough() {
3606 let program = Program {
3609 includes: vec![],
3610 unions: vec![],
3611 words: vec![WordDef {
3612 name: "test".to_string(),
3613 effect: Some(Effect::new(
3614 StackType::Cons {
3615 rest: Box::new(StackType::RowVar("rest".to_string())),
3616 top: Type::Var("a".to_string()),
3617 },
3618 StackType::Cons {
3619 rest: Box::new(StackType::RowVar("rest".to_string())),
3620 top: Type::Var("a".to_string()),
3621 },
3622 )),
3623 body: vec![], source: None,
3625 }],
3626 };
3627
3628 let mut checker = TypeChecker::new();
3629 let result = checker.check_program(&program);
3630 assert!(result.is_ok(), "Should accept: polymorphic identity");
3631 }
3632
3633 #[test]
3639 fn test_closure_basic_capture() {
3640 let program = Program {
3646 includes: vec![],
3647 unions: vec![],
3648 words: vec![WordDef {
3649 name: "make-adder".to_string(),
3650 effect: Some(Effect::new(
3651 StackType::singleton(Type::Int),
3652 StackType::singleton(Type::Closure {
3653 effect: Box::new(Effect::new(
3654 StackType::RowVar("r".to_string()).push(Type::Int),
3655 StackType::RowVar("r".to_string()).push(Type::Int),
3656 )),
3657 captures: vec![Type::Int], }),
3659 )),
3660 body: vec![Statement::Quotation {
3661 span: None,
3662 id: 0,
3663 body: vec![Statement::WordCall {
3664 name: "i.add".to_string(),
3665 span: None,
3666 }],
3667 }],
3668 source: None,
3669 }],
3670 };
3671
3672 let mut checker = TypeChecker::new();
3673 let result = checker.check_program(&program);
3674 assert!(
3675 result.is_ok(),
3676 "Basic closure capture should work: {:?}",
3677 result.err()
3678 );
3679 }
3680
3681 #[test]
3682 fn test_closure_nested_two_levels() {
3683 let program = Program {
3688 includes: vec![],
3689 unions: vec![],
3690 words: vec![WordDef {
3691 name: "outer".to_string(),
3692 effect: Some(Effect::new(
3693 StackType::Empty,
3694 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3695 StackType::RowVar("r".to_string()),
3696 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3697 Effect::new(
3698 StackType::RowVar("s".to_string()).push(Type::Int),
3699 StackType::RowVar("s".to_string()).push(Type::Int),
3700 ),
3701 ))),
3702 )))),
3703 )),
3704 body: vec![Statement::Quotation {
3705 span: None,
3706 id: 0,
3707 body: vec![Statement::Quotation {
3708 span: None,
3709 id: 1,
3710 body: vec![
3711 Statement::IntLiteral(1),
3712 Statement::WordCall {
3713 name: "i.add".to_string(),
3714 span: None,
3715 },
3716 ],
3717 }],
3718 }],
3719 source: None,
3720 }],
3721 };
3722
3723 let mut checker = TypeChecker::new();
3724 let result = checker.check_program(&program);
3725 assert!(
3726 result.is_ok(),
3727 "Two-level nested quotations should work: {:?}",
3728 result.err()
3729 );
3730 }
3731
3732 #[test]
3733 fn test_closure_nested_three_levels() {
3734 let inner_effect = Effect::new(
3738 StackType::RowVar("a".to_string()).push(Type::Int),
3739 StackType::RowVar("a".to_string()).push(Type::Int),
3740 );
3741 let middle_effect = Effect::new(
3742 StackType::RowVar("b".to_string()),
3743 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3744 );
3745 let outer_effect = Effect::new(
3746 StackType::RowVar("c".to_string()),
3747 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3748 );
3749
3750 let program = Program {
3751 includes: vec![],
3752 unions: vec![],
3753 words: vec![WordDef {
3754 name: "deep".to_string(),
3755 effect: Some(Effect::new(
3756 StackType::Empty,
3757 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3758 )),
3759 body: vec![Statement::Quotation {
3760 span: None,
3761 id: 0,
3762 body: vec![Statement::Quotation {
3763 span: None,
3764 id: 1,
3765 body: vec![Statement::Quotation {
3766 span: None,
3767 id: 2,
3768 body: vec![
3769 Statement::IntLiteral(1),
3770 Statement::WordCall {
3771 name: "i.add".to_string(),
3772 span: None,
3773 },
3774 ],
3775 }],
3776 }],
3777 }],
3778 source: None,
3779 }],
3780 };
3781
3782 let mut checker = TypeChecker::new();
3783 let result = checker.check_program(&program);
3784 assert!(
3785 result.is_ok(),
3786 "Three-level nested quotations should work: {:?}",
3787 result.err()
3788 );
3789 }
3790
3791 #[test]
3792 fn test_closure_use_after_creation() {
3793 let adder_type = Type::Closure {
3799 effect: Box::new(Effect::new(
3800 StackType::RowVar("r".to_string()).push(Type::Int),
3801 StackType::RowVar("r".to_string()).push(Type::Int),
3802 )),
3803 captures: vec![Type::Int],
3804 };
3805
3806 let program = Program {
3807 includes: vec![],
3808 unions: vec![],
3809 words: vec![
3810 WordDef {
3811 name: "make-adder".to_string(),
3812 effect: Some(Effect::new(
3813 StackType::singleton(Type::Int),
3814 StackType::singleton(adder_type.clone()),
3815 )),
3816 body: vec![Statement::Quotation {
3817 span: None,
3818 id: 0,
3819 body: vec![Statement::WordCall {
3820 name: "i.add".to_string(),
3821 span: None,
3822 }],
3823 }],
3824 source: None,
3825 },
3826 WordDef {
3827 name: "use-adder".to_string(),
3828 effect: Some(Effect::new(
3829 StackType::Empty,
3830 StackType::singleton(Type::Int),
3831 )),
3832 body: vec![
3833 Statement::IntLiteral(5),
3834 Statement::WordCall {
3835 name: "make-adder".to_string(),
3836 span: None,
3837 },
3838 Statement::IntLiteral(10),
3839 Statement::WordCall {
3840 name: "swap".to_string(),
3841 span: None,
3842 },
3843 Statement::WordCall {
3844 name: "call".to_string(),
3845 span: None,
3846 },
3847 ],
3848 source: None,
3849 },
3850 ],
3851 };
3852
3853 let mut checker = TypeChecker::new();
3854 let result = checker.check_program(&program);
3855 assert!(
3856 result.is_ok(),
3857 "Closure usage after creation should work: {:?}",
3858 result.err()
3859 );
3860 }
3861
3862 #[test]
3863 fn test_closure_wrong_call_type() {
3864 let adder_type = Type::Closure {
3868 effect: Box::new(Effect::new(
3869 StackType::RowVar("r".to_string()).push(Type::Int),
3870 StackType::RowVar("r".to_string()).push(Type::Int),
3871 )),
3872 captures: vec![Type::Int],
3873 };
3874
3875 let program = Program {
3876 includes: vec![],
3877 unions: vec![],
3878 words: vec![
3879 WordDef {
3880 name: "make-adder".to_string(),
3881 effect: Some(Effect::new(
3882 StackType::singleton(Type::Int),
3883 StackType::singleton(adder_type.clone()),
3884 )),
3885 body: vec![Statement::Quotation {
3886 span: None,
3887 id: 0,
3888 body: vec![Statement::WordCall {
3889 name: "i.add".to_string(),
3890 span: None,
3891 }],
3892 }],
3893 source: None,
3894 },
3895 WordDef {
3896 name: "bad-use".to_string(),
3897 effect: Some(Effect::new(
3898 StackType::Empty,
3899 StackType::singleton(Type::Int),
3900 )),
3901 body: vec![
3902 Statement::IntLiteral(5),
3903 Statement::WordCall {
3904 name: "make-adder".to_string(),
3905 span: None,
3906 },
3907 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
3909 name: "swap".to_string(),
3910 span: None,
3911 },
3912 Statement::WordCall {
3913 name: "call".to_string(),
3914 span: None,
3915 },
3916 ],
3917 source: None,
3918 },
3919 ],
3920 };
3921
3922 let mut checker = TypeChecker::new();
3923 let result = checker.check_program(&program);
3924 assert!(
3925 result.is_err(),
3926 "Calling Int closure with String should fail"
3927 );
3928 }
3929
3930 #[test]
3931 fn test_closure_multiple_captures() {
3932 let program = Program {
3939 includes: vec![],
3940 unions: vec![],
3941 words: vec![WordDef {
3942 name: "make-between".to_string(),
3943 effect: Some(Effect::new(
3944 StackType::Empty.push(Type::Int).push(Type::Int),
3945 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3946 StackType::RowVar("r".to_string()).push(Type::Int),
3947 StackType::RowVar("r".to_string()).push(Type::Bool),
3948 )))),
3949 )),
3950 body: vec![Statement::Quotation {
3951 span: None,
3952 id: 0,
3953 body: vec![
3954 Statement::WordCall {
3956 name: "i.>=".to_string(),
3957 span: None,
3958 },
3959 ],
3961 }],
3962 source: None,
3963 }],
3964 };
3965
3966 let mut checker = TypeChecker::new();
3967 let result = checker.check_program(&program);
3968 assert!(
3972 result.is_ok() || result.is_err(),
3973 "Multiple captures should be handled (pass or fail gracefully)"
3974 );
3975 }
3976
3977 #[test]
3978 fn test_quotation_type_preserved_through_word() {
3979 let quot_type = Type::Quotation(Box::new(Effect::new(
3982 StackType::RowVar("r".to_string()).push(Type::Int),
3983 StackType::RowVar("r".to_string()).push(Type::Int),
3984 )));
3985
3986 let program = Program {
3987 includes: vec![],
3988 unions: vec![],
3989 words: vec![WordDef {
3990 name: "identity-quot".to_string(),
3991 effect: Some(Effect::new(
3992 StackType::singleton(quot_type.clone()),
3993 StackType::singleton(quot_type.clone()),
3994 )),
3995 body: vec![], source: None,
3997 }],
3998 };
3999
4000 let mut checker = TypeChecker::new();
4001 let result = checker.check_program(&program);
4002 assert!(
4003 result.is_ok(),
4004 "Quotation type should be preserved through identity word: {:?}",
4005 result.err()
4006 );
4007 }
4008
4009 #[test]
4010 fn test_closure_captures_value_for_inner_quotation() {
4011 let closure_effect = Effect::new(
4017 StackType::RowVar("r".to_string()).push(Type::Int),
4018 StackType::RowVar("r".to_string()).push(Type::Int),
4019 );
4020
4021 let program = Program {
4022 includes: vec![],
4023 unions: vec![],
4024 words: vec![WordDef {
4025 name: "make-inner-adder".to_string(),
4026 effect: Some(Effect::new(
4027 StackType::singleton(Type::Int),
4028 StackType::singleton(Type::Closure {
4029 effect: Box::new(closure_effect),
4030 captures: vec![Type::Int],
4031 }),
4032 )),
4033 body: vec![Statement::Quotation {
4034 span: None,
4035 id: 0,
4036 body: vec![
4037 Statement::WordCall {
4039 name: "i.add".to_string(),
4040 span: None,
4041 },
4042 ],
4043 }],
4044 source: None,
4045 }],
4046 };
4047
4048 let mut checker = TypeChecker::new();
4049 let result = checker.check_program(&program);
4050 assert!(
4051 result.is_ok(),
4052 "Closure with capture for inner work should pass: {:?}",
4053 result.err()
4054 );
4055 }
4056}