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
16fn format_line_prefix(line: usize) -> String {
19 format!("at line {}: ", line + 1)
20}
21
22pub struct TypeChecker {
23 env: HashMap<String, Effect>,
25 unions: HashMap<String, UnionTypeInfo>,
28 fresh_counter: std::cell::Cell<usize>,
30 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
34 expected_quotation_type: std::cell::RefCell<Option<Type>>,
37 current_word: std::cell::RefCell<Option<(String, Option<usize>)>>,
41 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
45 call_graph: Option<CallGraph>,
48 current_aux_stack: std::cell::RefCell<StackType>,
51 aux_max_depths: std::cell::RefCell<HashMap<String, usize>>,
54 in_quotation_scope: std::cell::Cell<bool>,
57}
58
59impl TypeChecker {
60 pub fn new() -> Self {
61 TypeChecker {
62 env: HashMap::new(),
63 unions: HashMap::new(),
64 fresh_counter: std::cell::Cell::new(0),
65 quotation_types: std::cell::RefCell::new(HashMap::new()),
66 expected_quotation_type: std::cell::RefCell::new(None),
67 current_word: std::cell::RefCell::new(None),
68 statement_top_types: std::cell::RefCell::new(HashMap::new()),
69 call_graph: None,
70 current_aux_stack: std::cell::RefCell::new(StackType::Empty),
71 aux_max_depths: std::cell::RefCell::new(HashMap::new()),
72 in_quotation_scope: std::cell::Cell::new(false),
73 }
74 }
75
76 pub fn set_call_graph(&mut self, call_graph: CallGraph) {
81 self.call_graph = Some(call_graph);
82 }
83
84 fn line_prefix(&self) -> String {
86 self.current_word
87 .borrow()
88 .as_ref()
89 .and_then(|(_, line)| line.map(format_line_prefix))
90 .unwrap_or_default()
91 }
92
93 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
95 self.unions.get(name)
96 }
97
98 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
100 &self.unions
101 }
102
103 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
107 for (union_name, union_info) in &self.unions {
108 for variant in &union_info.variants {
109 if variant.name == variant_name {
110 return Some((union_name.as_str(), variant));
111 }
112 }
113 }
114 None
115 }
116
117 pub fn register_external_words(&mut self, words: &[(&str, &Effect)]) {
121 for (name, effect) in words {
122 self.env.insert(name.to_string(), (*effect).clone());
123 }
124 }
125
126 pub fn register_external_unions(&mut self, union_names: &[&str]) {
132 for name in union_names {
133 self.unions.insert(
136 name.to_string(),
137 UnionTypeInfo {
138 name: name.to_string(),
139 variants: vec![],
140 },
141 );
142 }
143 }
144
145 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
151 self.quotation_types.replace(HashMap::new())
152 }
153
154 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
157 self.statement_top_types.replace(HashMap::new())
158 }
159
160 pub fn take_aux_max_depths(&self) -> HashMap<String, usize> {
162 self.aux_max_depths.replace(HashMap::new())
163 }
164
165 fn stack_depth(stack: &StackType) -> usize {
167 let mut depth = 0;
168 let mut current = stack;
169 while let StackType::Cons { rest, .. } = current {
170 depth += 1;
171 current = rest;
172 }
173 depth
174 }
175
176 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
179 match stack {
180 StackType::Cons { top, .. } => match top {
181 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
182 _ => None,
183 },
184 _ => None,
185 }
186 }
187
188 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
190 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
191 self.statement_top_types
192 .borrow_mut()
193 .insert((word_name.to_string(), stmt_index), top_type);
194 }
195 }
196
197 fn fresh_var(&self, prefix: &str) -> String {
199 let n = self.fresh_counter.get();
200 self.fresh_counter.set(n + 1);
201 format!("{}${}", prefix, n)
202 }
203
204 fn freshen_effect(&self, effect: &Effect) -> Effect {
206 let mut type_map = HashMap::new();
207 let mut row_map = HashMap::new();
208
209 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
210 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
211
212 let fresh_effects = effect
214 .effects
215 .iter()
216 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
217 .collect();
218
219 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
220 }
221
222 fn freshen_side_effect(
223 &self,
224 effect: &SideEffect,
225 type_map: &mut HashMap<String, String>,
226 row_map: &mut HashMap<String, String>,
227 ) -> SideEffect {
228 match effect {
229 SideEffect::Yield(ty) => {
230 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
231 }
232 }
233 }
234
235 fn freshen_stack(
236 &self,
237 stack: &StackType,
238 type_map: &mut HashMap<String, String>,
239 row_map: &mut HashMap<String, String>,
240 ) -> StackType {
241 match stack {
242 StackType::Empty => StackType::Empty,
243 StackType::RowVar(name) => {
244 let fresh_name = row_map
245 .entry(name.clone())
246 .or_insert_with(|| self.fresh_var(name));
247 StackType::RowVar(fresh_name.clone())
248 }
249 StackType::Cons { rest, top } => {
250 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
251 let fresh_top = self.freshen_type(top, type_map, row_map);
252 StackType::Cons {
253 rest: Box::new(fresh_rest),
254 top: fresh_top,
255 }
256 }
257 }
258 }
259
260 fn freshen_type(
261 &self,
262 ty: &Type,
263 type_map: &mut HashMap<String, String>,
264 row_map: &mut HashMap<String, String>,
265 ) -> Type {
266 match ty {
267 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
268 ty.clone()
269 }
270 Type::Var(name) => {
271 let fresh_name = type_map
272 .entry(name.clone())
273 .or_insert_with(|| self.fresh_var(name));
274 Type::Var(fresh_name.clone())
275 }
276 Type::Quotation(effect) => {
277 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
278 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
279 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
280 }
281 Type::Closure { effect, captures } => {
282 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
283 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
284 let fresh_captures = captures
285 .iter()
286 .map(|t| self.freshen_type(t, type_map, row_map))
287 .collect();
288 Type::Closure {
289 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
290 captures: fresh_captures,
291 }
292 }
293 Type::Union(name) => Type::Union(name.clone()),
295 }
296 }
297
298 fn parse_type_name(&self, name: &str) -> Type {
302 match name {
303 "Int" => Type::Int,
304 "Float" => Type::Float,
305 "Bool" => Type::Bool,
306 "String" => Type::String,
307 "Channel" => Type::Channel,
308 other => Type::Union(other.to_string()),
310 }
311 }
312
313 fn is_valid_type_name(&self, name: &str) -> bool {
318 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
319 || self.unions.contains_key(name)
320 }
321
322 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
326 for union_def in &program.unions {
327 for variant in &union_def.variants {
328 for field in &variant.fields {
329 if !self.is_valid_type_name(&field.type_name) {
330 return Err(format!(
331 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
332 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
333 field.type_name, field.name, variant.name, union_def.name
334 ));
335 }
336 }
337 }
338 }
339 Ok(())
340 }
341
342 fn validate_effect_types(&self, effect: &Effect, word_name: &str) -> Result<(), String> {
348 self.validate_stack_types(&effect.inputs, word_name)?;
349 self.validate_stack_types(&effect.outputs, word_name)?;
350 Ok(())
351 }
352
353 fn validate_stack_types(&self, stack: &StackType, word_name: &str) -> Result<(), String> {
355 match stack {
356 StackType::Empty | StackType::RowVar(_) => Ok(()),
357 StackType::Cons { rest, top } => {
358 self.validate_type(top, word_name)?;
359 self.validate_stack_types(rest, word_name)
360 }
361 }
362 }
363
364 fn validate_type(&self, ty: &Type, word_name: &str) -> Result<(), String> {
369 match ty {
370 Type::Var(_) => {
371 Ok(())
376 }
377 Type::Quotation(effect) => self.validate_effect_types(effect, word_name),
378 Type::Closure { effect, captures } => {
379 self.validate_effect_types(effect, word_name)?;
380 for cap in captures {
381 self.validate_type(cap, word_name)?;
382 }
383 Ok(())
384 }
385 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
387 Ok(())
388 }
389 Type::Union(name) => {
391 if !self.unions.contains_key(name) {
392 return Err(format!(
393 "In word '{}': Unknown union type '{}' in stack effect.\n\
394 Make sure this union is defined before the word that uses it.",
395 word_name, name
396 ));
397 }
398 Ok(())
399 }
400 }
401 }
402
403 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
405 for union_def in &program.unions {
407 let variants = union_def
408 .variants
409 .iter()
410 .map(|v| VariantInfo {
411 name: v.name.clone(),
412 fields: v
413 .fields
414 .iter()
415 .map(|f| VariantFieldInfo {
416 name: f.name.clone(),
417 field_type: self.parse_type_name(&f.type_name),
418 })
419 .collect(),
420 })
421 .collect();
422
423 self.unions.insert(
424 union_def.name.clone(),
425 UnionTypeInfo {
426 name: union_def.name.clone(),
427 variants,
428 },
429 );
430 }
431
432 self.validate_union_field_types(program)?;
434
435 for word in &program.words {
438 if let Some(effect) = &word.effect {
439 self.validate_effect_types(effect, &word.name)?;
443 self.env.insert(word.name.clone(), effect.clone());
444 } else {
445 return Err(format!(
446 "Word '{}' is missing a stack effect declaration.\n\
447 All words must declare their stack effect, e.g.: : {} ( -- ) ... ;",
448 word.name, word.name
449 ));
450 }
451 }
452
453 for word in &program.words {
455 self.check_word(word)?;
456 }
457
458 Ok(())
459 }
460
461 fn check_word(&self, word: &WordDef) -> Result<(), String> {
463 let line = word.source.as_ref().map(|s| s.start_line);
465 *self.current_word.borrow_mut() = Some((word.name.clone(), line));
466
467 *self.current_aux_stack.borrow_mut() = StackType::Empty;
469
470 let declared_effect = word.effect.as_ref().expect("word must have effect");
472
473 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
476 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
477 {
478 *self.expected_quotation_type.borrow_mut() = Some(top_type);
479 }
480
481 let (result_stack, _subst, inferred_effects) =
483 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
484
485 *self.expected_quotation_type.borrow_mut() = None;
487
488 let line_info = line.map(format_line_prefix).unwrap_or_default();
490 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
491 format!(
492 "{}Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
493 line_info, word.name, declared_effect.outputs, result_stack, e
494 )
495 })?;
496
497 for inferred in &inferred_effects {
501 if !self.effect_matches_any(inferred, &declared_effect.effects) {
502 return Err(format!(
503 "{}Word '{}': body produces effect '{}' but no matching effect is declared.\n\
504 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
505 line_info, word.name, inferred
506 ));
507 }
508 }
509
510 for declared in &declared_effect.effects {
513 if !self.effect_matches_any(declared, &inferred_effects) {
514 return Err(format!(
515 "{}Word '{}': declares effect '{}' but body doesn't produce it.\n\
516 Hint: Remove the effect declaration or ensure the body uses yield.",
517 line_info, word.name, declared
518 ));
519 }
520 }
521
522 let aux_stack = self.current_aux_stack.borrow().clone();
524 if aux_stack != StackType::Empty {
525 return Err(format!(
526 "{}Word '{}': aux stack is not empty at word return.\n\
527 Remaining aux stack: {}\n\
528 Every >aux must be matched by a corresponding aux> before the word returns.",
529 line_info, word.name, aux_stack
530 ));
531 }
532
533 *self.current_word.borrow_mut() = None;
535
536 Ok(())
537 }
538
539 fn infer_statements_from(
546 &self,
547 statements: &[Statement],
548 start_stack: &StackType,
549 capture_stmt_types: bool,
550 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
551 let mut current_stack = start_stack.clone();
552 let mut accumulated_subst = Subst::empty();
553 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
554 let mut skip_next = false;
555
556 for (i, stmt) in statements.iter().enumerate() {
557 if skip_next {
559 skip_next = false;
560 continue;
561 }
562
563 if let Statement::IntLiteral(n) = stmt
566 && let Some(Statement::WordCall {
567 name: next_word, ..
568 }) = statements.get(i + 1)
569 {
570 if next_word == "pick" {
571 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
572 current_stack = new_stack;
573 accumulated_subst = accumulated_subst.compose(&subst);
574 skip_next = true; continue;
576 } else if next_word == "roll" {
577 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
578 current_stack = new_stack;
579 accumulated_subst = accumulated_subst.compose(&subst);
580 skip_next = true; continue;
582 }
583 }
584
585 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
588 let saved = self.expected_quotation_type.borrow().clone();
590
591 if let Some(Statement::WordCall {
593 name: next_word, ..
594 }) = statements.get(i + 1)
595 {
596 if let Some(next_effect) = self.lookup_word_effect(next_word) {
598 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
601 && matches!(quot_type, Type::Quotation(_))
602 {
603 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
604 }
605 }
606 }
607 Some(saved)
608 } else {
609 None
610 };
611
612 if capture_stmt_types && let Some((word_name, _)) = self.current_word.borrow().as_ref()
616 {
617 self.capture_statement_type(word_name, i, ¤t_stack);
618 }
619
620 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
621 current_stack = new_stack;
622 accumulated_subst = accumulated_subst.compose(&subst);
623
624 for effect in effects {
626 if !accumulated_effects.contains(&effect) {
627 accumulated_effects.push(effect);
628 }
629 }
630
631 if let Some(saved) = saved_expected_type {
633 *self.expected_quotation_type.borrow_mut() = saved;
634 }
635 }
636
637 Ok((current_stack, accumulated_subst, accumulated_effects))
638 }
639
640 fn handle_literal_pick(
651 &self,
652 n: i64,
653 current_stack: StackType,
654 ) -> Result<(StackType, Subst), String> {
655 if n < 0 {
656 return Err(format!("pick: index must be non-negative, got {}", n));
657 }
658
659 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
661
662 Ok((current_stack.push(type_at_n), Subst::empty()))
664 }
665
666 fn handle_literal_roll(
677 &self,
678 n: i64,
679 current_stack: StackType,
680 ) -> Result<(StackType, Subst), String> {
681 if n < 0 {
682 return Err(format!("roll: index must be non-negative, got {}", n));
683 }
684
685 self.rotate_type_to_top(current_stack, n as usize)
690 }
691
692 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
694 let mut current = stack;
695 let mut pos = 0;
696
697 loop {
698 match current {
699 StackType::Cons { rest, top } => {
700 if pos == n {
701 return Ok(top.clone());
702 }
703 pos += 1;
704 current = rest;
705 }
706 StackType::RowVar(name) => {
707 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
717 return Ok(fresh_type);
718 }
719 StackType::Empty => {
720 return Err(format!(
721 "{}{}: stack underflow - position {} requested but stack has only {} concrete items",
722 self.line_prefix(),
723 op,
724 n,
725 pos
726 ));
727 }
728 }
729 }
730 }
731
732 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
734 if n == 0 {
735 return Ok((stack, Subst::empty()));
737 }
738
739 let mut types_above: Vec<Type> = Vec::new();
741 let mut current = stack;
742 let mut pos = 0;
743
744 loop {
746 match current {
747 StackType::Cons { rest, top } => {
748 if pos == n {
749 let mut result = *rest;
752 for ty in types_above.into_iter().rev() {
754 result = result.push(ty);
755 }
756 result = result.push(top);
758 return Ok((result, Subst::empty()));
759 }
760 types_above.push(top);
761 pos += 1;
762 current = *rest;
763 }
764 StackType::RowVar(name) => {
765 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
778
779 let mut result = StackType::RowVar(name.clone());
781 for ty in types_above.into_iter().rev() {
782 result = result.push(ty);
783 }
784 result = result.push(fresh_type);
785 return Ok((result, Subst::empty()));
786 }
787 StackType::Empty => {
788 return Err(format!(
789 "{}roll: stack underflow - position {} requested but stack has only {} items",
790 self.line_prefix(),
791 n,
792 pos
793 ));
794 }
795 }
796 }
797 }
798
799 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
803 let start = StackType::RowVar("input".to_string());
804 let (result, subst, effects) = self.infer_statements_from(statements, &start, false)?;
806
807 let normalized_start = subst.apply_stack(&start);
810 let normalized_result = subst.apply_stack(&result);
811
812 Ok(Effect::with_effects(
813 normalized_start,
814 normalized_result,
815 effects,
816 ))
817 }
818
819 fn infer_match(
821 &self,
822 arms: &[crate::ast::MatchArm],
823 match_span: &Option<crate::ast::Span>,
824 current_stack: StackType,
825 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
826 if arms.is_empty() {
827 return Err("match expression must have at least one arm".to_string());
828 }
829
830 let (stack_after_match, _matched_type) =
832 self.pop_type(¤t_stack, "match expression")?;
833
834 let mut arm_results: Vec<StackType> = Vec::new();
836 let mut combined_subst = Subst::empty();
837 let mut merged_effects: Vec<SideEffect> = Vec::new();
838
839 let aux_before_match = self.current_aux_stack.borrow().clone();
841 let mut aux_after_arms: Vec<StackType> = Vec::new();
842
843 for arm in arms {
844 *self.current_aux_stack.borrow_mut() = aux_before_match.clone();
846
847 let variant_name = match &arm.pattern {
849 crate::ast::Pattern::Variant(name) => name.as_str(),
850 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
851 };
852
853 let (_union_name, variant_info) = self
855 .find_variant(variant_name)
856 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
857
858 let arm_stack = self.push_variant_fields(
860 &stack_after_match,
861 &arm.pattern,
862 variant_info,
863 variant_name,
864 )?;
865
866 let (arm_result, arm_subst, arm_effects) =
869 self.infer_statements_from(&arm.body, &arm_stack, false)?;
870
871 combined_subst = combined_subst.compose(&arm_subst);
872 arm_results.push(arm_result);
873 aux_after_arms.push(self.current_aux_stack.borrow().clone());
874
875 for effect in arm_effects {
877 if !merged_effects.contains(&effect) {
878 merged_effects.push(effect);
879 }
880 }
881 }
882
883 if aux_after_arms.len() > 1 {
885 let first_aux = &aux_after_arms[0];
886 for (i, arm_aux) in aux_after_arms.iter().enumerate().skip(1) {
887 if arm_aux != first_aux {
888 let match_line = match_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
889 return Err(format!(
890 "at line {}: match arms have incompatible aux stack effects:\n\
891 \x20 arm 0 aux: {}\n\
892 \x20 arm {} aux: {}\n\
893 \x20 All match arms must leave the aux stack in the same state.",
894 match_line, first_aux, i, arm_aux
895 ));
896 }
897 }
898 }
899 if let Some(aux) = aux_after_arms.into_iter().next() {
901 *self.current_aux_stack.borrow_mut() = aux;
902 }
903
904 let mut final_result = arm_results[0].clone();
906 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
907 let match_line = match_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
909 let arm0_line = arms[0].span.as_ref().map(|s| s.line + 1).unwrap_or(0);
910 let arm_i_line = arms[i].span.as_ref().map(|s| s.line + 1).unwrap_or(0);
911
912 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
913 if match_line > 0 && arm0_line > 0 && arm_i_line > 0 {
914 format!(
915 "at line {}: match arms have incompatible stack effects:\n\
916 \x20 arm 0 (line {}) produces: {}\n\
917 \x20 arm {} (line {}) produces: {}\n\
918 \x20 All match arms must produce the same stack shape.\n\
919 \x20 Error: {}",
920 match_line, arm0_line, final_result, i, arm_i_line, arm_result, e
921 )
922 } else {
923 format!(
924 "match arms have incompatible stack effects:\n\
925 \x20 arm 0 produces: {}\n\
926 \x20 arm {} produces: {}\n\
927 \x20 All match arms must produce the same stack shape.\n\
928 \x20 Error: {}",
929 final_result, i, arm_result, e
930 )
931 }
932 })?;
933 combined_subst = combined_subst.compose(&arm_subst);
934 final_result = arm_subst.apply_stack(&final_result);
935 }
936
937 Ok((final_result, combined_subst, merged_effects))
938 }
939
940 fn push_variant_fields(
942 &self,
943 stack: &StackType,
944 pattern: &crate::ast::Pattern,
945 variant_info: &VariantInfo,
946 variant_name: &str,
947 ) -> Result<StackType, String> {
948 let mut arm_stack = stack.clone();
949 match pattern {
950 crate::ast::Pattern::Variant(_) => {
951 for field in &variant_info.fields {
953 arm_stack = arm_stack.push(field.field_type.clone());
954 }
955 }
956 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
957 for binding in bindings {
959 let field = variant_info
960 .fields
961 .iter()
962 .find(|f| &f.name == binding)
963 .ok_or_else(|| {
964 let available: Vec<_> = variant_info
965 .fields
966 .iter()
967 .map(|f| f.name.as_str())
968 .collect();
969 format!(
970 "Unknown field '{}' in pattern for variant '{}'.\n\
971 Available fields: {}",
972 binding,
973 variant_name,
974 available.join(", ")
975 )
976 })?;
977 arm_stack = arm_stack.push(field.field_type.clone());
978 }
979 }
980 }
981 Ok(arm_stack)
982 }
983
984 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
1005 let Some((current_word_name, _)) = self.current_word.borrow().as_ref().cloned() else {
1006 return false;
1007 };
1008 let Some(Statement::WordCall { name, .. }) = statements.last() else {
1009 return false;
1010 };
1011
1012 if name == ¤t_word_name {
1014 return true;
1015 }
1016
1017 if let Some(ref graph) = self.call_graph
1019 && graph.are_mutually_recursive(¤t_word_name, name)
1020 {
1021 return true;
1022 }
1023
1024 false
1025 }
1026
1027 fn infer_if(
1029 &self,
1030 then_branch: &[Statement],
1031 else_branch: &Option<Vec<Statement>>,
1032 if_span: &Option<crate::ast::Span>,
1033 current_stack: StackType,
1034 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1035 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
1037
1038 let cond_subst = unify_stacks(
1040 &StackType::singleton(Type::Bool),
1041 &StackType::singleton(cond_type),
1042 )
1043 .map_err(|e| format!("if condition must be Bool: {}", e))?;
1044
1045 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
1046
1047 let then_diverges = self.is_divergent_branch(then_branch);
1049 let else_diverges = else_branch
1050 .as_ref()
1051 .map(|stmts| self.is_divergent_branch(stmts))
1052 .unwrap_or(false);
1053
1054 let aux_before_branches = self.current_aux_stack.borrow().clone();
1056
1057 let (then_result, then_subst, then_effects) =
1060 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
1061 let aux_after_then = self.current_aux_stack.borrow().clone();
1062
1063 *self.current_aux_stack.borrow_mut() = aux_before_branches.clone();
1065
1066 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
1068 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
1069 } else {
1070 (stack_after_cond.clone(), Subst::empty(), vec![])
1071 };
1072 let aux_after_else = self.current_aux_stack.borrow().clone();
1073
1074 if !then_diverges && !else_diverges && aux_after_then != aux_after_else {
1077 let if_line = if_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
1078 return Err(format!(
1079 "at line {}: if/else branches have incompatible aux stack effects:\n\
1080 \x20 then branch aux: {}\n\
1081 \x20 else branch aux: {}\n\
1082 \x20 Both branches must leave the aux stack in the same state.",
1083 if_line, aux_after_then, aux_after_else
1084 ));
1085 }
1086
1087 if then_diverges && !else_diverges {
1089 *self.current_aux_stack.borrow_mut() = aux_after_else;
1090 } else {
1091 *self.current_aux_stack.borrow_mut() = aux_after_then;
1092 }
1093
1094 let mut merged_effects = then_effects;
1096 for effect in else_effects {
1097 if !merged_effects.contains(&effect) {
1098 merged_effects.push(effect);
1099 }
1100 }
1101
1102 let (result, branch_subst) = if then_diverges && !else_diverges {
1108 (else_result, Subst::empty())
1110 } else if else_diverges && !then_diverges {
1111 (then_result, Subst::empty())
1113 } else {
1114 let if_line = if_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
1116 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
1117 if if_line > 0 {
1118 format!(
1119 "at line {}: if/else branches have incompatible stack effects:\n\
1120 \x20 then branch produces: {}\n\
1121 \x20 else branch produces: {}\n\
1122 \x20 Both branches of an if/else must produce the same stack shape.\n\
1123 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
1124 \x20 Error: {}",
1125 if_line, then_result, else_result, e
1126 )
1127 } else {
1128 format!(
1129 "if/else branches have incompatible stack effects:\n\
1130 \x20 then branch produces: {}\n\
1131 \x20 else branch produces: {}\n\
1132 \x20 Both branches of an if/else must produce the same stack shape.\n\
1133 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
1134 \x20 Error: {}",
1135 then_result, else_result, e
1136 )
1137 }
1138 })?;
1139 (branch_subst.apply_stack(&then_result), branch_subst)
1140 };
1141
1142 let total_subst = cond_subst
1144 .compose(&then_subst)
1145 .compose(&else_subst)
1146 .compose(&branch_subst);
1147 Ok((result, total_subst, merged_effects))
1148 }
1149
1150 fn infer_quotation(
1153 &self,
1154 id: usize,
1155 body: &[Statement],
1156 current_stack: StackType,
1157 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1158 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
1161 *self.expected_quotation_type.borrow_mut() = None;
1162
1163 let saved_aux = self.current_aux_stack.borrow().clone();
1168 *self.current_aux_stack.borrow_mut() = StackType::Empty;
1169 let saved_in_quotation = self.in_quotation_scope.get();
1170 self.in_quotation_scope.set(true);
1171
1172 let body_effect = self.infer_statements(body)?;
1174
1175 let quot_aux = self.current_aux_stack.borrow().clone();
1177 if quot_aux != StackType::Empty {
1178 return Err(format!(
1179 "Quotation has unbalanced aux stack.\n\
1180 Remaining aux stack: {}\n\
1181 Every >aux must be matched by a corresponding aux> within the quotation.",
1182 quot_aux
1183 ));
1184 }
1185
1186 *self.current_aux_stack.borrow_mut() = saved_aux;
1188 self.in_quotation_scope.set(saved_in_quotation);
1189
1190 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
1192
1193 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
1195
1196 self.quotation_types
1198 .borrow_mut()
1199 .insert(id, quot_type.clone());
1200
1201 let result_stack = match "_type {
1203 Type::Quotation(_) => {
1204 current_stack.push(quot_type)
1206 }
1207 Type::Closure { captures, .. } => {
1208 let mut stack = current_stack.clone();
1210 for _ in 0..captures.len() {
1211 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
1212 stack = new_stack;
1213 }
1214 stack.push(quot_type)
1215 }
1216 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
1217 };
1218
1219 Ok((result_stack, Subst::empty(), vec![]))
1223 }
1224
1225 fn infer_word_call(
1227 &self,
1228 name: &str,
1229 span: &Option<crate::ast::Span>,
1230 current_stack: StackType,
1231 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1232 if name == ">aux" {
1234 return self.infer_to_aux(span, current_stack);
1235 }
1236 if name == "aux>" {
1237 return self.infer_from_aux(span, current_stack);
1238 }
1239
1240 if name == "call" {
1243 return self.infer_call(span, current_stack);
1244 }
1245
1246 let effect = self
1248 .lookup_word_effect(name)
1249 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
1250
1251 let fresh_effect = self.freshen_effect(&effect);
1253
1254 let adjusted_stack = if name == "strand.spawn" {
1256 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1257 } else {
1258 current_stack
1259 };
1260
1261 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name, span)?;
1263
1264 let propagated_effects = fresh_effect.effects.clone();
1268
1269 Ok((result_stack, subst, propagated_effects))
1270 }
1271
1272 fn infer_to_aux(
1274 &self,
1275 _span: &Option<crate::ast::Span>,
1276 current_stack: StackType,
1277 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1278 if self.in_quotation_scope.get() {
1279 return Err(">aux is not supported inside quotations.\n\
1280 Quotations are compiled as separate functions without aux stack slots.\n\
1281 Extract the quotation body into a named word if you need aux."
1282 .to_string());
1283 }
1284 let (rest, top_type) = self.pop_type(¤t_stack, ">aux")?;
1285
1286 let mut aux = self.current_aux_stack.borrow_mut();
1288 *aux = aux.clone().push(top_type);
1289
1290 let depth = Self::stack_depth(&aux);
1292 if let Some((word_name, _)) = self.current_word.borrow().as_ref() {
1293 let mut depths = self.aux_max_depths.borrow_mut();
1294 let entry = depths.entry(word_name.clone()).or_insert(0);
1295 if depth > *entry {
1296 *entry = depth;
1297 }
1298 }
1299
1300 Ok((rest, Subst::empty(), vec![]))
1301 }
1302
1303 fn infer_from_aux(
1305 &self,
1306 _span: &Option<crate::ast::Span>,
1307 current_stack: StackType,
1308 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1309 if self.in_quotation_scope.get() {
1310 return Err("aux> is not supported inside quotations.\n\
1311 Quotations are compiled as separate functions without aux stack slots.\n\
1312 Extract the quotation body into a named word if you need aux."
1313 .to_string());
1314 }
1315 let mut aux = self.current_aux_stack.borrow_mut();
1316 match aux.clone().pop() {
1317 Some((rest, top_type)) => {
1318 *aux = rest;
1319 Ok((current_stack.push(top_type), Subst::empty(), vec![]))
1320 }
1321 None => {
1322 let line_info = self.line_prefix();
1323 Err(format!(
1324 "{}aux>: aux stack is empty. Every aux> must be paired with a preceding >aux.",
1325 line_info
1326 ))
1327 }
1328 }
1329 }
1330
1331 fn infer_call(
1337 &self,
1338 span: &Option<crate::ast::Span>,
1339 current_stack: StackType,
1340 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1341 let line_prefix = self.line_prefix();
1343 let (remaining_stack, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1344 format!(
1345 "{}call: stack underflow - expected quotation on stack",
1346 line_prefix
1347 )
1348 })?;
1349
1350 let quot_effect = match "_type {
1352 Type::Quotation(effect) => (**effect).clone(),
1353 Type::Closure { effect, .. } => (**effect).clone(),
1354 Type::Var(_) => {
1355 let effect = self
1358 .lookup_word_effect("call")
1359 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1360 let fresh_effect = self.freshen_effect(&effect);
1361 let (result_stack, subst) =
1362 self.apply_effect(&fresh_effect, current_stack, "call", span)?;
1363 return Ok((result_stack, subst, vec![]));
1364 }
1365 _ => {
1366 return Err(format!(
1367 "call: expected quotation or closure on stack, got {}",
1368 quot_type
1369 ));
1370 }
1371 };
1372
1373 if quot_effect.has_yield() {
1375 return Err("Cannot call quotation with Yield effect directly.\n\
1376 Quotations that yield values must be wrapped with `strand.weave`.\n\
1377 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1378 .to_string());
1379 }
1380
1381 let fresh_effect = self.freshen_effect("_effect);
1383
1384 let (result_stack, subst) =
1386 self.apply_effect(&fresh_effect, remaining_stack, "call", span)?;
1387
1388 let propagated_effects = fresh_effect.effects.clone();
1390
1391 Ok((result_stack, subst, propagated_effects))
1392 }
1393
1394 fn infer_statement(
1397 &self,
1398 statement: &Statement,
1399 current_stack: StackType,
1400 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1401 match statement {
1402 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1403 Statement::BoolLiteral(_) => {
1404 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1405 }
1406 Statement::StringLiteral(_) => {
1407 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1408 }
1409 Statement::FloatLiteral(_) => {
1410 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1411 }
1412 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1413 Statement::Match { arms, span } => self.infer_match(arms, span, current_stack),
1414 Statement::WordCall { name, span } => self.infer_word_call(name, span, current_stack),
1415 Statement::If {
1416 then_branch,
1417 else_branch,
1418 span,
1419 } => self.infer_if(then_branch, else_branch, span, current_stack),
1420 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1421 }
1422 }
1423
1424 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1426 if let Some(effect) = builtin_signature(name) {
1428 return Some(effect);
1429 }
1430
1431 self.env.get(name).cloned()
1433 }
1434
1435 fn apply_effect(
1440 &self,
1441 effect: &Effect,
1442 current_stack: StackType,
1443 operation: &str,
1444 span: &Option<crate::ast::Span>,
1445 ) -> Result<(StackType, Subst), String> {
1446 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1454 let stack_concrete = Self::count_concrete_types(¤t_stack);
1455
1456 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1457 let is_rigid = row_var_name == "rest";
1470
1471 if is_rigid && effect_concrete > stack_concrete {
1472 let word_name = self
1473 .current_word
1474 .borrow()
1475 .as_ref()
1476 .map(|(n, _)| n.clone())
1477 .unwrap_or_else(|| "unknown".to_string());
1478 return Err(format!(
1479 "{}In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1480 self.line_prefix(),
1481 word_name,
1482 operation,
1483 effect_concrete,
1484 stack_concrete
1485 ));
1486 }
1487 }
1488
1489 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1491 let line_info = span
1492 .as_ref()
1493 .map(|s| format_line_prefix(s.line))
1494 .unwrap_or_default();
1495 format!(
1496 "{}{}: stack type mismatch. Expected {}, got {}: {}",
1497 line_info, operation, effect.inputs, current_stack, e
1498 )
1499 })?;
1500
1501 let result_stack = subst.apply_stack(&effect.outputs);
1503
1504 Ok((result_stack, subst))
1505 }
1506
1507 fn count_concrete_types(stack: &StackType) -> usize {
1509 let mut count = 0;
1510 let mut current = stack;
1511 while let StackType::Cons { rest, top: _ } = current {
1512 count += 1;
1513 current = rest;
1514 }
1515 count
1516 }
1517
1518 fn get_row_var_base(stack: &StackType) -> Option<String> {
1520 let mut current = stack;
1521 while let StackType::Cons { rest, top: _ } = current {
1522 current = rest;
1523 }
1524 match current {
1525 StackType::RowVar(name) => Some(name.clone()),
1526 _ => None,
1527 }
1528 }
1529
1530 fn adjust_stack_for_spawn(
1535 &self,
1536 current_stack: StackType,
1537 spawn_effect: &Effect,
1538 ) -> Result<StackType, String> {
1539 let expected_quot_type = match &spawn_effect.inputs {
1542 StackType::Cons { top, rest: _ } => {
1543 if !matches!(top, Type::Quotation(_)) {
1544 return Ok(current_stack); }
1546 top
1547 }
1548 _ => return Ok(current_stack),
1549 };
1550
1551 let (rest_stack, actual_type) = match ¤t_stack {
1553 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1554 _ => return Ok(current_stack), };
1556
1557 if let Type::Quotation(actual_effect) = actual_type {
1559 if !matches!(actual_effect.inputs, StackType::Empty) {
1561 let expected_effect = match expected_quot_type {
1563 Type::Quotation(eff) => eff.as_ref(),
1564 _ => return Ok(current_stack),
1565 };
1566
1567 let captures = calculate_captures(actual_effect, expected_effect)?;
1569
1570 let closure_type = Type::Closure {
1572 effect: Box::new(expected_effect.clone()),
1573 captures: captures.clone(),
1574 };
1575
1576 let mut adjusted_stack = rest_stack;
1579 for _ in &captures {
1580 adjusted_stack = match adjusted_stack {
1581 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1582 _ => {
1583 return Err(format!(
1584 "strand.spawn: not enough values on stack to capture. Need {} values",
1585 captures.len()
1586 ));
1587 }
1588 };
1589 }
1590
1591 return Ok(adjusted_stack.push(closure_type));
1593 }
1594 }
1595
1596 Ok(current_stack)
1597 }
1598
1599 fn analyze_captures(
1625 &self,
1626 body_effect: &Effect,
1627 _current_stack: &StackType,
1628 ) -> Result<Type, String> {
1629 let expected = self.expected_quotation_type.borrow().clone();
1631
1632 match expected {
1633 Some(Type::Closure { effect, .. }) => {
1634 let captures = calculate_captures(body_effect, &effect)?;
1636 Ok(Type::Closure { effect, captures })
1637 }
1638 Some(Type::Quotation(expected_effect)) => {
1639 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1645 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1646
1647 if expected_is_empty && body_needs_inputs {
1648 let captures = calculate_captures(body_effect, &expected_effect)?;
1651 Ok(Type::Closure {
1652 effect: expected_effect,
1653 captures,
1654 })
1655 } else {
1656 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1662 let expected_quot = Type::Quotation(expected_effect.clone());
1663 unify_types(&body_quot, &expected_quot).map_err(|e| {
1664 format!(
1665 "quotation effect mismatch: expected {}, got {}: {}",
1666 expected_effect, body_effect, e
1667 )
1668 })?;
1669
1670 Ok(Type::Quotation(expected_effect))
1672 }
1673 }
1674 _ => {
1675 Ok(Type::Quotation(Box::new(body_effect.clone())))
1677 }
1678 }
1679 }
1680
1681 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1685 declared.iter().any(|decl| match (inferred, decl) {
1686 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1687 })
1688 }
1689
1690 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1692 match stack {
1693 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1694 StackType::Empty => Err(format!(
1695 "{}: stack underflow - expected value on stack but stack is empty",
1696 context
1697 )),
1698 StackType::RowVar(_) => {
1699 Err(format!(
1703 "{}: cannot pop from polymorphic stack without more type information",
1704 context
1705 ))
1706 }
1707 }
1708 }
1709}
1710
1711impl Default for TypeChecker {
1712 fn default() -> Self {
1713 Self::new()
1714 }
1715}
1716
1717#[cfg(test)]
1718mod tests {
1719 use super::*;
1720
1721 #[test]
1722 fn test_simple_literal() {
1723 let program = Program {
1724 includes: vec![],
1725 unions: vec![],
1726 words: vec![WordDef {
1727 name: "test".to_string(),
1728 effect: Some(Effect::new(
1729 StackType::Empty,
1730 StackType::singleton(Type::Int),
1731 )),
1732 body: vec![Statement::IntLiteral(42)],
1733 source: None,
1734 allowed_lints: vec![],
1735 }],
1736 };
1737
1738 let mut checker = TypeChecker::new();
1739 assert!(checker.check_program(&program).is_ok());
1740 }
1741
1742 #[test]
1743 fn test_simple_operation() {
1744 let program = Program {
1746 includes: vec![],
1747 unions: vec![],
1748 words: vec![WordDef {
1749 name: "test".to_string(),
1750 effect: Some(Effect::new(
1751 StackType::Empty.push(Type::Int).push(Type::Int),
1752 StackType::singleton(Type::Int),
1753 )),
1754 body: vec![Statement::WordCall {
1755 name: "i.add".to_string(),
1756 span: None,
1757 }],
1758 source: None,
1759 allowed_lints: vec![],
1760 }],
1761 };
1762
1763 let mut checker = TypeChecker::new();
1764 assert!(checker.check_program(&program).is_ok());
1765 }
1766
1767 #[test]
1768 fn test_type_mismatch() {
1769 let program = Program {
1771 includes: vec![],
1772 unions: vec![],
1773 words: vec![WordDef {
1774 name: "test".to_string(),
1775 effect: Some(Effect::new(
1776 StackType::singleton(Type::String),
1777 StackType::Empty,
1778 )),
1779 body: vec![
1780 Statement::IntLiteral(42), Statement::WordCall {
1782 name: "io.write-line".to_string(),
1783 span: None,
1784 },
1785 ],
1786 source: None,
1787 allowed_lints: vec![],
1788 }],
1789 };
1790
1791 let mut checker = TypeChecker::new();
1792 let result = checker.check_program(&program);
1793 assert!(result.is_err());
1794 assert!(result.unwrap_err().contains("Type mismatch"));
1795 }
1796
1797 #[test]
1798 fn test_polymorphic_dup() {
1799 let program = Program {
1801 includes: vec![],
1802 unions: vec![],
1803 words: vec![WordDef {
1804 name: "my-dup".to_string(),
1805 effect: Some(Effect::new(
1806 StackType::singleton(Type::Int),
1807 StackType::Empty.push(Type::Int).push(Type::Int),
1808 )),
1809 body: vec![Statement::WordCall {
1810 name: "dup".to_string(),
1811 span: None,
1812 }],
1813 source: None,
1814 allowed_lints: vec![],
1815 }],
1816 };
1817
1818 let mut checker = TypeChecker::new();
1819 assert!(checker.check_program(&program).is_ok());
1820 }
1821
1822 #[test]
1823 fn test_conditional_branches() {
1824 let program = Program {
1827 includes: vec![],
1828 unions: vec![],
1829 words: vec![WordDef {
1830 name: "test".to_string(),
1831 effect: Some(Effect::new(
1832 StackType::Empty.push(Type::Int).push(Type::Int),
1833 StackType::singleton(Type::String),
1834 )),
1835 body: vec![
1836 Statement::WordCall {
1837 name: "i.>".to_string(),
1838 span: None,
1839 },
1840 Statement::If {
1841 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1842 else_branch: Some(vec![Statement::StringLiteral(
1843 "not greater".to_string(),
1844 )]),
1845 span: None,
1846 },
1847 ],
1848 source: None,
1849 allowed_lints: vec![],
1850 }],
1851 };
1852
1853 let mut checker = TypeChecker::new();
1854 assert!(checker.check_program(&program).is_ok());
1855 }
1856
1857 #[test]
1858 fn test_mismatched_branches() {
1859 let program = Program {
1862 includes: vec![],
1863 unions: vec![],
1864 words: vec![WordDef {
1865 name: "test".to_string(),
1866 effect: Some(Effect::new(
1867 StackType::Empty,
1868 StackType::singleton(Type::Int),
1869 )),
1870 body: vec![
1871 Statement::BoolLiteral(true),
1872 Statement::If {
1873 then_branch: vec![Statement::IntLiteral(42)],
1874 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1875 span: None,
1876 },
1877 ],
1878 source: None,
1879 allowed_lints: vec![],
1880 }],
1881 };
1882
1883 let mut checker = TypeChecker::new();
1884 let result = checker.check_program(&program);
1885 assert!(result.is_err());
1886 assert!(result.unwrap_err().contains("incompatible"));
1887 }
1888
1889 #[test]
1890 fn test_user_defined_word_call() {
1891 let program = Program {
1894 includes: vec![],
1895 unions: vec![],
1896 words: vec![
1897 WordDef {
1898 name: "helper".to_string(),
1899 effect: Some(Effect::new(
1900 StackType::singleton(Type::Int),
1901 StackType::singleton(Type::String),
1902 )),
1903 body: vec![Statement::WordCall {
1904 name: "int->string".to_string(),
1905 span: None,
1906 }],
1907 source: None,
1908 allowed_lints: vec![],
1909 },
1910 WordDef {
1911 name: "main".to_string(),
1912 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1913 body: vec![
1914 Statement::IntLiteral(42),
1915 Statement::WordCall {
1916 name: "helper".to_string(),
1917 span: None,
1918 },
1919 Statement::WordCall {
1920 name: "io.write-line".to_string(),
1921 span: None,
1922 },
1923 ],
1924 source: None,
1925 allowed_lints: vec![],
1926 },
1927 ],
1928 };
1929
1930 let mut checker = TypeChecker::new();
1931 assert!(checker.check_program(&program).is_ok());
1932 }
1933
1934 #[test]
1935 fn test_arithmetic_chain() {
1936 let program = Program {
1939 includes: vec![],
1940 unions: vec![],
1941 words: vec![WordDef {
1942 name: "test".to_string(),
1943 effect: Some(Effect::new(
1944 StackType::Empty
1945 .push(Type::Int)
1946 .push(Type::Int)
1947 .push(Type::Int),
1948 StackType::singleton(Type::Int),
1949 )),
1950 body: vec![
1951 Statement::WordCall {
1952 name: "i.add".to_string(),
1953 span: None,
1954 },
1955 Statement::WordCall {
1956 name: "i.multiply".to_string(),
1957 span: None,
1958 },
1959 ],
1960 source: None,
1961 allowed_lints: vec![],
1962 }],
1963 };
1964
1965 let mut checker = TypeChecker::new();
1966 assert!(checker.check_program(&program).is_ok());
1967 }
1968
1969 #[test]
1970 fn test_write_line_type_error() {
1971 let program = Program {
1973 includes: vec![],
1974 unions: vec![],
1975 words: vec![WordDef {
1976 name: "test".to_string(),
1977 effect: Some(Effect::new(
1978 StackType::singleton(Type::Int),
1979 StackType::Empty,
1980 )),
1981 body: vec![Statement::WordCall {
1982 name: "io.write-line".to_string(),
1983 span: None,
1984 }],
1985 source: None,
1986 allowed_lints: vec![],
1987 }],
1988 };
1989
1990 let mut checker = TypeChecker::new();
1991 let result = checker.check_program(&program);
1992 assert!(result.is_err());
1993 assert!(result.unwrap_err().contains("Type mismatch"));
1994 }
1995
1996 #[test]
1997 fn test_stack_underflow_drop() {
1998 let program = Program {
2000 includes: vec![],
2001 unions: vec![],
2002 words: vec![WordDef {
2003 name: "test".to_string(),
2004 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2005 body: vec![Statement::WordCall {
2006 name: "drop".to_string(),
2007 span: None,
2008 }],
2009 source: None,
2010 allowed_lints: vec![],
2011 }],
2012 };
2013
2014 let mut checker = TypeChecker::new();
2015 let result = checker.check_program(&program);
2016 assert!(result.is_err());
2017 assert!(result.unwrap_err().contains("mismatch"));
2018 }
2019
2020 #[test]
2021 fn test_stack_underflow_add() {
2022 let program = Program {
2024 includes: vec![],
2025 unions: vec![],
2026 words: vec![WordDef {
2027 name: "test".to_string(),
2028 effect: Some(Effect::new(
2029 StackType::singleton(Type::Int),
2030 StackType::singleton(Type::Int),
2031 )),
2032 body: vec![Statement::WordCall {
2033 name: "i.add".to_string(),
2034 span: None,
2035 }],
2036 source: None,
2037 allowed_lints: vec![],
2038 }],
2039 };
2040
2041 let mut checker = TypeChecker::new();
2042 let result = checker.check_program(&program);
2043 assert!(result.is_err());
2044 assert!(result.unwrap_err().contains("mismatch"));
2045 }
2046
2047 #[test]
2050 fn test_stack_underflow_rot_issue_169() {
2051 let program = Program {
2055 includes: vec![],
2056 unions: vec![],
2057 words: vec![WordDef {
2058 name: "test".to_string(),
2059 effect: Some(Effect::new(
2060 StackType::RowVar("rest".to_string()),
2061 StackType::RowVar("rest".to_string()),
2062 )),
2063 body: vec![
2064 Statement::IntLiteral(3),
2065 Statement::IntLiteral(4),
2066 Statement::WordCall {
2067 name: "rot".to_string(),
2068 span: None,
2069 },
2070 ],
2071 source: None,
2072 allowed_lints: vec![],
2073 }],
2074 };
2075
2076 let mut checker = TypeChecker::new();
2077 let result = checker.check_program(&program);
2078 assert!(result.is_err(), "rot with 2 values should fail");
2079 let err = result.unwrap_err();
2080 assert!(
2081 err.contains("stack underflow") || err.contains("requires 3"),
2082 "Error should mention underflow: {}",
2083 err
2084 );
2085 }
2086
2087 #[test]
2088 fn test_csp_operations() {
2089 let program = Program {
2096 includes: vec![],
2097 unions: vec![],
2098 words: vec![WordDef {
2099 name: "test".to_string(),
2100 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2101 body: vec![
2102 Statement::WordCall {
2103 name: "chan.make".to_string(),
2104 span: None,
2105 },
2106 Statement::IntLiteral(42),
2107 Statement::WordCall {
2108 name: "swap".to_string(),
2109 span: None,
2110 },
2111 Statement::WordCall {
2112 name: "chan.send".to_string(),
2113 span: None,
2114 },
2115 Statement::WordCall {
2116 name: "drop".to_string(),
2117 span: None,
2118 },
2119 ],
2120 source: None,
2121 allowed_lints: vec![],
2122 }],
2123 };
2124
2125 let mut checker = TypeChecker::new();
2126 assert!(checker.check_program(&program).is_ok());
2127 }
2128
2129 #[test]
2130 fn test_complex_stack_shuffling() {
2131 let program = Program {
2134 includes: vec![],
2135 unions: vec![],
2136 words: vec![WordDef {
2137 name: "test".to_string(),
2138 effect: Some(Effect::new(
2139 StackType::Empty
2140 .push(Type::Int)
2141 .push(Type::Int)
2142 .push(Type::Int),
2143 StackType::singleton(Type::Int),
2144 )),
2145 body: vec![
2146 Statement::WordCall {
2147 name: "rot".to_string(),
2148 span: None,
2149 },
2150 Statement::WordCall {
2151 name: "i.add".to_string(),
2152 span: None,
2153 },
2154 Statement::WordCall {
2155 name: "i.add".to_string(),
2156 span: None,
2157 },
2158 ],
2159 source: None,
2160 allowed_lints: vec![],
2161 }],
2162 };
2163
2164 let mut checker = TypeChecker::new();
2165 assert!(checker.check_program(&program).is_ok());
2166 }
2167
2168 #[test]
2169 fn test_empty_program() {
2170 let program = Program {
2172 includes: vec![],
2173 unions: vec![],
2174 words: vec![],
2175 };
2176
2177 let mut checker = TypeChecker::new();
2178 assert!(checker.check_program(&program).is_ok());
2179 }
2180
2181 #[test]
2182 fn test_word_without_effect_declaration() {
2183 let program = Program {
2185 includes: vec![],
2186 unions: vec![],
2187 words: vec![WordDef {
2188 name: "helper".to_string(),
2189 effect: None,
2190 body: vec![Statement::IntLiteral(42)],
2191 source: None,
2192 allowed_lints: vec![],
2193 }],
2194 };
2195
2196 let mut checker = TypeChecker::new();
2197 let result = checker.check_program(&program);
2198 assert!(result.is_err());
2199 assert!(
2200 result
2201 .unwrap_err()
2202 .contains("missing a stack effect declaration")
2203 );
2204 }
2205
2206 #[test]
2207 fn test_nested_conditionals() {
2208 let program = Program {
2217 includes: vec![],
2218 unions: vec![],
2219 words: vec![WordDef {
2220 name: "test".to_string(),
2221 effect: Some(Effect::new(
2222 StackType::Empty
2223 .push(Type::Int)
2224 .push(Type::Int)
2225 .push(Type::Int)
2226 .push(Type::Int),
2227 StackType::singleton(Type::String),
2228 )),
2229 body: vec![
2230 Statement::WordCall {
2231 name: "i.>".to_string(),
2232 span: None,
2233 },
2234 Statement::If {
2235 then_branch: vec![
2236 Statement::WordCall {
2237 name: "i.>".to_string(),
2238 span: None,
2239 },
2240 Statement::If {
2241 then_branch: vec![Statement::StringLiteral(
2242 "both true".to_string(),
2243 )],
2244 else_branch: Some(vec![Statement::StringLiteral(
2245 "first true".to_string(),
2246 )]),
2247 span: None,
2248 },
2249 ],
2250 else_branch: Some(vec![
2251 Statement::WordCall {
2252 name: "drop".to_string(),
2253 span: None,
2254 },
2255 Statement::WordCall {
2256 name: "drop".to_string(),
2257 span: None,
2258 },
2259 Statement::StringLiteral("first false".to_string()),
2260 ]),
2261 span: None,
2262 },
2263 ],
2264 source: None,
2265 allowed_lints: vec![],
2266 }],
2267 };
2268
2269 let mut checker = TypeChecker::new();
2270 match checker.check_program(&program) {
2271 Ok(_) => {}
2272 Err(e) => panic!("Type check failed: {}", e),
2273 }
2274 }
2275
2276 #[test]
2277 fn test_conditional_without_else() {
2278 let program = Program {
2282 includes: vec![],
2283 unions: vec![],
2284 words: vec![WordDef {
2285 name: "test".to_string(),
2286 effect: Some(Effect::new(
2287 StackType::Empty.push(Type::Int).push(Type::Int),
2288 StackType::singleton(Type::Int),
2289 )),
2290 body: vec![
2291 Statement::WordCall {
2292 name: "i.>".to_string(),
2293 span: None,
2294 },
2295 Statement::If {
2296 then_branch: vec![Statement::IntLiteral(100)],
2297 else_branch: None, span: None,
2299 },
2300 ],
2301 source: None,
2302 allowed_lints: vec![],
2303 }],
2304 };
2305
2306 let mut checker = TypeChecker::new();
2307 let result = checker.check_program(&program);
2308 assert!(result.is_err());
2310 }
2311
2312 #[test]
2313 fn test_multiple_word_chain() {
2314 let program = Program {
2318 includes: vec![],
2319 unions: vec![],
2320 words: vec![
2321 WordDef {
2322 name: "helper1".to_string(),
2323 effect: Some(Effect::new(
2324 StackType::singleton(Type::Int),
2325 StackType::singleton(Type::String),
2326 )),
2327 body: vec![Statement::WordCall {
2328 name: "int->string".to_string(),
2329 span: None,
2330 }],
2331 source: None,
2332 allowed_lints: vec![],
2333 },
2334 WordDef {
2335 name: "helper2".to_string(),
2336 effect: Some(Effect::new(
2337 StackType::singleton(Type::String),
2338 StackType::Empty,
2339 )),
2340 body: vec![Statement::WordCall {
2341 name: "io.write-line".to_string(),
2342 span: None,
2343 }],
2344 source: None,
2345 allowed_lints: vec![],
2346 },
2347 WordDef {
2348 name: "main".to_string(),
2349 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2350 body: vec![
2351 Statement::IntLiteral(42),
2352 Statement::WordCall {
2353 name: "helper1".to_string(),
2354 span: None,
2355 },
2356 Statement::WordCall {
2357 name: "helper2".to_string(),
2358 span: None,
2359 },
2360 ],
2361 source: None,
2362 allowed_lints: vec![],
2363 },
2364 ],
2365 };
2366
2367 let mut checker = TypeChecker::new();
2368 assert!(checker.check_program(&program).is_ok());
2369 }
2370
2371 #[test]
2372 fn test_all_stack_ops() {
2373 let program = Program {
2376 includes: vec![],
2377 unions: vec![],
2378 words: vec![WordDef {
2379 name: "test".to_string(),
2380 effect: Some(Effect::new(
2381 StackType::Empty
2382 .push(Type::Int)
2383 .push(Type::Int)
2384 .push(Type::Int),
2385 StackType::Empty
2386 .push(Type::Int)
2387 .push(Type::Int)
2388 .push(Type::Int)
2389 .push(Type::Int),
2390 )),
2391 body: vec![
2392 Statement::WordCall {
2393 name: "over".to_string(),
2394 span: None,
2395 },
2396 Statement::WordCall {
2397 name: "nip".to_string(),
2398 span: None,
2399 },
2400 Statement::WordCall {
2401 name: "tuck".to_string(),
2402 span: None,
2403 },
2404 ],
2405 source: None,
2406 allowed_lints: vec![],
2407 }],
2408 };
2409
2410 let mut checker = TypeChecker::new();
2411 assert!(checker.check_program(&program).is_ok());
2412 }
2413
2414 #[test]
2415 fn test_mixed_types_complex() {
2416 let program = Program {
2425 includes: vec![],
2426 unions: vec![],
2427 words: vec![WordDef {
2428 name: "test".to_string(),
2429 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2430 body: vec![
2431 Statement::IntLiteral(42),
2432 Statement::WordCall {
2433 name: "int->string".to_string(),
2434 span: None,
2435 },
2436 Statement::IntLiteral(100),
2437 Statement::IntLiteral(200),
2438 Statement::WordCall {
2439 name: "i.>".to_string(),
2440 span: None,
2441 },
2442 Statement::If {
2443 then_branch: vec![Statement::WordCall {
2444 name: "io.write-line".to_string(),
2445 span: None,
2446 }],
2447 else_branch: Some(vec![Statement::WordCall {
2448 name: "io.write-line".to_string(),
2449 span: None,
2450 }]),
2451 span: None,
2452 },
2453 ],
2454 source: None,
2455 allowed_lints: vec![],
2456 }],
2457 };
2458
2459 let mut checker = TypeChecker::new();
2460 assert!(checker.check_program(&program).is_ok());
2461 }
2462
2463 #[test]
2464 fn test_string_literal() {
2465 let program = Program {
2467 includes: vec![],
2468 unions: vec![],
2469 words: vec![WordDef {
2470 name: "test".to_string(),
2471 effect: Some(Effect::new(
2472 StackType::Empty,
2473 StackType::singleton(Type::String),
2474 )),
2475 body: vec![Statement::StringLiteral("hello".to_string())],
2476 source: None,
2477 allowed_lints: vec![],
2478 }],
2479 };
2480
2481 let mut checker = TypeChecker::new();
2482 assert!(checker.check_program(&program).is_ok());
2483 }
2484
2485 #[test]
2486 fn test_bool_literal() {
2487 let program = Program {
2490 includes: vec![],
2491 unions: vec![],
2492 words: vec![WordDef {
2493 name: "test".to_string(),
2494 effect: Some(Effect::new(
2495 StackType::Empty,
2496 StackType::singleton(Type::Bool),
2497 )),
2498 body: vec![Statement::BoolLiteral(true)],
2499 source: None,
2500 allowed_lints: vec![],
2501 }],
2502 };
2503
2504 let mut checker = TypeChecker::new();
2505 assert!(checker.check_program(&program).is_ok());
2506 }
2507
2508 #[test]
2509 fn test_type_error_in_nested_conditional() {
2510 let program = Program {
2517 includes: vec![],
2518 unions: vec![],
2519 words: vec![WordDef {
2520 name: "test".to_string(),
2521 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2522 body: vec![
2523 Statement::IntLiteral(10),
2524 Statement::IntLiteral(20),
2525 Statement::WordCall {
2526 name: "i.>".to_string(),
2527 span: None,
2528 },
2529 Statement::If {
2530 then_branch: vec![
2531 Statement::IntLiteral(42),
2532 Statement::WordCall {
2533 name: "io.write-line".to_string(),
2534 span: None,
2535 },
2536 ],
2537 else_branch: Some(vec![
2538 Statement::StringLiteral("ok".to_string()),
2539 Statement::WordCall {
2540 name: "io.write-line".to_string(),
2541 span: None,
2542 },
2543 ]),
2544 span: None,
2545 },
2546 ],
2547 source: None,
2548 allowed_lints: vec![],
2549 }],
2550 };
2551
2552 let mut checker = TypeChecker::new();
2553 let result = checker.check_program(&program);
2554 assert!(result.is_err());
2555 assert!(result.unwrap_err().contains("Type mismatch"));
2556 }
2557
2558 #[test]
2559 fn test_read_line_operation() {
2560 let program = Program {
2563 includes: vec![],
2564 unions: vec![],
2565 words: vec![WordDef {
2566 name: "test".to_string(),
2567 effect: Some(Effect::new(
2568 StackType::Empty,
2569 StackType::from_vec(vec![Type::String, Type::Bool]),
2570 )),
2571 body: vec![Statement::WordCall {
2572 name: "io.read-line".to_string(),
2573 span: None,
2574 }],
2575 source: None,
2576 allowed_lints: vec![],
2577 }],
2578 };
2579
2580 let mut checker = TypeChecker::new();
2581 assert!(checker.check_program(&program).is_ok());
2582 }
2583
2584 #[test]
2585 fn test_comparison_operations() {
2586 let program = Program {
2591 includes: vec![],
2592 unions: vec![],
2593 words: vec![WordDef {
2594 name: "test".to_string(),
2595 effect: Some(Effect::new(
2596 StackType::Empty.push(Type::Int).push(Type::Int),
2597 StackType::singleton(Type::Bool),
2598 )),
2599 body: vec![Statement::WordCall {
2600 name: "i.<=".to_string(),
2601 span: None,
2602 }],
2603 source: None,
2604 allowed_lints: vec![],
2605 }],
2606 };
2607
2608 let mut checker = TypeChecker::new();
2609 assert!(checker.check_program(&program).is_ok());
2610 }
2611
2612 #[test]
2613 fn test_recursive_word_definitions() {
2614 let program = Program {
2620 includes: vec![],
2621 unions: vec![],
2622 words: vec![
2623 WordDef {
2624 name: "is-even".to_string(),
2625 effect: Some(Effect::new(
2626 StackType::singleton(Type::Int),
2627 StackType::singleton(Type::Int),
2628 )),
2629 body: vec![
2630 Statement::WordCall {
2631 name: "dup".to_string(),
2632 span: None,
2633 },
2634 Statement::IntLiteral(0),
2635 Statement::WordCall {
2636 name: "i.=".to_string(),
2637 span: None,
2638 },
2639 Statement::If {
2640 then_branch: vec![
2641 Statement::WordCall {
2642 name: "drop".to_string(),
2643 span: None,
2644 },
2645 Statement::IntLiteral(1),
2646 ],
2647 else_branch: Some(vec![
2648 Statement::IntLiteral(1),
2649 Statement::WordCall {
2650 name: "i.subtract".to_string(),
2651 span: None,
2652 },
2653 Statement::WordCall {
2654 name: "is-odd".to_string(),
2655 span: None,
2656 },
2657 ]),
2658 span: None,
2659 },
2660 ],
2661 source: None,
2662 allowed_lints: vec![],
2663 },
2664 WordDef {
2665 name: "is-odd".to_string(),
2666 effect: Some(Effect::new(
2667 StackType::singleton(Type::Int),
2668 StackType::singleton(Type::Int),
2669 )),
2670 body: vec![
2671 Statement::WordCall {
2672 name: "dup".to_string(),
2673 span: None,
2674 },
2675 Statement::IntLiteral(0),
2676 Statement::WordCall {
2677 name: "i.=".to_string(),
2678 span: None,
2679 },
2680 Statement::If {
2681 then_branch: vec![
2682 Statement::WordCall {
2683 name: "drop".to_string(),
2684 span: None,
2685 },
2686 Statement::IntLiteral(0),
2687 ],
2688 else_branch: Some(vec![
2689 Statement::IntLiteral(1),
2690 Statement::WordCall {
2691 name: "i.subtract".to_string(),
2692 span: None,
2693 },
2694 Statement::WordCall {
2695 name: "is-even".to_string(),
2696 span: None,
2697 },
2698 ]),
2699 span: None,
2700 },
2701 ],
2702 source: None,
2703 allowed_lints: vec![],
2704 },
2705 ],
2706 };
2707
2708 let mut checker = TypeChecker::new();
2709 assert!(checker.check_program(&program).is_ok());
2710 }
2711
2712 #[test]
2713 fn test_word_calling_word_with_row_polymorphism() {
2714 let program = Program {
2719 includes: vec![],
2720 unions: vec![],
2721 words: vec![
2722 WordDef {
2723 name: "apply-twice".to_string(),
2724 effect: Some(Effect::new(
2725 StackType::singleton(Type::Int),
2726 StackType::singleton(Type::Int),
2727 )),
2728 body: vec![
2729 Statement::WordCall {
2730 name: "dup".to_string(),
2731 span: None,
2732 },
2733 Statement::WordCall {
2734 name: "i.add".to_string(),
2735 span: None,
2736 },
2737 ],
2738 source: None,
2739 allowed_lints: vec![],
2740 },
2741 WordDef {
2742 name: "quad".to_string(),
2743 effect: Some(Effect::new(
2744 StackType::singleton(Type::Int),
2745 StackType::singleton(Type::Int),
2746 )),
2747 body: vec![
2748 Statement::WordCall {
2749 name: "apply-twice".to_string(),
2750 span: None,
2751 },
2752 Statement::WordCall {
2753 name: "apply-twice".to_string(),
2754 span: None,
2755 },
2756 ],
2757 source: None,
2758 allowed_lints: vec![],
2759 },
2760 ],
2761 };
2762
2763 let mut checker = TypeChecker::new();
2764 assert!(checker.check_program(&program).is_ok());
2765 }
2766
2767 #[test]
2768 fn test_deep_stack_types() {
2769 let mut stack_type = StackType::Empty;
2773 for _ in 0..10 {
2774 stack_type = stack_type.push(Type::Int);
2775 }
2776
2777 let program = Program {
2778 includes: vec![],
2779 unions: vec![],
2780 words: vec![WordDef {
2781 name: "test".to_string(),
2782 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2783 body: vec![
2784 Statement::WordCall {
2785 name: "i.add".to_string(),
2786 span: None,
2787 },
2788 Statement::WordCall {
2789 name: "i.add".to_string(),
2790 span: None,
2791 },
2792 Statement::WordCall {
2793 name: "i.add".to_string(),
2794 span: None,
2795 },
2796 Statement::WordCall {
2797 name: "i.add".to_string(),
2798 span: None,
2799 },
2800 Statement::WordCall {
2801 name: "i.add".to_string(),
2802 span: None,
2803 },
2804 Statement::WordCall {
2805 name: "i.add".to_string(),
2806 span: None,
2807 },
2808 Statement::WordCall {
2809 name: "i.add".to_string(),
2810 span: None,
2811 },
2812 Statement::WordCall {
2813 name: "i.add".to_string(),
2814 span: None,
2815 },
2816 Statement::WordCall {
2817 name: "i.add".to_string(),
2818 span: None,
2819 },
2820 ],
2821 source: None,
2822 allowed_lints: vec![],
2823 }],
2824 };
2825
2826 let mut checker = TypeChecker::new();
2827 assert!(checker.check_program(&program).is_ok());
2828 }
2829
2830 #[test]
2831 fn test_simple_quotation() {
2832 let program = Program {
2836 includes: vec![],
2837 unions: vec![],
2838 words: vec![WordDef {
2839 name: "test".to_string(),
2840 effect: Some(Effect::new(
2841 StackType::Empty,
2842 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2843 StackType::RowVar("input".to_string()).push(Type::Int),
2844 StackType::RowVar("input".to_string()).push(Type::Int),
2845 )))),
2846 )),
2847 body: vec![Statement::Quotation {
2848 span: None,
2849 id: 0,
2850 body: vec![
2851 Statement::IntLiteral(1),
2852 Statement::WordCall {
2853 name: "i.add".to_string(),
2854 span: None,
2855 },
2856 ],
2857 }],
2858 source: None,
2859 allowed_lints: vec![],
2860 }],
2861 };
2862
2863 let mut checker = TypeChecker::new();
2864 match checker.check_program(&program) {
2865 Ok(_) => {}
2866 Err(e) => panic!("Type check failed: {}", e),
2867 }
2868 }
2869
2870 #[test]
2871 fn test_empty_quotation() {
2872 let program = Program {
2876 includes: vec![],
2877 unions: vec![],
2878 words: vec![WordDef {
2879 name: "test".to_string(),
2880 effect: Some(Effect::new(
2881 StackType::Empty,
2882 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2883 StackType::RowVar("input".to_string()),
2884 StackType::RowVar("input".to_string()),
2885 )))),
2886 )),
2887 body: vec![Statement::Quotation {
2888 span: None,
2889 id: 1,
2890 body: vec![],
2891 }],
2892 source: None,
2893 allowed_lints: vec![],
2894 }],
2895 };
2896
2897 let mut checker = TypeChecker::new();
2898 assert!(checker.check_program(&program).is_ok());
2899 }
2900
2901 #[test]
2902 fn test_nested_quotation() {
2903 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2907 StackType::RowVar("input".to_string()).push(Type::Int),
2908 StackType::RowVar("input".to_string()).push(Type::Int),
2909 )));
2910
2911 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2912 StackType::RowVar("input".to_string()),
2913 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2914 )));
2915
2916 let program = Program {
2917 includes: vec![],
2918 unions: vec![],
2919 words: vec![WordDef {
2920 name: "test".to_string(),
2921 effect: Some(Effect::new(
2922 StackType::Empty,
2923 StackType::singleton(outer_quot_type),
2924 )),
2925 body: vec![Statement::Quotation {
2926 span: None,
2927 id: 2,
2928 body: vec![Statement::Quotation {
2929 span: None,
2930 id: 3,
2931 body: vec![
2932 Statement::IntLiteral(1),
2933 Statement::WordCall {
2934 name: "i.add".to_string(),
2935 span: None,
2936 },
2937 ],
2938 }],
2939 }],
2940 source: None,
2941 allowed_lints: vec![],
2942 }],
2943 };
2944
2945 let mut checker = TypeChecker::new();
2946 assert!(checker.check_program(&program).is_ok());
2947 }
2948
2949 #[test]
2950 fn test_invalid_field_type_error() {
2951 use crate::ast::{UnionDef, UnionField, UnionVariant};
2952
2953 let program = Program {
2954 includes: vec![],
2955 unions: vec![UnionDef {
2956 name: "Message".to_string(),
2957 variants: vec![UnionVariant {
2958 name: "Get".to_string(),
2959 fields: vec![UnionField {
2960 name: "chan".to_string(),
2961 type_name: "InvalidType".to_string(),
2962 }],
2963 source: None,
2964 }],
2965 source: None,
2966 }],
2967 words: vec![],
2968 };
2969
2970 let mut checker = TypeChecker::new();
2971 let result = checker.check_program(&program);
2972 assert!(result.is_err());
2973 let err = result.unwrap_err();
2974 assert!(err.contains("Unknown type 'InvalidType'"));
2975 assert!(err.contains("chan"));
2976 assert!(err.contains("Get"));
2977 assert!(err.contains("Message"));
2978 }
2979
2980 #[test]
2981 fn test_roll_inside_conditional_with_concrete_stack() {
2982 let program = Program {
2990 includes: vec![],
2991 unions: vec![],
2992 words: vec![WordDef {
2993 name: "test".to_string(),
2994 effect: Some(Effect::new(
2995 StackType::Empty
2996 .push(Type::Int)
2997 .push(Type::Int)
2998 .push(Type::Int)
2999 .push(Type::Int),
3000 StackType::Empty
3001 .push(Type::Int)
3002 .push(Type::Int)
3003 .push(Type::Int)
3004 .push(Type::Int),
3005 )),
3006 body: vec![
3007 Statement::WordCall {
3008 name: "dup".to_string(),
3009 span: None,
3010 },
3011 Statement::IntLiteral(0),
3012 Statement::WordCall {
3013 name: "i.>".to_string(),
3014 span: None,
3015 },
3016 Statement::If {
3017 then_branch: vec![
3018 Statement::IntLiteral(3),
3019 Statement::WordCall {
3020 name: "roll".to_string(),
3021 span: None,
3022 },
3023 ],
3024 else_branch: Some(vec![
3025 Statement::WordCall {
3026 name: "rot".to_string(),
3027 span: None,
3028 },
3029 Statement::WordCall {
3030 name: "rot".to_string(),
3031 span: None,
3032 },
3033 ]),
3034 span: None,
3035 },
3036 ],
3037 source: None,
3038 allowed_lints: vec![],
3039 }],
3040 };
3041
3042 let mut checker = TypeChecker::new();
3043 match checker.check_program(&program) {
3045 Ok(_) => {}
3046 Err(e) => panic!("Type check failed: {}", e),
3047 }
3048 }
3049
3050 #[test]
3051 fn test_roll_inside_match_arm_with_concrete_stack() {
3052 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
3055
3056 let union_def = UnionDef {
3058 name: "Result".to_string(),
3059 variants: vec![
3060 UnionVariant {
3061 name: "Ok".to_string(),
3062 fields: vec![],
3063 source: None,
3064 },
3065 UnionVariant {
3066 name: "Err".to_string(),
3067 fields: vec![],
3068 source: None,
3069 },
3070 ],
3071 source: None,
3072 };
3073
3074 let program = Program {
3080 includes: vec![],
3081 unions: vec![union_def],
3082 words: vec![WordDef {
3083 name: "test".to_string(),
3084 effect: Some(Effect::new(
3085 StackType::Empty
3086 .push(Type::Int)
3087 .push(Type::Int)
3088 .push(Type::Int)
3089 .push(Type::Int)
3090 .push(Type::Union("Result".to_string())),
3091 StackType::Empty
3092 .push(Type::Int)
3093 .push(Type::Int)
3094 .push(Type::Int)
3095 .push(Type::Int),
3096 )),
3097 body: vec![Statement::Match {
3098 arms: vec![
3099 MatchArm {
3100 pattern: Pattern::Variant("Ok".to_string()),
3101 body: vec![
3102 Statement::IntLiteral(3),
3103 Statement::WordCall {
3104 name: "roll".to_string(),
3105 span: None,
3106 },
3107 ],
3108 span: None,
3109 },
3110 MatchArm {
3111 pattern: Pattern::Variant("Err".to_string()),
3112 body: vec![
3113 Statement::WordCall {
3114 name: "rot".to_string(),
3115 span: None,
3116 },
3117 Statement::WordCall {
3118 name: "rot".to_string(),
3119 span: None,
3120 },
3121 ],
3122 span: None,
3123 },
3124 ],
3125 span: None,
3126 }],
3127 source: None,
3128 allowed_lints: vec![],
3129 }],
3130 };
3131
3132 let mut checker = TypeChecker::new();
3133 match checker.check_program(&program) {
3134 Ok(_) => {}
3135 Err(e) => panic!("Type check failed: {}", e),
3136 }
3137 }
3138
3139 #[test]
3140 fn test_roll_with_row_polymorphic_input() {
3141 let program = Program {
3145 includes: vec![],
3146 unions: vec![],
3147 words: vec![WordDef {
3148 name: "test".to_string(),
3149 effect: Some(Effect::new(
3150 StackType::Empty
3151 .push(Type::Var("T".to_string()))
3152 .push(Type::Var("U".to_string()))
3153 .push(Type::Var("V".to_string()))
3154 .push(Type::Var("W".to_string())),
3155 StackType::Empty
3156 .push(Type::Var("U".to_string()))
3157 .push(Type::Var("V".to_string()))
3158 .push(Type::Var("W".to_string()))
3159 .push(Type::Var("T".to_string())),
3160 )),
3161 body: vec![
3162 Statement::IntLiteral(3),
3163 Statement::WordCall {
3164 name: "roll".to_string(),
3165 span: None,
3166 },
3167 ],
3168 source: None,
3169 allowed_lints: vec![],
3170 }],
3171 };
3172
3173 let mut checker = TypeChecker::new();
3174 let result = checker.check_program(&program);
3175 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
3176 }
3177
3178 #[test]
3179 fn test_pick_with_row_polymorphic_input() {
3180 let program = Program {
3184 includes: vec![],
3185 unions: vec![],
3186 words: vec![WordDef {
3187 name: "test".to_string(),
3188 effect: Some(Effect::new(
3189 StackType::Empty
3190 .push(Type::Var("T".to_string()))
3191 .push(Type::Var("U".to_string()))
3192 .push(Type::Var("V".to_string())),
3193 StackType::Empty
3194 .push(Type::Var("T".to_string()))
3195 .push(Type::Var("U".to_string()))
3196 .push(Type::Var("V".to_string()))
3197 .push(Type::Var("T".to_string())),
3198 )),
3199 body: vec![
3200 Statement::IntLiteral(2),
3201 Statement::WordCall {
3202 name: "pick".to_string(),
3203 span: None,
3204 },
3205 ],
3206 source: None,
3207 allowed_lints: vec![],
3208 }],
3209 };
3210
3211 let mut checker = TypeChecker::new();
3212 assert!(checker.check_program(&program).is_ok());
3213 }
3214
3215 #[test]
3216 fn test_valid_union_reference_in_field() {
3217 use crate::ast::{UnionDef, UnionField, UnionVariant};
3218
3219 let program = Program {
3220 includes: vec![],
3221 unions: vec![
3222 UnionDef {
3223 name: "Inner".to_string(),
3224 variants: vec![UnionVariant {
3225 name: "Val".to_string(),
3226 fields: vec![UnionField {
3227 name: "x".to_string(),
3228 type_name: "Int".to_string(),
3229 }],
3230 source: None,
3231 }],
3232 source: None,
3233 },
3234 UnionDef {
3235 name: "Outer".to_string(),
3236 variants: vec![UnionVariant {
3237 name: "Wrap".to_string(),
3238 fields: vec![UnionField {
3239 name: "inner".to_string(),
3240 type_name: "Inner".to_string(), }],
3242 source: None,
3243 }],
3244 source: None,
3245 },
3246 ],
3247 words: vec![],
3248 };
3249
3250 let mut checker = TypeChecker::new();
3251 assert!(
3252 checker.check_program(&program).is_ok(),
3253 "Union reference in field should be valid"
3254 );
3255 }
3256
3257 #[test]
3258 fn test_divergent_recursive_tail_call() {
3259 let program = Program {
3281 includes: vec![],
3282 unions: vec![],
3283 words: vec![WordDef {
3284 name: "store-loop".to_string(),
3285 effect: Some(Effect::new(
3286 StackType::singleton(Type::Channel), StackType::Empty,
3288 )),
3289 body: vec![
3290 Statement::WordCall {
3292 name: "dup".to_string(),
3293 span: None,
3294 },
3295 Statement::WordCall {
3297 name: "chan.receive".to_string(),
3298 span: None,
3299 },
3300 Statement::WordCall {
3302 name: "not".to_string(),
3303 span: None,
3304 },
3305 Statement::If {
3307 then_branch: vec![
3308 Statement::WordCall {
3310 name: "drop".to_string(),
3311 span: None,
3312 },
3313 Statement::WordCall {
3315 name: "store-loop".to_string(), span: None,
3317 },
3318 ],
3319 else_branch: None, span: None,
3321 },
3322 Statement::WordCall {
3324 name: "drop".to_string(),
3325 span: None,
3326 },
3327 Statement::WordCall {
3328 name: "drop".to_string(),
3329 span: None,
3330 },
3331 ],
3332 source: None,
3333 allowed_lints: vec![],
3334 }],
3335 };
3336
3337 let mut checker = TypeChecker::new();
3338 let result = checker.check_program(&program);
3339 assert!(
3340 result.is_ok(),
3341 "Divergent recursive tail call should be accepted: {:?}",
3342 result.err()
3343 );
3344 }
3345
3346 #[test]
3347 fn test_divergent_else_branch() {
3348 let program = Program {
3361 includes: vec![],
3362 unions: vec![],
3363 words: vec![WordDef {
3364 name: "process-loop".to_string(),
3365 effect: Some(Effect::new(
3366 StackType::singleton(Type::Channel), StackType::Empty,
3368 )),
3369 body: vec![
3370 Statement::WordCall {
3371 name: "dup".to_string(),
3372 span: None,
3373 },
3374 Statement::WordCall {
3375 name: "chan.receive".to_string(),
3376 span: None,
3377 },
3378 Statement::If {
3379 then_branch: vec![
3380 Statement::WordCall {
3382 name: "drop".to_string(),
3383 span: None,
3384 },
3385 Statement::WordCall {
3386 name: "drop".to_string(),
3387 span: None,
3388 },
3389 ],
3390 else_branch: Some(vec![
3391 Statement::WordCall {
3393 name: "drop".to_string(),
3394 span: None,
3395 },
3396 Statement::WordCall {
3397 name: "process-loop".to_string(), span: None,
3399 },
3400 ]),
3401 span: None,
3402 },
3403 ],
3404 source: None,
3405 allowed_lints: vec![],
3406 }],
3407 };
3408
3409 let mut checker = TypeChecker::new();
3410 let result = checker.check_program(&program);
3411 assert!(
3412 result.is_ok(),
3413 "Divergent else branch should be accepted: {:?}",
3414 result.err()
3415 );
3416 }
3417
3418 #[test]
3419 fn test_non_tail_call_recursion_not_divergent() {
3420 let program = Program {
3437 includes: vec![],
3438 unions: vec![],
3439 words: vec![WordDef {
3440 name: "bad-loop".to_string(),
3441 effect: Some(Effect::new(
3442 StackType::singleton(Type::Int),
3443 StackType::singleton(Type::Int),
3444 )),
3445 body: vec![
3446 Statement::WordCall {
3447 name: "dup".to_string(),
3448 span: None,
3449 },
3450 Statement::IntLiteral(0),
3451 Statement::WordCall {
3452 name: "i.>".to_string(),
3453 span: None,
3454 },
3455 Statement::If {
3456 then_branch: vec![
3457 Statement::IntLiteral(1),
3458 Statement::WordCall {
3459 name: "i.subtract".to_string(),
3460 span: None,
3461 },
3462 Statement::WordCall {
3463 name: "bad-loop".to_string(), span: None,
3465 },
3466 Statement::IntLiteral(1),
3467 Statement::WordCall {
3468 name: "i.add".to_string(), span: None,
3470 },
3471 ],
3472 else_branch: None,
3473 span: None,
3474 },
3475 ],
3476 source: None,
3477 allowed_lints: vec![],
3478 }],
3479 };
3480
3481 let mut checker = TypeChecker::new();
3482 let result = checker.check_program(&program);
3487 assert!(
3488 result.is_ok(),
3489 "Non-tail recursion should type check normally: {:?}",
3490 result.err()
3491 );
3492 }
3493
3494 #[test]
3495 fn test_call_yield_quotation_error() {
3496 let program = Program {
3500 includes: vec![],
3501 unions: vec![],
3502 words: vec![WordDef {
3503 name: "bad".to_string(),
3504 effect: Some(Effect::new(
3505 StackType::singleton(Type::Var("Ctx".to_string())),
3506 StackType::singleton(Type::Var("Ctx".to_string())),
3507 )),
3508 body: vec![
3509 Statement::IntLiteral(42),
3511 Statement::Quotation {
3512 span: None,
3513 id: 0,
3514 body: vec![Statement::WordCall {
3515 name: "yield".to_string(),
3516 span: None,
3517 }],
3518 },
3519 Statement::WordCall {
3520 name: "call".to_string(),
3521 span: None,
3522 },
3523 ],
3524 source: None,
3525 allowed_lints: vec![],
3526 }],
3527 };
3528
3529 let mut checker = TypeChecker::new();
3530 let result = checker.check_program(&program);
3531 assert!(
3532 result.is_err(),
3533 "Calling yield quotation directly should fail"
3534 );
3535 let err = result.unwrap_err();
3536 assert!(
3537 err.contains("Yield") || err.contains("strand.weave"),
3538 "Error should mention Yield or strand.weave: {}",
3539 err
3540 );
3541 }
3542
3543 #[test]
3544 fn test_strand_weave_yield_quotation_ok() {
3545 let program = Program {
3548 includes: vec![],
3549 unions: vec![],
3550 words: vec![WordDef {
3551 name: "good".to_string(),
3552 effect: Some(Effect::new(
3553 StackType::Empty,
3554 StackType::Empty
3555 .push(Type::Int)
3556 .push(Type::Var("Handle".to_string())),
3557 )),
3558 body: vec![
3559 Statement::IntLiteral(42),
3560 Statement::Quotation {
3561 span: None,
3562 id: 0,
3563 body: vec![Statement::WordCall {
3564 name: "yield".to_string(),
3565 span: None,
3566 }],
3567 },
3568 Statement::WordCall {
3569 name: "strand.weave".to_string(),
3570 span: None,
3571 },
3572 ],
3573 source: None,
3574 allowed_lints: vec![],
3575 }],
3576 };
3577
3578 let mut checker = TypeChecker::new();
3579 let result = checker.check_program(&program);
3580 assert!(
3581 result.is_ok(),
3582 "strand.weave on yield quotation should pass: {:?}",
3583 result.err()
3584 );
3585 }
3586
3587 #[test]
3588 fn test_call_pure_quotation_ok() {
3589 let program = Program {
3592 includes: vec![],
3593 unions: vec![],
3594 words: vec![WordDef {
3595 name: "ok".to_string(),
3596 effect: Some(Effect::new(
3597 StackType::singleton(Type::Int),
3598 StackType::singleton(Type::Int),
3599 )),
3600 body: vec![
3601 Statement::Quotation {
3602 span: None,
3603 id: 0,
3604 body: vec![
3605 Statement::IntLiteral(1),
3606 Statement::WordCall {
3607 name: "i.add".to_string(),
3608 span: None,
3609 },
3610 ],
3611 },
3612 Statement::WordCall {
3613 name: "call".to_string(),
3614 span: None,
3615 },
3616 ],
3617 source: None,
3618 allowed_lints: vec![],
3619 }],
3620 };
3621
3622 let mut checker = TypeChecker::new();
3623 let result = checker.check_program(&program);
3624 assert!(
3625 result.is_ok(),
3626 "Calling pure quotation should pass: {:?}",
3627 result.err()
3628 );
3629 }
3630
3631 #[test]
3637 fn test_pollution_extra_push() {
3638 let program = Program {
3642 includes: vec![],
3643 unions: vec![],
3644 words: vec![WordDef {
3645 name: "test".to_string(),
3646 effect: Some(Effect::new(
3647 StackType::singleton(Type::Int),
3648 StackType::singleton(Type::Int),
3649 )),
3650 body: vec![Statement::IntLiteral(42)],
3651 source: None,
3652 allowed_lints: vec![],
3653 }],
3654 };
3655
3656 let mut checker = TypeChecker::new();
3657 let result = checker.check_program(&program);
3658 assert!(
3659 result.is_err(),
3660 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3661 );
3662 }
3663
3664 #[test]
3665 fn test_pollution_extra_dup() {
3666 let program = Program {
3669 includes: vec![],
3670 unions: vec![],
3671 words: vec![WordDef {
3672 name: "test".to_string(),
3673 effect: Some(Effect::new(
3674 StackType::singleton(Type::Int),
3675 StackType::singleton(Type::Int),
3676 )),
3677 body: vec![Statement::WordCall {
3678 name: "dup".to_string(),
3679 span: None,
3680 }],
3681 source: None,
3682 allowed_lints: vec![],
3683 }],
3684 };
3685
3686 let mut checker = TypeChecker::new();
3687 let result = checker.check_program(&program);
3688 assert!(
3689 result.is_err(),
3690 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3691 );
3692 }
3693
3694 #[test]
3695 fn test_pollution_consumes_extra() {
3696 let program = Program {
3699 includes: vec![],
3700 unions: vec![],
3701 words: vec![WordDef {
3702 name: "test".to_string(),
3703 effect: Some(Effect::new(
3704 StackType::singleton(Type::Int),
3705 StackType::singleton(Type::Int),
3706 )),
3707 body: vec![
3708 Statement::WordCall {
3709 name: "drop".to_string(),
3710 span: None,
3711 },
3712 Statement::WordCall {
3713 name: "drop".to_string(),
3714 span: None,
3715 },
3716 Statement::IntLiteral(42),
3717 ],
3718 source: None,
3719 allowed_lints: vec![],
3720 }],
3721 };
3722
3723 let mut checker = TypeChecker::new();
3724 let result = checker.check_program(&program);
3725 assert!(
3726 result.is_err(),
3727 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3728 );
3729 }
3730
3731 #[test]
3732 fn test_pollution_in_then_branch() {
3733 let program = Program {
3737 includes: vec![],
3738 unions: vec![],
3739 words: vec![WordDef {
3740 name: "test".to_string(),
3741 effect: Some(Effect::new(
3742 StackType::singleton(Type::Bool),
3743 StackType::singleton(Type::Int),
3744 )),
3745 body: vec![Statement::If {
3746 then_branch: vec![
3747 Statement::IntLiteral(1),
3748 Statement::IntLiteral(2), ],
3750 else_branch: Some(vec![Statement::IntLiteral(3)]),
3751 span: None,
3752 }],
3753 source: None,
3754 allowed_lints: vec![],
3755 }],
3756 };
3757
3758 let mut checker = TypeChecker::new();
3759 let result = checker.check_program(&program);
3760 assert!(
3761 result.is_err(),
3762 "Should reject: then branch pushes 2 values, else pushes 1"
3763 );
3764 }
3765
3766 #[test]
3767 fn test_pollution_in_else_branch() {
3768 let program = Program {
3772 includes: vec![],
3773 unions: vec![],
3774 words: vec![WordDef {
3775 name: "test".to_string(),
3776 effect: Some(Effect::new(
3777 StackType::singleton(Type::Bool),
3778 StackType::singleton(Type::Int),
3779 )),
3780 body: vec![Statement::If {
3781 then_branch: vec![Statement::IntLiteral(1)],
3782 else_branch: Some(vec![
3783 Statement::IntLiteral(2),
3784 Statement::IntLiteral(3), ]),
3786 span: None,
3787 }],
3788 source: None,
3789 allowed_lints: vec![],
3790 }],
3791 };
3792
3793 let mut checker = TypeChecker::new();
3794 let result = checker.check_program(&program);
3795 assert!(
3796 result.is_err(),
3797 "Should reject: then branch pushes 1 value, else pushes 2"
3798 );
3799 }
3800
3801 #[test]
3802 fn test_pollution_both_branches_extra() {
3803 let program = Program {
3807 includes: vec![],
3808 unions: vec![],
3809 words: vec![WordDef {
3810 name: "test".to_string(),
3811 effect: Some(Effect::new(
3812 StackType::singleton(Type::Bool),
3813 StackType::singleton(Type::Int),
3814 )),
3815 body: vec![Statement::If {
3816 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3817 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3818 span: None,
3819 }],
3820 source: None,
3821 allowed_lints: vec![],
3822 }],
3823 };
3824
3825 let mut checker = TypeChecker::new();
3826 let result = checker.check_program(&program);
3827 assert!(
3828 result.is_err(),
3829 "Should reject: both branches push 2 values, but declared output is 1"
3830 );
3831 }
3832
3833 #[test]
3834 fn test_pollution_branch_consumes_extra() {
3835 let program = Program {
3839 includes: vec![],
3840 unions: vec![],
3841 words: vec![WordDef {
3842 name: "test".to_string(),
3843 effect: Some(Effect::new(
3844 StackType::Empty.push(Type::Bool).push(Type::Int),
3845 StackType::singleton(Type::Int),
3846 )),
3847 body: vec![Statement::If {
3848 then_branch: vec![
3849 Statement::WordCall {
3850 name: "drop".to_string(),
3851 span: None,
3852 },
3853 Statement::WordCall {
3854 name: "drop".to_string(),
3855 span: None,
3856 },
3857 Statement::IntLiteral(1),
3858 ],
3859 else_branch: Some(vec![]),
3860 span: None,
3861 }],
3862 source: None,
3863 allowed_lints: vec![],
3864 }],
3865 };
3866
3867 let mut checker = TypeChecker::new();
3868 let result = checker.check_program(&program);
3869 assert!(
3870 result.is_err(),
3871 "Should reject: then branch consumes Bool (should only have Int after if)"
3872 );
3873 }
3874
3875 #[test]
3876 fn test_pollution_quotation_wrong_arity_output() {
3877 let program = Program {
3881 includes: vec![],
3882 unions: vec![],
3883 words: vec![WordDef {
3884 name: "test".to_string(),
3885 effect: Some(Effect::new(
3886 StackType::singleton(Type::Int),
3887 StackType::singleton(Type::Int),
3888 )),
3889 body: vec![
3890 Statement::Quotation {
3891 span: None,
3892 id: 0,
3893 body: vec![Statement::WordCall {
3894 name: "dup".to_string(),
3895 span: None,
3896 }],
3897 },
3898 Statement::WordCall {
3899 name: "call".to_string(),
3900 span: None,
3901 },
3902 ],
3903 source: None,
3904 allowed_lints: vec![],
3905 }],
3906 };
3907
3908 let mut checker = TypeChecker::new();
3909 let result = checker.check_program(&program);
3910 assert!(
3911 result.is_err(),
3912 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3913 );
3914 }
3915
3916 #[test]
3917 fn test_pollution_quotation_wrong_arity_input() {
3918 let program = Program {
3922 includes: vec![],
3923 unions: vec![],
3924 words: vec![WordDef {
3925 name: "test".to_string(),
3926 effect: Some(Effect::new(
3927 StackType::singleton(Type::Int),
3928 StackType::singleton(Type::Int),
3929 )),
3930 body: vec![
3931 Statement::Quotation {
3932 span: None,
3933 id: 0,
3934 body: vec![
3935 Statement::WordCall {
3936 name: "drop".to_string(),
3937 span: None,
3938 },
3939 Statement::WordCall {
3940 name: "drop".to_string(),
3941 span: None,
3942 },
3943 Statement::IntLiteral(42),
3944 ],
3945 },
3946 Statement::WordCall {
3947 name: "call".to_string(),
3948 span: None,
3949 },
3950 ],
3951 source: None,
3952 allowed_lints: vec![],
3953 }],
3954 };
3955
3956 let mut checker = TypeChecker::new();
3957 let result = checker.check_program(&program);
3958 assert!(
3959 result.is_err(),
3960 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3961 );
3962 }
3963
3964 #[test]
3965 fn test_missing_effect_provides_helpful_error() {
3966 let program = Program {
3969 includes: vec![],
3970 unions: vec![],
3971 words: vec![WordDef {
3972 name: "myword".to_string(),
3973 effect: None, body: vec![Statement::IntLiteral(42)],
3975 source: None,
3976 allowed_lints: vec![],
3977 }],
3978 };
3979
3980 let mut checker = TypeChecker::new();
3981 let result = checker.check_program(&program);
3982 assert!(result.is_err());
3983 let err = result.unwrap_err();
3984 assert!(err.contains("myword"), "Error should mention word name");
3985 assert!(
3986 err.contains("stack effect"),
3987 "Error should mention stack effect"
3988 );
3989 }
3990
3991 #[test]
3992 fn test_valid_effect_exact_match() {
3993 let program = Program {
3996 includes: vec![],
3997 unions: vec![],
3998 words: vec![WordDef {
3999 name: "test".to_string(),
4000 effect: Some(Effect::new(
4001 StackType::Empty.push(Type::Int).push(Type::Int),
4002 StackType::singleton(Type::Int),
4003 )),
4004 body: vec![Statement::WordCall {
4005 name: "i.add".to_string(),
4006 span: None,
4007 }],
4008 source: None,
4009 allowed_lints: vec![],
4010 }],
4011 };
4012
4013 let mut checker = TypeChecker::new();
4014 let result = checker.check_program(&program);
4015 assert!(result.is_ok(), "Should accept: effect matches exactly");
4016 }
4017
4018 #[test]
4019 fn test_valid_polymorphic_passthrough() {
4020 let program = Program {
4023 includes: vec![],
4024 unions: vec![],
4025 words: vec![WordDef {
4026 name: "test".to_string(),
4027 effect: Some(Effect::new(
4028 StackType::Cons {
4029 rest: Box::new(StackType::RowVar("rest".to_string())),
4030 top: Type::Var("a".to_string()),
4031 },
4032 StackType::Cons {
4033 rest: Box::new(StackType::RowVar("rest".to_string())),
4034 top: Type::Var("a".to_string()),
4035 },
4036 )),
4037 body: vec![], source: None,
4039 allowed_lints: vec![],
4040 }],
4041 };
4042
4043 let mut checker = TypeChecker::new();
4044 let result = checker.check_program(&program);
4045 assert!(result.is_ok(), "Should accept: polymorphic identity");
4046 }
4047
4048 #[test]
4054 fn test_closure_basic_capture() {
4055 let program = Program {
4061 includes: vec![],
4062 unions: vec![],
4063 words: vec![WordDef {
4064 name: "make-adder".to_string(),
4065 effect: Some(Effect::new(
4066 StackType::singleton(Type::Int),
4067 StackType::singleton(Type::Closure {
4068 effect: Box::new(Effect::new(
4069 StackType::RowVar("r".to_string()).push(Type::Int),
4070 StackType::RowVar("r".to_string()).push(Type::Int),
4071 )),
4072 captures: vec![Type::Int], }),
4074 )),
4075 body: vec![Statement::Quotation {
4076 span: None,
4077 id: 0,
4078 body: vec![Statement::WordCall {
4079 name: "i.add".to_string(),
4080 span: None,
4081 }],
4082 }],
4083 source: None,
4084 allowed_lints: vec![],
4085 }],
4086 };
4087
4088 let mut checker = TypeChecker::new();
4089 let result = checker.check_program(&program);
4090 assert!(
4091 result.is_ok(),
4092 "Basic closure capture should work: {:?}",
4093 result.err()
4094 );
4095 }
4096
4097 #[test]
4098 fn test_closure_nested_two_levels() {
4099 let program = Program {
4104 includes: vec![],
4105 unions: vec![],
4106 words: vec![WordDef {
4107 name: "outer".to_string(),
4108 effect: Some(Effect::new(
4109 StackType::Empty,
4110 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4111 StackType::RowVar("r".to_string()),
4112 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
4113 Effect::new(
4114 StackType::RowVar("s".to_string()).push(Type::Int),
4115 StackType::RowVar("s".to_string()).push(Type::Int),
4116 ),
4117 ))),
4118 )))),
4119 )),
4120 body: vec![Statement::Quotation {
4121 span: None,
4122 id: 0,
4123 body: vec![Statement::Quotation {
4124 span: None,
4125 id: 1,
4126 body: vec![
4127 Statement::IntLiteral(1),
4128 Statement::WordCall {
4129 name: "i.add".to_string(),
4130 span: None,
4131 },
4132 ],
4133 }],
4134 }],
4135 source: None,
4136 allowed_lints: vec![],
4137 }],
4138 };
4139
4140 let mut checker = TypeChecker::new();
4141 let result = checker.check_program(&program);
4142 assert!(
4143 result.is_ok(),
4144 "Two-level nested quotations should work: {:?}",
4145 result.err()
4146 );
4147 }
4148
4149 #[test]
4150 fn test_closure_nested_three_levels() {
4151 let inner_effect = Effect::new(
4155 StackType::RowVar("a".to_string()).push(Type::Int),
4156 StackType::RowVar("a".to_string()).push(Type::Int),
4157 );
4158 let middle_effect = Effect::new(
4159 StackType::RowVar("b".to_string()),
4160 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
4161 );
4162 let outer_effect = Effect::new(
4163 StackType::RowVar("c".to_string()),
4164 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
4165 );
4166
4167 let program = Program {
4168 includes: vec![],
4169 unions: vec![],
4170 words: vec![WordDef {
4171 name: "deep".to_string(),
4172 effect: Some(Effect::new(
4173 StackType::Empty,
4174 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
4175 )),
4176 body: vec![Statement::Quotation {
4177 span: None,
4178 id: 0,
4179 body: vec![Statement::Quotation {
4180 span: None,
4181 id: 1,
4182 body: vec![Statement::Quotation {
4183 span: None,
4184 id: 2,
4185 body: vec![
4186 Statement::IntLiteral(1),
4187 Statement::WordCall {
4188 name: "i.add".to_string(),
4189 span: None,
4190 },
4191 ],
4192 }],
4193 }],
4194 }],
4195 source: None,
4196 allowed_lints: vec![],
4197 }],
4198 };
4199
4200 let mut checker = TypeChecker::new();
4201 let result = checker.check_program(&program);
4202 assert!(
4203 result.is_ok(),
4204 "Three-level nested quotations should work: {:?}",
4205 result.err()
4206 );
4207 }
4208
4209 #[test]
4210 fn test_closure_use_after_creation() {
4211 let adder_type = Type::Closure {
4217 effect: Box::new(Effect::new(
4218 StackType::RowVar("r".to_string()).push(Type::Int),
4219 StackType::RowVar("r".to_string()).push(Type::Int),
4220 )),
4221 captures: vec![Type::Int],
4222 };
4223
4224 let program = Program {
4225 includes: vec![],
4226 unions: vec![],
4227 words: vec![
4228 WordDef {
4229 name: "make-adder".to_string(),
4230 effect: Some(Effect::new(
4231 StackType::singleton(Type::Int),
4232 StackType::singleton(adder_type.clone()),
4233 )),
4234 body: vec![Statement::Quotation {
4235 span: None,
4236 id: 0,
4237 body: vec![Statement::WordCall {
4238 name: "i.add".to_string(),
4239 span: None,
4240 }],
4241 }],
4242 source: None,
4243 allowed_lints: vec![],
4244 },
4245 WordDef {
4246 name: "use-adder".to_string(),
4247 effect: Some(Effect::new(
4248 StackType::Empty,
4249 StackType::singleton(Type::Int),
4250 )),
4251 body: vec![
4252 Statement::IntLiteral(5),
4253 Statement::WordCall {
4254 name: "make-adder".to_string(),
4255 span: None,
4256 },
4257 Statement::IntLiteral(10),
4258 Statement::WordCall {
4259 name: "swap".to_string(),
4260 span: None,
4261 },
4262 Statement::WordCall {
4263 name: "call".to_string(),
4264 span: None,
4265 },
4266 ],
4267 source: None,
4268 allowed_lints: vec![],
4269 },
4270 ],
4271 };
4272
4273 let mut checker = TypeChecker::new();
4274 let result = checker.check_program(&program);
4275 assert!(
4276 result.is_ok(),
4277 "Closure usage after creation should work: {:?}",
4278 result.err()
4279 );
4280 }
4281
4282 #[test]
4283 fn test_closure_wrong_call_type() {
4284 let adder_type = Type::Closure {
4288 effect: Box::new(Effect::new(
4289 StackType::RowVar("r".to_string()).push(Type::Int),
4290 StackType::RowVar("r".to_string()).push(Type::Int),
4291 )),
4292 captures: vec![Type::Int],
4293 };
4294
4295 let program = Program {
4296 includes: vec![],
4297 unions: vec![],
4298 words: vec![
4299 WordDef {
4300 name: "make-adder".to_string(),
4301 effect: Some(Effect::new(
4302 StackType::singleton(Type::Int),
4303 StackType::singleton(adder_type.clone()),
4304 )),
4305 body: vec![Statement::Quotation {
4306 span: None,
4307 id: 0,
4308 body: vec![Statement::WordCall {
4309 name: "i.add".to_string(),
4310 span: None,
4311 }],
4312 }],
4313 source: None,
4314 allowed_lints: vec![],
4315 },
4316 WordDef {
4317 name: "bad-use".to_string(),
4318 effect: Some(Effect::new(
4319 StackType::Empty,
4320 StackType::singleton(Type::Int),
4321 )),
4322 body: vec![
4323 Statement::IntLiteral(5),
4324 Statement::WordCall {
4325 name: "make-adder".to_string(),
4326 span: None,
4327 },
4328 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
4330 name: "swap".to_string(),
4331 span: None,
4332 },
4333 Statement::WordCall {
4334 name: "call".to_string(),
4335 span: None,
4336 },
4337 ],
4338 source: None,
4339 allowed_lints: vec![],
4340 },
4341 ],
4342 };
4343
4344 let mut checker = TypeChecker::new();
4345 let result = checker.check_program(&program);
4346 assert!(
4347 result.is_err(),
4348 "Calling Int closure with String should fail"
4349 );
4350 }
4351
4352 #[test]
4353 fn test_closure_multiple_captures() {
4354 let program = Program {
4361 includes: vec![],
4362 unions: vec![],
4363 words: vec![WordDef {
4364 name: "make-between".to_string(),
4365 effect: Some(Effect::new(
4366 StackType::Empty.push(Type::Int).push(Type::Int),
4367 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4368 StackType::RowVar("r".to_string()).push(Type::Int),
4369 StackType::RowVar("r".to_string()).push(Type::Bool),
4370 )))),
4371 )),
4372 body: vec![Statement::Quotation {
4373 span: None,
4374 id: 0,
4375 body: vec![
4376 Statement::WordCall {
4378 name: "i.>=".to_string(),
4379 span: None,
4380 },
4381 ],
4383 }],
4384 source: None,
4385 allowed_lints: vec![],
4386 }],
4387 };
4388
4389 let mut checker = TypeChecker::new();
4390 let result = checker.check_program(&program);
4391 assert!(
4395 result.is_ok() || result.is_err(),
4396 "Multiple captures should be handled (pass or fail gracefully)"
4397 );
4398 }
4399
4400 #[test]
4401 fn test_quotation_type_preserved_through_word() {
4402 let quot_type = Type::Quotation(Box::new(Effect::new(
4405 StackType::RowVar("r".to_string()).push(Type::Int),
4406 StackType::RowVar("r".to_string()).push(Type::Int),
4407 )));
4408
4409 let program = Program {
4410 includes: vec![],
4411 unions: vec![],
4412 words: vec![WordDef {
4413 name: "identity-quot".to_string(),
4414 effect: Some(Effect::new(
4415 StackType::singleton(quot_type.clone()),
4416 StackType::singleton(quot_type.clone()),
4417 )),
4418 body: vec![], source: None,
4420 allowed_lints: vec![],
4421 }],
4422 };
4423
4424 let mut checker = TypeChecker::new();
4425 let result = checker.check_program(&program);
4426 assert!(
4427 result.is_ok(),
4428 "Quotation type should be preserved through identity word: {:?}",
4429 result.err()
4430 );
4431 }
4432
4433 #[test]
4434 fn test_closure_captures_value_for_inner_quotation() {
4435 let closure_effect = Effect::new(
4441 StackType::RowVar("r".to_string()).push(Type::Int),
4442 StackType::RowVar("r".to_string()).push(Type::Int),
4443 );
4444
4445 let program = Program {
4446 includes: vec![],
4447 unions: vec![],
4448 words: vec![WordDef {
4449 name: "make-inner-adder".to_string(),
4450 effect: Some(Effect::new(
4451 StackType::singleton(Type::Int),
4452 StackType::singleton(Type::Closure {
4453 effect: Box::new(closure_effect),
4454 captures: vec![Type::Int],
4455 }),
4456 )),
4457 body: vec![Statement::Quotation {
4458 span: None,
4459 id: 0,
4460 body: vec![
4461 Statement::WordCall {
4463 name: "i.add".to_string(),
4464 span: None,
4465 },
4466 ],
4467 }],
4468 source: None,
4469 allowed_lints: vec![],
4470 }],
4471 };
4472
4473 let mut checker = TypeChecker::new();
4474 let result = checker.check_program(&program);
4475 assert!(
4476 result.is_ok(),
4477 "Closure with capture for inner work should pass: {:?}",
4478 result.err()
4479 );
4480 }
4481
4482 #[test]
4483 fn test_union_type_mismatch_should_fail() {
4484 use crate::ast::{UnionDef, UnionField, UnionVariant};
4487
4488 let mut program = Program {
4489 includes: vec![],
4490 unions: vec![
4491 UnionDef {
4492 name: "UnionA".to_string(),
4493 variants: vec![UnionVariant {
4494 name: "AVal".to_string(),
4495 fields: vec![UnionField {
4496 name: "x".to_string(),
4497 type_name: "Int".to_string(),
4498 }],
4499 source: None,
4500 }],
4501 source: None,
4502 },
4503 UnionDef {
4504 name: "UnionB".to_string(),
4505 variants: vec![UnionVariant {
4506 name: "BVal".to_string(),
4507 fields: vec![UnionField {
4508 name: "y".to_string(),
4509 type_name: "Int".to_string(),
4510 }],
4511 source: None,
4512 }],
4513 source: None,
4514 },
4515 ],
4516 words: vec![
4517 WordDef {
4519 name: "takes-a".to_string(),
4520 effect: Some(Effect::new(
4521 StackType::RowVar("rest".to_string())
4522 .push(Type::Union("UnionA".to_string())),
4523 StackType::RowVar("rest".to_string()),
4524 )),
4525 body: vec![Statement::WordCall {
4526 name: "drop".to_string(),
4527 span: None,
4528 }],
4529 source: None,
4530 allowed_lints: vec![],
4531 },
4532 WordDef {
4535 name: "main".to_string(),
4536 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4537 body: vec![
4538 Statement::IntLiteral(99),
4539 Statement::WordCall {
4540 name: "Make-BVal".to_string(),
4541 span: None,
4542 },
4543 Statement::WordCall {
4544 name: "takes-a".to_string(),
4545 span: None,
4546 },
4547 ],
4548 source: None,
4549 allowed_lints: vec![],
4550 },
4551 ],
4552 };
4553
4554 program.generate_constructors().unwrap();
4556
4557 let mut checker = TypeChecker::new();
4558 let result = checker.check_program(&program);
4559
4560 assert!(
4562 result.is_err(),
4563 "Passing UnionB to function expecting UnionA should fail, but got: {:?}",
4564 result
4565 );
4566 let err = result.unwrap_err();
4567 assert!(
4568 err.contains("Union") || err.contains("mismatch"),
4569 "Error should mention union type mismatch, got: {}",
4570 err
4571 );
4572 }
4573
4574 fn make_word_call(name: &str) -> Statement {
4579 Statement::WordCall {
4580 name: name.to_string(),
4581 span: None,
4582 }
4583 }
4584
4585 #[test]
4586 fn test_aux_basic_round_trip() {
4587 let program = Program {
4589 includes: vec![],
4590 unions: vec![],
4591 words: vec![WordDef {
4592 name: "test".to_string(),
4593 effect: Some(Effect::new(
4594 StackType::singleton(Type::Int),
4595 StackType::singleton(Type::Int),
4596 )),
4597 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4598 source: None,
4599 allowed_lints: vec![],
4600 }],
4601 };
4602
4603 let mut checker = TypeChecker::new();
4604 assert!(checker.check_program(&program).is_ok());
4605 }
4606
4607 #[test]
4608 fn test_aux_preserves_type() {
4609 let program = Program {
4611 includes: vec![],
4612 unions: vec![],
4613 words: vec![WordDef {
4614 name: "test".to_string(),
4615 effect: Some(Effect::new(
4616 StackType::singleton(Type::String),
4617 StackType::singleton(Type::String),
4618 )),
4619 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4620 source: None,
4621 allowed_lints: vec![],
4622 }],
4623 };
4624
4625 let mut checker = TypeChecker::new();
4626 assert!(checker.check_program(&program).is_ok());
4627 }
4628
4629 #[test]
4630 fn test_aux_unbalanced_error() {
4631 let program = Program {
4633 includes: vec![],
4634 unions: vec![],
4635 words: vec![WordDef {
4636 name: "test".to_string(),
4637 effect: Some(Effect::new(
4638 StackType::singleton(Type::Int),
4639 StackType::Empty,
4640 )),
4641 body: vec![make_word_call(">aux")],
4642 source: None,
4643 allowed_lints: vec![],
4644 }],
4645 };
4646
4647 let mut checker = TypeChecker::new();
4648 let result = checker.check_program(&program);
4649 assert!(result.is_err());
4650 let err = result.unwrap_err();
4651 assert!(
4652 err.contains("aux stack is not empty"),
4653 "Expected aux stack balance error, got: {}",
4654 err
4655 );
4656 }
4657
4658 #[test]
4659 fn test_aux_pop_empty_error() {
4660 let program = Program {
4662 includes: vec![],
4663 unions: vec![],
4664 words: vec![WordDef {
4665 name: "test".to_string(),
4666 effect: Some(Effect::new(
4667 StackType::Empty,
4668 StackType::singleton(Type::Int),
4669 )),
4670 body: vec![make_word_call("aux>")],
4671 source: None,
4672 allowed_lints: vec![],
4673 }],
4674 };
4675
4676 let mut checker = TypeChecker::new();
4677 let result = checker.check_program(&program);
4678 assert!(result.is_err());
4679 let err = result.unwrap_err();
4680 assert!(
4681 err.contains("aux stack is empty"),
4682 "Expected aux empty error, got: {}",
4683 err
4684 );
4685 }
4686
4687 #[test]
4688 fn test_aux_multiple_values() {
4689 let program = Program {
4693 includes: vec![],
4694 unions: vec![],
4695 words: vec![WordDef {
4696 name: "test".to_string(),
4697 effect: Some(Effect::new(
4698 StackType::Empty.push(Type::Int).push(Type::String),
4699 StackType::Empty.push(Type::Int).push(Type::String),
4700 )),
4701 body: vec![
4702 make_word_call(">aux"),
4703 make_word_call(">aux"),
4704 make_word_call("aux>"),
4705 make_word_call("aux>"),
4706 ],
4707 source: None,
4708 allowed_lints: vec![],
4709 }],
4710 };
4711
4712 let mut checker = TypeChecker::new();
4713 assert!(checker.check_program(&program).is_ok());
4714 }
4715
4716 #[test]
4717 fn test_aux_max_depths_tracked() {
4718 let program = Program {
4720 includes: vec![],
4721 unions: vec![],
4722 words: vec![WordDef {
4723 name: "test".to_string(),
4724 effect: Some(Effect::new(
4725 StackType::singleton(Type::Int),
4726 StackType::singleton(Type::Int),
4727 )),
4728 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4729 source: None,
4730 allowed_lints: vec![],
4731 }],
4732 };
4733
4734 let mut checker = TypeChecker::new();
4735 checker.check_program(&program).unwrap();
4736 let depths = checker.take_aux_max_depths();
4737 assert_eq!(depths.get("test"), Some(&1));
4738 }
4739
4740 #[test]
4741 fn test_aux_in_match_balanced() {
4742 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
4744
4745 let union_def = UnionDef {
4746 name: "Choice".to_string(),
4747 variants: vec![
4748 UnionVariant {
4749 name: "Left".to_string(),
4750 fields: vec![],
4751 source: None,
4752 },
4753 UnionVariant {
4754 name: "Right".to_string(),
4755 fields: vec![],
4756 source: None,
4757 },
4758 ],
4759 source: None,
4760 };
4761
4762 let program = Program {
4765 includes: vec![],
4766 unions: vec![union_def],
4767 words: vec![WordDef {
4768 name: "test".to_string(),
4769 effect: Some(Effect::new(
4770 StackType::Empty
4771 .push(Type::Int)
4772 .push(Type::Union("Choice".to_string())),
4773 StackType::singleton(Type::Int),
4774 )),
4775 body: vec![
4776 make_word_call("swap"),
4777 make_word_call(">aux"),
4778 Statement::Match {
4779 arms: vec![
4780 MatchArm {
4781 pattern: Pattern::Variant("Left".to_string()),
4782 body: vec![make_word_call("aux>")],
4783 span: None,
4784 },
4785 MatchArm {
4786 pattern: Pattern::Variant("Right".to_string()),
4787 body: vec![make_word_call("aux>")],
4788 span: None,
4789 },
4790 ],
4791 span: None,
4792 },
4793 ],
4794 source: None,
4795 allowed_lints: vec![],
4796 }],
4797 };
4798
4799 let mut checker = TypeChecker::new();
4800 assert!(checker.check_program(&program).is_ok());
4801 }
4802
4803 #[test]
4804 fn test_aux_in_match_unbalanced_error() {
4805 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
4807
4808 let union_def = UnionDef {
4809 name: "Choice".to_string(),
4810 variants: vec![
4811 UnionVariant {
4812 name: "Left".to_string(),
4813 fields: vec![],
4814 source: None,
4815 },
4816 UnionVariant {
4817 name: "Right".to_string(),
4818 fields: vec![],
4819 source: None,
4820 },
4821 ],
4822 source: None,
4823 };
4824
4825 let program = Program {
4828 includes: vec![],
4829 unions: vec![union_def],
4830 words: vec![WordDef {
4831 name: "test".to_string(),
4832 effect: Some(Effect::new(
4833 StackType::Empty
4834 .push(Type::Int)
4835 .push(Type::Union("Choice".to_string())),
4836 StackType::singleton(Type::Int),
4837 )),
4838 body: vec![
4839 make_word_call("swap"),
4840 make_word_call(">aux"),
4841 Statement::Match {
4842 arms: vec![
4843 MatchArm {
4844 pattern: Pattern::Variant("Left".to_string()),
4845 body: vec![make_word_call("aux>")],
4846 span: None,
4847 },
4848 MatchArm {
4849 pattern: Pattern::Variant("Right".to_string()),
4850 body: vec![],
4851 span: None,
4852 },
4853 ],
4854 span: None,
4855 },
4856 ],
4857 source: None,
4858 allowed_lints: vec![],
4859 }],
4860 };
4861
4862 let mut checker = TypeChecker::new();
4863 let result = checker.check_program(&program);
4864 assert!(result.is_err());
4865 let err = result.unwrap_err();
4866 assert!(
4867 err.contains("aux stack"),
4868 "Expected aux stack mismatch error, got: {}",
4869 err
4870 );
4871 }
4872
4873 #[test]
4874 fn test_aux_in_quotation_rejected() {
4875 let program = Program {
4877 includes: vec![],
4878 unions: vec![],
4879 words: vec![WordDef {
4880 name: "test".to_string(),
4881 effect: Some(Effect::new(
4882 StackType::singleton(Type::Int),
4883 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4884 StackType::singleton(Type::Int),
4885 StackType::singleton(Type::Int),
4886 )))),
4887 )),
4888 body: vec![Statement::Quotation {
4889 span: None,
4890 id: 0,
4891 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4892 }],
4893 source: None,
4894 allowed_lints: vec![],
4895 }],
4896 };
4897
4898 let mut checker = TypeChecker::new();
4899 let result = checker.check_program(&program);
4900 assert!(result.is_err());
4901 let err = result.unwrap_err();
4902 assert!(
4903 err.contains("not supported inside quotations"),
4904 "Expected quotation rejection error, got: {}",
4905 err
4906 );
4907 }
4908}