1use crate::ast::{Program, Statement, WordDef};
7use crate::builtins::builtin_signature;
8use crate::call_graph::CallGraph;
9use crate::capture_analysis::calculate_captures;
10use crate::types::{
11 Effect, SideEffect, StackType, Type, UnionTypeInfo, VariantFieldInfo, VariantInfo,
12};
13use crate::unification::{Subst, unify_stacks, unify_types};
14use std::collections::HashMap;
15
16pub struct TypeChecker {
17 env: HashMap<String, Effect>,
19 unions: HashMap<String, UnionTypeInfo>,
22 fresh_counter: std::cell::Cell<usize>,
24 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
28 expected_quotation_type: std::cell::RefCell<Option<Type>>,
31 current_word: std::cell::RefCell<Option<String>>,
34 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
38 call_graph: Option<CallGraph>,
41}
42
43impl TypeChecker {
44 pub fn new() -> Self {
45 TypeChecker {
46 env: HashMap::new(),
47 unions: HashMap::new(),
48 fresh_counter: std::cell::Cell::new(0),
49 quotation_types: std::cell::RefCell::new(HashMap::new()),
50 expected_quotation_type: std::cell::RefCell::new(None),
51 current_word: std::cell::RefCell::new(None),
52 statement_top_types: std::cell::RefCell::new(HashMap::new()),
53 call_graph: None,
54 }
55 }
56
57 pub fn set_call_graph(&mut self, call_graph: CallGraph) {
62 self.call_graph = Some(call_graph);
63 }
64
65 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
67 self.unions.get(name)
68 }
69
70 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
72 &self.unions
73 }
74
75 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
79 for (union_name, union_info) in &self.unions {
80 for variant in &union_info.variants {
81 if variant.name == variant_name {
82 return Some((union_name.as_str(), variant));
83 }
84 }
85 }
86 None
87 }
88
89 pub fn register_external_words(&mut self, words: &[(&str, &Effect)]) {
93 for (name, effect) in words {
94 self.env.insert(name.to_string(), (*effect).clone());
95 }
96 }
97
98 pub fn register_external_unions(&mut self, union_names: &[&str]) {
104 for name in union_names {
105 self.unions.insert(
108 name.to_string(),
109 UnionTypeInfo {
110 name: name.to_string(),
111 variants: vec![],
112 },
113 );
114 }
115 }
116
117 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
123 self.quotation_types.replace(HashMap::new())
124 }
125
126 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
129 self.statement_top_types.replace(HashMap::new())
130 }
131
132 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
135 match stack {
136 StackType::Cons { top, .. } => match top {
137 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
138 _ => None,
139 },
140 _ => None,
141 }
142 }
143
144 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
146 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
147 self.statement_top_types
148 .borrow_mut()
149 .insert((word_name.to_string(), stmt_index), top_type);
150 }
151 }
152
153 fn fresh_var(&self, prefix: &str) -> String {
155 let n = self.fresh_counter.get();
156 self.fresh_counter.set(n + 1);
157 format!("{}${}", prefix, n)
158 }
159
160 fn freshen_effect(&self, effect: &Effect) -> Effect {
162 let mut type_map = HashMap::new();
163 let mut row_map = HashMap::new();
164
165 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
166 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
167
168 let fresh_effects = effect
170 .effects
171 .iter()
172 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
173 .collect();
174
175 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
176 }
177
178 fn freshen_side_effect(
179 &self,
180 effect: &SideEffect,
181 type_map: &mut HashMap<String, String>,
182 row_map: &mut HashMap<String, String>,
183 ) -> SideEffect {
184 match effect {
185 SideEffect::Yield(ty) => {
186 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
187 }
188 }
189 }
190
191 fn freshen_stack(
192 &self,
193 stack: &StackType,
194 type_map: &mut HashMap<String, String>,
195 row_map: &mut HashMap<String, String>,
196 ) -> StackType {
197 match stack {
198 StackType::Empty => StackType::Empty,
199 StackType::RowVar(name) => {
200 let fresh_name = row_map
201 .entry(name.clone())
202 .or_insert_with(|| self.fresh_var(name));
203 StackType::RowVar(fresh_name.clone())
204 }
205 StackType::Cons { rest, top } => {
206 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
207 let fresh_top = self.freshen_type(top, type_map, row_map);
208 StackType::Cons {
209 rest: Box::new(fresh_rest),
210 top: fresh_top,
211 }
212 }
213 }
214 }
215
216 fn freshen_type(
217 &self,
218 ty: &Type,
219 type_map: &mut HashMap<String, String>,
220 row_map: &mut HashMap<String, String>,
221 ) -> Type {
222 match ty {
223 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
224 ty.clone()
225 }
226 Type::Var(name) => {
227 let fresh_name = type_map
228 .entry(name.clone())
229 .or_insert_with(|| self.fresh_var(name));
230 Type::Var(fresh_name.clone())
231 }
232 Type::Quotation(effect) => {
233 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
234 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
235 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
236 }
237 Type::Closure { effect, captures } => {
238 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
239 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
240 let fresh_captures = captures
241 .iter()
242 .map(|t| self.freshen_type(t, type_map, row_map))
243 .collect();
244 Type::Closure {
245 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
246 captures: fresh_captures,
247 }
248 }
249 Type::Union(name) => Type::Union(name.clone()),
251 }
252 }
253
254 fn parse_type_name(&self, name: &str) -> Type {
258 match name {
259 "Int" => Type::Int,
260 "Float" => Type::Float,
261 "Bool" => Type::Bool,
262 "String" => Type::String,
263 "Channel" => Type::Channel,
264 other => Type::Union(other.to_string()),
266 }
267 }
268
269 fn is_valid_type_name(&self, name: &str) -> bool {
274 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
275 || self.unions.contains_key(name)
276 }
277
278 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
282 for union_def in &program.unions {
283 for variant in &union_def.variants {
284 for field in &variant.fields {
285 if !self.is_valid_type_name(&field.type_name) {
286 return Err(format!(
287 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
288 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
289 field.type_name, field.name, variant.name, union_def.name
290 ));
291 }
292 }
293 }
294 }
295 Ok(())
296 }
297
298 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
300 for union_def in &program.unions {
302 let variants = union_def
303 .variants
304 .iter()
305 .map(|v| VariantInfo {
306 name: v.name.clone(),
307 fields: v
308 .fields
309 .iter()
310 .map(|f| VariantFieldInfo {
311 name: f.name.clone(),
312 field_type: self.parse_type_name(&f.type_name),
313 })
314 .collect(),
315 })
316 .collect();
317
318 self.unions.insert(
319 union_def.name.clone(),
320 UnionTypeInfo {
321 name: union_def.name.clone(),
322 variants,
323 },
324 );
325 }
326
327 self.validate_union_field_types(program)?;
329
330 for word in &program.words {
333 if let Some(effect) = &word.effect {
334 self.env.insert(word.name.clone(), effect.clone());
335 } else {
336 return Err(format!(
337 "Word '{}' is missing a stack effect declaration.\n\
338 All words must declare their stack effect, e.g.: : {} ( -- ) ... ;",
339 word.name, word.name
340 ));
341 }
342 }
343
344 for word in &program.words {
346 self.check_word(word)?;
347 }
348
349 Ok(())
350 }
351
352 fn check_word(&self, word: &WordDef) -> Result<(), String> {
354 *self.current_word.borrow_mut() = Some(word.name.clone());
356
357 let declared_effect = word.effect.as_ref().expect("word must have effect");
359
360 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
363 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
364 {
365 *self.expected_quotation_type.borrow_mut() = Some(top_type);
366 }
367
368 let (result_stack, _subst, inferred_effects) =
370 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
371
372 *self.expected_quotation_type.borrow_mut() = None;
374
375 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
377 format!(
378 "Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
379 word.name, declared_effect.outputs, result_stack, e
380 )
381 })?;
382
383 for inferred in &inferred_effects {
387 if !self.effect_matches_any(inferred, &declared_effect.effects) {
388 return Err(format!(
389 "Word '{}': body produces effect '{}' but no matching effect is declared.\n\
390 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
391 word.name, inferred
392 ));
393 }
394 }
395
396 for declared in &declared_effect.effects {
399 if !self.effect_matches_any(declared, &inferred_effects) {
400 return Err(format!(
401 "Word '{}': declares effect '{}' but body doesn't produce it.\n\
402 Hint: Remove the effect declaration or ensure the body uses yield.",
403 word.name, declared
404 ));
405 }
406 }
407
408 *self.current_word.borrow_mut() = None;
410
411 Ok(())
412 }
413
414 fn infer_statements_from(
421 &self,
422 statements: &[Statement],
423 start_stack: &StackType,
424 capture_stmt_types: bool,
425 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
426 let mut current_stack = start_stack.clone();
427 let mut accumulated_subst = Subst::empty();
428 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
429 let mut skip_next = false;
430
431 for (i, stmt) in statements.iter().enumerate() {
432 if skip_next {
434 skip_next = false;
435 continue;
436 }
437
438 if let Statement::IntLiteral(n) = stmt
441 && let Some(Statement::WordCall {
442 name: next_word, ..
443 }) = statements.get(i + 1)
444 {
445 if next_word == "pick" {
446 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
447 current_stack = new_stack;
448 accumulated_subst = accumulated_subst.compose(&subst);
449 skip_next = true; continue;
451 } else if next_word == "roll" {
452 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
453 current_stack = new_stack;
454 accumulated_subst = accumulated_subst.compose(&subst);
455 skip_next = true; continue;
457 }
458 }
459
460 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
463 let saved = self.expected_quotation_type.borrow().clone();
465
466 if let Some(Statement::WordCall {
468 name: next_word, ..
469 }) = statements.get(i + 1)
470 {
471 if let Some(next_effect) = self.lookup_word_effect(next_word) {
473 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
476 && matches!(quot_type, Type::Quotation(_))
477 {
478 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
479 }
480 }
481 }
482 Some(saved)
483 } else {
484 None
485 };
486
487 if capture_stmt_types && let Some(word_name) = self.current_word.borrow().as_ref() {
491 self.capture_statement_type(word_name, i, ¤t_stack);
492 }
493
494 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
495 current_stack = new_stack;
496 accumulated_subst = accumulated_subst.compose(&subst);
497
498 for effect in effects {
500 if !accumulated_effects.contains(&effect) {
501 accumulated_effects.push(effect);
502 }
503 }
504
505 if let Some(saved) = saved_expected_type {
507 *self.expected_quotation_type.borrow_mut() = saved;
508 }
509 }
510
511 Ok((current_stack, accumulated_subst, accumulated_effects))
512 }
513
514 fn handle_literal_pick(
525 &self,
526 n: i64,
527 current_stack: StackType,
528 ) -> Result<(StackType, Subst), String> {
529 if n < 0 {
530 return Err(format!("pick: index must be non-negative, got {}", n));
531 }
532
533 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
535
536 Ok((current_stack.push(type_at_n), Subst::empty()))
538 }
539
540 fn handle_literal_roll(
551 &self,
552 n: i64,
553 current_stack: StackType,
554 ) -> Result<(StackType, Subst), String> {
555 if n < 0 {
556 return Err(format!("roll: index must be non-negative, got {}", n));
557 }
558
559 self.rotate_type_to_top(current_stack, n as usize)
564 }
565
566 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
568 let mut current = stack;
569 let mut pos = 0;
570
571 loop {
572 match current {
573 StackType::Cons { rest, top } => {
574 if pos == n {
575 return Ok(top.clone());
576 }
577 pos += 1;
578 current = rest;
579 }
580 StackType::RowVar(name) => {
581 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
591 return Ok(fresh_type);
592 }
593 StackType::Empty => {
594 return Err(format!(
595 "{}: stack underflow - position {} requested but stack has only {} concrete items",
596 op, n, pos
597 ));
598 }
599 }
600 }
601 }
602
603 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
605 if n == 0 {
606 return Ok((stack, Subst::empty()));
608 }
609
610 let mut types_above: Vec<Type> = Vec::new();
612 let mut current = stack;
613 let mut pos = 0;
614
615 loop {
617 match current {
618 StackType::Cons { rest, top } => {
619 if pos == n {
620 let mut result = *rest;
623 for ty in types_above.into_iter().rev() {
625 result = result.push(ty);
626 }
627 result = result.push(top);
629 return Ok((result, Subst::empty()));
630 }
631 types_above.push(top);
632 pos += 1;
633 current = *rest;
634 }
635 StackType::RowVar(name) => {
636 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
649
650 let mut result = StackType::RowVar(name.clone());
652 for ty in types_above.into_iter().rev() {
653 result = result.push(ty);
654 }
655 result = result.push(fresh_type);
656 return Ok((result, Subst::empty()));
657 }
658 StackType::Empty => {
659 return Err(format!(
660 "roll: stack underflow - position {} requested but stack has only {} items",
661 n, pos
662 ));
663 }
664 }
665 }
666 }
667
668 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
672 let start = StackType::RowVar("input".to_string());
673 let (result, subst, effects) = self.infer_statements_from(statements, &start, false)?;
675
676 let normalized_start = subst.apply_stack(&start);
679 let normalized_result = subst.apply_stack(&result);
680
681 Ok(Effect::with_effects(
682 normalized_start,
683 normalized_result,
684 effects,
685 ))
686 }
687
688 fn infer_match(
690 &self,
691 arms: &[crate::ast::MatchArm],
692 current_stack: StackType,
693 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
694 if arms.is_empty() {
695 return Err("match expression must have at least one arm".to_string());
696 }
697
698 let (stack_after_match, _matched_type) =
700 self.pop_type(¤t_stack, "match expression")?;
701
702 let mut arm_results: Vec<StackType> = Vec::new();
704 let mut combined_subst = Subst::empty();
705 let mut merged_effects: Vec<SideEffect> = Vec::new();
706
707 for arm in arms {
708 let variant_name = match &arm.pattern {
710 crate::ast::Pattern::Variant(name) => name.as_str(),
711 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
712 };
713
714 let (_union_name, variant_info) = self
716 .find_variant(variant_name)
717 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
718
719 let arm_stack = self.push_variant_fields(
721 &stack_after_match,
722 &arm.pattern,
723 variant_info,
724 variant_name,
725 )?;
726
727 let (arm_result, arm_subst, arm_effects) =
730 self.infer_statements_from(&arm.body, &arm_stack, false)?;
731
732 combined_subst = combined_subst.compose(&arm_subst);
733 arm_results.push(arm_result);
734
735 for effect in arm_effects {
737 if !merged_effects.contains(&effect) {
738 merged_effects.push(effect);
739 }
740 }
741 }
742
743 let mut final_result = arm_results[0].clone();
745 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
746 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
747 format!(
748 "match arms have incompatible stack effects:\n\
749 \x20 arm 0 produces: {}\n\
750 \x20 arm {} produces: {}\n\
751 \x20 All match arms must produce the same stack shape.\n\
752 \x20 Error: {}",
753 final_result, i, arm_result, e
754 )
755 })?;
756 combined_subst = combined_subst.compose(&arm_subst);
757 final_result = arm_subst.apply_stack(&final_result);
758 }
759
760 Ok((final_result, combined_subst, merged_effects))
761 }
762
763 fn push_variant_fields(
765 &self,
766 stack: &StackType,
767 pattern: &crate::ast::Pattern,
768 variant_info: &VariantInfo,
769 variant_name: &str,
770 ) -> Result<StackType, String> {
771 let mut arm_stack = stack.clone();
772 match pattern {
773 crate::ast::Pattern::Variant(_) => {
774 for field in &variant_info.fields {
776 arm_stack = arm_stack.push(field.field_type.clone());
777 }
778 }
779 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
780 for binding in bindings {
782 let field = variant_info
783 .fields
784 .iter()
785 .find(|f| &f.name == binding)
786 .ok_or_else(|| {
787 let available: Vec<_> = variant_info
788 .fields
789 .iter()
790 .map(|f| f.name.as_str())
791 .collect();
792 format!(
793 "Unknown field '{}' in pattern for variant '{}'.\n\
794 Available fields: {}",
795 binding,
796 variant_name,
797 available.join(", ")
798 )
799 })?;
800 arm_stack = arm_stack.push(field.field_type.clone());
801 }
802 }
803 }
804 Ok(arm_stack)
805 }
806
807 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
828 let Some(current_word) = self.current_word.borrow().as_ref().cloned() else {
829 return false;
830 };
831 let Some(Statement::WordCall { name, .. }) = statements.last() else {
832 return false;
833 };
834
835 if name == ¤t_word {
837 return true;
838 }
839
840 if let Some(ref graph) = self.call_graph
842 && graph.are_mutually_recursive(¤t_word, name)
843 {
844 return true;
845 }
846
847 false
848 }
849
850 fn infer_if(
852 &self,
853 then_branch: &[Statement],
854 else_branch: &Option<Vec<Statement>>,
855 current_stack: StackType,
856 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
857 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
859
860 let cond_subst = unify_stacks(
862 &StackType::singleton(Type::Bool),
863 &StackType::singleton(cond_type),
864 )
865 .map_err(|e| format!("if condition must be Bool: {}", e))?;
866
867 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
868
869 let then_diverges = self.is_divergent_branch(then_branch);
871 let else_diverges = else_branch
872 .as_ref()
873 .map(|stmts| self.is_divergent_branch(stmts))
874 .unwrap_or(false);
875
876 let (then_result, then_subst, then_effects) =
879 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
880
881 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
883 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
884 } else {
885 (stack_after_cond.clone(), Subst::empty(), vec![])
886 };
887
888 let mut merged_effects = then_effects;
890 for effect in else_effects {
891 if !merged_effects.contains(&effect) {
892 merged_effects.push(effect);
893 }
894 }
895
896 let (result, branch_subst) = if then_diverges && !else_diverges {
902 (else_result, Subst::empty())
904 } else if else_diverges && !then_diverges {
905 (then_result, Subst::empty())
907 } else {
908 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
910 format!(
911 "if/else branches have incompatible stack effects:\n\
912 \x20 then branch produces: {}\n\
913 \x20 else branch produces: {}\n\
914 \x20 Both branches of an if/else must produce the same stack shape.\n\
915 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
916 \x20 Error: {}",
917 then_result, else_result, e
918 )
919 })?;
920 (branch_subst.apply_stack(&then_result), branch_subst)
921 };
922
923 let total_subst = cond_subst
925 .compose(&then_subst)
926 .compose(&else_subst)
927 .compose(&branch_subst);
928 Ok((result, total_subst, merged_effects))
929 }
930
931 fn infer_quotation(
934 &self,
935 id: usize,
936 body: &[Statement],
937 current_stack: StackType,
938 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
939 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
942 *self.expected_quotation_type.borrow_mut() = None;
943
944 let body_effect = self.infer_statements(body)?;
946
947 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
949
950 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
952
953 self.quotation_types
955 .borrow_mut()
956 .insert(id, quot_type.clone());
957
958 let result_stack = match "_type {
960 Type::Quotation(_) => {
961 current_stack.push(quot_type)
963 }
964 Type::Closure { captures, .. } => {
965 let mut stack = current_stack.clone();
967 for _ in 0..captures.len() {
968 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
969 stack = new_stack;
970 }
971 stack.push(quot_type)
972 }
973 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
974 };
975
976 Ok((result_stack, Subst::empty(), vec![]))
980 }
981
982 fn infer_word_call(
984 &self,
985 name: &str,
986 current_stack: StackType,
987 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
988 if name == "call" {
991 return self.infer_call(current_stack);
992 }
993
994 let effect = self
996 .lookup_word_effect(name)
997 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
998
999 let fresh_effect = self.freshen_effect(&effect);
1001
1002 let adjusted_stack = if name == "strand.spawn" {
1004 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1005 } else {
1006 current_stack
1007 };
1008
1009 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name)?;
1011
1012 let propagated_effects = fresh_effect.effects.clone();
1016
1017 Ok((result_stack, subst, propagated_effects))
1018 }
1019
1020 fn infer_call(
1026 &self,
1027 current_stack: StackType,
1028 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1029 let (remaining_stack, quot_type) = current_stack
1031 .clone()
1032 .pop()
1033 .ok_or_else(|| "call: stack underflow - expected quotation on stack".to_string())?;
1034
1035 let quot_effect = match "_type {
1037 Type::Quotation(effect) => (**effect).clone(),
1038 Type::Closure { effect, .. } => (**effect).clone(),
1039 Type::Var(_) => {
1040 let effect = self
1043 .lookup_word_effect("call")
1044 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1045 let fresh_effect = self.freshen_effect(&effect);
1046 let (result_stack, subst) =
1047 self.apply_effect(&fresh_effect, current_stack, "call")?;
1048 return Ok((result_stack, subst, vec![]));
1049 }
1050 _ => {
1051 return Err(format!(
1052 "call: expected quotation or closure on stack, got {}",
1053 quot_type
1054 ));
1055 }
1056 };
1057
1058 if quot_effect.has_yield() {
1060 return Err("Cannot call quotation with Yield effect directly.\n\
1061 Quotations that yield values must be wrapped with `strand.weave`.\n\
1062 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1063 .to_string());
1064 }
1065
1066 let fresh_effect = self.freshen_effect("_effect);
1068
1069 let (result_stack, subst) = self.apply_effect(&fresh_effect, remaining_stack, "call")?;
1071
1072 let propagated_effects = fresh_effect.effects.clone();
1074
1075 Ok((result_stack, subst, propagated_effects))
1076 }
1077
1078 fn infer_statement(
1081 &self,
1082 statement: &Statement,
1083 current_stack: StackType,
1084 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1085 match statement {
1086 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1087 Statement::BoolLiteral(_) => {
1088 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1089 }
1090 Statement::StringLiteral(_) => {
1091 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1092 }
1093 Statement::FloatLiteral(_) => {
1094 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1095 }
1096 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1097 Statement::Match { arms } => self.infer_match(arms, current_stack),
1098 Statement::WordCall { name, .. } => self.infer_word_call(name, current_stack),
1099 Statement::If {
1100 then_branch,
1101 else_branch,
1102 } => self.infer_if(then_branch, else_branch, current_stack),
1103 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1104 }
1105 }
1106
1107 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1109 if let Some(effect) = builtin_signature(name) {
1111 return Some(effect);
1112 }
1113
1114 self.env.get(name).cloned()
1116 }
1117
1118 fn apply_effect(
1123 &self,
1124 effect: &Effect,
1125 current_stack: StackType,
1126 operation: &str,
1127 ) -> Result<(StackType, Subst), String> {
1128 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1136 let stack_concrete = Self::count_concrete_types(¤t_stack);
1137
1138 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1139 let is_rigid = row_var_name == "rest";
1152
1153 if is_rigid && effect_concrete > stack_concrete {
1154 let word_name = self.current_word.borrow().clone().unwrap_or_default();
1155 return Err(format!(
1156 "In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1157 word_name, operation, effect_concrete, stack_concrete
1158 ));
1159 }
1160 }
1161
1162 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1164 format!(
1165 "{}: stack type mismatch. Expected {}, got {}: {}",
1166 operation, effect.inputs, current_stack, e
1167 )
1168 })?;
1169
1170 let result_stack = subst.apply_stack(&effect.outputs);
1172
1173 Ok((result_stack, subst))
1174 }
1175
1176 fn count_concrete_types(stack: &StackType) -> usize {
1178 let mut count = 0;
1179 let mut current = stack;
1180 while let StackType::Cons { rest, top: _ } = current {
1181 count += 1;
1182 current = rest;
1183 }
1184 count
1185 }
1186
1187 fn get_row_var_base(stack: &StackType) -> Option<String> {
1189 let mut current = stack;
1190 while let StackType::Cons { rest, top: _ } = current {
1191 current = rest;
1192 }
1193 match current {
1194 StackType::RowVar(name) => Some(name.clone()),
1195 _ => None,
1196 }
1197 }
1198
1199 fn adjust_stack_for_spawn(
1204 &self,
1205 current_stack: StackType,
1206 spawn_effect: &Effect,
1207 ) -> Result<StackType, String> {
1208 let expected_quot_type = match &spawn_effect.inputs {
1211 StackType::Cons { top, rest: _ } => {
1212 if !matches!(top, Type::Quotation(_)) {
1213 return Ok(current_stack); }
1215 top
1216 }
1217 _ => return Ok(current_stack),
1218 };
1219
1220 let (rest_stack, actual_type) = match ¤t_stack {
1222 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1223 _ => return Ok(current_stack), };
1225
1226 if let Type::Quotation(actual_effect) = actual_type {
1228 if !matches!(actual_effect.inputs, StackType::Empty) {
1230 let expected_effect = match expected_quot_type {
1232 Type::Quotation(eff) => eff.as_ref(),
1233 _ => return Ok(current_stack),
1234 };
1235
1236 let captures = calculate_captures(actual_effect, expected_effect)?;
1238
1239 let closure_type = Type::Closure {
1241 effect: Box::new(expected_effect.clone()),
1242 captures: captures.clone(),
1243 };
1244
1245 let mut adjusted_stack = rest_stack;
1248 for _ in &captures {
1249 adjusted_stack = match adjusted_stack {
1250 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1251 _ => {
1252 return Err(format!(
1253 "strand.spawn: not enough values on stack to capture. Need {} values",
1254 captures.len()
1255 ));
1256 }
1257 };
1258 }
1259
1260 return Ok(adjusted_stack.push(closure_type));
1262 }
1263 }
1264
1265 Ok(current_stack)
1266 }
1267
1268 fn analyze_captures(
1294 &self,
1295 body_effect: &Effect,
1296 _current_stack: &StackType,
1297 ) -> Result<Type, String> {
1298 let expected = self.expected_quotation_type.borrow().clone();
1300
1301 match expected {
1302 Some(Type::Closure { effect, .. }) => {
1303 let captures = calculate_captures(body_effect, &effect)?;
1305 Ok(Type::Closure { effect, captures })
1306 }
1307 Some(Type::Quotation(expected_effect)) => {
1308 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1314 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1315
1316 if expected_is_empty && body_needs_inputs {
1317 let captures = calculate_captures(body_effect, &expected_effect)?;
1320 Ok(Type::Closure {
1321 effect: expected_effect,
1322 captures,
1323 })
1324 } else {
1325 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1331 let expected_quot = Type::Quotation(expected_effect.clone());
1332 unify_types(&body_quot, &expected_quot).map_err(|e| {
1333 format!(
1334 "quotation effect mismatch: expected {}, got {}: {}",
1335 expected_effect, body_effect, e
1336 )
1337 })?;
1338
1339 Ok(Type::Quotation(expected_effect))
1341 }
1342 }
1343 _ => {
1344 Ok(Type::Quotation(Box::new(body_effect.clone())))
1346 }
1347 }
1348 }
1349
1350 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1354 declared.iter().any(|decl| match (inferred, decl) {
1355 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1356 })
1357 }
1358
1359 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1361 match stack {
1362 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1363 StackType::Empty => Err(format!(
1364 "{}: stack underflow - expected value on stack but stack is empty",
1365 context
1366 )),
1367 StackType::RowVar(_) => {
1368 Err(format!(
1372 "{}: cannot pop from polymorphic stack without more type information",
1373 context
1374 ))
1375 }
1376 }
1377 }
1378}
1379
1380impl Default for TypeChecker {
1381 fn default() -> Self {
1382 Self::new()
1383 }
1384}
1385
1386#[cfg(test)]
1387mod tests {
1388 use super::*;
1389
1390 #[test]
1391 fn test_simple_literal() {
1392 let program = Program {
1393 includes: vec![],
1394 unions: vec![],
1395 words: vec![WordDef {
1396 name: "test".to_string(),
1397 effect: Some(Effect::new(
1398 StackType::Empty,
1399 StackType::singleton(Type::Int),
1400 )),
1401 body: vec![Statement::IntLiteral(42)],
1402 source: None,
1403 allowed_lints: vec![],
1404 }],
1405 };
1406
1407 let mut checker = TypeChecker::new();
1408 assert!(checker.check_program(&program).is_ok());
1409 }
1410
1411 #[test]
1412 fn test_simple_operation() {
1413 let program = Program {
1415 includes: vec![],
1416 unions: vec![],
1417 words: vec![WordDef {
1418 name: "test".to_string(),
1419 effect: Some(Effect::new(
1420 StackType::Empty.push(Type::Int).push(Type::Int),
1421 StackType::singleton(Type::Int),
1422 )),
1423 body: vec![Statement::WordCall {
1424 name: "i.add".to_string(),
1425 span: None,
1426 }],
1427 source: None,
1428 allowed_lints: vec![],
1429 }],
1430 };
1431
1432 let mut checker = TypeChecker::new();
1433 assert!(checker.check_program(&program).is_ok());
1434 }
1435
1436 #[test]
1437 fn test_type_mismatch() {
1438 let program = Program {
1440 includes: vec![],
1441 unions: vec![],
1442 words: vec![WordDef {
1443 name: "test".to_string(),
1444 effect: Some(Effect::new(
1445 StackType::singleton(Type::String),
1446 StackType::Empty,
1447 )),
1448 body: vec![
1449 Statement::IntLiteral(42), Statement::WordCall {
1451 name: "io.write-line".to_string(),
1452 span: None,
1453 },
1454 ],
1455 source: None,
1456 allowed_lints: vec![],
1457 }],
1458 };
1459
1460 let mut checker = TypeChecker::new();
1461 let result = checker.check_program(&program);
1462 assert!(result.is_err());
1463 assert!(result.unwrap_err().contains("Type mismatch"));
1464 }
1465
1466 #[test]
1467 fn test_polymorphic_dup() {
1468 let program = Program {
1470 includes: vec![],
1471 unions: vec![],
1472 words: vec![WordDef {
1473 name: "my-dup".to_string(),
1474 effect: Some(Effect::new(
1475 StackType::singleton(Type::Int),
1476 StackType::Empty.push(Type::Int).push(Type::Int),
1477 )),
1478 body: vec![Statement::WordCall {
1479 name: "dup".to_string(),
1480 span: None,
1481 }],
1482 source: None,
1483 allowed_lints: vec![],
1484 }],
1485 };
1486
1487 let mut checker = TypeChecker::new();
1488 assert!(checker.check_program(&program).is_ok());
1489 }
1490
1491 #[test]
1492 fn test_conditional_branches() {
1493 let program = Program {
1496 includes: vec![],
1497 unions: vec![],
1498 words: vec![WordDef {
1499 name: "test".to_string(),
1500 effect: Some(Effect::new(
1501 StackType::Empty.push(Type::Int).push(Type::Int),
1502 StackType::singleton(Type::String),
1503 )),
1504 body: vec![
1505 Statement::WordCall {
1506 name: "i.>".to_string(),
1507 span: None,
1508 },
1509 Statement::If {
1510 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1511 else_branch: Some(vec![Statement::StringLiteral(
1512 "not greater".to_string(),
1513 )]),
1514 },
1515 ],
1516 source: None,
1517 allowed_lints: vec![],
1518 }],
1519 };
1520
1521 let mut checker = TypeChecker::new();
1522 assert!(checker.check_program(&program).is_ok());
1523 }
1524
1525 #[test]
1526 fn test_mismatched_branches() {
1527 let program = Program {
1530 includes: vec![],
1531 unions: vec![],
1532 words: vec![WordDef {
1533 name: "test".to_string(),
1534 effect: Some(Effect::new(
1535 StackType::Empty,
1536 StackType::singleton(Type::Int),
1537 )),
1538 body: vec![
1539 Statement::BoolLiteral(true),
1540 Statement::If {
1541 then_branch: vec![Statement::IntLiteral(42)],
1542 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1543 },
1544 ],
1545 source: None,
1546 allowed_lints: vec![],
1547 }],
1548 };
1549
1550 let mut checker = TypeChecker::new();
1551 let result = checker.check_program(&program);
1552 assert!(result.is_err());
1553 assert!(result.unwrap_err().contains("incompatible"));
1554 }
1555
1556 #[test]
1557 fn test_user_defined_word_call() {
1558 let program = Program {
1561 includes: vec![],
1562 unions: vec![],
1563 words: vec![
1564 WordDef {
1565 name: "helper".to_string(),
1566 effect: Some(Effect::new(
1567 StackType::singleton(Type::Int),
1568 StackType::singleton(Type::String),
1569 )),
1570 body: vec![Statement::WordCall {
1571 name: "int->string".to_string(),
1572 span: None,
1573 }],
1574 source: None,
1575 allowed_lints: vec![],
1576 },
1577 WordDef {
1578 name: "main".to_string(),
1579 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1580 body: vec![
1581 Statement::IntLiteral(42),
1582 Statement::WordCall {
1583 name: "helper".to_string(),
1584 span: None,
1585 },
1586 Statement::WordCall {
1587 name: "io.write-line".to_string(),
1588 span: None,
1589 },
1590 ],
1591 source: None,
1592 allowed_lints: vec![],
1593 },
1594 ],
1595 };
1596
1597 let mut checker = TypeChecker::new();
1598 assert!(checker.check_program(&program).is_ok());
1599 }
1600
1601 #[test]
1602 fn test_arithmetic_chain() {
1603 let program = Program {
1606 includes: vec![],
1607 unions: vec![],
1608 words: vec![WordDef {
1609 name: "test".to_string(),
1610 effect: Some(Effect::new(
1611 StackType::Empty
1612 .push(Type::Int)
1613 .push(Type::Int)
1614 .push(Type::Int),
1615 StackType::singleton(Type::Int),
1616 )),
1617 body: vec![
1618 Statement::WordCall {
1619 name: "i.add".to_string(),
1620 span: None,
1621 },
1622 Statement::WordCall {
1623 name: "i.multiply".to_string(),
1624 span: None,
1625 },
1626 ],
1627 source: None,
1628 allowed_lints: vec![],
1629 }],
1630 };
1631
1632 let mut checker = TypeChecker::new();
1633 assert!(checker.check_program(&program).is_ok());
1634 }
1635
1636 #[test]
1637 fn test_write_line_type_error() {
1638 let program = Program {
1640 includes: vec![],
1641 unions: vec![],
1642 words: vec![WordDef {
1643 name: "test".to_string(),
1644 effect: Some(Effect::new(
1645 StackType::singleton(Type::Int),
1646 StackType::Empty,
1647 )),
1648 body: vec![Statement::WordCall {
1649 name: "io.write-line".to_string(),
1650 span: None,
1651 }],
1652 source: None,
1653 allowed_lints: vec![],
1654 }],
1655 };
1656
1657 let mut checker = TypeChecker::new();
1658 let result = checker.check_program(&program);
1659 assert!(result.is_err());
1660 assert!(result.unwrap_err().contains("Type mismatch"));
1661 }
1662
1663 #[test]
1664 fn test_stack_underflow_drop() {
1665 let program = Program {
1667 includes: vec![],
1668 unions: vec![],
1669 words: vec![WordDef {
1670 name: "test".to_string(),
1671 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1672 body: vec![Statement::WordCall {
1673 name: "drop".to_string(),
1674 span: None,
1675 }],
1676 source: None,
1677 allowed_lints: vec![],
1678 }],
1679 };
1680
1681 let mut checker = TypeChecker::new();
1682 let result = checker.check_program(&program);
1683 assert!(result.is_err());
1684 assert!(result.unwrap_err().contains("mismatch"));
1685 }
1686
1687 #[test]
1688 fn test_stack_underflow_add() {
1689 let program = Program {
1691 includes: vec![],
1692 unions: vec![],
1693 words: vec![WordDef {
1694 name: "test".to_string(),
1695 effect: Some(Effect::new(
1696 StackType::singleton(Type::Int),
1697 StackType::singleton(Type::Int),
1698 )),
1699 body: vec![Statement::WordCall {
1700 name: "i.add".to_string(),
1701 span: None,
1702 }],
1703 source: None,
1704 allowed_lints: vec![],
1705 }],
1706 };
1707
1708 let mut checker = TypeChecker::new();
1709 let result = checker.check_program(&program);
1710 assert!(result.is_err());
1711 assert!(result.unwrap_err().contains("mismatch"));
1712 }
1713
1714 #[test]
1717 fn test_stack_underflow_rot_issue_169() {
1718 let program = Program {
1722 includes: vec![],
1723 unions: vec![],
1724 words: vec![WordDef {
1725 name: "test".to_string(),
1726 effect: Some(Effect::new(
1727 StackType::RowVar("rest".to_string()),
1728 StackType::RowVar("rest".to_string()),
1729 )),
1730 body: vec![
1731 Statement::IntLiteral(3),
1732 Statement::IntLiteral(4),
1733 Statement::WordCall {
1734 name: "rot".to_string(),
1735 span: None,
1736 },
1737 ],
1738 source: None,
1739 allowed_lints: vec![],
1740 }],
1741 };
1742
1743 let mut checker = TypeChecker::new();
1744 let result = checker.check_program(&program);
1745 assert!(result.is_err(), "rot with 2 values should fail");
1746 let err = result.unwrap_err();
1747 assert!(
1748 err.contains("stack underflow") || err.contains("requires 3"),
1749 "Error should mention underflow: {}",
1750 err
1751 );
1752 }
1753
1754 #[test]
1755 fn test_csp_operations() {
1756 let program = Program {
1763 includes: vec![],
1764 unions: vec![],
1765 words: vec![WordDef {
1766 name: "test".to_string(),
1767 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1768 body: vec![
1769 Statement::WordCall {
1770 name: "chan.make".to_string(),
1771 span: None,
1772 },
1773 Statement::IntLiteral(42),
1774 Statement::WordCall {
1775 name: "swap".to_string(),
1776 span: None,
1777 },
1778 Statement::WordCall {
1779 name: "chan.send".to_string(),
1780 span: None,
1781 },
1782 Statement::WordCall {
1783 name: "drop".to_string(),
1784 span: None,
1785 },
1786 ],
1787 source: None,
1788 allowed_lints: vec![],
1789 }],
1790 };
1791
1792 let mut checker = TypeChecker::new();
1793 assert!(checker.check_program(&program).is_ok());
1794 }
1795
1796 #[test]
1797 fn test_complex_stack_shuffling() {
1798 let program = Program {
1801 includes: vec![],
1802 unions: vec![],
1803 words: vec![WordDef {
1804 name: "test".to_string(),
1805 effect: Some(Effect::new(
1806 StackType::Empty
1807 .push(Type::Int)
1808 .push(Type::Int)
1809 .push(Type::Int),
1810 StackType::singleton(Type::Int),
1811 )),
1812 body: vec![
1813 Statement::WordCall {
1814 name: "rot".to_string(),
1815 span: None,
1816 },
1817 Statement::WordCall {
1818 name: "i.add".to_string(),
1819 span: None,
1820 },
1821 Statement::WordCall {
1822 name: "i.add".to_string(),
1823 span: None,
1824 },
1825 ],
1826 source: None,
1827 allowed_lints: vec![],
1828 }],
1829 };
1830
1831 let mut checker = TypeChecker::new();
1832 assert!(checker.check_program(&program).is_ok());
1833 }
1834
1835 #[test]
1836 fn test_empty_program() {
1837 let program = Program {
1839 includes: vec![],
1840 unions: vec![],
1841 words: vec![],
1842 };
1843
1844 let mut checker = TypeChecker::new();
1845 assert!(checker.check_program(&program).is_ok());
1846 }
1847
1848 #[test]
1849 fn test_word_without_effect_declaration() {
1850 let program = Program {
1852 includes: vec![],
1853 unions: vec![],
1854 words: vec![WordDef {
1855 name: "helper".to_string(),
1856 effect: None,
1857 body: vec![Statement::IntLiteral(42)],
1858 source: None,
1859 allowed_lints: vec![],
1860 }],
1861 };
1862
1863 let mut checker = TypeChecker::new();
1864 let result = checker.check_program(&program);
1865 assert!(result.is_err());
1866 assert!(
1867 result
1868 .unwrap_err()
1869 .contains("missing a stack effect declaration")
1870 );
1871 }
1872
1873 #[test]
1874 fn test_nested_conditionals() {
1875 let program = Program {
1884 includes: vec![],
1885 unions: vec![],
1886 words: vec![WordDef {
1887 name: "test".to_string(),
1888 effect: Some(Effect::new(
1889 StackType::Empty
1890 .push(Type::Int)
1891 .push(Type::Int)
1892 .push(Type::Int)
1893 .push(Type::Int),
1894 StackType::singleton(Type::String),
1895 )),
1896 body: vec![
1897 Statement::WordCall {
1898 name: "i.>".to_string(),
1899 span: None,
1900 },
1901 Statement::If {
1902 then_branch: vec![
1903 Statement::WordCall {
1904 name: "i.>".to_string(),
1905 span: None,
1906 },
1907 Statement::If {
1908 then_branch: vec![Statement::StringLiteral(
1909 "both true".to_string(),
1910 )],
1911 else_branch: Some(vec![Statement::StringLiteral(
1912 "first true".to_string(),
1913 )]),
1914 },
1915 ],
1916 else_branch: Some(vec![
1917 Statement::WordCall {
1918 name: "drop".to_string(),
1919 span: None,
1920 },
1921 Statement::WordCall {
1922 name: "drop".to_string(),
1923 span: None,
1924 },
1925 Statement::StringLiteral("first false".to_string()),
1926 ]),
1927 },
1928 ],
1929 source: None,
1930 allowed_lints: vec![],
1931 }],
1932 };
1933
1934 let mut checker = TypeChecker::new();
1935 match checker.check_program(&program) {
1936 Ok(_) => {}
1937 Err(e) => panic!("Type check failed: {}", e),
1938 }
1939 }
1940
1941 #[test]
1942 fn test_conditional_without_else() {
1943 let program = Program {
1947 includes: vec![],
1948 unions: vec![],
1949 words: vec![WordDef {
1950 name: "test".to_string(),
1951 effect: Some(Effect::new(
1952 StackType::Empty.push(Type::Int).push(Type::Int),
1953 StackType::singleton(Type::Int),
1954 )),
1955 body: vec![
1956 Statement::WordCall {
1957 name: "i.>".to_string(),
1958 span: None,
1959 },
1960 Statement::If {
1961 then_branch: vec![Statement::IntLiteral(100)],
1962 else_branch: None, },
1964 ],
1965 source: None,
1966 allowed_lints: vec![],
1967 }],
1968 };
1969
1970 let mut checker = TypeChecker::new();
1971 let result = checker.check_program(&program);
1972 assert!(result.is_err());
1974 }
1975
1976 #[test]
1977 fn test_multiple_word_chain() {
1978 let program = Program {
1982 includes: vec![],
1983 unions: vec![],
1984 words: vec![
1985 WordDef {
1986 name: "helper1".to_string(),
1987 effect: Some(Effect::new(
1988 StackType::singleton(Type::Int),
1989 StackType::singleton(Type::String),
1990 )),
1991 body: vec![Statement::WordCall {
1992 name: "int->string".to_string(),
1993 span: None,
1994 }],
1995 source: None,
1996 allowed_lints: vec![],
1997 },
1998 WordDef {
1999 name: "helper2".to_string(),
2000 effect: Some(Effect::new(
2001 StackType::singleton(Type::String),
2002 StackType::Empty,
2003 )),
2004 body: vec![Statement::WordCall {
2005 name: "io.write-line".to_string(),
2006 span: None,
2007 }],
2008 source: None,
2009 allowed_lints: vec![],
2010 },
2011 WordDef {
2012 name: "main".to_string(),
2013 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2014 body: vec![
2015 Statement::IntLiteral(42),
2016 Statement::WordCall {
2017 name: "helper1".to_string(),
2018 span: None,
2019 },
2020 Statement::WordCall {
2021 name: "helper2".to_string(),
2022 span: None,
2023 },
2024 ],
2025 source: None,
2026 allowed_lints: vec![],
2027 },
2028 ],
2029 };
2030
2031 let mut checker = TypeChecker::new();
2032 assert!(checker.check_program(&program).is_ok());
2033 }
2034
2035 #[test]
2036 fn test_all_stack_ops() {
2037 let program = Program {
2040 includes: vec![],
2041 unions: vec![],
2042 words: vec![WordDef {
2043 name: "test".to_string(),
2044 effect: Some(Effect::new(
2045 StackType::Empty
2046 .push(Type::Int)
2047 .push(Type::Int)
2048 .push(Type::Int),
2049 StackType::Empty
2050 .push(Type::Int)
2051 .push(Type::Int)
2052 .push(Type::Int)
2053 .push(Type::Int),
2054 )),
2055 body: vec![
2056 Statement::WordCall {
2057 name: "over".to_string(),
2058 span: None,
2059 },
2060 Statement::WordCall {
2061 name: "nip".to_string(),
2062 span: None,
2063 },
2064 Statement::WordCall {
2065 name: "tuck".to_string(),
2066 span: None,
2067 },
2068 ],
2069 source: None,
2070 allowed_lints: vec![],
2071 }],
2072 };
2073
2074 let mut checker = TypeChecker::new();
2075 assert!(checker.check_program(&program).is_ok());
2076 }
2077
2078 #[test]
2079 fn test_mixed_types_complex() {
2080 let program = Program {
2089 includes: vec![],
2090 unions: vec![],
2091 words: vec![WordDef {
2092 name: "test".to_string(),
2093 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2094 body: vec![
2095 Statement::IntLiteral(42),
2096 Statement::WordCall {
2097 name: "int->string".to_string(),
2098 span: None,
2099 },
2100 Statement::IntLiteral(100),
2101 Statement::IntLiteral(200),
2102 Statement::WordCall {
2103 name: "i.>".to_string(),
2104 span: None,
2105 },
2106 Statement::If {
2107 then_branch: vec![Statement::WordCall {
2108 name: "io.write-line".to_string(),
2109 span: None,
2110 }],
2111 else_branch: Some(vec![Statement::WordCall {
2112 name: "io.write-line".to_string(),
2113 span: None,
2114 }]),
2115 },
2116 ],
2117 source: None,
2118 allowed_lints: vec![],
2119 }],
2120 };
2121
2122 let mut checker = TypeChecker::new();
2123 assert!(checker.check_program(&program).is_ok());
2124 }
2125
2126 #[test]
2127 fn test_string_literal() {
2128 let program = Program {
2130 includes: vec![],
2131 unions: vec![],
2132 words: vec![WordDef {
2133 name: "test".to_string(),
2134 effect: Some(Effect::new(
2135 StackType::Empty,
2136 StackType::singleton(Type::String),
2137 )),
2138 body: vec![Statement::StringLiteral("hello".to_string())],
2139 source: None,
2140 allowed_lints: vec![],
2141 }],
2142 };
2143
2144 let mut checker = TypeChecker::new();
2145 assert!(checker.check_program(&program).is_ok());
2146 }
2147
2148 #[test]
2149 fn test_bool_literal() {
2150 let program = Program {
2153 includes: vec![],
2154 unions: vec![],
2155 words: vec![WordDef {
2156 name: "test".to_string(),
2157 effect: Some(Effect::new(
2158 StackType::Empty,
2159 StackType::singleton(Type::Bool),
2160 )),
2161 body: vec![Statement::BoolLiteral(true)],
2162 source: None,
2163 allowed_lints: vec![],
2164 }],
2165 };
2166
2167 let mut checker = TypeChecker::new();
2168 assert!(checker.check_program(&program).is_ok());
2169 }
2170
2171 #[test]
2172 fn test_type_error_in_nested_conditional() {
2173 let program = Program {
2180 includes: vec![],
2181 unions: vec![],
2182 words: vec![WordDef {
2183 name: "test".to_string(),
2184 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2185 body: vec![
2186 Statement::IntLiteral(10),
2187 Statement::IntLiteral(20),
2188 Statement::WordCall {
2189 name: "i.>".to_string(),
2190 span: None,
2191 },
2192 Statement::If {
2193 then_branch: vec![
2194 Statement::IntLiteral(42),
2195 Statement::WordCall {
2196 name: "io.write-line".to_string(),
2197 span: None,
2198 },
2199 ],
2200 else_branch: Some(vec![
2201 Statement::StringLiteral("ok".to_string()),
2202 Statement::WordCall {
2203 name: "io.write-line".to_string(),
2204 span: None,
2205 },
2206 ]),
2207 },
2208 ],
2209 source: None,
2210 allowed_lints: vec![],
2211 }],
2212 };
2213
2214 let mut checker = TypeChecker::new();
2215 let result = checker.check_program(&program);
2216 assert!(result.is_err());
2217 assert!(result.unwrap_err().contains("Type mismatch"));
2218 }
2219
2220 #[test]
2221 fn test_read_line_operation() {
2222 let program = Program {
2225 includes: vec![],
2226 unions: vec![],
2227 words: vec![WordDef {
2228 name: "test".to_string(),
2229 effect: Some(Effect::new(
2230 StackType::Empty,
2231 StackType::from_vec(vec![Type::String, Type::Bool]),
2232 )),
2233 body: vec![Statement::WordCall {
2234 name: "io.read-line".to_string(),
2235 span: None,
2236 }],
2237 source: None,
2238 allowed_lints: vec![],
2239 }],
2240 };
2241
2242 let mut checker = TypeChecker::new();
2243 assert!(checker.check_program(&program).is_ok());
2244 }
2245
2246 #[test]
2247 fn test_comparison_operations() {
2248 let program = Program {
2253 includes: vec![],
2254 unions: vec![],
2255 words: vec![WordDef {
2256 name: "test".to_string(),
2257 effect: Some(Effect::new(
2258 StackType::Empty.push(Type::Int).push(Type::Int),
2259 StackType::singleton(Type::Bool),
2260 )),
2261 body: vec![Statement::WordCall {
2262 name: "i.<=".to_string(),
2263 span: None,
2264 }],
2265 source: None,
2266 allowed_lints: vec![],
2267 }],
2268 };
2269
2270 let mut checker = TypeChecker::new();
2271 assert!(checker.check_program(&program).is_ok());
2272 }
2273
2274 #[test]
2275 fn test_recursive_word_definitions() {
2276 let program = Program {
2282 includes: vec![],
2283 unions: vec![],
2284 words: vec![
2285 WordDef {
2286 name: "is-even".to_string(),
2287 effect: Some(Effect::new(
2288 StackType::singleton(Type::Int),
2289 StackType::singleton(Type::Int),
2290 )),
2291 body: vec![
2292 Statement::WordCall {
2293 name: "dup".to_string(),
2294 span: None,
2295 },
2296 Statement::IntLiteral(0),
2297 Statement::WordCall {
2298 name: "i.=".to_string(),
2299 span: None,
2300 },
2301 Statement::If {
2302 then_branch: vec![
2303 Statement::WordCall {
2304 name: "drop".to_string(),
2305 span: None,
2306 },
2307 Statement::IntLiteral(1),
2308 ],
2309 else_branch: Some(vec![
2310 Statement::IntLiteral(1),
2311 Statement::WordCall {
2312 name: "i.subtract".to_string(),
2313 span: None,
2314 },
2315 Statement::WordCall {
2316 name: "is-odd".to_string(),
2317 span: None,
2318 },
2319 ]),
2320 },
2321 ],
2322 source: None,
2323 allowed_lints: vec![],
2324 },
2325 WordDef {
2326 name: "is-odd".to_string(),
2327 effect: Some(Effect::new(
2328 StackType::singleton(Type::Int),
2329 StackType::singleton(Type::Int),
2330 )),
2331 body: vec![
2332 Statement::WordCall {
2333 name: "dup".to_string(),
2334 span: None,
2335 },
2336 Statement::IntLiteral(0),
2337 Statement::WordCall {
2338 name: "i.=".to_string(),
2339 span: None,
2340 },
2341 Statement::If {
2342 then_branch: vec![
2343 Statement::WordCall {
2344 name: "drop".to_string(),
2345 span: None,
2346 },
2347 Statement::IntLiteral(0),
2348 ],
2349 else_branch: Some(vec![
2350 Statement::IntLiteral(1),
2351 Statement::WordCall {
2352 name: "i.subtract".to_string(),
2353 span: None,
2354 },
2355 Statement::WordCall {
2356 name: "is-even".to_string(),
2357 span: None,
2358 },
2359 ]),
2360 },
2361 ],
2362 source: None,
2363 allowed_lints: vec![],
2364 },
2365 ],
2366 };
2367
2368 let mut checker = TypeChecker::new();
2369 assert!(checker.check_program(&program).is_ok());
2370 }
2371
2372 #[test]
2373 fn test_word_calling_word_with_row_polymorphism() {
2374 let program = Program {
2379 includes: vec![],
2380 unions: vec![],
2381 words: vec![
2382 WordDef {
2383 name: "apply-twice".to_string(),
2384 effect: Some(Effect::new(
2385 StackType::singleton(Type::Int),
2386 StackType::singleton(Type::Int),
2387 )),
2388 body: vec![
2389 Statement::WordCall {
2390 name: "dup".to_string(),
2391 span: None,
2392 },
2393 Statement::WordCall {
2394 name: "i.add".to_string(),
2395 span: None,
2396 },
2397 ],
2398 source: None,
2399 allowed_lints: vec![],
2400 },
2401 WordDef {
2402 name: "quad".to_string(),
2403 effect: Some(Effect::new(
2404 StackType::singleton(Type::Int),
2405 StackType::singleton(Type::Int),
2406 )),
2407 body: vec![
2408 Statement::WordCall {
2409 name: "apply-twice".to_string(),
2410 span: None,
2411 },
2412 Statement::WordCall {
2413 name: "apply-twice".to_string(),
2414 span: None,
2415 },
2416 ],
2417 source: None,
2418 allowed_lints: vec![],
2419 },
2420 ],
2421 };
2422
2423 let mut checker = TypeChecker::new();
2424 assert!(checker.check_program(&program).is_ok());
2425 }
2426
2427 #[test]
2428 fn test_deep_stack_types() {
2429 let mut stack_type = StackType::Empty;
2433 for _ in 0..10 {
2434 stack_type = stack_type.push(Type::Int);
2435 }
2436
2437 let program = Program {
2438 includes: vec![],
2439 unions: vec![],
2440 words: vec![WordDef {
2441 name: "test".to_string(),
2442 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2443 body: vec![
2444 Statement::WordCall {
2445 name: "i.add".to_string(),
2446 span: None,
2447 },
2448 Statement::WordCall {
2449 name: "i.add".to_string(),
2450 span: None,
2451 },
2452 Statement::WordCall {
2453 name: "i.add".to_string(),
2454 span: None,
2455 },
2456 Statement::WordCall {
2457 name: "i.add".to_string(),
2458 span: None,
2459 },
2460 Statement::WordCall {
2461 name: "i.add".to_string(),
2462 span: None,
2463 },
2464 Statement::WordCall {
2465 name: "i.add".to_string(),
2466 span: None,
2467 },
2468 Statement::WordCall {
2469 name: "i.add".to_string(),
2470 span: None,
2471 },
2472 Statement::WordCall {
2473 name: "i.add".to_string(),
2474 span: None,
2475 },
2476 Statement::WordCall {
2477 name: "i.add".to_string(),
2478 span: None,
2479 },
2480 ],
2481 source: None,
2482 allowed_lints: vec![],
2483 }],
2484 };
2485
2486 let mut checker = TypeChecker::new();
2487 assert!(checker.check_program(&program).is_ok());
2488 }
2489
2490 #[test]
2491 fn test_simple_quotation() {
2492 let program = Program {
2496 includes: vec![],
2497 unions: vec![],
2498 words: vec![WordDef {
2499 name: "test".to_string(),
2500 effect: Some(Effect::new(
2501 StackType::Empty,
2502 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2503 StackType::RowVar("input".to_string()).push(Type::Int),
2504 StackType::RowVar("input".to_string()).push(Type::Int),
2505 )))),
2506 )),
2507 body: vec![Statement::Quotation {
2508 span: None,
2509 id: 0,
2510 body: vec![
2511 Statement::IntLiteral(1),
2512 Statement::WordCall {
2513 name: "i.add".to_string(),
2514 span: None,
2515 },
2516 ],
2517 }],
2518 source: None,
2519 allowed_lints: vec![],
2520 }],
2521 };
2522
2523 let mut checker = TypeChecker::new();
2524 match checker.check_program(&program) {
2525 Ok(_) => {}
2526 Err(e) => panic!("Type check failed: {}", e),
2527 }
2528 }
2529
2530 #[test]
2531 fn test_empty_quotation() {
2532 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(Type::Quotation(Box::new(Effect::new(
2543 StackType::RowVar("input".to_string()),
2544 StackType::RowVar("input".to_string()),
2545 )))),
2546 )),
2547 body: vec![Statement::Quotation {
2548 span: None,
2549 id: 1,
2550 body: vec![],
2551 }],
2552 source: None,
2553 allowed_lints: vec![],
2554 }],
2555 };
2556
2557 let mut checker = TypeChecker::new();
2558 assert!(checker.check_program(&program).is_ok());
2559 }
2560
2561 #[test]
2588 fn test_nested_quotation() {
2589 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2593 StackType::RowVar("input".to_string()).push(Type::Int),
2594 StackType::RowVar("input".to_string()).push(Type::Int),
2595 )));
2596
2597 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2598 StackType::RowVar("input".to_string()),
2599 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2600 )));
2601
2602 let program = Program {
2603 includes: vec![],
2604 unions: vec![],
2605 words: vec![WordDef {
2606 name: "test".to_string(),
2607 effect: Some(Effect::new(
2608 StackType::Empty,
2609 StackType::singleton(outer_quot_type),
2610 )),
2611 body: vec![Statement::Quotation {
2612 span: None,
2613 id: 2,
2614 body: vec![Statement::Quotation {
2615 span: None,
2616 id: 3,
2617 body: vec![
2618 Statement::IntLiteral(1),
2619 Statement::WordCall {
2620 name: "i.add".to_string(),
2621 span: None,
2622 },
2623 ],
2624 }],
2625 }],
2626 source: None,
2627 allowed_lints: vec![],
2628 }],
2629 };
2630
2631 let mut checker = TypeChecker::new();
2632 assert!(checker.check_program(&program).is_ok());
2633 }
2634
2635 #[test]
2636 fn test_invalid_field_type_error() {
2637 use crate::ast::{UnionDef, UnionField, UnionVariant};
2638
2639 let program = Program {
2640 includes: vec![],
2641 unions: vec![UnionDef {
2642 name: "Message".to_string(),
2643 variants: vec![UnionVariant {
2644 name: "Get".to_string(),
2645 fields: vec![UnionField {
2646 name: "chan".to_string(),
2647 type_name: "InvalidType".to_string(),
2648 }],
2649 source: None,
2650 }],
2651 source: None,
2652 }],
2653 words: vec![],
2654 };
2655
2656 let mut checker = TypeChecker::new();
2657 let result = checker.check_program(&program);
2658 assert!(result.is_err());
2659 let err = result.unwrap_err();
2660 assert!(err.contains("Unknown type 'InvalidType'"));
2661 assert!(err.contains("chan"));
2662 assert!(err.contains("Get"));
2663 assert!(err.contains("Message"));
2664 }
2665
2666 #[test]
2667 fn test_roll_inside_conditional_with_concrete_stack() {
2668 let program = Program {
2676 includes: vec![],
2677 unions: vec![],
2678 words: vec![WordDef {
2679 name: "test".to_string(),
2680 effect: Some(Effect::new(
2681 StackType::Empty
2682 .push(Type::Int)
2683 .push(Type::Int)
2684 .push(Type::Int)
2685 .push(Type::Int),
2686 StackType::Empty
2687 .push(Type::Int)
2688 .push(Type::Int)
2689 .push(Type::Int)
2690 .push(Type::Int),
2691 )),
2692 body: vec![
2693 Statement::WordCall {
2694 name: "dup".to_string(),
2695 span: None,
2696 },
2697 Statement::IntLiteral(0),
2698 Statement::WordCall {
2699 name: "i.>".to_string(),
2700 span: None,
2701 },
2702 Statement::If {
2703 then_branch: vec![
2704 Statement::IntLiteral(3),
2705 Statement::WordCall {
2706 name: "roll".to_string(),
2707 span: None,
2708 },
2709 ],
2710 else_branch: Some(vec![
2711 Statement::WordCall {
2712 name: "rot".to_string(),
2713 span: None,
2714 },
2715 Statement::WordCall {
2716 name: "rot".to_string(),
2717 span: None,
2718 },
2719 ]),
2720 },
2721 ],
2722 source: None,
2723 allowed_lints: vec![],
2724 }],
2725 };
2726
2727 let mut checker = TypeChecker::new();
2728 match checker.check_program(&program) {
2730 Ok(_) => {}
2731 Err(e) => panic!("Type check failed: {}", e),
2732 }
2733 }
2734
2735 #[test]
2736 fn test_roll_inside_match_arm_with_concrete_stack() {
2737 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2740
2741 let union_def = UnionDef {
2743 name: "Result".to_string(),
2744 variants: vec![
2745 UnionVariant {
2746 name: "Ok".to_string(),
2747 fields: vec![],
2748 source: None,
2749 },
2750 UnionVariant {
2751 name: "Err".to_string(),
2752 fields: vec![],
2753 source: None,
2754 },
2755 ],
2756 source: None,
2757 };
2758
2759 let program = Program {
2765 includes: vec![],
2766 unions: vec![union_def],
2767 words: vec![WordDef {
2768 name: "test".to_string(),
2769 effect: Some(Effect::new(
2770 StackType::Empty
2771 .push(Type::Int)
2772 .push(Type::Int)
2773 .push(Type::Int)
2774 .push(Type::Int)
2775 .push(Type::Union("Result".to_string())),
2776 StackType::Empty
2777 .push(Type::Int)
2778 .push(Type::Int)
2779 .push(Type::Int)
2780 .push(Type::Int),
2781 )),
2782 body: vec![Statement::Match {
2783 arms: vec![
2784 MatchArm {
2785 pattern: Pattern::Variant("Ok".to_string()),
2786 body: vec![
2787 Statement::IntLiteral(3),
2788 Statement::WordCall {
2789 name: "roll".to_string(),
2790 span: None,
2791 },
2792 ],
2793 },
2794 MatchArm {
2795 pattern: Pattern::Variant("Err".to_string()),
2796 body: vec![
2797 Statement::WordCall {
2798 name: "rot".to_string(),
2799 span: None,
2800 },
2801 Statement::WordCall {
2802 name: "rot".to_string(),
2803 span: None,
2804 },
2805 ],
2806 },
2807 ],
2808 }],
2809 source: None,
2810 allowed_lints: vec![],
2811 }],
2812 };
2813
2814 let mut checker = TypeChecker::new();
2815 match checker.check_program(&program) {
2816 Ok(_) => {}
2817 Err(e) => panic!("Type check failed: {}", e),
2818 }
2819 }
2820
2821 #[test]
2822 fn test_roll_with_row_polymorphic_input() {
2823 let program = Program {
2827 includes: vec![],
2828 unions: vec![],
2829 words: vec![WordDef {
2830 name: "test".to_string(),
2831 effect: Some(Effect::new(
2832 StackType::Empty
2833 .push(Type::Var("T".to_string()))
2834 .push(Type::Var("U".to_string()))
2835 .push(Type::Var("V".to_string()))
2836 .push(Type::Var("W".to_string())),
2837 StackType::Empty
2838 .push(Type::Var("U".to_string()))
2839 .push(Type::Var("V".to_string()))
2840 .push(Type::Var("W".to_string()))
2841 .push(Type::Var("T".to_string())),
2842 )),
2843 body: vec![
2844 Statement::IntLiteral(3),
2845 Statement::WordCall {
2846 name: "roll".to_string(),
2847 span: None,
2848 },
2849 ],
2850 source: None,
2851 allowed_lints: vec![],
2852 }],
2853 };
2854
2855 let mut checker = TypeChecker::new();
2856 let result = checker.check_program(&program);
2857 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
2858 }
2859
2860 #[test]
2861 fn test_pick_with_row_polymorphic_input() {
2862 let program = Program {
2866 includes: vec![],
2867 unions: vec![],
2868 words: vec![WordDef {
2869 name: "test".to_string(),
2870 effect: Some(Effect::new(
2871 StackType::Empty
2872 .push(Type::Var("T".to_string()))
2873 .push(Type::Var("U".to_string()))
2874 .push(Type::Var("V".to_string())),
2875 StackType::Empty
2876 .push(Type::Var("T".to_string()))
2877 .push(Type::Var("U".to_string()))
2878 .push(Type::Var("V".to_string()))
2879 .push(Type::Var("T".to_string())),
2880 )),
2881 body: vec![
2882 Statement::IntLiteral(2),
2883 Statement::WordCall {
2884 name: "pick".to_string(),
2885 span: None,
2886 },
2887 ],
2888 source: None,
2889 allowed_lints: vec![],
2890 }],
2891 };
2892
2893 let mut checker = TypeChecker::new();
2894 assert!(checker.check_program(&program).is_ok());
2895 }
2896
2897 #[test]
2898 fn test_valid_union_reference_in_field() {
2899 use crate::ast::{UnionDef, UnionField, UnionVariant};
2900
2901 let program = Program {
2902 includes: vec![],
2903 unions: vec![
2904 UnionDef {
2905 name: "Inner".to_string(),
2906 variants: vec![UnionVariant {
2907 name: "Val".to_string(),
2908 fields: vec![UnionField {
2909 name: "x".to_string(),
2910 type_name: "Int".to_string(),
2911 }],
2912 source: None,
2913 }],
2914 source: None,
2915 },
2916 UnionDef {
2917 name: "Outer".to_string(),
2918 variants: vec![UnionVariant {
2919 name: "Wrap".to_string(),
2920 fields: vec![UnionField {
2921 name: "inner".to_string(),
2922 type_name: "Inner".to_string(), }],
2924 source: None,
2925 }],
2926 source: None,
2927 },
2928 ],
2929 words: vec![],
2930 };
2931
2932 let mut checker = TypeChecker::new();
2933 assert!(
2934 checker.check_program(&program).is_ok(),
2935 "Union reference in field should be valid"
2936 );
2937 }
2938
2939 #[test]
2940 fn test_divergent_recursive_tail_call() {
2941 let program = Program {
2963 includes: vec![],
2964 unions: vec![],
2965 words: vec![WordDef {
2966 name: "store-loop".to_string(),
2967 effect: Some(Effect::new(
2968 StackType::singleton(Type::Channel), StackType::Empty,
2970 )),
2971 body: vec![
2972 Statement::WordCall {
2974 name: "dup".to_string(),
2975 span: None,
2976 },
2977 Statement::WordCall {
2979 name: "chan.receive".to_string(),
2980 span: None,
2981 },
2982 Statement::WordCall {
2984 name: "not".to_string(),
2985 span: None,
2986 },
2987 Statement::If {
2989 then_branch: vec![
2990 Statement::WordCall {
2992 name: "drop".to_string(),
2993 span: None,
2994 },
2995 Statement::WordCall {
2997 name: "store-loop".to_string(), span: None,
2999 },
3000 ],
3001 else_branch: None, },
3003 Statement::WordCall {
3005 name: "drop".to_string(),
3006 span: None,
3007 },
3008 Statement::WordCall {
3009 name: "drop".to_string(),
3010 span: None,
3011 },
3012 ],
3013 source: None,
3014 allowed_lints: vec![],
3015 }],
3016 };
3017
3018 let mut checker = TypeChecker::new();
3019 let result = checker.check_program(&program);
3020 assert!(
3021 result.is_ok(),
3022 "Divergent recursive tail call should be accepted: {:?}",
3023 result.err()
3024 );
3025 }
3026
3027 #[test]
3028 fn test_divergent_else_branch() {
3029 let program = Program {
3042 includes: vec![],
3043 unions: vec![],
3044 words: vec![WordDef {
3045 name: "process-loop".to_string(),
3046 effect: Some(Effect::new(
3047 StackType::singleton(Type::Channel), StackType::Empty,
3049 )),
3050 body: vec![
3051 Statement::WordCall {
3052 name: "dup".to_string(),
3053 span: None,
3054 },
3055 Statement::WordCall {
3056 name: "chan.receive".to_string(),
3057 span: None,
3058 },
3059 Statement::If {
3060 then_branch: vec![
3061 Statement::WordCall {
3063 name: "drop".to_string(),
3064 span: None,
3065 },
3066 Statement::WordCall {
3067 name: "drop".to_string(),
3068 span: None,
3069 },
3070 ],
3071 else_branch: Some(vec![
3072 Statement::WordCall {
3074 name: "drop".to_string(),
3075 span: None,
3076 },
3077 Statement::WordCall {
3078 name: "process-loop".to_string(), span: None,
3080 },
3081 ]),
3082 },
3083 ],
3084 source: None,
3085 allowed_lints: vec![],
3086 }],
3087 };
3088
3089 let mut checker = TypeChecker::new();
3090 let result = checker.check_program(&program);
3091 assert!(
3092 result.is_ok(),
3093 "Divergent else branch should be accepted: {:?}",
3094 result.err()
3095 );
3096 }
3097
3098 #[test]
3099 fn test_non_tail_call_recursion_not_divergent() {
3100 let program = Program {
3117 includes: vec![],
3118 unions: vec![],
3119 words: vec![WordDef {
3120 name: "bad-loop".to_string(),
3121 effect: Some(Effect::new(
3122 StackType::singleton(Type::Int),
3123 StackType::singleton(Type::Int),
3124 )),
3125 body: vec![
3126 Statement::WordCall {
3127 name: "dup".to_string(),
3128 span: None,
3129 },
3130 Statement::IntLiteral(0),
3131 Statement::WordCall {
3132 name: "i.>".to_string(),
3133 span: None,
3134 },
3135 Statement::If {
3136 then_branch: vec![
3137 Statement::IntLiteral(1),
3138 Statement::WordCall {
3139 name: "i.subtract".to_string(),
3140 span: None,
3141 },
3142 Statement::WordCall {
3143 name: "bad-loop".to_string(), span: None,
3145 },
3146 Statement::IntLiteral(1),
3147 Statement::WordCall {
3148 name: "i.add".to_string(), span: None,
3150 },
3151 ],
3152 else_branch: None,
3153 },
3154 ],
3155 source: None,
3156 allowed_lints: vec![],
3157 }],
3158 };
3159
3160 let mut checker = TypeChecker::new();
3161 let result = checker.check_program(&program);
3166 assert!(
3167 result.is_ok(),
3168 "Non-tail recursion should type check normally: {:?}",
3169 result.err()
3170 );
3171 }
3172
3173 #[test]
3174 fn test_call_yield_quotation_error() {
3175 let program = Program {
3179 includes: vec![],
3180 unions: vec![],
3181 words: vec![WordDef {
3182 name: "bad".to_string(),
3183 effect: Some(Effect::new(
3184 StackType::singleton(Type::Var("Ctx".to_string())),
3185 StackType::singleton(Type::Var("Ctx".to_string())),
3186 )),
3187 body: vec![
3188 Statement::IntLiteral(42),
3190 Statement::Quotation {
3191 span: None,
3192 id: 0,
3193 body: vec![Statement::WordCall {
3194 name: "yield".to_string(),
3195 span: None,
3196 }],
3197 },
3198 Statement::WordCall {
3199 name: "call".to_string(),
3200 span: None,
3201 },
3202 ],
3203 source: None,
3204 allowed_lints: vec![],
3205 }],
3206 };
3207
3208 let mut checker = TypeChecker::new();
3209 let result = checker.check_program(&program);
3210 assert!(
3211 result.is_err(),
3212 "Calling yield quotation directly should fail"
3213 );
3214 let err = result.unwrap_err();
3215 assert!(
3216 err.contains("Yield") || err.contains("strand.weave"),
3217 "Error should mention Yield or strand.weave: {}",
3218 err
3219 );
3220 }
3221
3222 #[test]
3223 fn test_strand_weave_yield_quotation_ok() {
3224 let program = Program {
3227 includes: vec![],
3228 unions: vec![],
3229 words: vec![WordDef {
3230 name: "good".to_string(),
3231 effect: Some(Effect::new(
3232 StackType::Empty,
3233 StackType::Empty
3234 .push(Type::Int)
3235 .push(Type::Var("Handle".to_string())),
3236 )),
3237 body: vec![
3238 Statement::IntLiteral(42),
3239 Statement::Quotation {
3240 span: None,
3241 id: 0,
3242 body: vec![Statement::WordCall {
3243 name: "yield".to_string(),
3244 span: None,
3245 }],
3246 },
3247 Statement::WordCall {
3248 name: "strand.weave".to_string(),
3249 span: None,
3250 },
3251 ],
3252 source: None,
3253 allowed_lints: vec![],
3254 }],
3255 };
3256
3257 let mut checker = TypeChecker::new();
3258 let result = checker.check_program(&program);
3259 assert!(
3260 result.is_ok(),
3261 "strand.weave on yield quotation should pass: {:?}",
3262 result.err()
3263 );
3264 }
3265
3266 #[test]
3267 fn test_call_pure_quotation_ok() {
3268 let program = Program {
3271 includes: vec![],
3272 unions: vec![],
3273 words: vec![WordDef {
3274 name: "ok".to_string(),
3275 effect: Some(Effect::new(
3276 StackType::singleton(Type::Int),
3277 StackType::singleton(Type::Int),
3278 )),
3279 body: vec![
3280 Statement::Quotation {
3281 span: None,
3282 id: 0,
3283 body: vec![
3284 Statement::IntLiteral(1),
3285 Statement::WordCall {
3286 name: "i.add".to_string(),
3287 span: None,
3288 },
3289 ],
3290 },
3291 Statement::WordCall {
3292 name: "call".to_string(),
3293 span: None,
3294 },
3295 ],
3296 source: None,
3297 allowed_lints: vec![],
3298 }],
3299 };
3300
3301 let mut checker = TypeChecker::new();
3302 let result = checker.check_program(&program);
3303 assert!(
3304 result.is_ok(),
3305 "Calling pure quotation should pass: {:?}",
3306 result.err()
3307 );
3308 }
3309
3310 #[test]
3316 fn test_pollution_extra_push() {
3317 let program = Program {
3321 includes: vec![],
3322 unions: vec![],
3323 words: vec![WordDef {
3324 name: "test".to_string(),
3325 effect: Some(Effect::new(
3326 StackType::singleton(Type::Int),
3327 StackType::singleton(Type::Int),
3328 )),
3329 body: vec![Statement::IntLiteral(42)],
3330 source: None,
3331 allowed_lints: vec![],
3332 }],
3333 };
3334
3335 let mut checker = TypeChecker::new();
3336 let result = checker.check_program(&program);
3337 assert!(
3338 result.is_err(),
3339 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3340 );
3341 }
3342
3343 #[test]
3344 fn test_pollution_extra_dup() {
3345 let program = Program {
3348 includes: vec![],
3349 unions: vec![],
3350 words: vec![WordDef {
3351 name: "test".to_string(),
3352 effect: Some(Effect::new(
3353 StackType::singleton(Type::Int),
3354 StackType::singleton(Type::Int),
3355 )),
3356 body: vec![Statement::WordCall {
3357 name: "dup".to_string(),
3358 span: None,
3359 }],
3360 source: None,
3361 allowed_lints: vec![],
3362 }],
3363 };
3364
3365 let mut checker = TypeChecker::new();
3366 let result = checker.check_program(&program);
3367 assert!(
3368 result.is_err(),
3369 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3370 );
3371 }
3372
3373 #[test]
3374 fn test_pollution_consumes_extra() {
3375 let program = Program {
3378 includes: vec![],
3379 unions: vec![],
3380 words: vec![WordDef {
3381 name: "test".to_string(),
3382 effect: Some(Effect::new(
3383 StackType::singleton(Type::Int),
3384 StackType::singleton(Type::Int),
3385 )),
3386 body: vec![
3387 Statement::WordCall {
3388 name: "drop".to_string(),
3389 span: None,
3390 },
3391 Statement::WordCall {
3392 name: "drop".to_string(),
3393 span: None,
3394 },
3395 Statement::IntLiteral(42),
3396 ],
3397 source: None,
3398 allowed_lints: vec![],
3399 }],
3400 };
3401
3402 let mut checker = TypeChecker::new();
3403 let result = checker.check_program(&program);
3404 assert!(
3405 result.is_err(),
3406 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3407 );
3408 }
3409
3410 #[test]
3411 fn test_pollution_in_then_branch() {
3412 let program = Program {
3416 includes: vec![],
3417 unions: vec![],
3418 words: vec![WordDef {
3419 name: "test".to_string(),
3420 effect: Some(Effect::new(
3421 StackType::singleton(Type::Bool),
3422 StackType::singleton(Type::Int),
3423 )),
3424 body: vec![Statement::If {
3425 then_branch: vec![
3426 Statement::IntLiteral(1),
3427 Statement::IntLiteral(2), ],
3429 else_branch: Some(vec![Statement::IntLiteral(3)]),
3430 }],
3431 source: None,
3432 allowed_lints: vec![],
3433 }],
3434 };
3435
3436 let mut checker = TypeChecker::new();
3437 let result = checker.check_program(&program);
3438 assert!(
3439 result.is_err(),
3440 "Should reject: then branch pushes 2 values, else pushes 1"
3441 );
3442 }
3443
3444 #[test]
3445 fn test_pollution_in_else_branch() {
3446 let program = Program {
3450 includes: vec![],
3451 unions: vec![],
3452 words: vec![WordDef {
3453 name: "test".to_string(),
3454 effect: Some(Effect::new(
3455 StackType::singleton(Type::Bool),
3456 StackType::singleton(Type::Int),
3457 )),
3458 body: vec![Statement::If {
3459 then_branch: vec![Statement::IntLiteral(1)],
3460 else_branch: Some(vec![
3461 Statement::IntLiteral(2),
3462 Statement::IntLiteral(3), ]),
3464 }],
3465 source: None,
3466 allowed_lints: vec![],
3467 }],
3468 };
3469
3470 let mut checker = TypeChecker::new();
3471 let result = checker.check_program(&program);
3472 assert!(
3473 result.is_err(),
3474 "Should reject: then branch pushes 1 value, else pushes 2"
3475 );
3476 }
3477
3478 #[test]
3479 fn test_pollution_both_branches_extra() {
3480 let program = Program {
3484 includes: vec![],
3485 unions: vec![],
3486 words: vec![WordDef {
3487 name: "test".to_string(),
3488 effect: Some(Effect::new(
3489 StackType::singleton(Type::Bool),
3490 StackType::singleton(Type::Int),
3491 )),
3492 body: vec![Statement::If {
3493 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3494 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3495 }],
3496 source: None,
3497 allowed_lints: vec![],
3498 }],
3499 };
3500
3501 let mut checker = TypeChecker::new();
3502 let result = checker.check_program(&program);
3503 assert!(
3504 result.is_err(),
3505 "Should reject: both branches push 2 values, but declared output is 1"
3506 );
3507 }
3508
3509 #[test]
3510 fn test_pollution_branch_consumes_extra() {
3511 let program = Program {
3515 includes: vec![],
3516 unions: vec![],
3517 words: vec![WordDef {
3518 name: "test".to_string(),
3519 effect: Some(Effect::new(
3520 StackType::Empty.push(Type::Bool).push(Type::Int),
3521 StackType::singleton(Type::Int),
3522 )),
3523 body: vec![Statement::If {
3524 then_branch: vec![
3525 Statement::WordCall {
3526 name: "drop".to_string(),
3527 span: None,
3528 },
3529 Statement::WordCall {
3530 name: "drop".to_string(),
3531 span: None,
3532 },
3533 Statement::IntLiteral(1),
3534 ],
3535 else_branch: Some(vec![]),
3536 }],
3537 source: None,
3538 allowed_lints: vec![],
3539 }],
3540 };
3541
3542 let mut checker = TypeChecker::new();
3543 let result = checker.check_program(&program);
3544 assert!(
3545 result.is_err(),
3546 "Should reject: then branch consumes Bool (should only have Int after if)"
3547 );
3548 }
3549
3550 #[test]
3551 fn test_pollution_quotation_wrong_arity_output() {
3552 let program = Program {
3556 includes: vec![],
3557 unions: vec![],
3558 words: vec![WordDef {
3559 name: "test".to_string(),
3560 effect: Some(Effect::new(
3561 StackType::singleton(Type::Int),
3562 StackType::singleton(Type::Int),
3563 )),
3564 body: vec![
3565 Statement::Quotation {
3566 span: None,
3567 id: 0,
3568 body: vec![Statement::WordCall {
3569 name: "dup".to_string(),
3570 span: None,
3571 }],
3572 },
3573 Statement::WordCall {
3574 name: "call".to_string(),
3575 span: None,
3576 },
3577 ],
3578 source: None,
3579 allowed_lints: vec![],
3580 }],
3581 };
3582
3583 let mut checker = TypeChecker::new();
3584 let result = checker.check_program(&program);
3585 assert!(
3586 result.is_err(),
3587 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3588 );
3589 }
3590
3591 #[test]
3592 fn test_pollution_quotation_wrong_arity_input() {
3593 let program = Program {
3597 includes: vec![],
3598 unions: vec![],
3599 words: vec![WordDef {
3600 name: "test".to_string(),
3601 effect: Some(Effect::new(
3602 StackType::singleton(Type::Int),
3603 StackType::singleton(Type::Int),
3604 )),
3605 body: vec![
3606 Statement::Quotation {
3607 span: None,
3608 id: 0,
3609 body: vec![
3610 Statement::WordCall {
3611 name: "drop".to_string(),
3612 span: None,
3613 },
3614 Statement::WordCall {
3615 name: "drop".to_string(),
3616 span: None,
3617 },
3618 Statement::IntLiteral(42),
3619 ],
3620 },
3621 Statement::WordCall {
3622 name: "call".to_string(),
3623 span: None,
3624 },
3625 ],
3626 source: None,
3627 allowed_lints: vec![],
3628 }],
3629 };
3630
3631 let mut checker = TypeChecker::new();
3632 let result = checker.check_program(&program);
3633 assert!(
3634 result.is_err(),
3635 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3636 );
3637 }
3638
3639 #[test]
3640 fn test_missing_effect_provides_helpful_error() {
3641 let program = Program {
3644 includes: vec![],
3645 unions: vec![],
3646 words: vec![WordDef {
3647 name: "myword".to_string(),
3648 effect: None, body: vec![Statement::IntLiteral(42)],
3650 source: None,
3651 allowed_lints: vec![],
3652 }],
3653 };
3654
3655 let mut checker = TypeChecker::new();
3656 let result = checker.check_program(&program);
3657 assert!(result.is_err());
3658 let err = result.unwrap_err();
3659 assert!(err.contains("myword"), "Error should mention word name");
3660 assert!(
3661 err.contains("stack effect"),
3662 "Error should mention stack effect"
3663 );
3664 }
3665
3666 #[test]
3667 fn test_valid_effect_exact_match() {
3668 let program = Program {
3671 includes: vec![],
3672 unions: vec![],
3673 words: vec![WordDef {
3674 name: "test".to_string(),
3675 effect: Some(Effect::new(
3676 StackType::Empty.push(Type::Int).push(Type::Int),
3677 StackType::singleton(Type::Int),
3678 )),
3679 body: vec![Statement::WordCall {
3680 name: "i.add".to_string(),
3681 span: None,
3682 }],
3683 source: None,
3684 allowed_lints: vec![],
3685 }],
3686 };
3687
3688 let mut checker = TypeChecker::new();
3689 let result = checker.check_program(&program);
3690 assert!(result.is_ok(), "Should accept: effect matches exactly");
3691 }
3692
3693 #[test]
3694 fn test_valid_polymorphic_passthrough() {
3695 let program = Program {
3698 includes: vec![],
3699 unions: vec![],
3700 words: vec![WordDef {
3701 name: "test".to_string(),
3702 effect: Some(Effect::new(
3703 StackType::Cons {
3704 rest: Box::new(StackType::RowVar("rest".to_string())),
3705 top: Type::Var("a".to_string()),
3706 },
3707 StackType::Cons {
3708 rest: Box::new(StackType::RowVar("rest".to_string())),
3709 top: Type::Var("a".to_string()),
3710 },
3711 )),
3712 body: vec![], source: None,
3714 allowed_lints: vec![],
3715 }],
3716 };
3717
3718 let mut checker = TypeChecker::new();
3719 let result = checker.check_program(&program);
3720 assert!(result.is_ok(), "Should accept: polymorphic identity");
3721 }
3722
3723 #[test]
3729 fn test_closure_basic_capture() {
3730 let program = Program {
3736 includes: vec![],
3737 unions: vec![],
3738 words: vec![WordDef {
3739 name: "make-adder".to_string(),
3740 effect: Some(Effect::new(
3741 StackType::singleton(Type::Int),
3742 StackType::singleton(Type::Closure {
3743 effect: Box::new(Effect::new(
3744 StackType::RowVar("r".to_string()).push(Type::Int),
3745 StackType::RowVar("r".to_string()).push(Type::Int),
3746 )),
3747 captures: vec![Type::Int], }),
3749 )),
3750 body: vec![Statement::Quotation {
3751 span: None,
3752 id: 0,
3753 body: vec![Statement::WordCall {
3754 name: "i.add".to_string(),
3755 span: None,
3756 }],
3757 }],
3758 source: None,
3759 allowed_lints: vec![],
3760 }],
3761 };
3762
3763 let mut checker = TypeChecker::new();
3764 let result = checker.check_program(&program);
3765 assert!(
3766 result.is_ok(),
3767 "Basic closure capture should work: {:?}",
3768 result.err()
3769 );
3770 }
3771
3772 #[test]
3773 fn test_closure_nested_two_levels() {
3774 let program = Program {
3779 includes: vec![],
3780 unions: vec![],
3781 words: vec![WordDef {
3782 name: "outer".to_string(),
3783 effect: Some(Effect::new(
3784 StackType::Empty,
3785 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3786 StackType::RowVar("r".to_string()),
3787 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3788 Effect::new(
3789 StackType::RowVar("s".to_string()).push(Type::Int),
3790 StackType::RowVar("s".to_string()).push(Type::Int),
3791 ),
3792 ))),
3793 )))),
3794 )),
3795 body: vec![Statement::Quotation {
3796 span: None,
3797 id: 0,
3798 body: vec![Statement::Quotation {
3799 span: None,
3800 id: 1,
3801 body: vec![
3802 Statement::IntLiteral(1),
3803 Statement::WordCall {
3804 name: "i.add".to_string(),
3805 span: None,
3806 },
3807 ],
3808 }],
3809 }],
3810 source: None,
3811 allowed_lints: vec![],
3812 }],
3813 };
3814
3815 let mut checker = TypeChecker::new();
3816 let result = checker.check_program(&program);
3817 assert!(
3818 result.is_ok(),
3819 "Two-level nested quotations should work: {:?}",
3820 result.err()
3821 );
3822 }
3823
3824 #[test]
3825 fn test_closure_nested_three_levels() {
3826 let inner_effect = Effect::new(
3830 StackType::RowVar("a".to_string()).push(Type::Int),
3831 StackType::RowVar("a".to_string()).push(Type::Int),
3832 );
3833 let middle_effect = Effect::new(
3834 StackType::RowVar("b".to_string()),
3835 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3836 );
3837 let outer_effect = Effect::new(
3838 StackType::RowVar("c".to_string()),
3839 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3840 );
3841
3842 let program = Program {
3843 includes: vec![],
3844 unions: vec![],
3845 words: vec![WordDef {
3846 name: "deep".to_string(),
3847 effect: Some(Effect::new(
3848 StackType::Empty,
3849 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3850 )),
3851 body: vec![Statement::Quotation {
3852 span: None,
3853 id: 0,
3854 body: vec![Statement::Quotation {
3855 span: None,
3856 id: 1,
3857 body: vec![Statement::Quotation {
3858 span: None,
3859 id: 2,
3860 body: vec![
3861 Statement::IntLiteral(1),
3862 Statement::WordCall {
3863 name: "i.add".to_string(),
3864 span: None,
3865 },
3866 ],
3867 }],
3868 }],
3869 }],
3870 source: None,
3871 allowed_lints: vec![],
3872 }],
3873 };
3874
3875 let mut checker = TypeChecker::new();
3876 let result = checker.check_program(&program);
3877 assert!(
3878 result.is_ok(),
3879 "Three-level nested quotations should work: {:?}",
3880 result.err()
3881 );
3882 }
3883
3884 #[test]
3885 fn test_closure_use_after_creation() {
3886 let adder_type = Type::Closure {
3892 effect: Box::new(Effect::new(
3893 StackType::RowVar("r".to_string()).push(Type::Int),
3894 StackType::RowVar("r".to_string()).push(Type::Int),
3895 )),
3896 captures: vec![Type::Int],
3897 };
3898
3899 let program = Program {
3900 includes: vec![],
3901 unions: vec![],
3902 words: vec![
3903 WordDef {
3904 name: "make-adder".to_string(),
3905 effect: Some(Effect::new(
3906 StackType::singleton(Type::Int),
3907 StackType::singleton(adder_type.clone()),
3908 )),
3909 body: vec![Statement::Quotation {
3910 span: None,
3911 id: 0,
3912 body: vec![Statement::WordCall {
3913 name: "i.add".to_string(),
3914 span: None,
3915 }],
3916 }],
3917 source: None,
3918 allowed_lints: vec![],
3919 },
3920 WordDef {
3921 name: "use-adder".to_string(),
3922 effect: Some(Effect::new(
3923 StackType::Empty,
3924 StackType::singleton(Type::Int),
3925 )),
3926 body: vec![
3927 Statement::IntLiteral(5),
3928 Statement::WordCall {
3929 name: "make-adder".to_string(),
3930 span: None,
3931 },
3932 Statement::IntLiteral(10),
3933 Statement::WordCall {
3934 name: "swap".to_string(),
3935 span: None,
3936 },
3937 Statement::WordCall {
3938 name: "call".to_string(),
3939 span: None,
3940 },
3941 ],
3942 source: None,
3943 allowed_lints: vec![],
3944 },
3945 ],
3946 };
3947
3948 let mut checker = TypeChecker::new();
3949 let result = checker.check_program(&program);
3950 assert!(
3951 result.is_ok(),
3952 "Closure usage after creation should work: {:?}",
3953 result.err()
3954 );
3955 }
3956
3957 #[test]
3958 fn test_closure_wrong_call_type() {
3959 let adder_type = Type::Closure {
3963 effect: Box::new(Effect::new(
3964 StackType::RowVar("r".to_string()).push(Type::Int),
3965 StackType::RowVar("r".to_string()).push(Type::Int),
3966 )),
3967 captures: vec![Type::Int],
3968 };
3969
3970 let program = Program {
3971 includes: vec![],
3972 unions: vec![],
3973 words: vec![
3974 WordDef {
3975 name: "make-adder".to_string(),
3976 effect: Some(Effect::new(
3977 StackType::singleton(Type::Int),
3978 StackType::singleton(adder_type.clone()),
3979 )),
3980 body: vec![Statement::Quotation {
3981 span: None,
3982 id: 0,
3983 body: vec![Statement::WordCall {
3984 name: "i.add".to_string(),
3985 span: None,
3986 }],
3987 }],
3988 source: None,
3989 allowed_lints: vec![],
3990 },
3991 WordDef {
3992 name: "bad-use".to_string(),
3993 effect: Some(Effect::new(
3994 StackType::Empty,
3995 StackType::singleton(Type::Int),
3996 )),
3997 body: vec![
3998 Statement::IntLiteral(5),
3999 Statement::WordCall {
4000 name: "make-adder".to_string(),
4001 span: None,
4002 },
4003 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
4005 name: "swap".to_string(),
4006 span: None,
4007 },
4008 Statement::WordCall {
4009 name: "call".to_string(),
4010 span: None,
4011 },
4012 ],
4013 source: None,
4014 allowed_lints: vec![],
4015 },
4016 ],
4017 };
4018
4019 let mut checker = TypeChecker::new();
4020 let result = checker.check_program(&program);
4021 assert!(
4022 result.is_err(),
4023 "Calling Int closure with String should fail"
4024 );
4025 }
4026
4027 #[test]
4028 fn test_closure_multiple_captures() {
4029 let program = Program {
4036 includes: vec![],
4037 unions: vec![],
4038 words: vec![WordDef {
4039 name: "make-between".to_string(),
4040 effect: Some(Effect::new(
4041 StackType::Empty.push(Type::Int).push(Type::Int),
4042 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4043 StackType::RowVar("r".to_string()).push(Type::Int),
4044 StackType::RowVar("r".to_string()).push(Type::Bool),
4045 )))),
4046 )),
4047 body: vec![Statement::Quotation {
4048 span: None,
4049 id: 0,
4050 body: vec![
4051 Statement::WordCall {
4053 name: "i.>=".to_string(),
4054 span: None,
4055 },
4056 ],
4058 }],
4059 source: None,
4060 allowed_lints: vec![],
4061 }],
4062 };
4063
4064 let mut checker = TypeChecker::new();
4065 let result = checker.check_program(&program);
4066 assert!(
4070 result.is_ok() || result.is_err(),
4071 "Multiple captures should be handled (pass or fail gracefully)"
4072 );
4073 }
4074
4075 #[test]
4076 fn test_quotation_type_preserved_through_word() {
4077 let quot_type = Type::Quotation(Box::new(Effect::new(
4080 StackType::RowVar("r".to_string()).push(Type::Int),
4081 StackType::RowVar("r".to_string()).push(Type::Int),
4082 )));
4083
4084 let program = Program {
4085 includes: vec![],
4086 unions: vec![],
4087 words: vec![WordDef {
4088 name: "identity-quot".to_string(),
4089 effect: Some(Effect::new(
4090 StackType::singleton(quot_type.clone()),
4091 StackType::singleton(quot_type.clone()),
4092 )),
4093 body: vec![], source: None,
4095 allowed_lints: vec![],
4096 }],
4097 };
4098
4099 let mut checker = TypeChecker::new();
4100 let result = checker.check_program(&program);
4101 assert!(
4102 result.is_ok(),
4103 "Quotation type should be preserved through identity word: {:?}",
4104 result.err()
4105 );
4106 }
4107
4108 #[test]
4109 fn test_closure_captures_value_for_inner_quotation() {
4110 let closure_effect = Effect::new(
4116 StackType::RowVar("r".to_string()).push(Type::Int),
4117 StackType::RowVar("r".to_string()).push(Type::Int),
4118 );
4119
4120 let program = Program {
4121 includes: vec![],
4122 unions: vec![],
4123 words: vec![WordDef {
4124 name: "make-inner-adder".to_string(),
4125 effect: Some(Effect::new(
4126 StackType::singleton(Type::Int),
4127 StackType::singleton(Type::Closure {
4128 effect: Box::new(closure_effect),
4129 captures: vec![Type::Int],
4130 }),
4131 )),
4132 body: vec![Statement::Quotation {
4133 span: None,
4134 id: 0,
4135 body: vec![
4136 Statement::WordCall {
4138 name: "i.add".to_string(),
4139 span: None,
4140 },
4141 ],
4142 }],
4143 source: None,
4144 allowed_lints: vec![],
4145 }],
4146 };
4147
4148 let mut checker = TypeChecker::new();
4149 let result = checker.check_program(&program);
4150 assert!(
4151 result.is_ok(),
4152 "Closure with capture for inner work should pass: {:?}",
4153 result.err()
4154 );
4155 }
4156}