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, Option<usize>)>>,
35 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
39 call_graph: Option<CallGraph>,
42}
43
44impl TypeChecker {
45 pub fn new() -> Self {
46 TypeChecker {
47 env: HashMap::new(),
48 unions: HashMap::new(),
49 fresh_counter: std::cell::Cell::new(0),
50 quotation_types: std::cell::RefCell::new(HashMap::new()),
51 expected_quotation_type: std::cell::RefCell::new(None),
52 current_word: std::cell::RefCell::new(None),
53 statement_top_types: std::cell::RefCell::new(HashMap::new()),
54 call_graph: None,
55 }
56 }
57
58 pub fn set_call_graph(&mut self, call_graph: CallGraph) {
63 self.call_graph = Some(call_graph);
64 }
65
66 fn line_prefix(&self) -> String {
68 self.current_word
69 .borrow()
70 .as_ref()
71 .and_then(|(_, line)| line.map(|l| format!("at line {}: ", l + 1)))
72 .unwrap_or_default()
73 }
74
75 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
77 self.unions.get(name)
78 }
79
80 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
82 &self.unions
83 }
84
85 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
89 for (union_name, union_info) in &self.unions {
90 for variant in &union_info.variants {
91 if variant.name == variant_name {
92 return Some((union_name.as_str(), variant));
93 }
94 }
95 }
96 None
97 }
98
99 pub fn register_external_words(&mut self, words: &[(&str, &Effect)]) {
103 for (name, effect) in words {
104 self.env.insert(name.to_string(), (*effect).clone());
105 }
106 }
107
108 pub fn register_external_unions(&mut self, union_names: &[&str]) {
114 for name in union_names {
115 self.unions.insert(
118 name.to_string(),
119 UnionTypeInfo {
120 name: name.to_string(),
121 variants: vec![],
122 },
123 );
124 }
125 }
126
127 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
133 self.quotation_types.replace(HashMap::new())
134 }
135
136 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
139 self.statement_top_types.replace(HashMap::new())
140 }
141
142 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
145 match stack {
146 StackType::Cons { top, .. } => match top {
147 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
148 _ => None,
149 },
150 _ => None,
151 }
152 }
153
154 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
156 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
157 self.statement_top_types
158 .borrow_mut()
159 .insert((word_name.to_string(), stmt_index), top_type);
160 }
161 }
162
163 fn fresh_var(&self, prefix: &str) -> String {
165 let n = self.fresh_counter.get();
166 self.fresh_counter.set(n + 1);
167 format!("{}${}", prefix, n)
168 }
169
170 fn freshen_effect(&self, effect: &Effect) -> Effect {
172 let mut type_map = HashMap::new();
173 let mut row_map = HashMap::new();
174
175 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
176 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
177
178 let fresh_effects = effect
180 .effects
181 .iter()
182 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
183 .collect();
184
185 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
186 }
187
188 fn freshen_side_effect(
189 &self,
190 effect: &SideEffect,
191 type_map: &mut HashMap<String, String>,
192 row_map: &mut HashMap<String, String>,
193 ) -> SideEffect {
194 match effect {
195 SideEffect::Yield(ty) => {
196 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
197 }
198 }
199 }
200
201 fn freshen_stack(
202 &self,
203 stack: &StackType,
204 type_map: &mut HashMap<String, String>,
205 row_map: &mut HashMap<String, String>,
206 ) -> StackType {
207 match stack {
208 StackType::Empty => StackType::Empty,
209 StackType::RowVar(name) => {
210 let fresh_name = row_map
211 .entry(name.clone())
212 .or_insert_with(|| self.fresh_var(name));
213 StackType::RowVar(fresh_name.clone())
214 }
215 StackType::Cons { rest, top } => {
216 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
217 let fresh_top = self.freshen_type(top, type_map, row_map);
218 StackType::Cons {
219 rest: Box::new(fresh_rest),
220 top: fresh_top,
221 }
222 }
223 }
224 }
225
226 fn freshen_type(
227 &self,
228 ty: &Type,
229 type_map: &mut HashMap<String, String>,
230 row_map: &mut HashMap<String, String>,
231 ) -> Type {
232 match ty {
233 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
234 ty.clone()
235 }
236 Type::Var(name) => {
237 let fresh_name = type_map
238 .entry(name.clone())
239 .or_insert_with(|| self.fresh_var(name));
240 Type::Var(fresh_name.clone())
241 }
242 Type::Quotation(effect) => {
243 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
244 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
245 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
246 }
247 Type::Closure { effect, captures } => {
248 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
249 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
250 let fresh_captures = captures
251 .iter()
252 .map(|t| self.freshen_type(t, type_map, row_map))
253 .collect();
254 Type::Closure {
255 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
256 captures: fresh_captures,
257 }
258 }
259 Type::Union(name) => Type::Union(name.clone()),
261 }
262 }
263
264 fn parse_type_name(&self, name: &str) -> Type {
268 match name {
269 "Int" => Type::Int,
270 "Float" => Type::Float,
271 "Bool" => Type::Bool,
272 "String" => Type::String,
273 "Channel" => Type::Channel,
274 other => Type::Union(other.to_string()),
276 }
277 }
278
279 fn is_valid_type_name(&self, name: &str) -> bool {
284 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
285 || self.unions.contains_key(name)
286 }
287
288 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
292 for union_def in &program.unions {
293 for variant in &union_def.variants {
294 for field in &variant.fields {
295 if !self.is_valid_type_name(&field.type_name) {
296 return Err(format!(
297 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
298 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
299 field.type_name, field.name, variant.name, union_def.name
300 ));
301 }
302 }
303 }
304 }
305 Ok(())
306 }
307
308 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
310 for union_def in &program.unions {
312 let variants = union_def
313 .variants
314 .iter()
315 .map(|v| VariantInfo {
316 name: v.name.clone(),
317 fields: v
318 .fields
319 .iter()
320 .map(|f| VariantFieldInfo {
321 name: f.name.clone(),
322 field_type: self.parse_type_name(&f.type_name),
323 })
324 .collect(),
325 })
326 .collect();
327
328 self.unions.insert(
329 union_def.name.clone(),
330 UnionTypeInfo {
331 name: union_def.name.clone(),
332 variants,
333 },
334 );
335 }
336
337 self.validate_union_field_types(program)?;
339
340 for word in &program.words {
343 if let Some(effect) = &word.effect {
344 self.env.insert(word.name.clone(), effect.clone());
345 } else {
346 return Err(format!(
347 "Word '{}' is missing a stack effect declaration.\n\
348 All words must declare their stack effect, e.g.: : {} ( -- ) ... ;",
349 word.name, word.name
350 ));
351 }
352 }
353
354 for word in &program.words {
356 self.check_word(word)?;
357 }
358
359 Ok(())
360 }
361
362 fn check_word(&self, word: &WordDef) -> Result<(), String> {
364 let line = word.source.as_ref().map(|s| s.start_line);
366 *self.current_word.borrow_mut() = Some((word.name.clone(), line));
367
368 let declared_effect = word.effect.as_ref().expect("word must have effect");
370
371 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
374 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
375 {
376 *self.expected_quotation_type.borrow_mut() = Some(top_type);
377 }
378
379 let (result_stack, _subst, inferred_effects) =
381 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
382
383 *self.expected_quotation_type.borrow_mut() = None;
385
386 let line_info = line
388 .map(|l| format!("at line {}: ", l + 1))
389 .unwrap_or_default();
390 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
391 format!(
392 "{}Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
393 line_info, word.name, declared_effect.outputs, result_stack, e
394 )
395 })?;
396
397 for inferred in &inferred_effects {
401 if !self.effect_matches_any(inferred, &declared_effect.effects) {
402 return Err(format!(
403 "{}Word '{}': body produces effect '{}' but no matching effect is declared.\n\
404 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
405 line_info, word.name, inferred
406 ));
407 }
408 }
409
410 for declared in &declared_effect.effects {
413 if !self.effect_matches_any(declared, &inferred_effects) {
414 return Err(format!(
415 "{}Word '{}': declares effect '{}' but body doesn't produce it.\n\
416 Hint: Remove the effect declaration or ensure the body uses yield.",
417 line_info, word.name, declared
418 ));
419 }
420 }
421
422 *self.current_word.borrow_mut() = None;
424
425 Ok(())
426 }
427
428 fn infer_statements_from(
435 &self,
436 statements: &[Statement],
437 start_stack: &StackType,
438 capture_stmt_types: bool,
439 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
440 let mut current_stack = start_stack.clone();
441 let mut accumulated_subst = Subst::empty();
442 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
443 let mut skip_next = false;
444
445 for (i, stmt) in statements.iter().enumerate() {
446 if skip_next {
448 skip_next = false;
449 continue;
450 }
451
452 if let Statement::IntLiteral(n) = stmt
455 && let Some(Statement::WordCall {
456 name: next_word, ..
457 }) = statements.get(i + 1)
458 {
459 if next_word == "pick" {
460 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
461 current_stack = new_stack;
462 accumulated_subst = accumulated_subst.compose(&subst);
463 skip_next = true; continue;
465 } else if next_word == "roll" {
466 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
467 current_stack = new_stack;
468 accumulated_subst = accumulated_subst.compose(&subst);
469 skip_next = true; continue;
471 }
472 }
473
474 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
477 let saved = self.expected_quotation_type.borrow().clone();
479
480 if let Some(Statement::WordCall {
482 name: next_word, ..
483 }) = statements.get(i + 1)
484 {
485 if let Some(next_effect) = self.lookup_word_effect(next_word) {
487 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
490 && matches!(quot_type, Type::Quotation(_))
491 {
492 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
493 }
494 }
495 }
496 Some(saved)
497 } else {
498 None
499 };
500
501 if capture_stmt_types && let Some((word_name, _)) = self.current_word.borrow().as_ref()
505 {
506 self.capture_statement_type(word_name, i, ¤t_stack);
507 }
508
509 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
510 current_stack = new_stack;
511 accumulated_subst = accumulated_subst.compose(&subst);
512
513 for effect in effects {
515 if !accumulated_effects.contains(&effect) {
516 accumulated_effects.push(effect);
517 }
518 }
519
520 if let Some(saved) = saved_expected_type {
522 *self.expected_quotation_type.borrow_mut() = saved;
523 }
524 }
525
526 Ok((current_stack, accumulated_subst, accumulated_effects))
527 }
528
529 fn handle_literal_pick(
540 &self,
541 n: i64,
542 current_stack: StackType,
543 ) -> Result<(StackType, Subst), String> {
544 if n < 0 {
545 return Err(format!("pick: index must be non-negative, got {}", n));
546 }
547
548 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
550
551 Ok((current_stack.push(type_at_n), Subst::empty()))
553 }
554
555 fn handle_literal_roll(
566 &self,
567 n: i64,
568 current_stack: StackType,
569 ) -> Result<(StackType, Subst), String> {
570 if n < 0 {
571 return Err(format!("roll: index must be non-negative, got {}", n));
572 }
573
574 self.rotate_type_to_top(current_stack, n as usize)
579 }
580
581 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
583 let mut current = stack;
584 let mut pos = 0;
585
586 loop {
587 match current {
588 StackType::Cons { rest, top } => {
589 if pos == n {
590 return Ok(top.clone());
591 }
592 pos += 1;
593 current = rest;
594 }
595 StackType::RowVar(name) => {
596 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
606 return Ok(fresh_type);
607 }
608 StackType::Empty => {
609 return Err(format!(
610 "{}{}: stack underflow - position {} requested but stack has only {} concrete items",
611 self.line_prefix(),
612 op,
613 n,
614 pos
615 ));
616 }
617 }
618 }
619 }
620
621 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
623 if n == 0 {
624 return Ok((stack, Subst::empty()));
626 }
627
628 let mut types_above: Vec<Type> = Vec::new();
630 let mut current = stack;
631 let mut pos = 0;
632
633 loop {
635 match current {
636 StackType::Cons { rest, top } => {
637 if pos == n {
638 let mut result = *rest;
641 for ty in types_above.into_iter().rev() {
643 result = result.push(ty);
644 }
645 result = result.push(top);
647 return Ok((result, Subst::empty()));
648 }
649 types_above.push(top);
650 pos += 1;
651 current = *rest;
652 }
653 StackType::RowVar(name) => {
654 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
667
668 let mut result = StackType::RowVar(name.clone());
670 for ty in types_above.into_iter().rev() {
671 result = result.push(ty);
672 }
673 result = result.push(fresh_type);
674 return Ok((result, Subst::empty()));
675 }
676 StackType::Empty => {
677 return Err(format!(
678 "{}roll: stack underflow - position {} requested but stack has only {} items",
679 self.line_prefix(),
680 n,
681 pos
682 ));
683 }
684 }
685 }
686 }
687
688 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
692 let start = StackType::RowVar("input".to_string());
693 let (result, subst, effects) = self.infer_statements_from(statements, &start, false)?;
695
696 let normalized_start = subst.apply_stack(&start);
699 let normalized_result = subst.apply_stack(&result);
700
701 Ok(Effect::with_effects(
702 normalized_start,
703 normalized_result,
704 effects,
705 ))
706 }
707
708 fn infer_match(
710 &self,
711 arms: &[crate::ast::MatchArm],
712 current_stack: StackType,
713 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
714 if arms.is_empty() {
715 return Err("match expression must have at least one arm".to_string());
716 }
717
718 let (stack_after_match, _matched_type) =
720 self.pop_type(¤t_stack, "match expression")?;
721
722 let mut arm_results: Vec<StackType> = Vec::new();
724 let mut combined_subst = Subst::empty();
725 let mut merged_effects: Vec<SideEffect> = Vec::new();
726
727 for arm in arms {
728 let variant_name = match &arm.pattern {
730 crate::ast::Pattern::Variant(name) => name.as_str(),
731 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
732 };
733
734 let (_union_name, variant_info) = self
736 .find_variant(variant_name)
737 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
738
739 let arm_stack = self.push_variant_fields(
741 &stack_after_match,
742 &arm.pattern,
743 variant_info,
744 variant_name,
745 )?;
746
747 let (arm_result, arm_subst, arm_effects) =
750 self.infer_statements_from(&arm.body, &arm_stack, false)?;
751
752 combined_subst = combined_subst.compose(&arm_subst);
753 arm_results.push(arm_result);
754
755 for effect in arm_effects {
757 if !merged_effects.contains(&effect) {
758 merged_effects.push(effect);
759 }
760 }
761 }
762
763 let mut final_result = arm_results[0].clone();
765 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
766 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
767 format!(
768 "match arms have incompatible stack effects:\n\
769 \x20 arm 0 produces: {}\n\
770 \x20 arm {} produces: {}\n\
771 \x20 All match arms must produce the same stack shape.\n\
772 \x20 Error: {}",
773 final_result, i, arm_result, e
774 )
775 })?;
776 combined_subst = combined_subst.compose(&arm_subst);
777 final_result = arm_subst.apply_stack(&final_result);
778 }
779
780 Ok((final_result, combined_subst, merged_effects))
781 }
782
783 fn push_variant_fields(
785 &self,
786 stack: &StackType,
787 pattern: &crate::ast::Pattern,
788 variant_info: &VariantInfo,
789 variant_name: &str,
790 ) -> Result<StackType, String> {
791 let mut arm_stack = stack.clone();
792 match pattern {
793 crate::ast::Pattern::Variant(_) => {
794 for field in &variant_info.fields {
796 arm_stack = arm_stack.push(field.field_type.clone());
797 }
798 }
799 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
800 for binding in bindings {
802 let field = variant_info
803 .fields
804 .iter()
805 .find(|f| &f.name == binding)
806 .ok_or_else(|| {
807 let available: Vec<_> = variant_info
808 .fields
809 .iter()
810 .map(|f| f.name.as_str())
811 .collect();
812 format!(
813 "Unknown field '{}' in pattern for variant '{}'.\n\
814 Available fields: {}",
815 binding,
816 variant_name,
817 available.join(", ")
818 )
819 })?;
820 arm_stack = arm_stack.push(field.field_type.clone());
821 }
822 }
823 }
824 Ok(arm_stack)
825 }
826
827 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
848 let Some((current_word_name, _)) = self.current_word.borrow().as_ref().cloned() else {
849 return false;
850 };
851 let Some(Statement::WordCall { name, .. }) = statements.last() else {
852 return false;
853 };
854
855 if name == ¤t_word_name {
857 return true;
858 }
859
860 if let Some(ref graph) = self.call_graph
862 && graph.are_mutually_recursive(¤t_word_name, name)
863 {
864 return true;
865 }
866
867 false
868 }
869
870 fn infer_if(
872 &self,
873 then_branch: &[Statement],
874 else_branch: &Option<Vec<Statement>>,
875 current_stack: StackType,
876 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
877 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
879
880 let cond_subst = unify_stacks(
882 &StackType::singleton(Type::Bool),
883 &StackType::singleton(cond_type),
884 )
885 .map_err(|e| format!("if condition must be Bool: {}", e))?;
886
887 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
888
889 let then_diverges = self.is_divergent_branch(then_branch);
891 let else_diverges = else_branch
892 .as_ref()
893 .map(|stmts| self.is_divergent_branch(stmts))
894 .unwrap_or(false);
895
896 let (then_result, then_subst, then_effects) =
899 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
900
901 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
903 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
904 } else {
905 (stack_after_cond.clone(), Subst::empty(), vec![])
906 };
907
908 let mut merged_effects = then_effects;
910 for effect in else_effects {
911 if !merged_effects.contains(&effect) {
912 merged_effects.push(effect);
913 }
914 }
915
916 let (result, branch_subst) = if then_diverges && !else_diverges {
922 (else_result, Subst::empty())
924 } else if else_diverges && !then_diverges {
925 (then_result, Subst::empty())
927 } else {
928 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
930 format!(
931 "if/else branches have incompatible stack effects:\n\
932 \x20 then branch produces: {}\n\
933 \x20 else branch produces: {}\n\
934 \x20 Both branches of an if/else must produce the same stack shape.\n\
935 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
936 \x20 Error: {}",
937 then_result, else_result, e
938 )
939 })?;
940 (branch_subst.apply_stack(&then_result), branch_subst)
941 };
942
943 let total_subst = cond_subst
945 .compose(&then_subst)
946 .compose(&else_subst)
947 .compose(&branch_subst);
948 Ok((result, total_subst, merged_effects))
949 }
950
951 fn infer_quotation(
954 &self,
955 id: usize,
956 body: &[Statement],
957 current_stack: StackType,
958 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
959 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
962 *self.expected_quotation_type.borrow_mut() = None;
963
964 let body_effect = self.infer_statements(body)?;
966
967 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
969
970 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
972
973 self.quotation_types
975 .borrow_mut()
976 .insert(id, quot_type.clone());
977
978 let result_stack = match "_type {
980 Type::Quotation(_) => {
981 current_stack.push(quot_type)
983 }
984 Type::Closure { captures, .. } => {
985 let mut stack = current_stack.clone();
987 for _ in 0..captures.len() {
988 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
989 stack = new_stack;
990 }
991 stack.push(quot_type)
992 }
993 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
994 };
995
996 Ok((result_stack, Subst::empty(), vec![]))
1000 }
1001
1002 fn infer_word_call(
1004 &self,
1005 name: &str,
1006 current_stack: StackType,
1007 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1008 if name == "call" {
1011 return self.infer_call(current_stack);
1012 }
1013
1014 let effect = self
1016 .lookup_word_effect(name)
1017 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
1018
1019 let fresh_effect = self.freshen_effect(&effect);
1021
1022 let adjusted_stack = if name == "strand.spawn" {
1024 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1025 } else {
1026 current_stack
1027 };
1028
1029 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name)?;
1031
1032 let propagated_effects = fresh_effect.effects.clone();
1036
1037 Ok((result_stack, subst, propagated_effects))
1038 }
1039
1040 fn infer_call(
1046 &self,
1047 current_stack: StackType,
1048 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1049 let line_prefix = self.line_prefix();
1051 let (remaining_stack, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1052 format!(
1053 "{}call: stack underflow - expected quotation on stack",
1054 line_prefix
1055 )
1056 })?;
1057
1058 let quot_effect = match "_type {
1060 Type::Quotation(effect) => (**effect).clone(),
1061 Type::Closure { effect, .. } => (**effect).clone(),
1062 Type::Var(_) => {
1063 let effect = self
1066 .lookup_word_effect("call")
1067 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1068 let fresh_effect = self.freshen_effect(&effect);
1069 let (result_stack, subst) =
1070 self.apply_effect(&fresh_effect, current_stack, "call")?;
1071 return Ok((result_stack, subst, vec![]));
1072 }
1073 _ => {
1074 return Err(format!(
1075 "call: expected quotation or closure on stack, got {}",
1076 quot_type
1077 ));
1078 }
1079 };
1080
1081 if quot_effect.has_yield() {
1083 return Err("Cannot call quotation with Yield effect directly.\n\
1084 Quotations that yield values must be wrapped with `strand.weave`.\n\
1085 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1086 .to_string());
1087 }
1088
1089 let fresh_effect = self.freshen_effect("_effect);
1091
1092 let (result_stack, subst) = self.apply_effect(&fresh_effect, remaining_stack, "call")?;
1094
1095 let propagated_effects = fresh_effect.effects.clone();
1097
1098 Ok((result_stack, subst, propagated_effects))
1099 }
1100
1101 fn infer_statement(
1104 &self,
1105 statement: &Statement,
1106 current_stack: StackType,
1107 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1108 match statement {
1109 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1110 Statement::BoolLiteral(_) => {
1111 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1112 }
1113 Statement::StringLiteral(_) => {
1114 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1115 }
1116 Statement::FloatLiteral(_) => {
1117 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1118 }
1119 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1120 Statement::Match { arms } => self.infer_match(arms, current_stack),
1121 Statement::WordCall { name, .. } => self.infer_word_call(name, current_stack),
1122 Statement::If {
1123 then_branch,
1124 else_branch,
1125 } => self.infer_if(then_branch, else_branch, current_stack),
1126 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1127 }
1128 }
1129
1130 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1132 if let Some(effect) = builtin_signature(name) {
1134 return Some(effect);
1135 }
1136
1137 self.env.get(name).cloned()
1139 }
1140
1141 fn apply_effect(
1146 &self,
1147 effect: &Effect,
1148 current_stack: StackType,
1149 operation: &str,
1150 ) -> Result<(StackType, Subst), String> {
1151 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1159 let stack_concrete = Self::count_concrete_types(¤t_stack);
1160
1161 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1162 let is_rigid = row_var_name == "rest";
1175
1176 if is_rigid && effect_concrete > stack_concrete {
1177 let word_name = self
1178 .current_word
1179 .borrow()
1180 .as_ref()
1181 .map(|(n, _)| n.clone())
1182 .unwrap_or_else(|| "unknown".to_string());
1183 return Err(format!(
1184 "{}In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1185 self.line_prefix(),
1186 word_name,
1187 operation,
1188 effect_concrete,
1189 stack_concrete
1190 ));
1191 }
1192 }
1193
1194 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1196 format!(
1197 "{}: stack type mismatch. Expected {}, got {}: {}",
1198 operation, effect.inputs, current_stack, e
1199 )
1200 })?;
1201
1202 let result_stack = subst.apply_stack(&effect.outputs);
1204
1205 Ok((result_stack, subst))
1206 }
1207
1208 fn count_concrete_types(stack: &StackType) -> usize {
1210 let mut count = 0;
1211 let mut current = stack;
1212 while let StackType::Cons { rest, top: _ } = current {
1213 count += 1;
1214 current = rest;
1215 }
1216 count
1217 }
1218
1219 fn get_row_var_base(stack: &StackType) -> Option<String> {
1221 let mut current = stack;
1222 while let StackType::Cons { rest, top: _ } = current {
1223 current = rest;
1224 }
1225 match current {
1226 StackType::RowVar(name) => Some(name.clone()),
1227 _ => None,
1228 }
1229 }
1230
1231 fn adjust_stack_for_spawn(
1236 &self,
1237 current_stack: StackType,
1238 spawn_effect: &Effect,
1239 ) -> Result<StackType, String> {
1240 let expected_quot_type = match &spawn_effect.inputs {
1243 StackType::Cons { top, rest: _ } => {
1244 if !matches!(top, Type::Quotation(_)) {
1245 return Ok(current_stack); }
1247 top
1248 }
1249 _ => return Ok(current_stack),
1250 };
1251
1252 let (rest_stack, actual_type) = match ¤t_stack {
1254 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1255 _ => return Ok(current_stack), };
1257
1258 if let Type::Quotation(actual_effect) = actual_type {
1260 if !matches!(actual_effect.inputs, StackType::Empty) {
1262 let expected_effect = match expected_quot_type {
1264 Type::Quotation(eff) => eff.as_ref(),
1265 _ => return Ok(current_stack),
1266 };
1267
1268 let captures = calculate_captures(actual_effect, expected_effect)?;
1270
1271 let closure_type = Type::Closure {
1273 effect: Box::new(expected_effect.clone()),
1274 captures: captures.clone(),
1275 };
1276
1277 let mut adjusted_stack = rest_stack;
1280 for _ in &captures {
1281 adjusted_stack = match adjusted_stack {
1282 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1283 _ => {
1284 return Err(format!(
1285 "strand.spawn: not enough values on stack to capture. Need {} values",
1286 captures.len()
1287 ));
1288 }
1289 };
1290 }
1291
1292 return Ok(adjusted_stack.push(closure_type));
1294 }
1295 }
1296
1297 Ok(current_stack)
1298 }
1299
1300 fn analyze_captures(
1326 &self,
1327 body_effect: &Effect,
1328 _current_stack: &StackType,
1329 ) -> Result<Type, String> {
1330 let expected = self.expected_quotation_type.borrow().clone();
1332
1333 match expected {
1334 Some(Type::Closure { effect, .. }) => {
1335 let captures = calculate_captures(body_effect, &effect)?;
1337 Ok(Type::Closure { effect, captures })
1338 }
1339 Some(Type::Quotation(expected_effect)) => {
1340 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1346 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1347
1348 if expected_is_empty && body_needs_inputs {
1349 let captures = calculate_captures(body_effect, &expected_effect)?;
1352 Ok(Type::Closure {
1353 effect: expected_effect,
1354 captures,
1355 })
1356 } else {
1357 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1363 let expected_quot = Type::Quotation(expected_effect.clone());
1364 unify_types(&body_quot, &expected_quot).map_err(|e| {
1365 format!(
1366 "quotation effect mismatch: expected {}, got {}: {}",
1367 expected_effect, body_effect, e
1368 )
1369 })?;
1370
1371 Ok(Type::Quotation(expected_effect))
1373 }
1374 }
1375 _ => {
1376 Ok(Type::Quotation(Box::new(body_effect.clone())))
1378 }
1379 }
1380 }
1381
1382 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1386 declared.iter().any(|decl| match (inferred, decl) {
1387 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1388 })
1389 }
1390
1391 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1393 match stack {
1394 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1395 StackType::Empty => Err(format!(
1396 "{}: stack underflow - expected value on stack but stack is empty",
1397 context
1398 )),
1399 StackType::RowVar(_) => {
1400 Err(format!(
1404 "{}: cannot pop from polymorphic stack without more type information",
1405 context
1406 ))
1407 }
1408 }
1409 }
1410}
1411
1412impl Default for TypeChecker {
1413 fn default() -> Self {
1414 Self::new()
1415 }
1416}
1417
1418#[cfg(test)]
1419mod tests {
1420 use super::*;
1421
1422 #[test]
1423 fn test_simple_literal() {
1424 let program = Program {
1425 includes: vec![],
1426 unions: vec![],
1427 words: vec![WordDef {
1428 name: "test".to_string(),
1429 effect: Some(Effect::new(
1430 StackType::Empty,
1431 StackType::singleton(Type::Int),
1432 )),
1433 body: vec![Statement::IntLiteral(42)],
1434 source: None,
1435 allowed_lints: vec![],
1436 }],
1437 };
1438
1439 let mut checker = TypeChecker::new();
1440 assert!(checker.check_program(&program).is_ok());
1441 }
1442
1443 #[test]
1444 fn test_simple_operation() {
1445 let program = Program {
1447 includes: vec![],
1448 unions: vec![],
1449 words: vec![WordDef {
1450 name: "test".to_string(),
1451 effect: Some(Effect::new(
1452 StackType::Empty.push(Type::Int).push(Type::Int),
1453 StackType::singleton(Type::Int),
1454 )),
1455 body: vec![Statement::WordCall {
1456 name: "i.add".to_string(),
1457 span: None,
1458 }],
1459 source: None,
1460 allowed_lints: vec![],
1461 }],
1462 };
1463
1464 let mut checker = TypeChecker::new();
1465 assert!(checker.check_program(&program).is_ok());
1466 }
1467
1468 #[test]
1469 fn test_type_mismatch() {
1470 let program = Program {
1472 includes: vec![],
1473 unions: vec![],
1474 words: vec![WordDef {
1475 name: "test".to_string(),
1476 effect: Some(Effect::new(
1477 StackType::singleton(Type::String),
1478 StackType::Empty,
1479 )),
1480 body: vec![
1481 Statement::IntLiteral(42), Statement::WordCall {
1483 name: "io.write-line".to_string(),
1484 span: None,
1485 },
1486 ],
1487 source: None,
1488 allowed_lints: vec![],
1489 }],
1490 };
1491
1492 let mut checker = TypeChecker::new();
1493 let result = checker.check_program(&program);
1494 assert!(result.is_err());
1495 assert!(result.unwrap_err().contains("Type mismatch"));
1496 }
1497
1498 #[test]
1499 fn test_polymorphic_dup() {
1500 let program = Program {
1502 includes: vec![],
1503 unions: vec![],
1504 words: vec![WordDef {
1505 name: "my-dup".to_string(),
1506 effect: Some(Effect::new(
1507 StackType::singleton(Type::Int),
1508 StackType::Empty.push(Type::Int).push(Type::Int),
1509 )),
1510 body: vec![Statement::WordCall {
1511 name: "dup".to_string(),
1512 span: None,
1513 }],
1514 source: None,
1515 allowed_lints: vec![],
1516 }],
1517 };
1518
1519 let mut checker = TypeChecker::new();
1520 assert!(checker.check_program(&program).is_ok());
1521 }
1522
1523 #[test]
1524 fn test_conditional_branches() {
1525 let program = Program {
1528 includes: vec![],
1529 unions: vec![],
1530 words: vec![WordDef {
1531 name: "test".to_string(),
1532 effect: Some(Effect::new(
1533 StackType::Empty.push(Type::Int).push(Type::Int),
1534 StackType::singleton(Type::String),
1535 )),
1536 body: vec![
1537 Statement::WordCall {
1538 name: "i.>".to_string(),
1539 span: None,
1540 },
1541 Statement::If {
1542 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1543 else_branch: Some(vec![Statement::StringLiteral(
1544 "not greater".to_string(),
1545 )]),
1546 },
1547 ],
1548 source: None,
1549 allowed_lints: vec![],
1550 }],
1551 };
1552
1553 let mut checker = TypeChecker::new();
1554 assert!(checker.check_program(&program).is_ok());
1555 }
1556
1557 #[test]
1558 fn test_mismatched_branches() {
1559 let program = Program {
1562 includes: vec![],
1563 unions: vec![],
1564 words: vec![WordDef {
1565 name: "test".to_string(),
1566 effect: Some(Effect::new(
1567 StackType::Empty,
1568 StackType::singleton(Type::Int),
1569 )),
1570 body: vec![
1571 Statement::BoolLiteral(true),
1572 Statement::If {
1573 then_branch: vec![Statement::IntLiteral(42)],
1574 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1575 },
1576 ],
1577 source: None,
1578 allowed_lints: vec![],
1579 }],
1580 };
1581
1582 let mut checker = TypeChecker::new();
1583 let result = checker.check_program(&program);
1584 assert!(result.is_err());
1585 assert!(result.unwrap_err().contains("incompatible"));
1586 }
1587
1588 #[test]
1589 fn test_user_defined_word_call() {
1590 let program = Program {
1593 includes: vec![],
1594 unions: vec![],
1595 words: vec![
1596 WordDef {
1597 name: "helper".to_string(),
1598 effect: Some(Effect::new(
1599 StackType::singleton(Type::Int),
1600 StackType::singleton(Type::String),
1601 )),
1602 body: vec![Statement::WordCall {
1603 name: "int->string".to_string(),
1604 span: None,
1605 }],
1606 source: None,
1607 allowed_lints: vec![],
1608 },
1609 WordDef {
1610 name: "main".to_string(),
1611 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1612 body: vec![
1613 Statement::IntLiteral(42),
1614 Statement::WordCall {
1615 name: "helper".to_string(),
1616 span: None,
1617 },
1618 Statement::WordCall {
1619 name: "io.write-line".to_string(),
1620 span: None,
1621 },
1622 ],
1623 source: None,
1624 allowed_lints: vec![],
1625 },
1626 ],
1627 };
1628
1629 let mut checker = TypeChecker::new();
1630 assert!(checker.check_program(&program).is_ok());
1631 }
1632
1633 #[test]
1634 fn test_arithmetic_chain() {
1635 let program = Program {
1638 includes: vec![],
1639 unions: vec![],
1640 words: vec![WordDef {
1641 name: "test".to_string(),
1642 effect: Some(Effect::new(
1643 StackType::Empty
1644 .push(Type::Int)
1645 .push(Type::Int)
1646 .push(Type::Int),
1647 StackType::singleton(Type::Int),
1648 )),
1649 body: vec![
1650 Statement::WordCall {
1651 name: "i.add".to_string(),
1652 span: None,
1653 },
1654 Statement::WordCall {
1655 name: "i.multiply".to_string(),
1656 span: None,
1657 },
1658 ],
1659 source: None,
1660 allowed_lints: vec![],
1661 }],
1662 };
1663
1664 let mut checker = TypeChecker::new();
1665 assert!(checker.check_program(&program).is_ok());
1666 }
1667
1668 #[test]
1669 fn test_write_line_type_error() {
1670 let program = Program {
1672 includes: vec![],
1673 unions: vec![],
1674 words: vec![WordDef {
1675 name: "test".to_string(),
1676 effect: Some(Effect::new(
1677 StackType::singleton(Type::Int),
1678 StackType::Empty,
1679 )),
1680 body: vec![Statement::WordCall {
1681 name: "io.write-line".to_string(),
1682 span: None,
1683 }],
1684 source: None,
1685 allowed_lints: vec![],
1686 }],
1687 };
1688
1689 let mut checker = TypeChecker::new();
1690 let result = checker.check_program(&program);
1691 assert!(result.is_err());
1692 assert!(result.unwrap_err().contains("Type mismatch"));
1693 }
1694
1695 #[test]
1696 fn test_stack_underflow_drop() {
1697 let program = Program {
1699 includes: vec![],
1700 unions: vec![],
1701 words: vec![WordDef {
1702 name: "test".to_string(),
1703 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1704 body: vec![Statement::WordCall {
1705 name: "drop".to_string(),
1706 span: None,
1707 }],
1708 source: None,
1709 allowed_lints: vec![],
1710 }],
1711 };
1712
1713 let mut checker = TypeChecker::new();
1714 let result = checker.check_program(&program);
1715 assert!(result.is_err());
1716 assert!(result.unwrap_err().contains("mismatch"));
1717 }
1718
1719 #[test]
1720 fn test_stack_underflow_add() {
1721 let program = Program {
1723 includes: vec![],
1724 unions: vec![],
1725 words: vec![WordDef {
1726 name: "test".to_string(),
1727 effect: Some(Effect::new(
1728 StackType::singleton(Type::Int),
1729 StackType::singleton(Type::Int),
1730 )),
1731 body: vec![Statement::WordCall {
1732 name: "i.add".to_string(),
1733 span: None,
1734 }],
1735 source: None,
1736 allowed_lints: vec![],
1737 }],
1738 };
1739
1740 let mut checker = TypeChecker::new();
1741 let result = checker.check_program(&program);
1742 assert!(result.is_err());
1743 assert!(result.unwrap_err().contains("mismatch"));
1744 }
1745
1746 #[test]
1749 fn test_stack_underflow_rot_issue_169() {
1750 let program = Program {
1754 includes: vec![],
1755 unions: vec![],
1756 words: vec![WordDef {
1757 name: "test".to_string(),
1758 effect: Some(Effect::new(
1759 StackType::RowVar("rest".to_string()),
1760 StackType::RowVar("rest".to_string()),
1761 )),
1762 body: vec![
1763 Statement::IntLiteral(3),
1764 Statement::IntLiteral(4),
1765 Statement::WordCall {
1766 name: "rot".to_string(),
1767 span: None,
1768 },
1769 ],
1770 source: None,
1771 allowed_lints: vec![],
1772 }],
1773 };
1774
1775 let mut checker = TypeChecker::new();
1776 let result = checker.check_program(&program);
1777 assert!(result.is_err(), "rot with 2 values should fail");
1778 let err = result.unwrap_err();
1779 assert!(
1780 err.contains("stack underflow") || err.contains("requires 3"),
1781 "Error should mention underflow: {}",
1782 err
1783 );
1784 }
1785
1786 #[test]
1787 fn test_csp_operations() {
1788 let program = Program {
1795 includes: vec![],
1796 unions: vec![],
1797 words: vec![WordDef {
1798 name: "test".to_string(),
1799 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1800 body: vec![
1801 Statement::WordCall {
1802 name: "chan.make".to_string(),
1803 span: None,
1804 },
1805 Statement::IntLiteral(42),
1806 Statement::WordCall {
1807 name: "swap".to_string(),
1808 span: None,
1809 },
1810 Statement::WordCall {
1811 name: "chan.send".to_string(),
1812 span: None,
1813 },
1814 Statement::WordCall {
1815 name: "drop".to_string(),
1816 span: None,
1817 },
1818 ],
1819 source: None,
1820 allowed_lints: vec![],
1821 }],
1822 };
1823
1824 let mut checker = TypeChecker::new();
1825 assert!(checker.check_program(&program).is_ok());
1826 }
1827
1828 #[test]
1829 fn test_complex_stack_shuffling() {
1830 let program = Program {
1833 includes: vec![],
1834 unions: vec![],
1835 words: vec![WordDef {
1836 name: "test".to_string(),
1837 effect: Some(Effect::new(
1838 StackType::Empty
1839 .push(Type::Int)
1840 .push(Type::Int)
1841 .push(Type::Int),
1842 StackType::singleton(Type::Int),
1843 )),
1844 body: vec![
1845 Statement::WordCall {
1846 name: "rot".to_string(),
1847 span: None,
1848 },
1849 Statement::WordCall {
1850 name: "i.add".to_string(),
1851 span: None,
1852 },
1853 Statement::WordCall {
1854 name: "i.add".to_string(),
1855 span: None,
1856 },
1857 ],
1858 source: None,
1859 allowed_lints: vec![],
1860 }],
1861 };
1862
1863 let mut checker = TypeChecker::new();
1864 assert!(checker.check_program(&program).is_ok());
1865 }
1866
1867 #[test]
1868 fn test_empty_program() {
1869 let program = Program {
1871 includes: vec![],
1872 unions: vec![],
1873 words: vec![],
1874 };
1875
1876 let mut checker = TypeChecker::new();
1877 assert!(checker.check_program(&program).is_ok());
1878 }
1879
1880 #[test]
1881 fn test_word_without_effect_declaration() {
1882 let program = Program {
1884 includes: vec![],
1885 unions: vec![],
1886 words: vec![WordDef {
1887 name: "helper".to_string(),
1888 effect: None,
1889 body: vec![Statement::IntLiteral(42)],
1890 source: None,
1891 allowed_lints: vec![],
1892 }],
1893 };
1894
1895 let mut checker = TypeChecker::new();
1896 let result = checker.check_program(&program);
1897 assert!(result.is_err());
1898 assert!(
1899 result
1900 .unwrap_err()
1901 .contains("missing a stack effect declaration")
1902 );
1903 }
1904
1905 #[test]
1906 fn test_nested_conditionals() {
1907 let program = Program {
1916 includes: vec![],
1917 unions: vec![],
1918 words: vec![WordDef {
1919 name: "test".to_string(),
1920 effect: Some(Effect::new(
1921 StackType::Empty
1922 .push(Type::Int)
1923 .push(Type::Int)
1924 .push(Type::Int)
1925 .push(Type::Int),
1926 StackType::singleton(Type::String),
1927 )),
1928 body: vec![
1929 Statement::WordCall {
1930 name: "i.>".to_string(),
1931 span: None,
1932 },
1933 Statement::If {
1934 then_branch: vec![
1935 Statement::WordCall {
1936 name: "i.>".to_string(),
1937 span: None,
1938 },
1939 Statement::If {
1940 then_branch: vec![Statement::StringLiteral(
1941 "both true".to_string(),
1942 )],
1943 else_branch: Some(vec![Statement::StringLiteral(
1944 "first true".to_string(),
1945 )]),
1946 },
1947 ],
1948 else_branch: Some(vec![
1949 Statement::WordCall {
1950 name: "drop".to_string(),
1951 span: None,
1952 },
1953 Statement::WordCall {
1954 name: "drop".to_string(),
1955 span: None,
1956 },
1957 Statement::StringLiteral("first false".to_string()),
1958 ]),
1959 },
1960 ],
1961 source: None,
1962 allowed_lints: vec![],
1963 }],
1964 };
1965
1966 let mut checker = TypeChecker::new();
1967 match checker.check_program(&program) {
1968 Ok(_) => {}
1969 Err(e) => panic!("Type check failed: {}", e),
1970 }
1971 }
1972
1973 #[test]
1974 fn test_conditional_without_else() {
1975 let program = Program {
1979 includes: vec![],
1980 unions: vec![],
1981 words: vec![WordDef {
1982 name: "test".to_string(),
1983 effect: Some(Effect::new(
1984 StackType::Empty.push(Type::Int).push(Type::Int),
1985 StackType::singleton(Type::Int),
1986 )),
1987 body: vec![
1988 Statement::WordCall {
1989 name: "i.>".to_string(),
1990 span: None,
1991 },
1992 Statement::If {
1993 then_branch: vec![Statement::IntLiteral(100)],
1994 else_branch: None, },
1996 ],
1997 source: None,
1998 allowed_lints: vec![],
1999 }],
2000 };
2001
2002 let mut checker = TypeChecker::new();
2003 let result = checker.check_program(&program);
2004 assert!(result.is_err());
2006 }
2007
2008 #[test]
2009 fn test_multiple_word_chain() {
2010 let program = Program {
2014 includes: vec![],
2015 unions: vec![],
2016 words: vec![
2017 WordDef {
2018 name: "helper1".to_string(),
2019 effect: Some(Effect::new(
2020 StackType::singleton(Type::Int),
2021 StackType::singleton(Type::String),
2022 )),
2023 body: vec![Statement::WordCall {
2024 name: "int->string".to_string(),
2025 span: None,
2026 }],
2027 source: None,
2028 allowed_lints: vec![],
2029 },
2030 WordDef {
2031 name: "helper2".to_string(),
2032 effect: Some(Effect::new(
2033 StackType::singleton(Type::String),
2034 StackType::Empty,
2035 )),
2036 body: vec![Statement::WordCall {
2037 name: "io.write-line".to_string(),
2038 span: None,
2039 }],
2040 source: None,
2041 allowed_lints: vec![],
2042 },
2043 WordDef {
2044 name: "main".to_string(),
2045 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2046 body: vec![
2047 Statement::IntLiteral(42),
2048 Statement::WordCall {
2049 name: "helper1".to_string(),
2050 span: None,
2051 },
2052 Statement::WordCall {
2053 name: "helper2".to_string(),
2054 span: None,
2055 },
2056 ],
2057 source: None,
2058 allowed_lints: vec![],
2059 },
2060 ],
2061 };
2062
2063 let mut checker = TypeChecker::new();
2064 assert!(checker.check_program(&program).is_ok());
2065 }
2066
2067 #[test]
2068 fn test_all_stack_ops() {
2069 let program = Program {
2072 includes: vec![],
2073 unions: vec![],
2074 words: vec![WordDef {
2075 name: "test".to_string(),
2076 effect: Some(Effect::new(
2077 StackType::Empty
2078 .push(Type::Int)
2079 .push(Type::Int)
2080 .push(Type::Int),
2081 StackType::Empty
2082 .push(Type::Int)
2083 .push(Type::Int)
2084 .push(Type::Int)
2085 .push(Type::Int),
2086 )),
2087 body: vec![
2088 Statement::WordCall {
2089 name: "over".to_string(),
2090 span: None,
2091 },
2092 Statement::WordCall {
2093 name: "nip".to_string(),
2094 span: None,
2095 },
2096 Statement::WordCall {
2097 name: "tuck".to_string(),
2098 span: None,
2099 },
2100 ],
2101 source: None,
2102 allowed_lints: vec![],
2103 }],
2104 };
2105
2106 let mut checker = TypeChecker::new();
2107 assert!(checker.check_program(&program).is_ok());
2108 }
2109
2110 #[test]
2111 fn test_mixed_types_complex() {
2112 let program = Program {
2121 includes: vec![],
2122 unions: vec![],
2123 words: vec![WordDef {
2124 name: "test".to_string(),
2125 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2126 body: vec![
2127 Statement::IntLiteral(42),
2128 Statement::WordCall {
2129 name: "int->string".to_string(),
2130 span: None,
2131 },
2132 Statement::IntLiteral(100),
2133 Statement::IntLiteral(200),
2134 Statement::WordCall {
2135 name: "i.>".to_string(),
2136 span: None,
2137 },
2138 Statement::If {
2139 then_branch: vec![Statement::WordCall {
2140 name: "io.write-line".to_string(),
2141 span: None,
2142 }],
2143 else_branch: Some(vec![Statement::WordCall {
2144 name: "io.write-line".to_string(),
2145 span: None,
2146 }]),
2147 },
2148 ],
2149 source: None,
2150 allowed_lints: vec![],
2151 }],
2152 };
2153
2154 let mut checker = TypeChecker::new();
2155 assert!(checker.check_program(&program).is_ok());
2156 }
2157
2158 #[test]
2159 fn test_string_literal() {
2160 let program = Program {
2162 includes: vec![],
2163 unions: vec![],
2164 words: vec![WordDef {
2165 name: "test".to_string(),
2166 effect: Some(Effect::new(
2167 StackType::Empty,
2168 StackType::singleton(Type::String),
2169 )),
2170 body: vec![Statement::StringLiteral("hello".to_string())],
2171 source: None,
2172 allowed_lints: vec![],
2173 }],
2174 };
2175
2176 let mut checker = TypeChecker::new();
2177 assert!(checker.check_program(&program).is_ok());
2178 }
2179
2180 #[test]
2181 fn test_bool_literal() {
2182 let program = Program {
2185 includes: vec![],
2186 unions: vec![],
2187 words: vec![WordDef {
2188 name: "test".to_string(),
2189 effect: Some(Effect::new(
2190 StackType::Empty,
2191 StackType::singleton(Type::Bool),
2192 )),
2193 body: vec![Statement::BoolLiteral(true)],
2194 source: None,
2195 allowed_lints: vec![],
2196 }],
2197 };
2198
2199 let mut checker = TypeChecker::new();
2200 assert!(checker.check_program(&program).is_ok());
2201 }
2202
2203 #[test]
2204 fn test_type_error_in_nested_conditional() {
2205 let program = Program {
2212 includes: vec![],
2213 unions: vec![],
2214 words: vec![WordDef {
2215 name: "test".to_string(),
2216 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2217 body: vec![
2218 Statement::IntLiteral(10),
2219 Statement::IntLiteral(20),
2220 Statement::WordCall {
2221 name: "i.>".to_string(),
2222 span: None,
2223 },
2224 Statement::If {
2225 then_branch: vec![
2226 Statement::IntLiteral(42),
2227 Statement::WordCall {
2228 name: "io.write-line".to_string(),
2229 span: None,
2230 },
2231 ],
2232 else_branch: Some(vec![
2233 Statement::StringLiteral("ok".to_string()),
2234 Statement::WordCall {
2235 name: "io.write-line".to_string(),
2236 span: None,
2237 },
2238 ]),
2239 },
2240 ],
2241 source: None,
2242 allowed_lints: vec![],
2243 }],
2244 };
2245
2246 let mut checker = TypeChecker::new();
2247 let result = checker.check_program(&program);
2248 assert!(result.is_err());
2249 assert!(result.unwrap_err().contains("Type mismatch"));
2250 }
2251
2252 #[test]
2253 fn test_read_line_operation() {
2254 let program = Program {
2257 includes: vec![],
2258 unions: vec![],
2259 words: vec![WordDef {
2260 name: "test".to_string(),
2261 effect: Some(Effect::new(
2262 StackType::Empty,
2263 StackType::from_vec(vec![Type::String, Type::Bool]),
2264 )),
2265 body: vec![Statement::WordCall {
2266 name: "io.read-line".to_string(),
2267 span: None,
2268 }],
2269 source: None,
2270 allowed_lints: vec![],
2271 }],
2272 };
2273
2274 let mut checker = TypeChecker::new();
2275 assert!(checker.check_program(&program).is_ok());
2276 }
2277
2278 #[test]
2279 fn test_comparison_operations() {
2280 let program = Program {
2285 includes: vec![],
2286 unions: vec![],
2287 words: vec![WordDef {
2288 name: "test".to_string(),
2289 effect: Some(Effect::new(
2290 StackType::Empty.push(Type::Int).push(Type::Int),
2291 StackType::singleton(Type::Bool),
2292 )),
2293 body: vec![Statement::WordCall {
2294 name: "i.<=".to_string(),
2295 span: None,
2296 }],
2297 source: None,
2298 allowed_lints: vec![],
2299 }],
2300 };
2301
2302 let mut checker = TypeChecker::new();
2303 assert!(checker.check_program(&program).is_ok());
2304 }
2305
2306 #[test]
2307 fn test_recursive_word_definitions() {
2308 let program = Program {
2314 includes: vec![],
2315 unions: vec![],
2316 words: vec![
2317 WordDef {
2318 name: "is-even".to_string(),
2319 effect: Some(Effect::new(
2320 StackType::singleton(Type::Int),
2321 StackType::singleton(Type::Int),
2322 )),
2323 body: vec![
2324 Statement::WordCall {
2325 name: "dup".to_string(),
2326 span: None,
2327 },
2328 Statement::IntLiteral(0),
2329 Statement::WordCall {
2330 name: "i.=".to_string(),
2331 span: None,
2332 },
2333 Statement::If {
2334 then_branch: vec![
2335 Statement::WordCall {
2336 name: "drop".to_string(),
2337 span: None,
2338 },
2339 Statement::IntLiteral(1),
2340 ],
2341 else_branch: Some(vec![
2342 Statement::IntLiteral(1),
2343 Statement::WordCall {
2344 name: "i.subtract".to_string(),
2345 span: None,
2346 },
2347 Statement::WordCall {
2348 name: "is-odd".to_string(),
2349 span: None,
2350 },
2351 ]),
2352 },
2353 ],
2354 source: None,
2355 allowed_lints: vec![],
2356 },
2357 WordDef {
2358 name: "is-odd".to_string(),
2359 effect: Some(Effect::new(
2360 StackType::singleton(Type::Int),
2361 StackType::singleton(Type::Int),
2362 )),
2363 body: vec![
2364 Statement::WordCall {
2365 name: "dup".to_string(),
2366 span: None,
2367 },
2368 Statement::IntLiteral(0),
2369 Statement::WordCall {
2370 name: "i.=".to_string(),
2371 span: None,
2372 },
2373 Statement::If {
2374 then_branch: vec![
2375 Statement::WordCall {
2376 name: "drop".to_string(),
2377 span: None,
2378 },
2379 Statement::IntLiteral(0),
2380 ],
2381 else_branch: Some(vec![
2382 Statement::IntLiteral(1),
2383 Statement::WordCall {
2384 name: "i.subtract".to_string(),
2385 span: None,
2386 },
2387 Statement::WordCall {
2388 name: "is-even".to_string(),
2389 span: None,
2390 },
2391 ]),
2392 },
2393 ],
2394 source: None,
2395 allowed_lints: vec![],
2396 },
2397 ],
2398 };
2399
2400 let mut checker = TypeChecker::new();
2401 assert!(checker.check_program(&program).is_ok());
2402 }
2403
2404 #[test]
2405 fn test_word_calling_word_with_row_polymorphism() {
2406 let program = Program {
2411 includes: vec![],
2412 unions: vec![],
2413 words: vec![
2414 WordDef {
2415 name: "apply-twice".to_string(),
2416 effect: Some(Effect::new(
2417 StackType::singleton(Type::Int),
2418 StackType::singleton(Type::Int),
2419 )),
2420 body: vec![
2421 Statement::WordCall {
2422 name: "dup".to_string(),
2423 span: None,
2424 },
2425 Statement::WordCall {
2426 name: "i.add".to_string(),
2427 span: None,
2428 },
2429 ],
2430 source: None,
2431 allowed_lints: vec![],
2432 },
2433 WordDef {
2434 name: "quad".to_string(),
2435 effect: Some(Effect::new(
2436 StackType::singleton(Type::Int),
2437 StackType::singleton(Type::Int),
2438 )),
2439 body: vec![
2440 Statement::WordCall {
2441 name: "apply-twice".to_string(),
2442 span: None,
2443 },
2444 Statement::WordCall {
2445 name: "apply-twice".to_string(),
2446 span: None,
2447 },
2448 ],
2449 source: None,
2450 allowed_lints: vec![],
2451 },
2452 ],
2453 };
2454
2455 let mut checker = TypeChecker::new();
2456 assert!(checker.check_program(&program).is_ok());
2457 }
2458
2459 #[test]
2460 fn test_deep_stack_types() {
2461 let mut stack_type = StackType::Empty;
2465 for _ in 0..10 {
2466 stack_type = stack_type.push(Type::Int);
2467 }
2468
2469 let program = Program {
2470 includes: vec![],
2471 unions: vec![],
2472 words: vec![WordDef {
2473 name: "test".to_string(),
2474 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2475 body: vec![
2476 Statement::WordCall {
2477 name: "i.add".to_string(),
2478 span: None,
2479 },
2480 Statement::WordCall {
2481 name: "i.add".to_string(),
2482 span: None,
2483 },
2484 Statement::WordCall {
2485 name: "i.add".to_string(),
2486 span: None,
2487 },
2488 Statement::WordCall {
2489 name: "i.add".to_string(),
2490 span: None,
2491 },
2492 Statement::WordCall {
2493 name: "i.add".to_string(),
2494 span: None,
2495 },
2496 Statement::WordCall {
2497 name: "i.add".to_string(),
2498 span: None,
2499 },
2500 Statement::WordCall {
2501 name: "i.add".to_string(),
2502 span: None,
2503 },
2504 Statement::WordCall {
2505 name: "i.add".to_string(),
2506 span: None,
2507 },
2508 Statement::WordCall {
2509 name: "i.add".to_string(),
2510 span: None,
2511 },
2512 ],
2513 source: None,
2514 allowed_lints: vec![],
2515 }],
2516 };
2517
2518 let mut checker = TypeChecker::new();
2519 assert!(checker.check_program(&program).is_ok());
2520 }
2521
2522 #[test]
2523 fn test_simple_quotation() {
2524 let program = Program {
2528 includes: vec![],
2529 unions: vec![],
2530 words: vec![WordDef {
2531 name: "test".to_string(),
2532 effect: Some(Effect::new(
2533 StackType::Empty,
2534 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2535 StackType::RowVar("input".to_string()).push(Type::Int),
2536 StackType::RowVar("input".to_string()).push(Type::Int),
2537 )))),
2538 )),
2539 body: vec![Statement::Quotation {
2540 span: None,
2541 id: 0,
2542 body: vec![
2543 Statement::IntLiteral(1),
2544 Statement::WordCall {
2545 name: "i.add".to_string(),
2546 span: None,
2547 },
2548 ],
2549 }],
2550 source: None,
2551 allowed_lints: vec![],
2552 }],
2553 };
2554
2555 let mut checker = TypeChecker::new();
2556 match checker.check_program(&program) {
2557 Ok(_) => {}
2558 Err(e) => panic!("Type check failed: {}", e),
2559 }
2560 }
2561
2562 #[test]
2563 fn test_empty_quotation() {
2564 let program = Program {
2568 includes: vec![],
2569 unions: vec![],
2570 words: vec![WordDef {
2571 name: "test".to_string(),
2572 effect: Some(Effect::new(
2573 StackType::Empty,
2574 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2575 StackType::RowVar("input".to_string()),
2576 StackType::RowVar("input".to_string()),
2577 )))),
2578 )),
2579 body: vec![Statement::Quotation {
2580 span: None,
2581 id: 1,
2582 body: vec![],
2583 }],
2584 source: None,
2585 allowed_lints: vec![],
2586 }],
2587 };
2588
2589 let mut checker = TypeChecker::new();
2590 assert!(checker.check_program(&program).is_ok());
2591 }
2592
2593 #[test]
2620 fn test_nested_quotation() {
2621 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2625 StackType::RowVar("input".to_string()).push(Type::Int),
2626 StackType::RowVar("input".to_string()).push(Type::Int),
2627 )));
2628
2629 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2630 StackType::RowVar("input".to_string()),
2631 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2632 )));
2633
2634 let program = Program {
2635 includes: vec![],
2636 unions: vec![],
2637 words: vec![WordDef {
2638 name: "test".to_string(),
2639 effect: Some(Effect::new(
2640 StackType::Empty,
2641 StackType::singleton(outer_quot_type),
2642 )),
2643 body: vec![Statement::Quotation {
2644 span: None,
2645 id: 2,
2646 body: vec![Statement::Quotation {
2647 span: None,
2648 id: 3,
2649 body: vec![
2650 Statement::IntLiteral(1),
2651 Statement::WordCall {
2652 name: "i.add".to_string(),
2653 span: None,
2654 },
2655 ],
2656 }],
2657 }],
2658 source: None,
2659 allowed_lints: vec![],
2660 }],
2661 };
2662
2663 let mut checker = TypeChecker::new();
2664 assert!(checker.check_program(&program).is_ok());
2665 }
2666
2667 #[test]
2668 fn test_invalid_field_type_error() {
2669 use crate::ast::{UnionDef, UnionField, UnionVariant};
2670
2671 let program = Program {
2672 includes: vec![],
2673 unions: vec![UnionDef {
2674 name: "Message".to_string(),
2675 variants: vec![UnionVariant {
2676 name: "Get".to_string(),
2677 fields: vec![UnionField {
2678 name: "chan".to_string(),
2679 type_name: "InvalidType".to_string(),
2680 }],
2681 source: None,
2682 }],
2683 source: None,
2684 }],
2685 words: vec![],
2686 };
2687
2688 let mut checker = TypeChecker::new();
2689 let result = checker.check_program(&program);
2690 assert!(result.is_err());
2691 let err = result.unwrap_err();
2692 assert!(err.contains("Unknown type 'InvalidType'"));
2693 assert!(err.contains("chan"));
2694 assert!(err.contains("Get"));
2695 assert!(err.contains("Message"));
2696 }
2697
2698 #[test]
2699 fn test_roll_inside_conditional_with_concrete_stack() {
2700 let program = Program {
2708 includes: vec![],
2709 unions: vec![],
2710 words: vec![WordDef {
2711 name: "test".to_string(),
2712 effect: Some(Effect::new(
2713 StackType::Empty
2714 .push(Type::Int)
2715 .push(Type::Int)
2716 .push(Type::Int)
2717 .push(Type::Int),
2718 StackType::Empty
2719 .push(Type::Int)
2720 .push(Type::Int)
2721 .push(Type::Int)
2722 .push(Type::Int),
2723 )),
2724 body: vec![
2725 Statement::WordCall {
2726 name: "dup".to_string(),
2727 span: None,
2728 },
2729 Statement::IntLiteral(0),
2730 Statement::WordCall {
2731 name: "i.>".to_string(),
2732 span: None,
2733 },
2734 Statement::If {
2735 then_branch: vec![
2736 Statement::IntLiteral(3),
2737 Statement::WordCall {
2738 name: "roll".to_string(),
2739 span: None,
2740 },
2741 ],
2742 else_branch: Some(vec![
2743 Statement::WordCall {
2744 name: "rot".to_string(),
2745 span: None,
2746 },
2747 Statement::WordCall {
2748 name: "rot".to_string(),
2749 span: None,
2750 },
2751 ]),
2752 },
2753 ],
2754 source: None,
2755 allowed_lints: vec![],
2756 }],
2757 };
2758
2759 let mut checker = TypeChecker::new();
2760 match checker.check_program(&program) {
2762 Ok(_) => {}
2763 Err(e) => panic!("Type check failed: {}", e),
2764 }
2765 }
2766
2767 #[test]
2768 fn test_roll_inside_match_arm_with_concrete_stack() {
2769 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2772
2773 let union_def = UnionDef {
2775 name: "Result".to_string(),
2776 variants: vec![
2777 UnionVariant {
2778 name: "Ok".to_string(),
2779 fields: vec![],
2780 source: None,
2781 },
2782 UnionVariant {
2783 name: "Err".to_string(),
2784 fields: vec![],
2785 source: None,
2786 },
2787 ],
2788 source: None,
2789 };
2790
2791 let program = Program {
2797 includes: vec![],
2798 unions: vec![union_def],
2799 words: vec![WordDef {
2800 name: "test".to_string(),
2801 effect: Some(Effect::new(
2802 StackType::Empty
2803 .push(Type::Int)
2804 .push(Type::Int)
2805 .push(Type::Int)
2806 .push(Type::Int)
2807 .push(Type::Union("Result".to_string())),
2808 StackType::Empty
2809 .push(Type::Int)
2810 .push(Type::Int)
2811 .push(Type::Int)
2812 .push(Type::Int),
2813 )),
2814 body: vec![Statement::Match {
2815 arms: vec![
2816 MatchArm {
2817 pattern: Pattern::Variant("Ok".to_string()),
2818 body: vec![
2819 Statement::IntLiteral(3),
2820 Statement::WordCall {
2821 name: "roll".to_string(),
2822 span: None,
2823 },
2824 ],
2825 },
2826 MatchArm {
2827 pattern: Pattern::Variant("Err".to_string()),
2828 body: vec![
2829 Statement::WordCall {
2830 name: "rot".to_string(),
2831 span: None,
2832 },
2833 Statement::WordCall {
2834 name: "rot".to_string(),
2835 span: None,
2836 },
2837 ],
2838 },
2839 ],
2840 }],
2841 source: None,
2842 allowed_lints: vec![],
2843 }],
2844 };
2845
2846 let mut checker = TypeChecker::new();
2847 match checker.check_program(&program) {
2848 Ok(_) => {}
2849 Err(e) => panic!("Type check failed: {}", e),
2850 }
2851 }
2852
2853 #[test]
2854 fn test_roll_with_row_polymorphic_input() {
2855 let program = Program {
2859 includes: vec![],
2860 unions: vec![],
2861 words: vec![WordDef {
2862 name: "test".to_string(),
2863 effect: Some(Effect::new(
2864 StackType::Empty
2865 .push(Type::Var("T".to_string()))
2866 .push(Type::Var("U".to_string()))
2867 .push(Type::Var("V".to_string()))
2868 .push(Type::Var("W".to_string())),
2869 StackType::Empty
2870 .push(Type::Var("U".to_string()))
2871 .push(Type::Var("V".to_string()))
2872 .push(Type::Var("W".to_string()))
2873 .push(Type::Var("T".to_string())),
2874 )),
2875 body: vec![
2876 Statement::IntLiteral(3),
2877 Statement::WordCall {
2878 name: "roll".to_string(),
2879 span: None,
2880 },
2881 ],
2882 source: None,
2883 allowed_lints: vec![],
2884 }],
2885 };
2886
2887 let mut checker = TypeChecker::new();
2888 let result = checker.check_program(&program);
2889 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
2890 }
2891
2892 #[test]
2893 fn test_pick_with_row_polymorphic_input() {
2894 let program = Program {
2898 includes: vec![],
2899 unions: vec![],
2900 words: vec![WordDef {
2901 name: "test".to_string(),
2902 effect: Some(Effect::new(
2903 StackType::Empty
2904 .push(Type::Var("T".to_string()))
2905 .push(Type::Var("U".to_string()))
2906 .push(Type::Var("V".to_string())),
2907 StackType::Empty
2908 .push(Type::Var("T".to_string()))
2909 .push(Type::Var("U".to_string()))
2910 .push(Type::Var("V".to_string()))
2911 .push(Type::Var("T".to_string())),
2912 )),
2913 body: vec![
2914 Statement::IntLiteral(2),
2915 Statement::WordCall {
2916 name: "pick".to_string(),
2917 span: None,
2918 },
2919 ],
2920 source: None,
2921 allowed_lints: vec![],
2922 }],
2923 };
2924
2925 let mut checker = TypeChecker::new();
2926 assert!(checker.check_program(&program).is_ok());
2927 }
2928
2929 #[test]
2930 fn test_valid_union_reference_in_field() {
2931 use crate::ast::{UnionDef, UnionField, UnionVariant};
2932
2933 let program = Program {
2934 includes: vec![],
2935 unions: vec![
2936 UnionDef {
2937 name: "Inner".to_string(),
2938 variants: vec![UnionVariant {
2939 name: "Val".to_string(),
2940 fields: vec![UnionField {
2941 name: "x".to_string(),
2942 type_name: "Int".to_string(),
2943 }],
2944 source: None,
2945 }],
2946 source: None,
2947 },
2948 UnionDef {
2949 name: "Outer".to_string(),
2950 variants: vec![UnionVariant {
2951 name: "Wrap".to_string(),
2952 fields: vec![UnionField {
2953 name: "inner".to_string(),
2954 type_name: "Inner".to_string(), }],
2956 source: None,
2957 }],
2958 source: None,
2959 },
2960 ],
2961 words: vec![],
2962 };
2963
2964 let mut checker = TypeChecker::new();
2965 assert!(
2966 checker.check_program(&program).is_ok(),
2967 "Union reference in field should be valid"
2968 );
2969 }
2970
2971 #[test]
2972 fn test_divergent_recursive_tail_call() {
2973 let program = Program {
2995 includes: vec![],
2996 unions: vec![],
2997 words: vec![WordDef {
2998 name: "store-loop".to_string(),
2999 effect: Some(Effect::new(
3000 StackType::singleton(Type::Channel), StackType::Empty,
3002 )),
3003 body: vec![
3004 Statement::WordCall {
3006 name: "dup".to_string(),
3007 span: None,
3008 },
3009 Statement::WordCall {
3011 name: "chan.receive".to_string(),
3012 span: None,
3013 },
3014 Statement::WordCall {
3016 name: "not".to_string(),
3017 span: None,
3018 },
3019 Statement::If {
3021 then_branch: vec![
3022 Statement::WordCall {
3024 name: "drop".to_string(),
3025 span: None,
3026 },
3027 Statement::WordCall {
3029 name: "store-loop".to_string(), span: None,
3031 },
3032 ],
3033 else_branch: None, },
3035 Statement::WordCall {
3037 name: "drop".to_string(),
3038 span: None,
3039 },
3040 Statement::WordCall {
3041 name: "drop".to_string(),
3042 span: None,
3043 },
3044 ],
3045 source: None,
3046 allowed_lints: vec![],
3047 }],
3048 };
3049
3050 let mut checker = TypeChecker::new();
3051 let result = checker.check_program(&program);
3052 assert!(
3053 result.is_ok(),
3054 "Divergent recursive tail call should be accepted: {:?}",
3055 result.err()
3056 );
3057 }
3058
3059 #[test]
3060 fn test_divergent_else_branch() {
3061 let program = Program {
3074 includes: vec![],
3075 unions: vec![],
3076 words: vec![WordDef {
3077 name: "process-loop".to_string(),
3078 effect: Some(Effect::new(
3079 StackType::singleton(Type::Channel), StackType::Empty,
3081 )),
3082 body: vec![
3083 Statement::WordCall {
3084 name: "dup".to_string(),
3085 span: None,
3086 },
3087 Statement::WordCall {
3088 name: "chan.receive".to_string(),
3089 span: None,
3090 },
3091 Statement::If {
3092 then_branch: vec![
3093 Statement::WordCall {
3095 name: "drop".to_string(),
3096 span: None,
3097 },
3098 Statement::WordCall {
3099 name: "drop".to_string(),
3100 span: None,
3101 },
3102 ],
3103 else_branch: Some(vec![
3104 Statement::WordCall {
3106 name: "drop".to_string(),
3107 span: None,
3108 },
3109 Statement::WordCall {
3110 name: "process-loop".to_string(), span: None,
3112 },
3113 ]),
3114 },
3115 ],
3116 source: None,
3117 allowed_lints: vec![],
3118 }],
3119 };
3120
3121 let mut checker = TypeChecker::new();
3122 let result = checker.check_program(&program);
3123 assert!(
3124 result.is_ok(),
3125 "Divergent else branch should be accepted: {:?}",
3126 result.err()
3127 );
3128 }
3129
3130 #[test]
3131 fn test_non_tail_call_recursion_not_divergent() {
3132 let program = Program {
3149 includes: vec![],
3150 unions: vec![],
3151 words: vec![WordDef {
3152 name: "bad-loop".to_string(),
3153 effect: Some(Effect::new(
3154 StackType::singleton(Type::Int),
3155 StackType::singleton(Type::Int),
3156 )),
3157 body: vec![
3158 Statement::WordCall {
3159 name: "dup".to_string(),
3160 span: None,
3161 },
3162 Statement::IntLiteral(0),
3163 Statement::WordCall {
3164 name: "i.>".to_string(),
3165 span: None,
3166 },
3167 Statement::If {
3168 then_branch: vec![
3169 Statement::IntLiteral(1),
3170 Statement::WordCall {
3171 name: "i.subtract".to_string(),
3172 span: None,
3173 },
3174 Statement::WordCall {
3175 name: "bad-loop".to_string(), span: None,
3177 },
3178 Statement::IntLiteral(1),
3179 Statement::WordCall {
3180 name: "i.add".to_string(), span: None,
3182 },
3183 ],
3184 else_branch: None,
3185 },
3186 ],
3187 source: None,
3188 allowed_lints: vec![],
3189 }],
3190 };
3191
3192 let mut checker = TypeChecker::new();
3193 let result = checker.check_program(&program);
3198 assert!(
3199 result.is_ok(),
3200 "Non-tail recursion should type check normally: {:?}",
3201 result.err()
3202 );
3203 }
3204
3205 #[test]
3206 fn test_call_yield_quotation_error() {
3207 let program = Program {
3211 includes: vec![],
3212 unions: vec![],
3213 words: vec![WordDef {
3214 name: "bad".to_string(),
3215 effect: Some(Effect::new(
3216 StackType::singleton(Type::Var("Ctx".to_string())),
3217 StackType::singleton(Type::Var("Ctx".to_string())),
3218 )),
3219 body: vec![
3220 Statement::IntLiteral(42),
3222 Statement::Quotation {
3223 span: None,
3224 id: 0,
3225 body: vec![Statement::WordCall {
3226 name: "yield".to_string(),
3227 span: None,
3228 }],
3229 },
3230 Statement::WordCall {
3231 name: "call".to_string(),
3232 span: None,
3233 },
3234 ],
3235 source: None,
3236 allowed_lints: vec![],
3237 }],
3238 };
3239
3240 let mut checker = TypeChecker::new();
3241 let result = checker.check_program(&program);
3242 assert!(
3243 result.is_err(),
3244 "Calling yield quotation directly should fail"
3245 );
3246 let err = result.unwrap_err();
3247 assert!(
3248 err.contains("Yield") || err.contains("strand.weave"),
3249 "Error should mention Yield or strand.weave: {}",
3250 err
3251 );
3252 }
3253
3254 #[test]
3255 fn test_strand_weave_yield_quotation_ok() {
3256 let program = Program {
3259 includes: vec![],
3260 unions: vec![],
3261 words: vec![WordDef {
3262 name: "good".to_string(),
3263 effect: Some(Effect::new(
3264 StackType::Empty,
3265 StackType::Empty
3266 .push(Type::Int)
3267 .push(Type::Var("Handle".to_string())),
3268 )),
3269 body: vec![
3270 Statement::IntLiteral(42),
3271 Statement::Quotation {
3272 span: None,
3273 id: 0,
3274 body: vec![Statement::WordCall {
3275 name: "yield".to_string(),
3276 span: None,
3277 }],
3278 },
3279 Statement::WordCall {
3280 name: "strand.weave".to_string(),
3281 span: None,
3282 },
3283 ],
3284 source: None,
3285 allowed_lints: vec![],
3286 }],
3287 };
3288
3289 let mut checker = TypeChecker::new();
3290 let result = checker.check_program(&program);
3291 assert!(
3292 result.is_ok(),
3293 "strand.weave on yield quotation should pass: {:?}",
3294 result.err()
3295 );
3296 }
3297
3298 #[test]
3299 fn test_call_pure_quotation_ok() {
3300 let program = Program {
3303 includes: vec![],
3304 unions: vec![],
3305 words: vec![WordDef {
3306 name: "ok".to_string(),
3307 effect: Some(Effect::new(
3308 StackType::singleton(Type::Int),
3309 StackType::singleton(Type::Int),
3310 )),
3311 body: vec![
3312 Statement::Quotation {
3313 span: None,
3314 id: 0,
3315 body: vec![
3316 Statement::IntLiteral(1),
3317 Statement::WordCall {
3318 name: "i.add".to_string(),
3319 span: None,
3320 },
3321 ],
3322 },
3323 Statement::WordCall {
3324 name: "call".to_string(),
3325 span: None,
3326 },
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_ok(),
3337 "Calling pure quotation should pass: {:?}",
3338 result.err()
3339 );
3340 }
3341
3342 #[test]
3348 fn test_pollution_extra_push() {
3349 let program = Program {
3353 includes: vec![],
3354 unions: vec![],
3355 words: vec![WordDef {
3356 name: "test".to_string(),
3357 effect: Some(Effect::new(
3358 StackType::singleton(Type::Int),
3359 StackType::singleton(Type::Int),
3360 )),
3361 body: vec![Statement::IntLiteral(42)],
3362 source: None,
3363 allowed_lints: vec![],
3364 }],
3365 };
3366
3367 let mut checker = TypeChecker::new();
3368 let result = checker.check_program(&program);
3369 assert!(
3370 result.is_err(),
3371 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3372 );
3373 }
3374
3375 #[test]
3376 fn test_pollution_extra_dup() {
3377 let program = Program {
3380 includes: vec![],
3381 unions: vec![],
3382 words: vec![WordDef {
3383 name: "test".to_string(),
3384 effect: Some(Effect::new(
3385 StackType::singleton(Type::Int),
3386 StackType::singleton(Type::Int),
3387 )),
3388 body: vec![Statement::WordCall {
3389 name: "dup".to_string(),
3390 span: None,
3391 }],
3392 source: None,
3393 allowed_lints: vec![],
3394 }],
3395 };
3396
3397 let mut checker = TypeChecker::new();
3398 let result = checker.check_program(&program);
3399 assert!(
3400 result.is_err(),
3401 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3402 );
3403 }
3404
3405 #[test]
3406 fn test_pollution_consumes_extra() {
3407 let program = Program {
3410 includes: vec![],
3411 unions: vec![],
3412 words: vec![WordDef {
3413 name: "test".to_string(),
3414 effect: Some(Effect::new(
3415 StackType::singleton(Type::Int),
3416 StackType::singleton(Type::Int),
3417 )),
3418 body: vec![
3419 Statement::WordCall {
3420 name: "drop".to_string(),
3421 span: None,
3422 },
3423 Statement::WordCall {
3424 name: "drop".to_string(),
3425 span: None,
3426 },
3427 Statement::IntLiteral(42),
3428 ],
3429 source: None,
3430 allowed_lints: vec![],
3431 }],
3432 };
3433
3434 let mut checker = TypeChecker::new();
3435 let result = checker.check_program(&program);
3436 assert!(
3437 result.is_err(),
3438 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3439 );
3440 }
3441
3442 #[test]
3443 fn test_pollution_in_then_branch() {
3444 let program = Program {
3448 includes: vec![],
3449 unions: vec![],
3450 words: vec![WordDef {
3451 name: "test".to_string(),
3452 effect: Some(Effect::new(
3453 StackType::singleton(Type::Bool),
3454 StackType::singleton(Type::Int),
3455 )),
3456 body: vec![Statement::If {
3457 then_branch: vec![
3458 Statement::IntLiteral(1),
3459 Statement::IntLiteral(2), ],
3461 else_branch: Some(vec![Statement::IntLiteral(3)]),
3462 }],
3463 source: None,
3464 allowed_lints: vec![],
3465 }],
3466 };
3467
3468 let mut checker = TypeChecker::new();
3469 let result = checker.check_program(&program);
3470 assert!(
3471 result.is_err(),
3472 "Should reject: then branch pushes 2 values, else pushes 1"
3473 );
3474 }
3475
3476 #[test]
3477 fn test_pollution_in_else_branch() {
3478 let program = Program {
3482 includes: vec![],
3483 unions: vec![],
3484 words: vec![WordDef {
3485 name: "test".to_string(),
3486 effect: Some(Effect::new(
3487 StackType::singleton(Type::Bool),
3488 StackType::singleton(Type::Int),
3489 )),
3490 body: vec![Statement::If {
3491 then_branch: vec![Statement::IntLiteral(1)],
3492 else_branch: Some(vec![
3493 Statement::IntLiteral(2),
3494 Statement::IntLiteral(3), ]),
3496 }],
3497 source: None,
3498 allowed_lints: vec![],
3499 }],
3500 };
3501
3502 let mut checker = TypeChecker::new();
3503 let result = checker.check_program(&program);
3504 assert!(
3505 result.is_err(),
3506 "Should reject: then branch pushes 1 value, else pushes 2"
3507 );
3508 }
3509
3510 #[test]
3511 fn test_pollution_both_branches_extra() {
3512 let program = Program {
3516 includes: vec![],
3517 unions: vec![],
3518 words: vec![WordDef {
3519 name: "test".to_string(),
3520 effect: Some(Effect::new(
3521 StackType::singleton(Type::Bool),
3522 StackType::singleton(Type::Int),
3523 )),
3524 body: vec![Statement::If {
3525 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3526 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3527 }],
3528 source: None,
3529 allowed_lints: vec![],
3530 }],
3531 };
3532
3533 let mut checker = TypeChecker::new();
3534 let result = checker.check_program(&program);
3535 assert!(
3536 result.is_err(),
3537 "Should reject: both branches push 2 values, but declared output is 1"
3538 );
3539 }
3540
3541 #[test]
3542 fn test_pollution_branch_consumes_extra() {
3543 let program = Program {
3547 includes: vec![],
3548 unions: vec![],
3549 words: vec![WordDef {
3550 name: "test".to_string(),
3551 effect: Some(Effect::new(
3552 StackType::Empty.push(Type::Bool).push(Type::Int),
3553 StackType::singleton(Type::Int),
3554 )),
3555 body: vec![Statement::If {
3556 then_branch: vec![
3557 Statement::WordCall {
3558 name: "drop".to_string(),
3559 span: None,
3560 },
3561 Statement::WordCall {
3562 name: "drop".to_string(),
3563 span: None,
3564 },
3565 Statement::IntLiteral(1),
3566 ],
3567 else_branch: Some(vec![]),
3568 }],
3569 source: None,
3570 allowed_lints: vec![],
3571 }],
3572 };
3573
3574 let mut checker = TypeChecker::new();
3575 let result = checker.check_program(&program);
3576 assert!(
3577 result.is_err(),
3578 "Should reject: then branch consumes Bool (should only have Int after if)"
3579 );
3580 }
3581
3582 #[test]
3583 fn test_pollution_quotation_wrong_arity_output() {
3584 let program = Program {
3588 includes: vec![],
3589 unions: vec![],
3590 words: vec![WordDef {
3591 name: "test".to_string(),
3592 effect: Some(Effect::new(
3593 StackType::singleton(Type::Int),
3594 StackType::singleton(Type::Int),
3595 )),
3596 body: vec![
3597 Statement::Quotation {
3598 span: None,
3599 id: 0,
3600 body: vec![Statement::WordCall {
3601 name: "dup".to_string(),
3602 span: None,
3603 }],
3604 },
3605 Statement::WordCall {
3606 name: "call".to_string(),
3607 span: None,
3608 },
3609 ],
3610 source: None,
3611 allowed_lints: vec![],
3612 }],
3613 };
3614
3615 let mut checker = TypeChecker::new();
3616 let result = checker.check_program(&program);
3617 assert!(
3618 result.is_err(),
3619 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3620 );
3621 }
3622
3623 #[test]
3624 fn test_pollution_quotation_wrong_arity_input() {
3625 let program = Program {
3629 includes: vec![],
3630 unions: vec![],
3631 words: vec![WordDef {
3632 name: "test".to_string(),
3633 effect: Some(Effect::new(
3634 StackType::singleton(Type::Int),
3635 StackType::singleton(Type::Int),
3636 )),
3637 body: vec![
3638 Statement::Quotation {
3639 span: None,
3640 id: 0,
3641 body: vec![
3642 Statement::WordCall {
3643 name: "drop".to_string(),
3644 span: None,
3645 },
3646 Statement::WordCall {
3647 name: "drop".to_string(),
3648 span: None,
3649 },
3650 Statement::IntLiteral(42),
3651 ],
3652 },
3653 Statement::WordCall {
3654 name: "call".to_string(),
3655 span: None,
3656 },
3657 ],
3658 source: None,
3659 allowed_lints: vec![],
3660 }],
3661 };
3662
3663 let mut checker = TypeChecker::new();
3664 let result = checker.check_program(&program);
3665 assert!(
3666 result.is_err(),
3667 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3668 );
3669 }
3670
3671 #[test]
3672 fn test_missing_effect_provides_helpful_error() {
3673 let program = Program {
3676 includes: vec![],
3677 unions: vec![],
3678 words: vec![WordDef {
3679 name: "myword".to_string(),
3680 effect: None, body: vec![Statement::IntLiteral(42)],
3682 source: None,
3683 allowed_lints: vec![],
3684 }],
3685 };
3686
3687 let mut checker = TypeChecker::new();
3688 let result = checker.check_program(&program);
3689 assert!(result.is_err());
3690 let err = result.unwrap_err();
3691 assert!(err.contains("myword"), "Error should mention word name");
3692 assert!(
3693 err.contains("stack effect"),
3694 "Error should mention stack effect"
3695 );
3696 }
3697
3698 #[test]
3699 fn test_valid_effect_exact_match() {
3700 let program = Program {
3703 includes: vec![],
3704 unions: vec![],
3705 words: vec![WordDef {
3706 name: "test".to_string(),
3707 effect: Some(Effect::new(
3708 StackType::Empty.push(Type::Int).push(Type::Int),
3709 StackType::singleton(Type::Int),
3710 )),
3711 body: vec![Statement::WordCall {
3712 name: "i.add".to_string(),
3713 span: None,
3714 }],
3715 source: None,
3716 allowed_lints: vec![],
3717 }],
3718 };
3719
3720 let mut checker = TypeChecker::new();
3721 let result = checker.check_program(&program);
3722 assert!(result.is_ok(), "Should accept: effect matches exactly");
3723 }
3724
3725 #[test]
3726 fn test_valid_polymorphic_passthrough() {
3727 let program = Program {
3730 includes: vec![],
3731 unions: vec![],
3732 words: vec![WordDef {
3733 name: "test".to_string(),
3734 effect: Some(Effect::new(
3735 StackType::Cons {
3736 rest: Box::new(StackType::RowVar("rest".to_string())),
3737 top: Type::Var("a".to_string()),
3738 },
3739 StackType::Cons {
3740 rest: Box::new(StackType::RowVar("rest".to_string())),
3741 top: Type::Var("a".to_string()),
3742 },
3743 )),
3744 body: vec![], source: None,
3746 allowed_lints: vec![],
3747 }],
3748 };
3749
3750 let mut checker = TypeChecker::new();
3751 let result = checker.check_program(&program);
3752 assert!(result.is_ok(), "Should accept: polymorphic identity");
3753 }
3754
3755 #[test]
3761 fn test_closure_basic_capture() {
3762 let program = Program {
3768 includes: vec![],
3769 unions: vec![],
3770 words: vec![WordDef {
3771 name: "make-adder".to_string(),
3772 effect: Some(Effect::new(
3773 StackType::singleton(Type::Int),
3774 StackType::singleton(Type::Closure {
3775 effect: Box::new(Effect::new(
3776 StackType::RowVar("r".to_string()).push(Type::Int),
3777 StackType::RowVar("r".to_string()).push(Type::Int),
3778 )),
3779 captures: vec![Type::Int], }),
3781 )),
3782 body: vec![Statement::Quotation {
3783 span: None,
3784 id: 0,
3785 body: vec![Statement::WordCall {
3786 name: "i.add".to_string(),
3787 span: None,
3788 }],
3789 }],
3790 source: None,
3791 allowed_lints: vec![],
3792 }],
3793 };
3794
3795 let mut checker = TypeChecker::new();
3796 let result = checker.check_program(&program);
3797 assert!(
3798 result.is_ok(),
3799 "Basic closure capture should work: {:?}",
3800 result.err()
3801 );
3802 }
3803
3804 #[test]
3805 fn test_closure_nested_two_levels() {
3806 let program = Program {
3811 includes: vec![],
3812 unions: vec![],
3813 words: vec![WordDef {
3814 name: "outer".to_string(),
3815 effect: Some(Effect::new(
3816 StackType::Empty,
3817 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3818 StackType::RowVar("r".to_string()),
3819 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3820 Effect::new(
3821 StackType::RowVar("s".to_string()).push(Type::Int),
3822 StackType::RowVar("s".to_string()).push(Type::Int),
3823 ),
3824 ))),
3825 )))),
3826 )),
3827 body: vec![Statement::Quotation {
3828 span: None,
3829 id: 0,
3830 body: vec![Statement::Quotation {
3831 span: None,
3832 id: 1,
3833 body: vec![
3834 Statement::IntLiteral(1),
3835 Statement::WordCall {
3836 name: "i.add".to_string(),
3837 span: None,
3838 },
3839 ],
3840 }],
3841 }],
3842 source: None,
3843 allowed_lints: vec![],
3844 }],
3845 };
3846
3847 let mut checker = TypeChecker::new();
3848 let result = checker.check_program(&program);
3849 assert!(
3850 result.is_ok(),
3851 "Two-level nested quotations should work: {:?}",
3852 result.err()
3853 );
3854 }
3855
3856 #[test]
3857 fn test_closure_nested_three_levels() {
3858 let inner_effect = Effect::new(
3862 StackType::RowVar("a".to_string()).push(Type::Int),
3863 StackType::RowVar("a".to_string()).push(Type::Int),
3864 );
3865 let middle_effect = Effect::new(
3866 StackType::RowVar("b".to_string()),
3867 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3868 );
3869 let outer_effect = Effect::new(
3870 StackType::RowVar("c".to_string()),
3871 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3872 );
3873
3874 let program = Program {
3875 includes: vec![],
3876 unions: vec![],
3877 words: vec![WordDef {
3878 name: "deep".to_string(),
3879 effect: Some(Effect::new(
3880 StackType::Empty,
3881 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3882 )),
3883 body: vec![Statement::Quotation {
3884 span: None,
3885 id: 0,
3886 body: vec![Statement::Quotation {
3887 span: None,
3888 id: 1,
3889 body: vec![Statement::Quotation {
3890 span: None,
3891 id: 2,
3892 body: vec![
3893 Statement::IntLiteral(1),
3894 Statement::WordCall {
3895 name: "i.add".to_string(),
3896 span: None,
3897 },
3898 ],
3899 }],
3900 }],
3901 }],
3902 source: None,
3903 allowed_lints: vec![],
3904 }],
3905 };
3906
3907 let mut checker = TypeChecker::new();
3908 let result = checker.check_program(&program);
3909 assert!(
3910 result.is_ok(),
3911 "Three-level nested quotations should work: {:?}",
3912 result.err()
3913 );
3914 }
3915
3916 #[test]
3917 fn test_closure_use_after_creation() {
3918 let adder_type = Type::Closure {
3924 effect: Box::new(Effect::new(
3925 StackType::RowVar("r".to_string()).push(Type::Int),
3926 StackType::RowVar("r".to_string()).push(Type::Int),
3927 )),
3928 captures: vec![Type::Int],
3929 };
3930
3931 let program = Program {
3932 includes: vec![],
3933 unions: vec![],
3934 words: vec![
3935 WordDef {
3936 name: "make-adder".to_string(),
3937 effect: Some(Effect::new(
3938 StackType::singleton(Type::Int),
3939 StackType::singleton(adder_type.clone()),
3940 )),
3941 body: vec![Statement::Quotation {
3942 span: None,
3943 id: 0,
3944 body: vec![Statement::WordCall {
3945 name: "i.add".to_string(),
3946 span: None,
3947 }],
3948 }],
3949 source: None,
3950 allowed_lints: vec![],
3951 },
3952 WordDef {
3953 name: "use-adder".to_string(),
3954 effect: Some(Effect::new(
3955 StackType::Empty,
3956 StackType::singleton(Type::Int),
3957 )),
3958 body: vec![
3959 Statement::IntLiteral(5),
3960 Statement::WordCall {
3961 name: "make-adder".to_string(),
3962 span: None,
3963 },
3964 Statement::IntLiteral(10),
3965 Statement::WordCall {
3966 name: "swap".to_string(),
3967 span: None,
3968 },
3969 Statement::WordCall {
3970 name: "call".to_string(),
3971 span: None,
3972 },
3973 ],
3974 source: None,
3975 allowed_lints: vec![],
3976 },
3977 ],
3978 };
3979
3980 let mut checker = TypeChecker::new();
3981 let result = checker.check_program(&program);
3982 assert!(
3983 result.is_ok(),
3984 "Closure usage after creation should work: {:?}",
3985 result.err()
3986 );
3987 }
3988
3989 #[test]
3990 fn test_closure_wrong_call_type() {
3991 let adder_type = Type::Closure {
3995 effect: Box::new(Effect::new(
3996 StackType::RowVar("r".to_string()).push(Type::Int),
3997 StackType::RowVar("r".to_string()).push(Type::Int),
3998 )),
3999 captures: vec![Type::Int],
4000 };
4001
4002 let program = Program {
4003 includes: vec![],
4004 unions: vec![],
4005 words: vec![
4006 WordDef {
4007 name: "make-adder".to_string(),
4008 effect: Some(Effect::new(
4009 StackType::singleton(Type::Int),
4010 StackType::singleton(adder_type.clone()),
4011 )),
4012 body: vec![Statement::Quotation {
4013 span: None,
4014 id: 0,
4015 body: vec![Statement::WordCall {
4016 name: "i.add".to_string(),
4017 span: None,
4018 }],
4019 }],
4020 source: None,
4021 allowed_lints: vec![],
4022 },
4023 WordDef {
4024 name: "bad-use".to_string(),
4025 effect: Some(Effect::new(
4026 StackType::Empty,
4027 StackType::singleton(Type::Int),
4028 )),
4029 body: vec![
4030 Statement::IntLiteral(5),
4031 Statement::WordCall {
4032 name: "make-adder".to_string(),
4033 span: None,
4034 },
4035 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
4037 name: "swap".to_string(),
4038 span: None,
4039 },
4040 Statement::WordCall {
4041 name: "call".to_string(),
4042 span: None,
4043 },
4044 ],
4045 source: None,
4046 allowed_lints: vec![],
4047 },
4048 ],
4049 };
4050
4051 let mut checker = TypeChecker::new();
4052 let result = checker.check_program(&program);
4053 assert!(
4054 result.is_err(),
4055 "Calling Int closure with String should fail"
4056 );
4057 }
4058
4059 #[test]
4060 fn test_closure_multiple_captures() {
4061 let program = Program {
4068 includes: vec![],
4069 unions: vec![],
4070 words: vec![WordDef {
4071 name: "make-between".to_string(),
4072 effect: Some(Effect::new(
4073 StackType::Empty.push(Type::Int).push(Type::Int),
4074 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4075 StackType::RowVar("r".to_string()).push(Type::Int),
4076 StackType::RowVar("r".to_string()).push(Type::Bool),
4077 )))),
4078 )),
4079 body: vec![Statement::Quotation {
4080 span: None,
4081 id: 0,
4082 body: vec![
4083 Statement::WordCall {
4085 name: "i.>=".to_string(),
4086 span: None,
4087 },
4088 ],
4090 }],
4091 source: None,
4092 allowed_lints: vec![],
4093 }],
4094 };
4095
4096 let mut checker = TypeChecker::new();
4097 let result = checker.check_program(&program);
4098 assert!(
4102 result.is_ok() || result.is_err(),
4103 "Multiple captures should be handled (pass or fail gracefully)"
4104 );
4105 }
4106
4107 #[test]
4108 fn test_quotation_type_preserved_through_word() {
4109 let quot_type = Type::Quotation(Box::new(Effect::new(
4112 StackType::RowVar("r".to_string()).push(Type::Int),
4113 StackType::RowVar("r".to_string()).push(Type::Int),
4114 )));
4115
4116 let program = Program {
4117 includes: vec![],
4118 unions: vec![],
4119 words: vec![WordDef {
4120 name: "identity-quot".to_string(),
4121 effect: Some(Effect::new(
4122 StackType::singleton(quot_type.clone()),
4123 StackType::singleton(quot_type.clone()),
4124 )),
4125 body: vec![], source: None,
4127 allowed_lints: vec![],
4128 }],
4129 };
4130
4131 let mut checker = TypeChecker::new();
4132 let result = checker.check_program(&program);
4133 assert!(
4134 result.is_ok(),
4135 "Quotation type should be preserved through identity word: {:?}",
4136 result.err()
4137 );
4138 }
4139
4140 #[test]
4141 fn test_closure_captures_value_for_inner_quotation() {
4142 let closure_effect = Effect::new(
4148 StackType::RowVar("r".to_string()).push(Type::Int),
4149 StackType::RowVar("r".to_string()).push(Type::Int),
4150 );
4151
4152 let program = Program {
4153 includes: vec![],
4154 unions: vec![],
4155 words: vec![WordDef {
4156 name: "make-inner-adder".to_string(),
4157 effect: Some(Effect::new(
4158 StackType::singleton(Type::Int),
4159 StackType::singleton(Type::Closure {
4160 effect: Box::new(closure_effect),
4161 captures: vec![Type::Int],
4162 }),
4163 )),
4164 body: vec![Statement::Quotation {
4165 span: None,
4166 id: 0,
4167 body: vec![
4168 Statement::WordCall {
4170 name: "i.add".to_string(),
4171 span: None,
4172 },
4173 ],
4174 }],
4175 source: None,
4176 allowed_lints: vec![],
4177 }],
4178 };
4179
4180 let mut checker = TypeChecker::new();
4181 let result = checker.check_program(&program);
4182 assert!(
4183 result.is_ok(),
4184 "Closure with capture for inner work should pass: {:?}",
4185 result.err()
4186 );
4187 }
4188}