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 allowed_lints: vec![],
1372 }],
1373 };
1374
1375 let mut checker = TypeChecker::new();
1376 assert!(checker.check_program(&program).is_ok());
1377 }
1378
1379 #[test]
1380 fn test_simple_operation() {
1381 let program = Program {
1383 includes: vec![],
1384 unions: vec![],
1385 words: vec![WordDef {
1386 name: "test".to_string(),
1387 effect: Some(Effect::new(
1388 StackType::Empty.push(Type::Int).push(Type::Int),
1389 StackType::singleton(Type::Int),
1390 )),
1391 body: vec![Statement::WordCall {
1392 name: "i.add".to_string(),
1393 span: None,
1394 }],
1395 source: None,
1396 allowed_lints: vec![],
1397 }],
1398 };
1399
1400 let mut checker = TypeChecker::new();
1401 assert!(checker.check_program(&program).is_ok());
1402 }
1403
1404 #[test]
1405 fn test_type_mismatch() {
1406 let program = Program {
1408 includes: vec![],
1409 unions: vec![],
1410 words: vec![WordDef {
1411 name: "test".to_string(),
1412 effect: Some(Effect::new(
1413 StackType::singleton(Type::String),
1414 StackType::Empty,
1415 )),
1416 body: vec![
1417 Statement::IntLiteral(42), Statement::WordCall {
1419 name: "io.write-line".to_string(),
1420 span: None,
1421 },
1422 ],
1423 source: None,
1424 allowed_lints: vec![],
1425 }],
1426 };
1427
1428 let mut checker = TypeChecker::new();
1429 let result = checker.check_program(&program);
1430 assert!(result.is_err());
1431 assert!(result.unwrap_err().contains("Type mismatch"));
1432 }
1433
1434 #[test]
1435 fn test_polymorphic_dup() {
1436 let program = Program {
1438 includes: vec![],
1439 unions: vec![],
1440 words: vec![WordDef {
1441 name: "my-dup".to_string(),
1442 effect: Some(Effect::new(
1443 StackType::singleton(Type::Int),
1444 StackType::Empty.push(Type::Int).push(Type::Int),
1445 )),
1446 body: vec![Statement::WordCall {
1447 name: "dup".to_string(),
1448 span: None,
1449 }],
1450 source: None,
1451 allowed_lints: vec![],
1452 }],
1453 };
1454
1455 let mut checker = TypeChecker::new();
1456 assert!(checker.check_program(&program).is_ok());
1457 }
1458
1459 #[test]
1460 fn test_conditional_branches() {
1461 let program = Program {
1464 includes: vec![],
1465 unions: vec![],
1466 words: vec![WordDef {
1467 name: "test".to_string(),
1468 effect: Some(Effect::new(
1469 StackType::Empty.push(Type::Int).push(Type::Int),
1470 StackType::singleton(Type::String),
1471 )),
1472 body: vec![
1473 Statement::WordCall {
1474 name: "i.>".to_string(),
1475 span: None,
1476 },
1477 Statement::If {
1478 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1479 else_branch: Some(vec![Statement::StringLiteral(
1480 "not greater".to_string(),
1481 )]),
1482 },
1483 ],
1484 source: None,
1485 allowed_lints: vec![],
1486 }],
1487 };
1488
1489 let mut checker = TypeChecker::new();
1490 assert!(checker.check_program(&program).is_ok());
1491 }
1492
1493 #[test]
1494 fn test_mismatched_branches() {
1495 let program = Program {
1498 includes: vec![],
1499 unions: vec![],
1500 words: vec![WordDef {
1501 name: "test".to_string(),
1502 effect: Some(Effect::new(
1503 StackType::Empty,
1504 StackType::singleton(Type::Int),
1505 )),
1506 body: vec![
1507 Statement::BoolLiteral(true),
1508 Statement::If {
1509 then_branch: vec![Statement::IntLiteral(42)],
1510 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1511 },
1512 ],
1513 source: None,
1514 allowed_lints: vec![],
1515 }],
1516 };
1517
1518 let mut checker = TypeChecker::new();
1519 let result = checker.check_program(&program);
1520 assert!(result.is_err());
1521 assert!(result.unwrap_err().contains("incompatible"));
1522 }
1523
1524 #[test]
1525 fn test_user_defined_word_call() {
1526 let program = Program {
1529 includes: vec![],
1530 unions: vec![],
1531 words: vec![
1532 WordDef {
1533 name: "helper".to_string(),
1534 effect: Some(Effect::new(
1535 StackType::singleton(Type::Int),
1536 StackType::singleton(Type::String),
1537 )),
1538 body: vec![Statement::WordCall {
1539 name: "int->string".to_string(),
1540 span: None,
1541 }],
1542 source: None,
1543 allowed_lints: vec![],
1544 },
1545 WordDef {
1546 name: "main".to_string(),
1547 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1548 body: vec![
1549 Statement::IntLiteral(42),
1550 Statement::WordCall {
1551 name: "helper".to_string(),
1552 span: None,
1553 },
1554 Statement::WordCall {
1555 name: "io.write-line".to_string(),
1556 span: None,
1557 },
1558 ],
1559 source: None,
1560 allowed_lints: vec![],
1561 },
1562 ],
1563 };
1564
1565 let mut checker = TypeChecker::new();
1566 assert!(checker.check_program(&program).is_ok());
1567 }
1568
1569 #[test]
1570 fn test_arithmetic_chain() {
1571 let program = Program {
1574 includes: vec![],
1575 unions: vec![],
1576 words: vec![WordDef {
1577 name: "test".to_string(),
1578 effect: Some(Effect::new(
1579 StackType::Empty
1580 .push(Type::Int)
1581 .push(Type::Int)
1582 .push(Type::Int),
1583 StackType::singleton(Type::Int),
1584 )),
1585 body: vec![
1586 Statement::WordCall {
1587 name: "i.add".to_string(),
1588 span: None,
1589 },
1590 Statement::WordCall {
1591 name: "i.multiply".to_string(),
1592 span: None,
1593 },
1594 ],
1595 source: None,
1596 allowed_lints: vec![],
1597 }],
1598 };
1599
1600 let mut checker = TypeChecker::new();
1601 assert!(checker.check_program(&program).is_ok());
1602 }
1603
1604 #[test]
1605 fn test_write_line_type_error() {
1606 let program = Program {
1608 includes: vec![],
1609 unions: vec![],
1610 words: vec![WordDef {
1611 name: "test".to_string(),
1612 effect: Some(Effect::new(
1613 StackType::singleton(Type::Int),
1614 StackType::Empty,
1615 )),
1616 body: vec![Statement::WordCall {
1617 name: "io.write-line".to_string(),
1618 span: None,
1619 }],
1620 source: None,
1621 allowed_lints: vec![],
1622 }],
1623 };
1624
1625 let mut checker = TypeChecker::new();
1626 let result = checker.check_program(&program);
1627 assert!(result.is_err());
1628 assert!(result.unwrap_err().contains("Type mismatch"));
1629 }
1630
1631 #[test]
1632 fn test_stack_underflow_drop() {
1633 let program = Program {
1635 includes: vec![],
1636 unions: vec![],
1637 words: vec![WordDef {
1638 name: "test".to_string(),
1639 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1640 body: vec![Statement::WordCall {
1641 name: "drop".to_string(),
1642 span: None,
1643 }],
1644 source: None,
1645 allowed_lints: vec![],
1646 }],
1647 };
1648
1649 let mut checker = TypeChecker::new();
1650 let result = checker.check_program(&program);
1651 assert!(result.is_err());
1652 assert!(result.unwrap_err().contains("mismatch"));
1653 }
1654
1655 #[test]
1656 fn test_stack_underflow_add() {
1657 let program = Program {
1659 includes: vec![],
1660 unions: vec![],
1661 words: vec![WordDef {
1662 name: "test".to_string(),
1663 effect: Some(Effect::new(
1664 StackType::singleton(Type::Int),
1665 StackType::singleton(Type::Int),
1666 )),
1667 body: vec![Statement::WordCall {
1668 name: "i.add".to_string(),
1669 span: None,
1670 }],
1671 source: None,
1672 allowed_lints: vec![],
1673 }],
1674 };
1675
1676 let mut checker = TypeChecker::new();
1677 let result = checker.check_program(&program);
1678 assert!(result.is_err());
1679 assert!(result.unwrap_err().contains("mismatch"));
1680 }
1681
1682 #[test]
1685 fn test_stack_underflow_rot_issue_169() {
1686 let program = Program {
1690 includes: vec![],
1691 unions: vec![],
1692 words: vec![WordDef {
1693 name: "test".to_string(),
1694 effect: Some(Effect::new(
1695 StackType::RowVar("rest".to_string()),
1696 StackType::RowVar("rest".to_string()),
1697 )),
1698 body: vec![
1699 Statement::IntLiteral(3),
1700 Statement::IntLiteral(4),
1701 Statement::WordCall {
1702 name: "rot".to_string(),
1703 span: None,
1704 },
1705 ],
1706 source: None,
1707 allowed_lints: vec![],
1708 }],
1709 };
1710
1711 let mut checker = TypeChecker::new();
1712 let result = checker.check_program(&program);
1713 assert!(result.is_err(), "rot with 2 values should fail");
1714 let err = result.unwrap_err();
1715 assert!(
1716 err.contains("stack underflow") || err.contains("requires 3"),
1717 "Error should mention underflow: {}",
1718 err
1719 );
1720 }
1721
1722 #[test]
1723 fn test_csp_operations() {
1724 let program = Program {
1731 includes: vec![],
1732 unions: vec![],
1733 words: vec![WordDef {
1734 name: "test".to_string(),
1735 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1736 body: vec![
1737 Statement::WordCall {
1738 name: "chan.make".to_string(),
1739 span: None,
1740 },
1741 Statement::IntLiteral(42),
1742 Statement::WordCall {
1743 name: "swap".to_string(),
1744 span: None,
1745 },
1746 Statement::WordCall {
1747 name: "chan.send".to_string(),
1748 span: None,
1749 },
1750 Statement::WordCall {
1751 name: "drop".to_string(),
1752 span: None,
1753 },
1754 ],
1755 source: None,
1756 allowed_lints: vec![],
1757 }],
1758 };
1759
1760 let mut checker = TypeChecker::new();
1761 assert!(checker.check_program(&program).is_ok());
1762 }
1763
1764 #[test]
1765 fn test_complex_stack_shuffling() {
1766 let program = Program {
1769 includes: vec![],
1770 unions: vec![],
1771 words: vec![WordDef {
1772 name: "test".to_string(),
1773 effect: Some(Effect::new(
1774 StackType::Empty
1775 .push(Type::Int)
1776 .push(Type::Int)
1777 .push(Type::Int),
1778 StackType::singleton(Type::Int),
1779 )),
1780 body: vec![
1781 Statement::WordCall {
1782 name: "rot".to_string(),
1783 span: None,
1784 },
1785 Statement::WordCall {
1786 name: "i.add".to_string(),
1787 span: None,
1788 },
1789 Statement::WordCall {
1790 name: "i.add".to_string(),
1791 span: None,
1792 },
1793 ],
1794 source: None,
1795 allowed_lints: vec![],
1796 }],
1797 };
1798
1799 let mut checker = TypeChecker::new();
1800 assert!(checker.check_program(&program).is_ok());
1801 }
1802
1803 #[test]
1804 fn test_empty_program() {
1805 let program = Program {
1807 includes: vec![],
1808 unions: vec![],
1809 words: vec![],
1810 };
1811
1812 let mut checker = TypeChecker::new();
1813 assert!(checker.check_program(&program).is_ok());
1814 }
1815
1816 #[test]
1817 fn test_word_without_effect_declaration() {
1818 let program = Program {
1820 includes: vec![],
1821 unions: vec![],
1822 words: vec![WordDef {
1823 name: "helper".to_string(),
1824 effect: None,
1825 body: vec![Statement::IntLiteral(42)],
1826 source: None,
1827 allowed_lints: vec![],
1828 }],
1829 };
1830
1831 let mut checker = TypeChecker::new();
1832 let result = checker.check_program(&program);
1833 assert!(result.is_err());
1834 assert!(
1835 result
1836 .unwrap_err()
1837 .contains("missing a stack effect declaration")
1838 );
1839 }
1840
1841 #[test]
1842 fn test_nested_conditionals() {
1843 let program = Program {
1852 includes: vec![],
1853 unions: vec![],
1854 words: vec![WordDef {
1855 name: "test".to_string(),
1856 effect: Some(Effect::new(
1857 StackType::Empty
1858 .push(Type::Int)
1859 .push(Type::Int)
1860 .push(Type::Int)
1861 .push(Type::Int),
1862 StackType::singleton(Type::String),
1863 )),
1864 body: vec![
1865 Statement::WordCall {
1866 name: "i.>".to_string(),
1867 span: None,
1868 },
1869 Statement::If {
1870 then_branch: vec![
1871 Statement::WordCall {
1872 name: "i.>".to_string(),
1873 span: None,
1874 },
1875 Statement::If {
1876 then_branch: vec![Statement::StringLiteral(
1877 "both true".to_string(),
1878 )],
1879 else_branch: Some(vec![Statement::StringLiteral(
1880 "first true".to_string(),
1881 )]),
1882 },
1883 ],
1884 else_branch: Some(vec![
1885 Statement::WordCall {
1886 name: "drop".to_string(),
1887 span: None,
1888 },
1889 Statement::WordCall {
1890 name: "drop".to_string(),
1891 span: None,
1892 },
1893 Statement::StringLiteral("first false".to_string()),
1894 ]),
1895 },
1896 ],
1897 source: None,
1898 allowed_lints: vec![],
1899 }],
1900 };
1901
1902 let mut checker = TypeChecker::new();
1903 match checker.check_program(&program) {
1904 Ok(_) => {}
1905 Err(e) => panic!("Type check failed: {}", e),
1906 }
1907 }
1908
1909 #[test]
1910 fn test_conditional_without_else() {
1911 let program = Program {
1915 includes: vec![],
1916 unions: vec![],
1917 words: vec![WordDef {
1918 name: "test".to_string(),
1919 effect: Some(Effect::new(
1920 StackType::Empty.push(Type::Int).push(Type::Int),
1921 StackType::singleton(Type::Int),
1922 )),
1923 body: vec![
1924 Statement::WordCall {
1925 name: "i.>".to_string(),
1926 span: None,
1927 },
1928 Statement::If {
1929 then_branch: vec![Statement::IntLiteral(100)],
1930 else_branch: None, },
1932 ],
1933 source: None,
1934 allowed_lints: vec![],
1935 }],
1936 };
1937
1938 let mut checker = TypeChecker::new();
1939 let result = checker.check_program(&program);
1940 assert!(result.is_err());
1942 }
1943
1944 #[test]
1945 fn test_multiple_word_chain() {
1946 let program = Program {
1950 includes: vec![],
1951 unions: vec![],
1952 words: vec![
1953 WordDef {
1954 name: "helper1".to_string(),
1955 effect: Some(Effect::new(
1956 StackType::singleton(Type::Int),
1957 StackType::singleton(Type::String),
1958 )),
1959 body: vec![Statement::WordCall {
1960 name: "int->string".to_string(),
1961 span: None,
1962 }],
1963 source: None,
1964 allowed_lints: vec![],
1965 },
1966 WordDef {
1967 name: "helper2".to_string(),
1968 effect: Some(Effect::new(
1969 StackType::singleton(Type::String),
1970 StackType::Empty,
1971 )),
1972 body: vec![Statement::WordCall {
1973 name: "io.write-line".to_string(),
1974 span: None,
1975 }],
1976 source: None,
1977 allowed_lints: vec![],
1978 },
1979 WordDef {
1980 name: "main".to_string(),
1981 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1982 body: vec![
1983 Statement::IntLiteral(42),
1984 Statement::WordCall {
1985 name: "helper1".to_string(),
1986 span: None,
1987 },
1988 Statement::WordCall {
1989 name: "helper2".to_string(),
1990 span: None,
1991 },
1992 ],
1993 source: None,
1994 allowed_lints: vec![],
1995 },
1996 ],
1997 };
1998
1999 let mut checker = TypeChecker::new();
2000 assert!(checker.check_program(&program).is_ok());
2001 }
2002
2003 #[test]
2004 fn test_all_stack_ops() {
2005 let program = Program {
2008 includes: vec![],
2009 unions: vec![],
2010 words: vec![WordDef {
2011 name: "test".to_string(),
2012 effect: Some(Effect::new(
2013 StackType::Empty
2014 .push(Type::Int)
2015 .push(Type::Int)
2016 .push(Type::Int),
2017 StackType::Empty
2018 .push(Type::Int)
2019 .push(Type::Int)
2020 .push(Type::Int)
2021 .push(Type::Int),
2022 )),
2023 body: vec![
2024 Statement::WordCall {
2025 name: "over".to_string(),
2026 span: None,
2027 },
2028 Statement::WordCall {
2029 name: "nip".to_string(),
2030 span: None,
2031 },
2032 Statement::WordCall {
2033 name: "tuck".to_string(),
2034 span: None,
2035 },
2036 ],
2037 source: None,
2038 allowed_lints: vec![],
2039 }],
2040 };
2041
2042 let mut checker = TypeChecker::new();
2043 assert!(checker.check_program(&program).is_ok());
2044 }
2045
2046 #[test]
2047 fn test_mixed_types_complex() {
2048 let program = Program {
2057 includes: vec![],
2058 unions: vec![],
2059 words: vec![WordDef {
2060 name: "test".to_string(),
2061 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2062 body: vec![
2063 Statement::IntLiteral(42),
2064 Statement::WordCall {
2065 name: "int->string".to_string(),
2066 span: None,
2067 },
2068 Statement::IntLiteral(100),
2069 Statement::IntLiteral(200),
2070 Statement::WordCall {
2071 name: "i.>".to_string(),
2072 span: None,
2073 },
2074 Statement::If {
2075 then_branch: vec![Statement::WordCall {
2076 name: "io.write-line".to_string(),
2077 span: None,
2078 }],
2079 else_branch: Some(vec![Statement::WordCall {
2080 name: "io.write-line".to_string(),
2081 span: None,
2082 }]),
2083 },
2084 ],
2085 source: None,
2086 allowed_lints: vec![],
2087 }],
2088 };
2089
2090 let mut checker = TypeChecker::new();
2091 assert!(checker.check_program(&program).is_ok());
2092 }
2093
2094 #[test]
2095 fn test_string_literal() {
2096 let program = Program {
2098 includes: vec![],
2099 unions: vec![],
2100 words: vec![WordDef {
2101 name: "test".to_string(),
2102 effect: Some(Effect::new(
2103 StackType::Empty,
2104 StackType::singleton(Type::String),
2105 )),
2106 body: vec![Statement::StringLiteral("hello".to_string())],
2107 source: None,
2108 allowed_lints: vec![],
2109 }],
2110 };
2111
2112 let mut checker = TypeChecker::new();
2113 assert!(checker.check_program(&program).is_ok());
2114 }
2115
2116 #[test]
2117 fn test_bool_literal() {
2118 let program = Program {
2121 includes: vec![],
2122 unions: vec![],
2123 words: vec![WordDef {
2124 name: "test".to_string(),
2125 effect: Some(Effect::new(
2126 StackType::Empty,
2127 StackType::singleton(Type::Bool),
2128 )),
2129 body: vec![Statement::BoolLiteral(true)],
2130 source: None,
2131 allowed_lints: vec![],
2132 }],
2133 };
2134
2135 let mut checker = TypeChecker::new();
2136 assert!(checker.check_program(&program).is_ok());
2137 }
2138
2139 #[test]
2140 fn test_type_error_in_nested_conditional() {
2141 let program = Program {
2148 includes: vec![],
2149 unions: vec![],
2150 words: vec![WordDef {
2151 name: "test".to_string(),
2152 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2153 body: vec![
2154 Statement::IntLiteral(10),
2155 Statement::IntLiteral(20),
2156 Statement::WordCall {
2157 name: "i.>".to_string(),
2158 span: None,
2159 },
2160 Statement::If {
2161 then_branch: vec![
2162 Statement::IntLiteral(42),
2163 Statement::WordCall {
2164 name: "io.write-line".to_string(),
2165 span: None,
2166 },
2167 ],
2168 else_branch: Some(vec![
2169 Statement::StringLiteral("ok".to_string()),
2170 Statement::WordCall {
2171 name: "io.write-line".to_string(),
2172 span: None,
2173 },
2174 ]),
2175 },
2176 ],
2177 source: None,
2178 allowed_lints: vec![],
2179 }],
2180 };
2181
2182 let mut checker = TypeChecker::new();
2183 let result = checker.check_program(&program);
2184 assert!(result.is_err());
2185 assert!(result.unwrap_err().contains("Type mismatch"));
2186 }
2187
2188 #[test]
2189 fn test_read_line_operation() {
2190 let program = Program {
2193 includes: vec![],
2194 unions: vec![],
2195 words: vec![WordDef {
2196 name: "test".to_string(),
2197 effect: Some(Effect::new(
2198 StackType::Empty,
2199 StackType::from_vec(vec![Type::String, Type::Bool]),
2200 )),
2201 body: vec![Statement::WordCall {
2202 name: "io.read-line".to_string(),
2203 span: None,
2204 }],
2205 source: None,
2206 allowed_lints: vec![],
2207 }],
2208 };
2209
2210 let mut checker = TypeChecker::new();
2211 assert!(checker.check_program(&program).is_ok());
2212 }
2213
2214 #[test]
2215 fn test_comparison_operations() {
2216 let program = Program {
2221 includes: vec![],
2222 unions: vec![],
2223 words: vec![WordDef {
2224 name: "test".to_string(),
2225 effect: Some(Effect::new(
2226 StackType::Empty.push(Type::Int).push(Type::Int),
2227 StackType::singleton(Type::Bool),
2228 )),
2229 body: vec![Statement::WordCall {
2230 name: "i.<=".to_string(),
2231 span: None,
2232 }],
2233 source: None,
2234 allowed_lints: vec![],
2235 }],
2236 };
2237
2238 let mut checker = TypeChecker::new();
2239 assert!(checker.check_program(&program).is_ok());
2240 }
2241
2242 #[test]
2243 fn test_recursive_word_definitions() {
2244 let program = Program {
2250 includes: vec![],
2251 unions: vec![],
2252 words: vec![
2253 WordDef {
2254 name: "is-even".to_string(),
2255 effect: Some(Effect::new(
2256 StackType::singleton(Type::Int),
2257 StackType::singleton(Type::Int),
2258 )),
2259 body: vec![
2260 Statement::WordCall {
2261 name: "dup".to_string(),
2262 span: None,
2263 },
2264 Statement::IntLiteral(0),
2265 Statement::WordCall {
2266 name: "i.=".to_string(),
2267 span: None,
2268 },
2269 Statement::If {
2270 then_branch: vec![
2271 Statement::WordCall {
2272 name: "drop".to_string(),
2273 span: None,
2274 },
2275 Statement::IntLiteral(1),
2276 ],
2277 else_branch: Some(vec![
2278 Statement::IntLiteral(1),
2279 Statement::WordCall {
2280 name: "i.subtract".to_string(),
2281 span: None,
2282 },
2283 Statement::WordCall {
2284 name: "is-odd".to_string(),
2285 span: None,
2286 },
2287 ]),
2288 },
2289 ],
2290 source: None,
2291 allowed_lints: vec![],
2292 },
2293 WordDef {
2294 name: "is-odd".to_string(),
2295 effect: Some(Effect::new(
2296 StackType::singleton(Type::Int),
2297 StackType::singleton(Type::Int),
2298 )),
2299 body: vec![
2300 Statement::WordCall {
2301 name: "dup".to_string(),
2302 span: None,
2303 },
2304 Statement::IntLiteral(0),
2305 Statement::WordCall {
2306 name: "i.=".to_string(),
2307 span: None,
2308 },
2309 Statement::If {
2310 then_branch: vec![
2311 Statement::WordCall {
2312 name: "drop".to_string(),
2313 span: None,
2314 },
2315 Statement::IntLiteral(0),
2316 ],
2317 else_branch: Some(vec![
2318 Statement::IntLiteral(1),
2319 Statement::WordCall {
2320 name: "i.subtract".to_string(),
2321 span: None,
2322 },
2323 Statement::WordCall {
2324 name: "is-even".to_string(),
2325 span: None,
2326 },
2327 ]),
2328 },
2329 ],
2330 source: None,
2331 allowed_lints: vec![],
2332 },
2333 ],
2334 };
2335
2336 let mut checker = TypeChecker::new();
2337 assert!(checker.check_program(&program).is_ok());
2338 }
2339
2340 #[test]
2341 fn test_word_calling_word_with_row_polymorphism() {
2342 let program = Program {
2347 includes: vec![],
2348 unions: vec![],
2349 words: vec![
2350 WordDef {
2351 name: "apply-twice".to_string(),
2352 effect: Some(Effect::new(
2353 StackType::singleton(Type::Int),
2354 StackType::singleton(Type::Int),
2355 )),
2356 body: vec![
2357 Statement::WordCall {
2358 name: "dup".to_string(),
2359 span: None,
2360 },
2361 Statement::WordCall {
2362 name: "i.add".to_string(),
2363 span: None,
2364 },
2365 ],
2366 source: None,
2367 allowed_lints: vec![],
2368 },
2369 WordDef {
2370 name: "quad".to_string(),
2371 effect: Some(Effect::new(
2372 StackType::singleton(Type::Int),
2373 StackType::singleton(Type::Int),
2374 )),
2375 body: vec![
2376 Statement::WordCall {
2377 name: "apply-twice".to_string(),
2378 span: None,
2379 },
2380 Statement::WordCall {
2381 name: "apply-twice".to_string(),
2382 span: None,
2383 },
2384 ],
2385 source: None,
2386 allowed_lints: vec![],
2387 },
2388 ],
2389 };
2390
2391 let mut checker = TypeChecker::new();
2392 assert!(checker.check_program(&program).is_ok());
2393 }
2394
2395 #[test]
2396 fn test_deep_stack_types() {
2397 let mut stack_type = StackType::Empty;
2401 for _ in 0..10 {
2402 stack_type = stack_type.push(Type::Int);
2403 }
2404
2405 let program = Program {
2406 includes: vec![],
2407 unions: vec![],
2408 words: vec![WordDef {
2409 name: "test".to_string(),
2410 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2411 body: vec![
2412 Statement::WordCall {
2413 name: "i.add".to_string(),
2414 span: None,
2415 },
2416 Statement::WordCall {
2417 name: "i.add".to_string(),
2418 span: None,
2419 },
2420 Statement::WordCall {
2421 name: "i.add".to_string(),
2422 span: None,
2423 },
2424 Statement::WordCall {
2425 name: "i.add".to_string(),
2426 span: None,
2427 },
2428 Statement::WordCall {
2429 name: "i.add".to_string(),
2430 span: None,
2431 },
2432 Statement::WordCall {
2433 name: "i.add".to_string(),
2434 span: None,
2435 },
2436 Statement::WordCall {
2437 name: "i.add".to_string(),
2438 span: None,
2439 },
2440 Statement::WordCall {
2441 name: "i.add".to_string(),
2442 span: None,
2443 },
2444 Statement::WordCall {
2445 name: "i.add".to_string(),
2446 span: None,
2447 },
2448 ],
2449 source: None,
2450 allowed_lints: vec![],
2451 }],
2452 };
2453
2454 let mut checker = TypeChecker::new();
2455 assert!(checker.check_program(&program).is_ok());
2456 }
2457
2458 #[test]
2459 fn test_simple_quotation() {
2460 let program = Program {
2464 includes: vec![],
2465 unions: vec![],
2466 words: vec![WordDef {
2467 name: "test".to_string(),
2468 effect: Some(Effect::new(
2469 StackType::Empty,
2470 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2471 StackType::RowVar("input".to_string()).push(Type::Int),
2472 StackType::RowVar("input".to_string()).push(Type::Int),
2473 )))),
2474 )),
2475 body: vec![Statement::Quotation {
2476 span: None,
2477 id: 0,
2478 body: vec![
2479 Statement::IntLiteral(1),
2480 Statement::WordCall {
2481 name: "i.add".to_string(),
2482 span: None,
2483 },
2484 ],
2485 }],
2486 source: None,
2487 allowed_lints: vec![],
2488 }],
2489 };
2490
2491 let mut checker = TypeChecker::new();
2492 match checker.check_program(&program) {
2493 Ok(_) => {}
2494 Err(e) => panic!("Type check failed: {}", e),
2495 }
2496 }
2497
2498 #[test]
2499 fn test_empty_quotation() {
2500 let program = Program {
2504 includes: vec![],
2505 unions: vec![],
2506 words: vec![WordDef {
2507 name: "test".to_string(),
2508 effect: Some(Effect::new(
2509 StackType::Empty,
2510 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2511 StackType::RowVar("input".to_string()),
2512 StackType::RowVar("input".to_string()),
2513 )))),
2514 )),
2515 body: vec![Statement::Quotation {
2516 span: None,
2517 id: 1,
2518 body: vec![],
2519 }],
2520 source: None,
2521 allowed_lints: vec![],
2522 }],
2523 };
2524
2525 let mut checker = TypeChecker::new();
2526 assert!(checker.check_program(&program).is_ok());
2527 }
2528
2529 #[test]
2556 fn test_nested_quotation() {
2557 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2561 StackType::RowVar("input".to_string()).push(Type::Int),
2562 StackType::RowVar("input".to_string()).push(Type::Int),
2563 )));
2564
2565 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2566 StackType::RowVar("input".to_string()),
2567 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2568 )));
2569
2570 let program = Program {
2571 includes: vec![],
2572 unions: vec![],
2573 words: vec![WordDef {
2574 name: "test".to_string(),
2575 effect: Some(Effect::new(
2576 StackType::Empty,
2577 StackType::singleton(outer_quot_type),
2578 )),
2579 body: vec![Statement::Quotation {
2580 span: None,
2581 id: 2,
2582 body: vec![Statement::Quotation {
2583 span: None,
2584 id: 3,
2585 body: vec![
2586 Statement::IntLiteral(1),
2587 Statement::WordCall {
2588 name: "i.add".to_string(),
2589 span: None,
2590 },
2591 ],
2592 }],
2593 }],
2594 source: None,
2595 allowed_lints: vec![],
2596 }],
2597 };
2598
2599 let mut checker = TypeChecker::new();
2600 assert!(checker.check_program(&program).is_ok());
2601 }
2602
2603 #[test]
2604 fn test_invalid_field_type_error() {
2605 use crate::ast::{UnionDef, UnionField, UnionVariant};
2606
2607 let program = Program {
2608 includes: vec![],
2609 unions: vec![UnionDef {
2610 name: "Message".to_string(),
2611 variants: vec![UnionVariant {
2612 name: "Get".to_string(),
2613 fields: vec![UnionField {
2614 name: "chan".to_string(),
2615 type_name: "InvalidType".to_string(),
2616 }],
2617 source: None,
2618 }],
2619 source: None,
2620 }],
2621 words: vec![],
2622 };
2623
2624 let mut checker = TypeChecker::new();
2625 let result = checker.check_program(&program);
2626 assert!(result.is_err());
2627 let err = result.unwrap_err();
2628 assert!(err.contains("Unknown type 'InvalidType'"));
2629 assert!(err.contains("chan"));
2630 assert!(err.contains("Get"));
2631 assert!(err.contains("Message"));
2632 }
2633
2634 #[test]
2635 fn test_roll_inside_conditional_with_concrete_stack() {
2636 let program = Program {
2644 includes: vec![],
2645 unions: vec![],
2646 words: vec![WordDef {
2647 name: "test".to_string(),
2648 effect: Some(Effect::new(
2649 StackType::Empty
2650 .push(Type::Int)
2651 .push(Type::Int)
2652 .push(Type::Int)
2653 .push(Type::Int),
2654 StackType::Empty
2655 .push(Type::Int)
2656 .push(Type::Int)
2657 .push(Type::Int)
2658 .push(Type::Int),
2659 )),
2660 body: vec![
2661 Statement::WordCall {
2662 name: "dup".to_string(),
2663 span: None,
2664 },
2665 Statement::IntLiteral(0),
2666 Statement::WordCall {
2667 name: "i.>".to_string(),
2668 span: None,
2669 },
2670 Statement::If {
2671 then_branch: vec![
2672 Statement::IntLiteral(3),
2673 Statement::WordCall {
2674 name: "roll".to_string(),
2675 span: None,
2676 },
2677 ],
2678 else_branch: Some(vec![
2679 Statement::WordCall {
2680 name: "rot".to_string(),
2681 span: None,
2682 },
2683 Statement::WordCall {
2684 name: "rot".to_string(),
2685 span: None,
2686 },
2687 ]),
2688 },
2689 ],
2690 source: None,
2691 allowed_lints: vec![],
2692 }],
2693 };
2694
2695 let mut checker = TypeChecker::new();
2696 match checker.check_program(&program) {
2698 Ok(_) => {}
2699 Err(e) => panic!("Type check failed: {}", e),
2700 }
2701 }
2702
2703 #[test]
2704 fn test_roll_inside_match_arm_with_concrete_stack() {
2705 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2708
2709 let union_def = UnionDef {
2711 name: "Result".to_string(),
2712 variants: vec![
2713 UnionVariant {
2714 name: "Ok".to_string(),
2715 fields: vec![],
2716 source: None,
2717 },
2718 UnionVariant {
2719 name: "Err".to_string(),
2720 fields: vec![],
2721 source: None,
2722 },
2723 ],
2724 source: None,
2725 };
2726
2727 let program = Program {
2733 includes: vec![],
2734 unions: vec![union_def],
2735 words: vec![WordDef {
2736 name: "test".to_string(),
2737 effect: Some(Effect::new(
2738 StackType::Empty
2739 .push(Type::Int)
2740 .push(Type::Int)
2741 .push(Type::Int)
2742 .push(Type::Int)
2743 .push(Type::Union("Result".to_string())),
2744 StackType::Empty
2745 .push(Type::Int)
2746 .push(Type::Int)
2747 .push(Type::Int)
2748 .push(Type::Int),
2749 )),
2750 body: vec![Statement::Match {
2751 arms: vec![
2752 MatchArm {
2753 pattern: Pattern::Variant("Ok".to_string()),
2754 body: vec![
2755 Statement::IntLiteral(3),
2756 Statement::WordCall {
2757 name: "roll".to_string(),
2758 span: None,
2759 },
2760 ],
2761 },
2762 MatchArm {
2763 pattern: Pattern::Variant("Err".to_string()),
2764 body: vec![
2765 Statement::WordCall {
2766 name: "rot".to_string(),
2767 span: None,
2768 },
2769 Statement::WordCall {
2770 name: "rot".to_string(),
2771 span: None,
2772 },
2773 ],
2774 },
2775 ],
2776 }],
2777 source: None,
2778 allowed_lints: vec![],
2779 }],
2780 };
2781
2782 let mut checker = TypeChecker::new();
2783 match checker.check_program(&program) {
2784 Ok(_) => {}
2785 Err(e) => panic!("Type check failed: {}", e),
2786 }
2787 }
2788
2789 #[test]
2790 fn test_roll_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 .push(Type::Var("W".to_string())),
2805 StackType::Empty
2806 .push(Type::Var("U".to_string()))
2807 .push(Type::Var("V".to_string()))
2808 .push(Type::Var("W".to_string()))
2809 .push(Type::Var("T".to_string())),
2810 )),
2811 body: vec![
2812 Statement::IntLiteral(3),
2813 Statement::WordCall {
2814 name: "roll".to_string(),
2815 span: None,
2816 },
2817 ],
2818 source: None,
2819 allowed_lints: vec![],
2820 }],
2821 };
2822
2823 let mut checker = TypeChecker::new();
2824 let result = checker.check_program(&program);
2825 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
2826 }
2827
2828 #[test]
2829 fn test_pick_with_row_polymorphic_input() {
2830 let program = Program {
2834 includes: vec![],
2835 unions: vec![],
2836 words: vec![WordDef {
2837 name: "test".to_string(),
2838 effect: Some(Effect::new(
2839 StackType::Empty
2840 .push(Type::Var("T".to_string()))
2841 .push(Type::Var("U".to_string()))
2842 .push(Type::Var("V".to_string())),
2843 StackType::Empty
2844 .push(Type::Var("T".to_string()))
2845 .push(Type::Var("U".to_string()))
2846 .push(Type::Var("V".to_string()))
2847 .push(Type::Var("T".to_string())),
2848 )),
2849 body: vec![
2850 Statement::IntLiteral(2),
2851 Statement::WordCall {
2852 name: "pick".to_string(),
2853 span: None,
2854 },
2855 ],
2856 source: None,
2857 allowed_lints: vec![],
2858 }],
2859 };
2860
2861 let mut checker = TypeChecker::new();
2862 assert!(checker.check_program(&program).is_ok());
2863 }
2864
2865 #[test]
2866 fn test_valid_union_reference_in_field() {
2867 use crate::ast::{UnionDef, UnionField, UnionVariant};
2868
2869 let program = Program {
2870 includes: vec![],
2871 unions: vec![
2872 UnionDef {
2873 name: "Inner".to_string(),
2874 variants: vec![UnionVariant {
2875 name: "Val".to_string(),
2876 fields: vec![UnionField {
2877 name: "x".to_string(),
2878 type_name: "Int".to_string(),
2879 }],
2880 source: None,
2881 }],
2882 source: None,
2883 },
2884 UnionDef {
2885 name: "Outer".to_string(),
2886 variants: vec![UnionVariant {
2887 name: "Wrap".to_string(),
2888 fields: vec![UnionField {
2889 name: "inner".to_string(),
2890 type_name: "Inner".to_string(), }],
2892 source: None,
2893 }],
2894 source: None,
2895 },
2896 ],
2897 words: vec![],
2898 };
2899
2900 let mut checker = TypeChecker::new();
2901 assert!(
2902 checker.check_program(&program).is_ok(),
2903 "Union reference in field should be valid"
2904 );
2905 }
2906
2907 #[test]
2908 fn test_divergent_recursive_tail_call() {
2909 let program = Program {
2931 includes: vec![],
2932 unions: vec![],
2933 words: vec![WordDef {
2934 name: "store-loop".to_string(),
2935 effect: Some(Effect::new(
2936 StackType::singleton(Type::Channel), StackType::Empty,
2938 )),
2939 body: vec![
2940 Statement::WordCall {
2942 name: "dup".to_string(),
2943 span: None,
2944 },
2945 Statement::WordCall {
2947 name: "chan.receive".to_string(),
2948 span: None,
2949 },
2950 Statement::WordCall {
2952 name: "not".to_string(),
2953 span: None,
2954 },
2955 Statement::If {
2957 then_branch: vec![
2958 Statement::WordCall {
2960 name: "drop".to_string(),
2961 span: None,
2962 },
2963 Statement::WordCall {
2965 name: "store-loop".to_string(), span: None,
2967 },
2968 ],
2969 else_branch: None, },
2971 Statement::WordCall {
2973 name: "drop".to_string(),
2974 span: None,
2975 },
2976 Statement::WordCall {
2977 name: "drop".to_string(),
2978 span: None,
2979 },
2980 ],
2981 source: None,
2982 allowed_lints: vec![],
2983 }],
2984 };
2985
2986 let mut checker = TypeChecker::new();
2987 let result = checker.check_program(&program);
2988 assert!(
2989 result.is_ok(),
2990 "Divergent recursive tail call should be accepted: {:?}",
2991 result.err()
2992 );
2993 }
2994
2995 #[test]
2996 fn test_divergent_else_branch() {
2997 let program = Program {
3010 includes: vec![],
3011 unions: vec![],
3012 words: vec![WordDef {
3013 name: "process-loop".to_string(),
3014 effect: Some(Effect::new(
3015 StackType::singleton(Type::Channel), StackType::Empty,
3017 )),
3018 body: vec![
3019 Statement::WordCall {
3020 name: "dup".to_string(),
3021 span: None,
3022 },
3023 Statement::WordCall {
3024 name: "chan.receive".to_string(),
3025 span: None,
3026 },
3027 Statement::If {
3028 then_branch: vec![
3029 Statement::WordCall {
3031 name: "drop".to_string(),
3032 span: None,
3033 },
3034 Statement::WordCall {
3035 name: "drop".to_string(),
3036 span: None,
3037 },
3038 ],
3039 else_branch: Some(vec![
3040 Statement::WordCall {
3042 name: "drop".to_string(),
3043 span: None,
3044 },
3045 Statement::WordCall {
3046 name: "process-loop".to_string(), span: None,
3048 },
3049 ]),
3050 },
3051 ],
3052 source: None,
3053 allowed_lints: vec![],
3054 }],
3055 };
3056
3057 let mut checker = TypeChecker::new();
3058 let result = checker.check_program(&program);
3059 assert!(
3060 result.is_ok(),
3061 "Divergent else branch should be accepted: {:?}",
3062 result.err()
3063 );
3064 }
3065
3066 #[test]
3067 fn test_non_tail_call_recursion_not_divergent() {
3068 let program = Program {
3085 includes: vec![],
3086 unions: vec![],
3087 words: vec![WordDef {
3088 name: "bad-loop".to_string(),
3089 effect: Some(Effect::new(
3090 StackType::singleton(Type::Int),
3091 StackType::singleton(Type::Int),
3092 )),
3093 body: vec![
3094 Statement::WordCall {
3095 name: "dup".to_string(),
3096 span: None,
3097 },
3098 Statement::IntLiteral(0),
3099 Statement::WordCall {
3100 name: "i.>".to_string(),
3101 span: None,
3102 },
3103 Statement::If {
3104 then_branch: vec![
3105 Statement::IntLiteral(1),
3106 Statement::WordCall {
3107 name: "i.subtract".to_string(),
3108 span: None,
3109 },
3110 Statement::WordCall {
3111 name: "bad-loop".to_string(), span: None,
3113 },
3114 Statement::IntLiteral(1),
3115 Statement::WordCall {
3116 name: "i.add".to_string(), span: None,
3118 },
3119 ],
3120 else_branch: None,
3121 },
3122 ],
3123 source: None,
3124 allowed_lints: vec![],
3125 }],
3126 };
3127
3128 let mut checker = TypeChecker::new();
3129 let result = checker.check_program(&program);
3134 assert!(
3135 result.is_ok(),
3136 "Non-tail recursion should type check normally: {:?}",
3137 result.err()
3138 );
3139 }
3140
3141 #[test]
3142 fn test_call_yield_quotation_error() {
3143 let program = Program {
3147 includes: vec![],
3148 unions: vec![],
3149 words: vec![WordDef {
3150 name: "bad".to_string(),
3151 effect: Some(Effect::new(
3152 StackType::singleton(Type::Var("Ctx".to_string())),
3153 StackType::singleton(Type::Var("Ctx".to_string())),
3154 )),
3155 body: vec![
3156 Statement::IntLiteral(42),
3158 Statement::Quotation {
3159 span: None,
3160 id: 0,
3161 body: vec![Statement::WordCall {
3162 name: "yield".to_string(),
3163 span: None,
3164 }],
3165 },
3166 Statement::WordCall {
3167 name: "call".to_string(),
3168 span: None,
3169 },
3170 ],
3171 source: None,
3172 allowed_lints: vec![],
3173 }],
3174 };
3175
3176 let mut checker = TypeChecker::new();
3177 let result = checker.check_program(&program);
3178 assert!(
3179 result.is_err(),
3180 "Calling yield quotation directly should fail"
3181 );
3182 let err = result.unwrap_err();
3183 assert!(
3184 err.contains("Yield") || err.contains("strand.weave"),
3185 "Error should mention Yield or strand.weave: {}",
3186 err
3187 );
3188 }
3189
3190 #[test]
3191 fn test_strand_weave_yield_quotation_ok() {
3192 let program = Program {
3195 includes: vec![],
3196 unions: vec![],
3197 words: vec![WordDef {
3198 name: "good".to_string(),
3199 effect: Some(Effect::new(
3200 StackType::Empty,
3201 StackType::Empty
3202 .push(Type::Int)
3203 .push(Type::Var("Handle".to_string())),
3204 )),
3205 body: vec![
3206 Statement::IntLiteral(42),
3207 Statement::Quotation {
3208 span: None,
3209 id: 0,
3210 body: vec![Statement::WordCall {
3211 name: "yield".to_string(),
3212 span: None,
3213 }],
3214 },
3215 Statement::WordCall {
3216 name: "strand.weave".to_string(),
3217 span: None,
3218 },
3219 ],
3220 source: None,
3221 allowed_lints: vec![],
3222 }],
3223 };
3224
3225 let mut checker = TypeChecker::new();
3226 let result = checker.check_program(&program);
3227 assert!(
3228 result.is_ok(),
3229 "strand.weave on yield quotation should pass: {:?}",
3230 result.err()
3231 );
3232 }
3233
3234 #[test]
3235 fn test_call_pure_quotation_ok() {
3236 let program = Program {
3239 includes: vec![],
3240 unions: vec![],
3241 words: vec![WordDef {
3242 name: "ok".to_string(),
3243 effect: Some(Effect::new(
3244 StackType::singleton(Type::Int),
3245 StackType::singleton(Type::Int),
3246 )),
3247 body: vec![
3248 Statement::Quotation {
3249 span: None,
3250 id: 0,
3251 body: vec![
3252 Statement::IntLiteral(1),
3253 Statement::WordCall {
3254 name: "i.add".to_string(),
3255 span: None,
3256 },
3257 ],
3258 },
3259 Statement::WordCall {
3260 name: "call".to_string(),
3261 span: None,
3262 },
3263 ],
3264 source: None,
3265 allowed_lints: vec![],
3266 }],
3267 };
3268
3269 let mut checker = TypeChecker::new();
3270 let result = checker.check_program(&program);
3271 assert!(
3272 result.is_ok(),
3273 "Calling pure quotation should pass: {:?}",
3274 result.err()
3275 );
3276 }
3277
3278 #[test]
3284 fn test_pollution_extra_push() {
3285 let program = Program {
3289 includes: vec![],
3290 unions: vec![],
3291 words: vec![WordDef {
3292 name: "test".to_string(),
3293 effect: Some(Effect::new(
3294 StackType::singleton(Type::Int),
3295 StackType::singleton(Type::Int),
3296 )),
3297 body: vec![Statement::IntLiteral(42)],
3298 source: None,
3299 allowed_lints: vec![],
3300 }],
3301 };
3302
3303 let mut checker = TypeChecker::new();
3304 let result = checker.check_program(&program);
3305 assert!(
3306 result.is_err(),
3307 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3308 );
3309 }
3310
3311 #[test]
3312 fn test_pollution_extra_dup() {
3313 let program = Program {
3316 includes: vec![],
3317 unions: vec![],
3318 words: vec![WordDef {
3319 name: "test".to_string(),
3320 effect: Some(Effect::new(
3321 StackType::singleton(Type::Int),
3322 StackType::singleton(Type::Int),
3323 )),
3324 body: vec![Statement::WordCall {
3325 name: "dup".to_string(),
3326 span: None,
3327 }],
3328 source: None,
3329 allowed_lints: vec![],
3330 }],
3331 };
3332
3333 let mut checker = TypeChecker::new();
3334 let result = checker.check_program(&program);
3335 assert!(
3336 result.is_err(),
3337 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3338 );
3339 }
3340
3341 #[test]
3342 fn test_pollution_consumes_extra() {
3343 let program = Program {
3346 includes: vec![],
3347 unions: vec![],
3348 words: vec![WordDef {
3349 name: "test".to_string(),
3350 effect: Some(Effect::new(
3351 StackType::singleton(Type::Int),
3352 StackType::singleton(Type::Int),
3353 )),
3354 body: vec![
3355 Statement::WordCall {
3356 name: "drop".to_string(),
3357 span: None,
3358 },
3359 Statement::WordCall {
3360 name: "drop".to_string(),
3361 span: None,
3362 },
3363 Statement::IntLiteral(42),
3364 ],
3365 source: None,
3366 allowed_lints: vec![],
3367 }],
3368 };
3369
3370 let mut checker = TypeChecker::new();
3371 let result = checker.check_program(&program);
3372 assert!(
3373 result.is_err(),
3374 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3375 );
3376 }
3377
3378 #[test]
3379 fn test_pollution_in_then_branch() {
3380 let program = Program {
3384 includes: vec![],
3385 unions: vec![],
3386 words: vec![WordDef {
3387 name: "test".to_string(),
3388 effect: Some(Effect::new(
3389 StackType::singleton(Type::Bool),
3390 StackType::singleton(Type::Int),
3391 )),
3392 body: vec![Statement::If {
3393 then_branch: vec![
3394 Statement::IntLiteral(1),
3395 Statement::IntLiteral(2), ],
3397 else_branch: Some(vec![Statement::IntLiteral(3)]),
3398 }],
3399 source: None,
3400 allowed_lints: vec![],
3401 }],
3402 };
3403
3404 let mut checker = TypeChecker::new();
3405 let result = checker.check_program(&program);
3406 assert!(
3407 result.is_err(),
3408 "Should reject: then branch pushes 2 values, else pushes 1"
3409 );
3410 }
3411
3412 #[test]
3413 fn test_pollution_in_else_branch() {
3414 let program = Program {
3418 includes: vec![],
3419 unions: vec![],
3420 words: vec![WordDef {
3421 name: "test".to_string(),
3422 effect: Some(Effect::new(
3423 StackType::singleton(Type::Bool),
3424 StackType::singleton(Type::Int),
3425 )),
3426 body: vec![Statement::If {
3427 then_branch: vec![Statement::IntLiteral(1)],
3428 else_branch: Some(vec![
3429 Statement::IntLiteral(2),
3430 Statement::IntLiteral(3), ]),
3432 }],
3433 source: None,
3434 allowed_lints: vec![],
3435 }],
3436 };
3437
3438 let mut checker = TypeChecker::new();
3439 let result = checker.check_program(&program);
3440 assert!(
3441 result.is_err(),
3442 "Should reject: then branch pushes 1 value, else pushes 2"
3443 );
3444 }
3445
3446 #[test]
3447 fn test_pollution_both_branches_extra() {
3448 let program = Program {
3452 includes: vec![],
3453 unions: vec![],
3454 words: vec![WordDef {
3455 name: "test".to_string(),
3456 effect: Some(Effect::new(
3457 StackType::singleton(Type::Bool),
3458 StackType::singleton(Type::Int),
3459 )),
3460 body: vec![Statement::If {
3461 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3462 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3463 }],
3464 source: None,
3465 allowed_lints: vec![],
3466 }],
3467 };
3468
3469 let mut checker = TypeChecker::new();
3470 let result = checker.check_program(&program);
3471 assert!(
3472 result.is_err(),
3473 "Should reject: both branches push 2 values, but declared output is 1"
3474 );
3475 }
3476
3477 #[test]
3478 fn test_pollution_branch_consumes_extra() {
3479 let program = Program {
3483 includes: vec![],
3484 unions: vec![],
3485 words: vec![WordDef {
3486 name: "test".to_string(),
3487 effect: Some(Effect::new(
3488 StackType::Empty.push(Type::Bool).push(Type::Int),
3489 StackType::singleton(Type::Int),
3490 )),
3491 body: vec![Statement::If {
3492 then_branch: vec![
3493 Statement::WordCall {
3494 name: "drop".to_string(),
3495 span: None,
3496 },
3497 Statement::WordCall {
3498 name: "drop".to_string(),
3499 span: None,
3500 },
3501 Statement::IntLiteral(1),
3502 ],
3503 else_branch: Some(vec![]),
3504 }],
3505 source: None,
3506 allowed_lints: vec![],
3507 }],
3508 };
3509
3510 let mut checker = TypeChecker::new();
3511 let result = checker.check_program(&program);
3512 assert!(
3513 result.is_err(),
3514 "Should reject: then branch consumes Bool (should only have Int after if)"
3515 );
3516 }
3517
3518 #[test]
3519 fn test_pollution_quotation_wrong_arity_output() {
3520 let program = Program {
3524 includes: vec![],
3525 unions: vec![],
3526 words: vec![WordDef {
3527 name: "test".to_string(),
3528 effect: Some(Effect::new(
3529 StackType::singleton(Type::Int),
3530 StackType::singleton(Type::Int),
3531 )),
3532 body: vec![
3533 Statement::Quotation {
3534 span: None,
3535 id: 0,
3536 body: vec![Statement::WordCall {
3537 name: "dup".to_string(),
3538 span: None,
3539 }],
3540 },
3541 Statement::WordCall {
3542 name: "call".to_string(),
3543 span: None,
3544 },
3545 ],
3546 source: None,
3547 allowed_lints: vec![],
3548 }],
3549 };
3550
3551 let mut checker = TypeChecker::new();
3552 let result = checker.check_program(&program);
3553 assert!(
3554 result.is_err(),
3555 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3556 );
3557 }
3558
3559 #[test]
3560 fn test_pollution_quotation_wrong_arity_input() {
3561 let program = Program {
3565 includes: vec![],
3566 unions: vec![],
3567 words: vec![WordDef {
3568 name: "test".to_string(),
3569 effect: Some(Effect::new(
3570 StackType::singleton(Type::Int),
3571 StackType::singleton(Type::Int),
3572 )),
3573 body: vec![
3574 Statement::Quotation {
3575 span: None,
3576 id: 0,
3577 body: vec![
3578 Statement::WordCall {
3579 name: "drop".to_string(),
3580 span: None,
3581 },
3582 Statement::WordCall {
3583 name: "drop".to_string(),
3584 span: None,
3585 },
3586 Statement::IntLiteral(42),
3587 ],
3588 },
3589 Statement::WordCall {
3590 name: "call".to_string(),
3591 span: None,
3592 },
3593 ],
3594 source: None,
3595 allowed_lints: vec![],
3596 }],
3597 };
3598
3599 let mut checker = TypeChecker::new();
3600 let result = checker.check_program(&program);
3601 assert!(
3602 result.is_err(),
3603 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3604 );
3605 }
3606
3607 #[test]
3608 fn test_missing_effect_provides_helpful_error() {
3609 let program = Program {
3612 includes: vec![],
3613 unions: vec![],
3614 words: vec![WordDef {
3615 name: "myword".to_string(),
3616 effect: None, body: vec![Statement::IntLiteral(42)],
3618 source: None,
3619 allowed_lints: vec![],
3620 }],
3621 };
3622
3623 let mut checker = TypeChecker::new();
3624 let result = checker.check_program(&program);
3625 assert!(result.is_err());
3626 let err = result.unwrap_err();
3627 assert!(err.contains("myword"), "Error should mention word name");
3628 assert!(
3629 err.contains("stack effect"),
3630 "Error should mention stack effect"
3631 );
3632 }
3633
3634 #[test]
3635 fn test_valid_effect_exact_match() {
3636 let program = Program {
3639 includes: vec![],
3640 unions: vec![],
3641 words: vec![WordDef {
3642 name: "test".to_string(),
3643 effect: Some(Effect::new(
3644 StackType::Empty.push(Type::Int).push(Type::Int),
3645 StackType::singleton(Type::Int),
3646 )),
3647 body: vec![Statement::WordCall {
3648 name: "i.add".to_string(),
3649 span: None,
3650 }],
3651 source: None,
3652 allowed_lints: vec![],
3653 }],
3654 };
3655
3656 let mut checker = TypeChecker::new();
3657 let result = checker.check_program(&program);
3658 assert!(result.is_ok(), "Should accept: effect matches exactly");
3659 }
3660
3661 #[test]
3662 fn test_valid_polymorphic_passthrough() {
3663 let program = Program {
3666 includes: vec![],
3667 unions: vec![],
3668 words: vec![WordDef {
3669 name: "test".to_string(),
3670 effect: Some(Effect::new(
3671 StackType::Cons {
3672 rest: Box::new(StackType::RowVar("rest".to_string())),
3673 top: Type::Var("a".to_string()),
3674 },
3675 StackType::Cons {
3676 rest: Box::new(StackType::RowVar("rest".to_string())),
3677 top: Type::Var("a".to_string()),
3678 },
3679 )),
3680 body: vec![], source: None,
3682 allowed_lints: vec![],
3683 }],
3684 };
3685
3686 let mut checker = TypeChecker::new();
3687 let result = checker.check_program(&program);
3688 assert!(result.is_ok(), "Should accept: polymorphic identity");
3689 }
3690
3691 #[test]
3697 fn test_closure_basic_capture() {
3698 let program = Program {
3704 includes: vec![],
3705 unions: vec![],
3706 words: vec![WordDef {
3707 name: "make-adder".to_string(),
3708 effect: Some(Effect::new(
3709 StackType::singleton(Type::Int),
3710 StackType::singleton(Type::Closure {
3711 effect: Box::new(Effect::new(
3712 StackType::RowVar("r".to_string()).push(Type::Int),
3713 StackType::RowVar("r".to_string()).push(Type::Int),
3714 )),
3715 captures: vec![Type::Int], }),
3717 )),
3718 body: vec![Statement::Quotation {
3719 span: None,
3720 id: 0,
3721 body: vec![Statement::WordCall {
3722 name: "i.add".to_string(),
3723 span: None,
3724 }],
3725 }],
3726 source: None,
3727 allowed_lints: vec![],
3728 }],
3729 };
3730
3731 let mut checker = TypeChecker::new();
3732 let result = checker.check_program(&program);
3733 assert!(
3734 result.is_ok(),
3735 "Basic closure capture should work: {:?}",
3736 result.err()
3737 );
3738 }
3739
3740 #[test]
3741 fn test_closure_nested_two_levels() {
3742 let program = Program {
3747 includes: vec![],
3748 unions: vec![],
3749 words: vec![WordDef {
3750 name: "outer".to_string(),
3751 effect: Some(Effect::new(
3752 StackType::Empty,
3753 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3754 StackType::RowVar("r".to_string()),
3755 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3756 Effect::new(
3757 StackType::RowVar("s".to_string()).push(Type::Int),
3758 StackType::RowVar("s".to_string()).push(Type::Int),
3759 ),
3760 ))),
3761 )))),
3762 )),
3763 body: vec![Statement::Quotation {
3764 span: None,
3765 id: 0,
3766 body: vec![Statement::Quotation {
3767 span: None,
3768 id: 1,
3769 body: vec![
3770 Statement::IntLiteral(1),
3771 Statement::WordCall {
3772 name: "i.add".to_string(),
3773 span: None,
3774 },
3775 ],
3776 }],
3777 }],
3778 source: None,
3779 allowed_lints: vec![],
3780 }],
3781 };
3782
3783 let mut checker = TypeChecker::new();
3784 let result = checker.check_program(&program);
3785 assert!(
3786 result.is_ok(),
3787 "Two-level nested quotations should work: {:?}",
3788 result.err()
3789 );
3790 }
3791
3792 #[test]
3793 fn test_closure_nested_three_levels() {
3794 let inner_effect = Effect::new(
3798 StackType::RowVar("a".to_string()).push(Type::Int),
3799 StackType::RowVar("a".to_string()).push(Type::Int),
3800 );
3801 let middle_effect = Effect::new(
3802 StackType::RowVar("b".to_string()),
3803 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3804 );
3805 let outer_effect = Effect::new(
3806 StackType::RowVar("c".to_string()),
3807 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3808 );
3809
3810 let program = Program {
3811 includes: vec![],
3812 unions: vec![],
3813 words: vec![WordDef {
3814 name: "deep".to_string(),
3815 effect: Some(Effect::new(
3816 StackType::Empty,
3817 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3818 )),
3819 body: vec![Statement::Quotation {
3820 span: None,
3821 id: 0,
3822 body: vec![Statement::Quotation {
3823 span: None,
3824 id: 1,
3825 body: vec![Statement::Quotation {
3826 span: None,
3827 id: 2,
3828 body: vec![
3829 Statement::IntLiteral(1),
3830 Statement::WordCall {
3831 name: "i.add".to_string(),
3832 span: None,
3833 },
3834 ],
3835 }],
3836 }],
3837 }],
3838 source: None,
3839 allowed_lints: vec![],
3840 }],
3841 };
3842
3843 let mut checker = TypeChecker::new();
3844 let result = checker.check_program(&program);
3845 assert!(
3846 result.is_ok(),
3847 "Three-level nested quotations should work: {:?}",
3848 result.err()
3849 );
3850 }
3851
3852 #[test]
3853 fn test_closure_use_after_creation() {
3854 let adder_type = Type::Closure {
3860 effect: Box::new(Effect::new(
3861 StackType::RowVar("r".to_string()).push(Type::Int),
3862 StackType::RowVar("r".to_string()).push(Type::Int),
3863 )),
3864 captures: vec![Type::Int],
3865 };
3866
3867 let program = Program {
3868 includes: vec![],
3869 unions: vec![],
3870 words: vec![
3871 WordDef {
3872 name: "make-adder".to_string(),
3873 effect: Some(Effect::new(
3874 StackType::singleton(Type::Int),
3875 StackType::singleton(adder_type.clone()),
3876 )),
3877 body: vec![Statement::Quotation {
3878 span: None,
3879 id: 0,
3880 body: vec![Statement::WordCall {
3881 name: "i.add".to_string(),
3882 span: None,
3883 }],
3884 }],
3885 source: None,
3886 allowed_lints: vec![],
3887 },
3888 WordDef {
3889 name: "use-adder".to_string(),
3890 effect: Some(Effect::new(
3891 StackType::Empty,
3892 StackType::singleton(Type::Int),
3893 )),
3894 body: vec![
3895 Statement::IntLiteral(5),
3896 Statement::WordCall {
3897 name: "make-adder".to_string(),
3898 span: None,
3899 },
3900 Statement::IntLiteral(10),
3901 Statement::WordCall {
3902 name: "swap".to_string(),
3903 span: None,
3904 },
3905 Statement::WordCall {
3906 name: "call".to_string(),
3907 span: None,
3908 },
3909 ],
3910 source: None,
3911 allowed_lints: vec![],
3912 },
3913 ],
3914 };
3915
3916 let mut checker = TypeChecker::new();
3917 let result = checker.check_program(&program);
3918 assert!(
3919 result.is_ok(),
3920 "Closure usage after creation should work: {:?}",
3921 result.err()
3922 );
3923 }
3924
3925 #[test]
3926 fn test_closure_wrong_call_type() {
3927 let adder_type = Type::Closure {
3931 effect: Box::new(Effect::new(
3932 StackType::RowVar("r".to_string()).push(Type::Int),
3933 StackType::RowVar("r".to_string()).push(Type::Int),
3934 )),
3935 captures: vec![Type::Int],
3936 };
3937
3938 let program = Program {
3939 includes: vec![],
3940 unions: vec![],
3941 words: vec![
3942 WordDef {
3943 name: "make-adder".to_string(),
3944 effect: Some(Effect::new(
3945 StackType::singleton(Type::Int),
3946 StackType::singleton(adder_type.clone()),
3947 )),
3948 body: vec![Statement::Quotation {
3949 span: None,
3950 id: 0,
3951 body: vec![Statement::WordCall {
3952 name: "i.add".to_string(),
3953 span: None,
3954 }],
3955 }],
3956 source: None,
3957 allowed_lints: vec![],
3958 },
3959 WordDef {
3960 name: "bad-use".to_string(),
3961 effect: Some(Effect::new(
3962 StackType::Empty,
3963 StackType::singleton(Type::Int),
3964 )),
3965 body: vec![
3966 Statement::IntLiteral(5),
3967 Statement::WordCall {
3968 name: "make-adder".to_string(),
3969 span: None,
3970 },
3971 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
3973 name: "swap".to_string(),
3974 span: None,
3975 },
3976 Statement::WordCall {
3977 name: "call".to_string(),
3978 span: None,
3979 },
3980 ],
3981 source: None,
3982 allowed_lints: vec![],
3983 },
3984 ],
3985 };
3986
3987 let mut checker = TypeChecker::new();
3988 let result = checker.check_program(&program);
3989 assert!(
3990 result.is_err(),
3991 "Calling Int closure with String should fail"
3992 );
3993 }
3994
3995 #[test]
3996 fn test_closure_multiple_captures() {
3997 let program = Program {
4004 includes: vec![],
4005 unions: vec![],
4006 words: vec![WordDef {
4007 name: "make-between".to_string(),
4008 effect: Some(Effect::new(
4009 StackType::Empty.push(Type::Int).push(Type::Int),
4010 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4011 StackType::RowVar("r".to_string()).push(Type::Int),
4012 StackType::RowVar("r".to_string()).push(Type::Bool),
4013 )))),
4014 )),
4015 body: vec![Statement::Quotation {
4016 span: None,
4017 id: 0,
4018 body: vec![
4019 Statement::WordCall {
4021 name: "i.>=".to_string(),
4022 span: None,
4023 },
4024 ],
4026 }],
4027 source: None,
4028 allowed_lints: vec![],
4029 }],
4030 };
4031
4032 let mut checker = TypeChecker::new();
4033 let result = checker.check_program(&program);
4034 assert!(
4038 result.is_ok() || result.is_err(),
4039 "Multiple captures should be handled (pass or fail gracefully)"
4040 );
4041 }
4042
4043 #[test]
4044 fn test_quotation_type_preserved_through_word() {
4045 let quot_type = Type::Quotation(Box::new(Effect::new(
4048 StackType::RowVar("r".to_string()).push(Type::Int),
4049 StackType::RowVar("r".to_string()).push(Type::Int),
4050 )));
4051
4052 let program = Program {
4053 includes: vec![],
4054 unions: vec![],
4055 words: vec![WordDef {
4056 name: "identity-quot".to_string(),
4057 effect: Some(Effect::new(
4058 StackType::singleton(quot_type.clone()),
4059 StackType::singleton(quot_type.clone()),
4060 )),
4061 body: vec![], source: None,
4063 allowed_lints: vec![],
4064 }],
4065 };
4066
4067 let mut checker = TypeChecker::new();
4068 let result = checker.check_program(&program);
4069 assert!(
4070 result.is_ok(),
4071 "Quotation type should be preserved through identity word: {:?}",
4072 result.err()
4073 );
4074 }
4075
4076 #[test]
4077 fn test_closure_captures_value_for_inner_quotation() {
4078 let closure_effect = Effect::new(
4084 StackType::RowVar("r".to_string()).push(Type::Int),
4085 StackType::RowVar("r".to_string()).push(Type::Int),
4086 );
4087
4088 let program = Program {
4089 includes: vec![],
4090 unions: vec![],
4091 words: vec![WordDef {
4092 name: "make-inner-adder".to_string(),
4093 effect: Some(Effect::new(
4094 StackType::singleton(Type::Int),
4095 StackType::singleton(Type::Closure {
4096 effect: Box::new(closure_effect),
4097 captures: vec![Type::Int],
4098 }),
4099 )),
4100 body: vec![Statement::Quotation {
4101 span: None,
4102 id: 0,
4103 body: vec![
4104 Statement::WordCall {
4106 name: "i.add".to_string(),
4107 span: None,
4108 },
4109 ],
4110 }],
4111 source: None,
4112 allowed_lints: vec![],
4113 }],
4114 };
4115
4116 let mut checker = TypeChecker::new();
4117 let result = checker.check_program(&program);
4118 assert!(
4119 result.is_ok(),
4120 "Closure with capture for inner work should pass: {:?}",
4121 result.err()
4122 );
4123 }
4124}