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 if name == "dip" {
1248 return self.infer_dip(span, current_stack);
1249 }
1250 if name == "keep" {
1251 return self.infer_keep(span, current_stack);
1252 }
1253 if name == "bi" {
1254 return self.infer_bi(span, current_stack);
1255 }
1256
1257 let effect = self
1259 .lookup_word_effect(name)
1260 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
1261
1262 let fresh_effect = self.freshen_effect(&effect);
1264
1265 let adjusted_stack = if name == "strand.spawn" {
1267 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1268 } else {
1269 current_stack
1270 };
1271
1272 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name, span)?;
1274
1275 let propagated_effects = fresh_effect.effects.clone();
1279
1280 Ok((result_stack, subst, propagated_effects))
1281 }
1282
1283 fn infer_to_aux(
1285 &self,
1286 _span: &Option<crate::ast::Span>,
1287 current_stack: StackType,
1288 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1289 if self.in_quotation_scope.get() {
1290 return Err(">aux is not supported inside quotations.\n\
1291 Quotations are compiled as separate functions without aux stack slots.\n\
1292 Extract the quotation body into a named word if you need aux."
1293 .to_string());
1294 }
1295 let (rest, top_type) = self.pop_type(¤t_stack, ">aux")?;
1296
1297 let mut aux = self.current_aux_stack.borrow_mut();
1299 *aux = aux.clone().push(top_type);
1300
1301 let depth = Self::stack_depth(&aux);
1303 if let Some((word_name, _)) = self.current_word.borrow().as_ref() {
1304 let mut depths = self.aux_max_depths.borrow_mut();
1305 let entry = depths.entry(word_name.clone()).or_insert(0);
1306 if depth > *entry {
1307 *entry = depth;
1308 }
1309 }
1310
1311 Ok((rest, Subst::empty(), vec![]))
1312 }
1313
1314 fn infer_from_aux(
1316 &self,
1317 _span: &Option<crate::ast::Span>,
1318 current_stack: StackType,
1319 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1320 if self.in_quotation_scope.get() {
1321 return Err("aux> is not supported inside quotations.\n\
1322 Quotations are compiled as separate functions without aux stack slots.\n\
1323 Extract the quotation body into a named word if you need aux."
1324 .to_string());
1325 }
1326 let mut aux = self.current_aux_stack.borrow_mut();
1327 match aux.clone().pop() {
1328 Some((rest, top_type)) => {
1329 *aux = rest;
1330 Ok((current_stack.push(top_type), Subst::empty(), vec![]))
1331 }
1332 None => {
1333 let line_info = self.line_prefix();
1334 Err(format!(
1335 "{}aux>: aux stack is empty. Every aux> must be paired with a preceding >aux.",
1336 line_info
1337 ))
1338 }
1339 }
1340 }
1341
1342 fn infer_call(
1348 &self,
1349 span: &Option<crate::ast::Span>,
1350 current_stack: StackType,
1351 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1352 let line_prefix = self.line_prefix();
1354 let (remaining_stack, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1355 format!(
1356 "{}call: stack underflow - expected quotation on stack",
1357 line_prefix
1358 )
1359 })?;
1360
1361 let quot_effect = match "_type {
1363 Type::Quotation(effect) => (**effect).clone(),
1364 Type::Closure { effect, .. } => (**effect).clone(),
1365 Type::Var(_) => {
1366 let effect = self
1369 .lookup_word_effect("call")
1370 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1371 let fresh_effect = self.freshen_effect(&effect);
1372 let (result_stack, subst) =
1373 self.apply_effect(&fresh_effect, current_stack, "call", span)?;
1374 return Ok((result_stack, subst, vec![]));
1375 }
1376 _ => {
1377 return Err(format!(
1378 "call: expected quotation or closure on stack, got {}",
1379 quot_type
1380 ));
1381 }
1382 };
1383
1384 if quot_effect.has_yield() {
1386 return Err("Cannot call quotation with Yield effect directly.\n\
1387 Quotations that yield values must be wrapped with `strand.weave`.\n\
1388 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1389 .to_string());
1390 }
1391
1392 let fresh_effect = self.freshen_effect("_effect);
1394
1395 let (result_stack, subst) =
1397 self.apply_effect(&fresh_effect, remaining_stack, "call", span)?;
1398
1399 let propagated_effects = fresh_effect.effects.clone();
1401
1402 Ok((result_stack, subst, propagated_effects))
1403 }
1404
1405 fn infer_dip(
1410 &self,
1411 span: &Option<crate::ast::Span>,
1412 current_stack: StackType,
1413 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1414 let line_prefix = self.line_prefix();
1415
1416 let (stack_after_quot, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1418 format!(
1419 "{}dip: stack underflow - expected quotation on stack",
1420 line_prefix
1421 )
1422 })?;
1423
1424 let quot_effect = match "_type {
1426 Type::Quotation(effect) => (**effect).clone(),
1427 Type::Closure { effect, .. } => (**effect).clone(),
1428 Type::Var(_) => {
1429 let effect = self
1431 .lookup_word_effect("dip")
1432 .ok_or_else(|| "Unknown word: 'dip'".to_string())?;
1433 let fresh_effect = self.freshen_effect(&effect);
1434 let (result_stack, subst) =
1435 self.apply_effect(&fresh_effect, current_stack, "dip", span)?;
1436 return Ok((result_stack, subst, vec![]));
1437 }
1438 _ => {
1439 return Err(format!(
1440 "{}dip: expected quotation or closure on top of stack, got {}",
1441 line_prefix, quot_type
1442 ));
1443 }
1444 };
1445
1446 if quot_effect.has_yield() {
1447 return Err("dip: quotation must not have Yield effects.\n\
1448 Use strand.weave for quotations that yield."
1449 .to_string());
1450 }
1451
1452 let (rest_stack, preserved_type) = stack_after_quot.clone().pop().ok_or_else(|| {
1454 format!(
1455 "{}dip: stack underflow - expected a value below the quotation",
1456 line_prefix
1457 )
1458 })?;
1459
1460 let fresh_effect = self.freshen_effect("_effect);
1462 let (result_stack, subst) =
1463 self.apply_effect(&fresh_effect, rest_stack, "dip (quotation)", span)?;
1464
1465 let resolved_preserved = subst.apply_type(&preserved_type);
1468 let result_stack = result_stack.push(resolved_preserved);
1469
1470 let propagated_effects = fresh_effect.effects.clone();
1471 Ok((result_stack, subst, propagated_effects))
1472 }
1473
1474 fn infer_keep(
1479 &self,
1480 span: &Option<crate::ast::Span>,
1481 current_stack: StackType,
1482 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1483 let line_prefix = self.line_prefix();
1484
1485 let (stack_after_quot, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1487 format!(
1488 "{}keep: stack underflow - expected quotation on stack",
1489 line_prefix
1490 )
1491 })?;
1492
1493 let quot_effect = match "_type {
1495 Type::Quotation(effect) => (**effect).clone(),
1496 Type::Closure { effect, .. } => (**effect).clone(),
1497 Type::Var(_) => {
1498 let effect = self
1499 .lookup_word_effect("keep")
1500 .ok_or_else(|| "Unknown word: 'keep'".to_string())?;
1501 let fresh_effect = self.freshen_effect(&effect);
1502 let (result_stack, subst) =
1503 self.apply_effect(&fresh_effect, current_stack, "keep", span)?;
1504 return Ok((result_stack, subst, vec![]));
1505 }
1506 _ => {
1507 return Err(format!(
1508 "{}keep: expected quotation or closure on top of stack, got {}",
1509 line_prefix, quot_type
1510 ));
1511 }
1512 };
1513
1514 if quot_effect.has_yield() {
1515 return Err("keep: quotation must not have Yield effects.\n\
1516 Use strand.weave for quotations that yield."
1517 .to_string());
1518 }
1519
1520 let (_rest_stack, preserved_type) = stack_after_quot.clone().pop().ok_or_else(|| {
1522 format!(
1523 "{}keep: stack underflow - expected a value below the quotation",
1524 line_prefix
1525 )
1526 })?;
1527
1528 let fresh_effect = self.freshen_effect("_effect);
1531 let (result_stack, subst) =
1532 self.apply_effect(&fresh_effect, stack_after_quot, "keep (quotation)", span)?;
1533
1534 let resolved_preserved = subst.apply_type(&preserved_type);
1537 let result_stack = result_stack.push(resolved_preserved);
1538
1539 let propagated_effects = fresh_effect.effects.clone();
1540 Ok((result_stack, subst, propagated_effects))
1541 }
1542
1543 fn infer_bi(
1548 &self,
1549 span: &Option<crate::ast::Span>,
1550 current_stack: StackType,
1551 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1552 let line_prefix = self.line_prefix();
1553
1554 let (stack1, quot2_type) = current_stack.clone().pop().ok_or_else(|| {
1556 format!(
1557 "{}bi: stack underflow - expected second quotation on stack",
1558 line_prefix
1559 )
1560 })?;
1561
1562 let (stack2, quot1_type) = stack1.clone().pop().ok_or_else(|| {
1564 format!(
1565 "{}bi: stack underflow - expected first quotation on stack",
1566 line_prefix
1567 )
1568 })?;
1569
1570 let quot1_effect = match "1_type {
1572 Type::Quotation(effect) => (**effect).clone(),
1573 Type::Closure { effect, .. } => (**effect).clone(),
1574 Type::Var(_) => {
1575 let effect = self
1576 .lookup_word_effect("bi")
1577 .ok_or_else(|| "Unknown word: 'bi'".to_string())?;
1578 let fresh_effect = self.freshen_effect(&effect);
1579 let (result_stack, subst) =
1580 self.apply_effect(&fresh_effect, current_stack, "bi", span)?;
1581 return Ok((result_stack, subst, vec![]));
1582 }
1583 _ => {
1584 return Err(format!(
1585 "{}bi: expected quotation or closure as first quotation, got {}",
1586 line_prefix, quot1_type
1587 ));
1588 }
1589 };
1590
1591 let quot2_effect = match "2_type {
1592 Type::Quotation(effect) => (**effect).clone(),
1593 Type::Closure { effect, .. } => (**effect).clone(),
1594 Type::Var(_) => {
1595 let effect = self
1596 .lookup_word_effect("bi")
1597 .ok_or_else(|| "Unknown word: 'bi'".to_string())?;
1598 let fresh_effect = self.freshen_effect(&effect);
1599 let (result_stack, subst) =
1600 self.apply_effect(&fresh_effect, current_stack, "bi", span)?;
1601 return Ok((result_stack, subst, vec![]));
1602 }
1603 _ => {
1604 return Err(format!(
1605 "{}bi: expected quotation or closure as second quotation, got {}",
1606 line_prefix, quot2_type
1607 ));
1608 }
1609 };
1610
1611 if quot1_effect.has_yield() || quot2_effect.has_yield() {
1612 return Err("bi: quotations must not have Yield effects.\n\
1613 Use strand.weave for quotations that yield."
1614 .to_string());
1615 }
1616
1617 let (_rest, preserved_type) = stack2.clone().pop().ok_or_else(|| {
1620 format!(
1621 "{}bi: stack underflow - expected a value below the quotations",
1622 line_prefix
1623 )
1624 })?;
1625
1626 let fresh_effect1 = self.freshen_effect("1_effect);
1628 let (after_quot1, subst1) =
1629 self.apply_effect(&fresh_effect1, stack2, "bi (first quotation)", span)?;
1630
1631 let resolved_preserved = subst1.apply_type(&preserved_type);
1634 let with_x = after_quot1.push(resolved_preserved);
1635
1636 let fresh_effect2 = self.freshen_effect("2_effect);
1638 let (result_stack, subst2) =
1639 self.apply_effect(&fresh_effect2, with_x, "bi (second quotation)", span)?;
1640
1641 let subst = subst1.compose(&subst2);
1642
1643 let mut effects = fresh_effect1.effects.clone();
1644 for e in fresh_effect2.effects.clone() {
1645 if !effects.contains(&e) {
1646 effects.push(e);
1647 }
1648 }
1649
1650 Ok((result_stack, subst, effects))
1651 }
1652
1653 fn infer_statement(
1656 &self,
1657 statement: &Statement,
1658 current_stack: StackType,
1659 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1660 match statement {
1661 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1662 Statement::BoolLiteral(_) => {
1663 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1664 }
1665 Statement::StringLiteral(_) => {
1666 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1667 }
1668 Statement::FloatLiteral(_) => {
1669 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1670 }
1671 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1672 Statement::Match { arms, span } => self.infer_match(arms, span, current_stack),
1673 Statement::WordCall { name, span } => self.infer_word_call(name, span, current_stack),
1674 Statement::If {
1675 then_branch,
1676 else_branch,
1677 span,
1678 } => self.infer_if(then_branch, else_branch, span, current_stack),
1679 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1680 }
1681 }
1682
1683 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1685 if let Some(effect) = builtin_signature(name) {
1687 return Some(effect);
1688 }
1689
1690 self.env.get(name).cloned()
1692 }
1693
1694 fn apply_effect(
1699 &self,
1700 effect: &Effect,
1701 current_stack: StackType,
1702 operation: &str,
1703 span: &Option<crate::ast::Span>,
1704 ) -> Result<(StackType, Subst), String> {
1705 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1713 let stack_concrete = Self::count_concrete_types(¤t_stack);
1714
1715 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1716 let is_rigid = row_var_name == "rest";
1729
1730 if is_rigid && effect_concrete > stack_concrete {
1731 let word_name = self
1732 .current_word
1733 .borrow()
1734 .as_ref()
1735 .map(|(n, _)| n.clone())
1736 .unwrap_or_else(|| "unknown".to_string());
1737 return Err(format!(
1738 "{}In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1739 self.line_prefix(),
1740 word_name,
1741 operation,
1742 effect_concrete,
1743 stack_concrete
1744 ));
1745 }
1746 }
1747
1748 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1750 let line_info = span
1751 .as_ref()
1752 .map(|s| format_line_prefix(s.line))
1753 .unwrap_or_default();
1754 format!(
1755 "{}{}: stack type mismatch. Expected {}, got {}: {}",
1756 line_info, operation, effect.inputs, current_stack, e
1757 )
1758 })?;
1759
1760 let result_stack = subst.apply_stack(&effect.outputs);
1762
1763 Ok((result_stack, subst))
1764 }
1765
1766 fn count_concrete_types(stack: &StackType) -> usize {
1768 let mut count = 0;
1769 let mut current = stack;
1770 while let StackType::Cons { rest, top: _ } = current {
1771 count += 1;
1772 current = rest;
1773 }
1774 count
1775 }
1776
1777 fn get_row_var_base(stack: &StackType) -> Option<String> {
1779 let mut current = stack;
1780 while let StackType::Cons { rest, top: _ } = current {
1781 current = rest;
1782 }
1783 match current {
1784 StackType::RowVar(name) => Some(name.clone()),
1785 _ => None,
1786 }
1787 }
1788
1789 fn adjust_stack_for_spawn(
1794 &self,
1795 current_stack: StackType,
1796 spawn_effect: &Effect,
1797 ) -> Result<StackType, String> {
1798 let expected_quot_type = match &spawn_effect.inputs {
1801 StackType::Cons { top, rest: _ } => {
1802 if !matches!(top, Type::Quotation(_)) {
1803 return Ok(current_stack); }
1805 top
1806 }
1807 _ => return Ok(current_stack),
1808 };
1809
1810 let (rest_stack, actual_type) = match ¤t_stack {
1812 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1813 _ => return Ok(current_stack), };
1815
1816 if let Type::Quotation(actual_effect) = actual_type {
1818 if !matches!(actual_effect.inputs, StackType::Empty) {
1820 let expected_effect = match expected_quot_type {
1822 Type::Quotation(eff) => eff.as_ref(),
1823 _ => return Ok(current_stack),
1824 };
1825
1826 let captures = calculate_captures(actual_effect, expected_effect)?;
1828
1829 let closure_type = Type::Closure {
1831 effect: Box::new(expected_effect.clone()),
1832 captures: captures.clone(),
1833 };
1834
1835 let mut adjusted_stack = rest_stack;
1838 for _ in &captures {
1839 adjusted_stack = match adjusted_stack {
1840 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1841 _ => {
1842 return Err(format!(
1843 "strand.spawn: not enough values on stack to capture. Need {} values",
1844 captures.len()
1845 ));
1846 }
1847 };
1848 }
1849
1850 return Ok(adjusted_stack.push(closure_type));
1852 }
1853 }
1854
1855 Ok(current_stack)
1856 }
1857
1858 fn analyze_captures(
1884 &self,
1885 body_effect: &Effect,
1886 _current_stack: &StackType,
1887 ) -> Result<Type, String> {
1888 let expected = self.expected_quotation_type.borrow().clone();
1890
1891 match expected {
1892 Some(Type::Closure { effect, .. }) => {
1893 let captures = calculate_captures(body_effect, &effect)?;
1895 Ok(Type::Closure { effect, captures })
1896 }
1897 Some(Type::Quotation(expected_effect)) => {
1898 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1904 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1905
1906 if expected_is_empty && body_needs_inputs {
1907 let captures = calculate_captures(body_effect, &expected_effect)?;
1910 Ok(Type::Closure {
1911 effect: expected_effect,
1912 captures,
1913 })
1914 } else {
1915 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1921 let expected_quot = Type::Quotation(expected_effect.clone());
1922 unify_types(&body_quot, &expected_quot).map_err(|e| {
1923 format!(
1924 "quotation effect mismatch: expected {}, got {}: {}",
1925 expected_effect, body_effect, e
1926 )
1927 })?;
1928
1929 Ok(Type::Quotation(expected_effect))
1931 }
1932 }
1933 _ => {
1934 Ok(Type::Quotation(Box::new(body_effect.clone())))
1936 }
1937 }
1938 }
1939
1940 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1944 declared.iter().any(|decl| match (inferred, decl) {
1945 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1946 })
1947 }
1948
1949 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1951 match stack {
1952 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1953 StackType::Empty => Err(format!(
1954 "{}: stack underflow - expected value on stack but stack is empty",
1955 context
1956 )),
1957 StackType::RowVar(_) => {
1958 Err(format!(
1962 "{}: cannot pop from polymorphic stack without more type information",
1963 context
1964 ))
1965 }
1966 }
1967 }
1968}
1969
1970impl Default for TypeChecker {
1971 fn default() -> Self {
1972 Self::new()
1973 }
1974}
1975
1976#[cfg(test)]
1977mod tests {
1978 use super::*;
1979
1980 #[test]
1981 fn test_simple_literal() {
1982 let program = Program {
1983 includes: vec![],
1984 unions: vec![],
1985 words: vec![WordDef {
1986 name: "test".to_string(),
1987 effect: Some(Effect::new(
1988 StackType::Empty,
1989 StackType::singleton(Type::Int),
1990 )),
1991 body: vec![Statement::IntLiteral(42)],
1992 source: None,
1993 allowed_lints: vec![],
1994 }],
1995 };
1996
1997 let mut checker = TypeChecker::new();
1998 assert!(checker.check_program(&program).is_ok());
1999 }
2000
2001 #[test]
2002 fn test_simple_operation() {
2003 let program = Program {
2005 includes: vec![],
2006 unions: vec![],
2007 words: vec![WordDef {
2008 name: "test".to_string(),
2009 effect: Some(Effect::new(
2010 StackType::Empty.push(Type::Int).push(Type::Int),
2011 StackType::singleton(Type::Int),
2012 )),
2013 body: vec![Statement::WordCall {
2014 name: "i.add".to_string(),
2015 span: None,
2016 }],
2017 source: None,
2018 allowed_lints: vec![],
2019 }],
2020 };
2021
2022 let mut checker = TypeChecker::new();
2023 assert!(checker.check_program(&program).is_ok());
2024 }
2025
2026 #[test]
2027 fn test_type_mismatch() {
2028 let program = Program {
2030 includes: vec![],
2031 unions: vec![],
2032 words: vec![WordDef {
2033 name: "test".to_string(),
2034 effect: Some(Effect::new(
2035 StackType::singleton(Type::String),
2036 StackType::Empty,
2037 )),
2038 body: vec![
2039 Statement::IntLiteral(42), Statement::WordCall {
2041 name: "io.write-line".to_string(),
2042 span: None,
2043 },
2044 ],
2045 source: None,
2046 allowed_lints: vec![],
2047 }],
2048 };
2049
2050 let mut checker = TypeChecker::new();
2051 let result = checker.check_program(&program);
2052 assert!(result.is_err());
2053 assert!(result.unwrap_err().contains("Type mismatch"));
2054 }
2055
2056 #[test]
2057 fn test_polymorphic_dup() {
2058 let program = Program {
2060 includes: vec![],
2061 unions: vec![],
2062 words: vec![WordDef {
2063 name: "my-dup".to_string(),
2064 effect: Some(Effect::new(
2065 StackType::singleton(Type::Int),
2066 StackType::Empty.push(Type::Int).push(Type::Int),
2067 )),
2068 body: vec![Statement::WordCall {
2069 name: "dup".to_string(),
2070 span: None,
2071 }],
2072 source: None,
2073 allowed_lints: vec![],
2074 }],
2075 };
2076
2077 let mut checker = TypeChecker::new();
2078 assert!(checker.check_program(&program).is_ok());
2079 }
2080
2081 #[test]
2082 fn test_conditional_branches() {
2083 let program = Program {
2086 includes: vec![],
2087 unions: vec![],
2088 words: vec![WordDef {
2089 name: "test".to_string(),
2090 effect: Some(Effect::new(
2091 StackType::Empty.push(Type::Int).push(Type::Int),
2092 StackType::singleton(Type::String),
2093 )),
2094 body: vec![
2095 Statement::WordCall {
2096 name: "i.>".to_string(),
2097 span: None,
2098 },
2099 Statement::If {
2100 then_branch: vec![Statement::StringLiteral("greater".to_string())],
2101 else_branch: Some(vec![Statement::StringLiteral(
2102 "not greater".to_string(),
2103 )]),
2104 span: None,
2105 },
2106 ],
2107 source: None,
2108 allowed_lints: vec![],
2109 }],
2110 };
2111
2112 let mut checker = TypeChecker::new();
2113 assert!(checker.check_program(&program).is_ok());
2114 }
2115
2116 #[test]
2117 fn test_mismatched_branches() {
2118 let program = Program {
2121 includes: vec![],
2122 unions: vec![],
2123 words: vec![WordDef {
2124 name: "test".to_string(),
2125 effect: Some(Effect::new(
2126 StackType::Empty,
2127 StackType::singleton(Type::Int),
2128 )),
2129 body: vec![
2130 Statement::BoolLiteral(true),
2131 Statement::If {
2132 then_branch: vec![Statement::IntLiteral(42)],
2133 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
2134 span: None,
2135 },
2136 ],
2137 source: None,
2138 allowed_lints: vec![],
2139 }],
2140 };
2141
2142 let mut checker = TypeChecker::new();
2143 let result = checker.check_program(&program);
2144 assert!(result.is_err());
2145 assert!(result.unwrap_err().contains("incompatible"));
2146 }
2147
2148 #[test]
2149 fn test_user_defined_word_call() {
2150 let program = Program {
2153 includes: vec![],
2154 unions: vec![],
2155 words: vec![
2156 WordDef {
2157 name: "helper".to_string(),
2158 effect: Some(Effect::new(
2159 StackType::singleton(Type::Int),
2160 StackType::singleton(Type::String),
2161 )),
2162 body: vec![Statement::WordCall {
2163 name: "int->string".to_string(),
2164 span: None,
2165 }],
2166 source: None,
2167 allowed_lints: vec![],
2168 },
2169 WordDef {
2170 name: "main".to_string(),
2171 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2172 body: vec![
2173 Statement::IntLiteral(42),
2174 Statement::WordCall {
2175 name: "helper".to_string(),
2176 span: None,
2177 },
2178 Statement::WordCall {
2179 name: "io.write-line".to_string(),
2180 span: None,
2181 },
2182 ],
2183 source: None,
2184 allowed_lints: vec![],
2185 },
2186 ],
2187 };
2188
2189 let mut checker = TypeChecker::new();
2190 assert!(checker.check_program(&program).is_ok());
2191 }
2192
2193 #[test]
2194 fn test_arithmetic_chain() {
2195 let program = Program {
2198 includes: vec![],
2199 unions: vec![],
2200 words: vec![WordDef {
2201 name: "test".to_string(),
2202 effect: Some(Effect::new(
2203 StackType::Empty
2204 .push(Type::Int)
2205 .push(Type::Int)
2206 .push(Type::Int),
2207 StackType::singleton(Type::Int),
2208 )),
2209 body: vec![
2210 Statement::WordCall {
2211 name: "i.add".to_string(),
2212 span: None,
2213 },
2214 Statement::WordCall {
2215 name: "i.multiply".to_string(),
2216 span: None,
2217 },
2218 ],
2219 source: None,
2220 allowed_lints: vec![],
2221 }],
2222 };
2223
2224 let mut checker = TypeChecker::new();
2225 assert!(checker.check_program(&program).is_ok());
2226 }
2227
2228 #[test]
2229 fn test_write_line_type_error() {
2230 let program = Program {
2232 includes: vec![],
2233 unions: vec![],
2234 words: vec![WordDef {
2235 name: "test".to_string(),
2236 effect: Some(Effect::new(
2237 StackType::singleton(Type::Int),
2238 StackType::Empty,
2239 )),
2240 body: vec![Statement::WordCall {
2241 name: "io.write-line".to_string(),
2242 span: None,
2243 }],
2244 source: None,
2245 allowed_lints: vec![],
2246 }],
2247 };
2248
2249 let mut checker = TypeChecker::new();
2250 let result = checker.check_program(&program);
2251 assert!(result.is_err());
2252 assert!(result.unwrap_err().contains("Type mismatch"));
2253 }
2254
2255 #[test]
2256 fn test_stack_underflow_drop() {
2257 let program = Program {
2259 includes: vec![],
2260 unions: vec![],
2261 words: vec![WordDef {
2262 name: "test".to_string(),
2263 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2264 body: vec![Statement::WordCall {
2265 name: "drop".to_string(),
2266 span: None,
2267 }],
2268 source: None,
2269 allowed_lints: vec![],
2270 }],
2271 };
2272
2273 let mut checker = TypeChecker::new();
2274 let result = checker.check_program(&program);
2275 assert!(result.is_err());
2276 assert!(result.unwrap_err().contains("mismatch"));
2277 }
2278
2279 #[test]
2280 fn test_stack_underflow_add() {
2281 let program = Program {
2283 includes: vec![],
2284 unions: vec![],
2285 words: vec![WordDef {
2286 name: "test".to_string(),
2287 effect: Some(Effect::new(
2288 StackType::singleton(Type::Int),
2289 StackType::singleton(Type::Int),
2290 )),
2291 body: vec![Statement::WordCall {
2292 name: "i.add".to_string(),
2293 span: None,
2294 }],
2295 source: None,
2296 allowed_lints: vec![],
2297 }],
2298 };
2299
2300 let mut checker = TypeChecker::new();
2301 let result = checker.check_program(&program);
2302 assert!(result.is_err());
2303 assert!(result.unwrap_err().contains("mismatch"));
2304 }
2305
2306 #[test]
2309 fn test_stack_underflow_rot_issue_169() {
2310 let program = Program {
2314 includes: vec![],
2315 unions: vec![],
2316 words: vec![WordDef {
2317 name: "test".to_string(),
2318 effect: Some(Effect::new(
2319 StackType::RowVar("rest".to_string()),
2320 StackType::RowVar("rest".to_string()),
2321 )),
2322 body: vec![
2323 Statement::IntLiteral(3),
2324 Statement::IntLiteral(4),
2325 Statement::WordCall {
2326 name: "rot".to_string(),
2327 span: None,
2328 },
2329 ],
2330 source: None,
2331 allowed_lints: vec![],
2332 }],
2333 };
2334
2335 let mut checker = TypeChecker::new();
2336 let result = checker.check_program(&program);
2337 assert!(result.is_err(), "rot with 2 values should fail");
2338 let err = result.unwrap_err();
2339 assert!(
2340 err.contains("stack underflow") || err.contains("requires 3"),
2341 "Error should mention underflow: {}",
2342 err
2343 );
2344 }
2345
2346 #[test]
2347 fn test_csp_operations() {
2348 let program = Program {
2355 includes: vec![],
2356 unions: vec![],
2357 words: vec![WordDef {
2358 name: "test".to_string(),
2359 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2360 body: vec![
2361 Statement::WordCall {
2362 name: "chan.make".to_string(),
2363 span: None,
2364 },
2365 Statement::IntLiteral(42),
2366 Statement::WordCall {
2367 name: "swap".to_string(),
2368 span: None,
2369 },
2370 Statement::WordCall {
2371 name: "chan.send".to_string(),
2372 span: None,
2373 },
2374 Statement::WordCall {
2375 name: "drop".to_string(),
2376 span: None,
2377 },
2378 ],
2379 source: None,
2380 allowed_lints: vec![],
2381 }],
2382 };
2383
2384 let mut checker = TypeChecker::new();
2385 assert!(checker.check_program(&program).is_ok());
2386 }
2387
2388 #[test]
2389 fn test_complex_stack_shuffling() {
2390 let program = Program {
2393 includes: vec![],
2394 unions: vec![],
2395 words: vec![WordDef {
2396 name: "test".to_string(),
2397 effect: Some(Effect::new(
2398 StackType::Empty
2399 .push(Type::Int)
2400 .push(Type::Int)
2401 .push(Type::Int),
2402 StackType::singleton(Type::Int),
2403 )),
2404 body: vec![
2405 Statement::WordCall {
2406 name: "rot".to_string(),
2407 span: None,
2408 },
2409 Statement::WordCall {
2410 name: "i.add".to_string(),
2411 span: None,
2412 },
2413 Statement::WordCall {
2414 name: "i.add".to_string(),
2415 span: None,
2416 },
2417 ],
2418 source: None,
2419 allowed_lints: vec![],
2420 }],
2421 };
2422
2423 let mut checker = TypeChecker::new();
2424 assert!(checker.check_program(&program).is_ok());
2425 }
2426
2427 #[test]
2428 fn test_empty_program() {
2429 let program = Program {
2431 includes: vec![],
2432 unions: vec![],
2433 words: vec![],
2434 };
2435
2436 let mut checker = TypeChecker::new();
2437 assert!(checker.check_program(&program).is_ok());
2438 }
2439
2440 #[test]
2441 fn test_word_without_effect_declaration() {
2442 let program = Program {
2444 includes: vec![],
2445 unions: vec![],
2446 words: vec![WordDef {
2447 name: "helper".to_string(),
2448 effect: None,
2449 body: vec![Statement::IntLiteral(42)],
2450 source: None,
2451 allowed_lints: vec![],
2452 }],
2453 };
2454
2455 let mut checker = TypeChecker::new();
2456 let result = checker.check_program(&program);
2457 assert!(result.is_err());
2458 assert!(
2459 result
2460 .unwrap_err()
2461 .contains("missing a stack effect declaration")
2462 );
2463 }
2464
2465 #[test]
2466 fn test_nested_conditionals() {
2467 let program = Program {
2476 includes: vec![],
2477 unions: vec![],
2478 words: vec![WordDef {
2479 name: "test".to_string(),
2480 effect: Some(Effect::new(
2481 StackType::Empty
2482 .push(Type::Int)
2483 .push(Type::Int)
2484 .push(Type::Int)
2485 .push(Type::Int),
2486 StackType::singleton(Type::String),
2487 )),
2488 body: vec![
2489 Statement::WordCall {
2490 name: "i.>".to_string(),
2491 span: None,
2492 },
2493 Statement::If {
2494 then_branch: vec![
2495 Statement::WordCall {
2496 name: "i.>".to_string(),
2497 span: None,
2498 },
2499 Statement::If {
2500 then_branch: vec![Statement::StringLiteral(
2501 "both true".to_string(),
2502 )],
2503 else_branch: Some(vec![Statement::StringLiteral(
2504 "first true".to_string(),
2505 )]),
2506 span: None,
2507 },
2508 ],
2509 else_branch: Some(vec![
2510 Statement::WordCall {
2511 name: "drop".to_string(),
2512 span: None,
2513 },
2514 Statement::WordCall {
2515 name: "drop".to_string(),
2516 span: None,
2517 },
2518 Statement::StringLiteral("first false".to_string()),
2519 ]),
2520 span: None,
2521 },
2522 ],
2523 source: None,
2524 allowed_lints: vec![],
2525 }],
2526 };
2527
2528 let mut checker = TypeChecker::new();
2529 match checker.check_program(&program) {
2530 Ok(_) => {}
2531 Err(e) => panic!("Type check failed: {}", e),
2532 }
2533 }
2534
2535 #[test]
2536 fn test_conditional_without_else() {
2537 let program = Program {
2541 includes: vec![],
2542 unions: vec![],
2543 words: vec![WordDef {
2544 name: "test".to_string(),
2545 effect: Some(Effect::new(
2546 StackType::Empty.push(Type::Int).push(Type::Int),
2547 StackType::singleton(Type::Int),
2548 )),
2549 body: vec![
2550 Statement::WordCall {
2551 name: "i.>".to_string(),
2552 span: None,
2553 },
2554 Statement::If {
2555 then_branch: vec![Statement::IntLiteral(100)],
2556 else_branch: None, span: None,
2558 },
2559 ],
2560 source: None,
2561 allowed_lints: vec![],
2562 }],
2563 };
2564
2565 let mut checker = TypeChecker::new();
2566 let result = checker.check_program(&program);
2567 assert!(result.is_err());
2569 }
2570
2571 #[test]
2572 fn test_multiple_word_chain() {
2573 let program = Program {
2577 includes: vec![],
2578 unions: vec![],
2579 words: vec![
2580 WordDef {
2581 name: "helper1".to_string(),
2582 effect: Some(Effect::new(
2583 StackType::singleton(Type::Int),
2584 StackType::singleton(Type::String),
2585 )),
2586 body: vec![Statement::WordCall {
2587 name: "int->string".to_string(),
2588 span: None,
2589 }],
2590 source: None,
2591 allowed_lints: vec![],
2592 },
2593 WordDef {
2594 name: "helper2".to_string(),
2595 effect: Some(Effect::new(
2596 StackType::singleton(Type::String),
2597 StackType::Empty,
2598 )),
2599 body: vec![Statement::WordCall {
2600 name: "io.write-line".to_string(),
2601 span: None,
2602 }],
2603 source: None,
2604 allowed_lints: vec![],
2605 },
2606 WordDef {
2607 name: "main".to_string(),
2608 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2609 body: vec![
2610 Statement::IntLiteral(42),
2611 Statement::WordCall {
2612 name: "helper1".to_string(),
2613 span: None,
2614 },
2615 Statement::WordCall {
2616 name: "helper2".to_string(),
2617 span: None,
2618 },
2619 ],
2620 source: None,
2621 allowed_lints: vec![],
2622 },
2623 ],
2624 };
2625
2626 let mut checker = TypeChecker::new();
2627 assert!(checker.check_program(&program).is_ok());
2628 }
2629
2630 #[test]
2631 fn test_all_stack_ops() {
2632 let program = Program {
2635 includes: vec![],
2636 unions: vec![],
2637 words: vec![WordDef {
2638 name: "test".to_string(),
2639 effect: Some(Effect::new(
2640 StackType::Empty
2641 .push(Type::Int)
2642 .push(Type::Int)
2643 .push(Type::Int),
2644 StackType::Empty
2645 .push(Type::Int)
2646 .push(Type::Int)
2647 .push(Type::Int)
2648 .push(Type::Int),
2649 )),
2650 body: vec![
2651 Statement::WordCall {
2652 name: "over".to_string(),
2653 span: None,
2654 },
2655 Statement::WordCall {
2656 name: "nip".to_string(),
2657 span: None,
2658 },
2659 Statement::WordCall {
2660 name: "tuck".to_string(),
2661 span: None,
2662 },
2663 ],
2664 source: None,
2665 allowed_lints: vec![],
2666 }],
2667 };
2668
2669 let mut checker = TypeChecker::new();
2670 assert!(checker.check_program(&program).is_ok());
2671 }
2672
2673 #[test]
2674 fn test_mixed_types_complex() {
2675 let program = Program {
2684 includes: vec![],
2685 unions: vec![],
2686 words: vec![WordDef {
2687 name: "test".to_string(),
2688 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2689 body: vec![
2690 Statement::IntLiteral(42),
2691 Statement::WordCall {
2692 name: "int->string".to_string(),
2693 span: None,
2694 },
2695 Statement::IntLiteral(100),
2696 Statement::IntLiteral(200),
2697 Statement::WordCall {
2698 name: "i.>".to_string(),
2699 span: None,
2700 },
2701 Statement::If {
2702 then_branch: vec![Statement::WordCall {
2703 name: "io.write-line".to_string(),
2704 span: None,
2705 }],
2706 else_branch: Some(vec![Statement::WordCall {
2707 name: "io.write-line".to_string(),
2708 span: None,
2709 }]),
2710 span: None,
2711 },
2712 ],
2713 source: None,
2714 allowed_lints: vec![],
2715 }],
2716 };
2717
2718 let mut checker = TypeChecker::new();
2719 assert!(checker.check_program(&program).is_ok());
2720 }
2721
2722 #[test]
2723 fn test_string_literal() {
2724 let program = Program {
2726 includes: vec![],
2727 unions: vec![],
2728 words: vec![WordDef {
2729 name: "test".to_string(),
2730 effect: Some(Effect::new(
2731 StackType::Empty,
2732 StackType::singleton(Type::String),
2733 )),
2734 body: vec![Statement::StringLiteral("hello".to_string())],
2735 source: None,
2736 allowed_lints: vec![],
2737 }],
2738 };
2739
2740 let mut checker = TypeChecker::new();
2741 assert!(checker.check_program(&program).is_ok());
2742 }
2743
2744 #[test]
2745 fn test_bool_literal() {
2746 let program = Program {
2749 includes: vec![],
2750 unions: vec![],
2751 words: vec![WordDef {
2752 name: "test".to_string(),
2753 effect: Some(Effect::new(
2754 StackType::Empty,
2755 StackType::singleton(Type::Bool),
2756 )),
2757 body: vec![Statement::BoolLiteral(true)],
2758 source: None,
2759 allowed_lints: vec![],
2760 }],
2761 };
2762
2763 let mut checker = TypeChecker::new();
2764 assert!(checker.check_program(&program).is_ok());
2765 }
2766
2767 #[test]
2768 fn test_type_error_in_nested_conditional() {
2769 let program = Program {
2776 includes: vec![],
2777 unions: vec![],
2778 words: vec![WordDef {
2779 name: "test".to_string(),
2780 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2781 body: vec![
2782 Statement::IntLiteral(10),
2783 Statement::IntLiteral(20),
2784 Statement::WordCall {
2785 name: "i.>".to_string(),
2786 span: None,
2787 },
2788 Statement::If {
2789 then_branch: vec![
2790 Statement::IntLiteral(42),
2791 Statement::WordCall {
2792 name: "io.write-line".to_string(),
2793 span: None,
2794 },
2795 ],
2796 else_branch: Some(vec![
2797 Statement::StringLiteral("ok".to_string()),
2798 Statement::WordCall {
2799 name: "io.write-line".to_string(),
2800 span: None,
2801 },
2802 ]),
2803 span: None,
2804 },
2805 ],
2806 source: None,
2807 allowed_lints: vec![],
2808 }],
2809 };
2810
2811 let mut checker = TypeChecker::new();
2812 let result = checker.check_program(&program);
2813 assert!(result.is_err());
2814 assert!(result.unwrap_err().contains("Type mismatch"));
2815 }
2816
2817 #[test]
2818 fn test_read_line_operation() {
2819 let program = Program {
2822 includes: vec![],
2823 unions: vec![],
2824 words: vec![WordDef {
2825 name: "test".to_string(),
2826 effect: Some(Effect::new(
2827 StackType::Empty,
2828 StackType::from_vec(vec![Type::String, Type::Bool]),
2829 )),
2830 body: vec![Statement::WordCall {
2831 name: "io.read-line".to_string(),
2832 span: None,
2833 }],
2834 source: None,
2835 allowed_lints: vec![],
2836 }],
2837 };
2838
2839 let mut checker = TypeChecker::new();
2840 assert!(checker.check_program(&program).is_ok());
2841 }
2842
2843 #[test]
2844 fn test_comparison_operations() {
2845 let program = Program {
2850 includes: vec![],
2851 unions: vec![],
2852 words: vec![WordDef {
2853 name: "test".to_string(),
2854 effect: Some(Effect::new(
2855 StackType::Empty.push(Type::Int).push(Type::Int),
2856 StackType::singleton(Type::Bool),
2857 )),
2858 body: vec![Statement::WordCall {
2859 name: "i.<=".to_string(),
2860 span: None,
2861 }],
2862 source: None,
2863 allowed_lints: vec![],
2864 }],
2865 };
2866
2867 let mut checker = TypeChecker::new();
2868 assert!(checker.check_program(&program).is_ok());
2869 }
2870
2871 #[test]
2872 fn test_recursive_word_definitions() {
2873 let program = Program {
2879 includes: vec![],
2880 unions: vec![],
2881 words: vec![
2882 WordDef {
2883 name: "is-even".to_string(),
2884 effect: Some(Effect::new(
2885 StackType::singleton(Type::Int),
2886 StackType::singleton(Type::Int),
2887 )),
2888 body: vec![
2889 Statement::WordCall {
2890 name: "dup".to_string(),
2891 span: None,
2892 },
2893 Statement::IntLiteral(0),
2894 Statement::WordCall {
2895 name: "i.=".to_string(),
2896 span: None,
2897 },
2898 Statement::If {
2899 then_branch: vec![
2900 Statement::WordCall {
2901 name: "drop".to_string(),
2902 span: None,
2903 },
2904 Statement::IntLiteral(1),
2905 ],
2906 else_branch: Some(vec![
2907 Statement::IntLiteral(1),
2908 Statement::WordCall {
2909 name: "i.subtract".to_string(),
2910 span: None,
2911 },
2912 Statement::WordCall {
2913 name: "is-odd".to_string(),
2914 span: None,
2915 },
2916 ]),
2917 span: None,
2918 },
2919 ],
2920 source: None,
2921 allowed_lints: vec![],
2922 },
2923 WordDef {
2924 name: "is-odd".to_string(),
2925 effect: Some(Effect::new(
2926 StackType::singleton(Type::Int),
2927 StackType::singleton(Type::Int),
2928 )),
2929 body: vec![
2930 Statement::WordCall {
2931 name: "dup".to_string(),
2932 span: None,
2933 },
2934 Statement::IntLiteral(0),
2935 Statement::WordCall {
2936 name: "i.=".to_string(),
2937 span: None,
2938 },
2939 Statement::If {
2940 then_branch: vec![
2941 Statement::WordCall {
2942 name: "drop".to_string(),
2943 span: None,
2944 },
2945 Statement::IntLiteral(0),
2946 ],
2947 else_branch: Some(vec![
2948 Statement::IntLiteral(1),
2949 Statement::WordCall {
2950 name: "i.subtract".to_string(),
2951 span: None,
2952 },
2953 Statement::WordCall {
2954 name: "is-even".to_string(),
2955 span: None,
2956 },
2957 ]),
2958 span: None,
2959 },
2960 ],
2961 source: None,
2962 allowed_lints: vec![],
2963 },
2964 ],
2965 };
2966
2967 let mut checker = TypeChecker::new();
2968 assert!(checker.check_program(&program).is_ok());
2969 }
2970
2971 #[test]
2972 fn test_word_calling_word_with_row_polymorphism() {
2973 let program = Program {
2978 includes: vec![],
2979 unions: vec![],
2980 words: vec![
2981 WordDef {
2982 name: "apply-twice".to_string(),
2983 effect: Some(Effect::new(
2984 StackType::singleton(Type::Int),
2985 StackType::singleton(Type::Int),
2986 )),
2987 body: vec![
2988 Statement::WordCall {
2989 name: "dup".to_string(),
2990 span: None,
2991 },
2992 Statement::WordCall {
2993 name: "i.add".to_string(),
2994 span: None,
2995 },
2996 ],
2997 source: None,
2998 allowed_lints: vec![],
2999 },
3000 WordDef {
3001 name: "quad".to_string(),
3002 effect: Some(Effect::new(
3003 StackType::singleton(Type::Int),
3004 StackType::singleton(Type::Int),
3005 )),
3006 body: vec![
3007 Statement::WordCall {
3008 name: "apply-twice".to_string(),
3009 span: None,
3010 },
3011 Statement::WordCall {
3012 name: "apply-twice".to_string(),
3013 span: None,
3014 },
3015 ],
3016 source: None,
3017 allowed_lints: vec![],
3018 },
3019 ],
3020 };
3021
3022 let mut checker = TypeChecker::new();
3023 assert!(checker.check_program(&program).is_ok());
3024 }
3025
3026 #[test]
3027 fn test_deep_stack_types() {
3028 let mut stack_type = StackType::Empty;
3032 for _ in 0..10 {
3033 stack_type = stack_type.push(Type::Int);
3034 }
3035
3036 let program = Program {
3037 includes: vec![],
3038 unions: vec![],
3039 words: vec![WordDef {
3040 name: "test".to_string(),
3041 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
3042 body: vec![
3043 Statement::WordCall {
3044 name: "i.add".to_string(),
3045 span: None,
3046 },
3047 Statement::WordCall {
3048 name: "i.add".to_string(),
3049 span: None,
3050 },
3051 Statement::WordCall {
3052 name: "i.add".to_string(),
3053 span: None,
3054 },
3055 Statement::WordCall {
3056 name: "i.add".to_string(),
3057 span: None,
3058 },
3059 Statement::WordCall {
3060 name: "i.add".to_string(),
3061 span: None,
3062 },
3063 Statement::WordCall {
3064 name: "i.add".to_string(),
3065 span: None,
3066 },
3067 Statement::WordCall {
3068 name: "i.add".to_string(),
3069 span: None,
3070 },
3071 Statement::WordCall {
3072 name: "i.add".to_string(),
3073 span: None,
3074 },
3075 Statement::WordCall {
3076 name: "i.add".to_string(),
3077 span: None,
3078 },
3079 ],
3080 source: None,
3081 allowed_lints: vec![],
3082 }],
3083 };
3084
3085 let mut checker = TypeChecker::new();
3086 assert!(checker.check_program(&program).is_ok());
3087 }
3088
3089 #[test]
3090 fn test_simple_quotation() {
3091 let program = Program {
3095 includes: vec![],
3096 unions: vec![],
3097 words: vec![WordDef {
3098 name: "test".to_string(),
3099 effect: Some(Effect::new(
3100 StackType::Empty,
3101 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3102 StackType::RowVar("input".to_string()).push(Type::Int),
3103 StackType::RowVar("input".to_string()).push(Type::Int),
3104 )))),
3105 )),
3106 body: vec![Statement::Quotation {
3107 span: None,
3108 id: 0,
3109 body: vec![
3110 Statement::IntLiteral(1),
3111 Statement::WordCall {
3112 name: "i.add".to_string(),
3113 span: None,
3114 },
3115 ],
3116 }],
3117 source: None,
3118 allowed_lints: vec![],
3119 }],
3120 };
3121
3122 let mut checker = TypeChecker::new();
3123 match checker.check_program(&program) {
3124 Ok(_) => {}
3125 Err(e) => panic!("Type check failed: {}", e),
3126 }
3127 }
3128
3129 #[test]
3130 fn test_empty_quotation() {
3131 let program = Program {
3135 includes: vec![],
3136 unions: vec![],
3137 words: vec![WordDef {
3138 name: "test".to_string(),
3139 effect: Some(Effect::new(
3140 StackType::Empty,
3141 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3142 StackType::RowVar("input".to_string()),
3143 StackType::RowVar("input".to_string()),
3144 )))),
3145 )),
3146 body: vec![Statement::Quotation {
3147 span: None,
3148 id: 1,
3149 body: vec![],
3150 }],
3151 source: None,
3152 allowed_lints: vec![],
3153 }],
3154 };
3155
3156 let mut checker = TypeChecker::new();
3157 assert!(checker.check_program(&program).is_ok());
3158 }
3159
3160 #[test]
3161 fn test_nested_quotation() {
3162 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
3166 StackType::RowVar("input".to_string()).push(Type::Int),
3167 StackType::RowVar("input".to_string()).push(Type::Int),
3168 )));
3169
3170 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
3171 StackType::RowVar("input".to_string()),
3172 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
3173 )));
3174
3175 let program = Program {
3176 includes: vec![],
3177 unions: vec![],
3178 words: vec![WordDef {
3179 name: "test".to_string(),
3180 effect: Some(Effect::new(
3181 StackType::Empty,
3182 StackType::singleton(outer_quot_type),
3183 )),
3184 body: vec![Statement::Quotation {
3185 span: None,
3186 id: 2,
3187 body: vec![Statement::Quotation {
3188 span: None,
3189 id: 3,
3190 body: vec![
3191 Statement::IntLiteral(1),
3192 Statement::WordCall {
3193 name: "i.add".to_string(),
3194 span: None,
3195 },
3196 ],
3197 }],
3198 }],
3199 source: None,
3200 allowed_lints: vec![],
3201 }],
3202 };
3203
3204 let mut checker = TypeChecker::new();
3205 assert!(checker.check_program(&program).is_ok());
3206 }
3207
3208 #[test]
3209 fn test_invalid_field_type_error() {
3210 use crate::ast::{UnionDef, UnionField, UnionVariant};
3211
3212 let program = Program {
3213 includes: vec![],
3214 unions: vec![UnionDef {
3215 name: "Message".to_string(),
3216 variants: vec![UnionVariant {
3217 name: "Get".to_string(),
3218 fields: vec![UnionField {
3219 name: "chan".to_string(),
3220 type_name: "InvalidType".to_string(),
3221 }],
3222 source: None,
3223 }],
3224 source: None,
3225 }],
3226 words: vec![],
3227 };
3228
3229 let mut checker = TypeChecker::new();
3230 let result = checker.check_program(&program);
3231 assert!(result.is_err());
3232 let err = result.unwrap_err();
3233 assert!(err.contains("Unknown type 'InvalidType'"));
3234 assert!(err.contains("chan"));
3235 assert!(err.contains("Get"));
3236 assert!(err.contains("Message"));
3237 }
3238
3239 #[test]
3240 fn test_roll_inside_conditional_with_concrete_stack() {
3241 let program = Program {
3249 includes: vec![],
3250 unions: vec![],
3251 words: vec![WordDef {
3252 name: "test".to_string(),
3253 effect: Some(Effect::new(
3254 StackType::Empty
3255 .push(Type::Int)
3256 .push(Type::Int)
3257 .push(Type::Int)
3258 .push(Type::Int),
3259 StackType::Empty
3260 .push(Type::Int)
3261 .push(Type::Int)
3262 .push(Type::Int)
3263 .push(Type::Int),
3264 )),
3265 body: vec![
3266 Statement::WordCall {
3267 name: "dup".to_string(),
3268 span: None,
3269 },
3270 Statement::IntLiteral(0),
3271 Statement::WordCall {
3272 name: "i.>".to_string(),
3273 span: None,
3274 },
3275 Statement::If {
3276 then_branch: vec![
3277 Statement::IntLiteral(3),
3278 Statement::WordCall {
3279 name: "roll".to_string(),
3280 span: None,
3281 },
3282 ],
3283 else_branch: Some(vec![
3284 Statement::WordCall {
3285 name: "rot".to_string(),
3286 span: None,
3287 },
3288 Statement::WordCall {
3289 name: "rot".to_string(),
3290 span: None,
3291 },
3292 ]),
3293 span: None,
3294 },
3295 ],
3296 source: None,
3297 allowed_lints: vec![],
3298 }],
3299 };
3300
3301 let mut checker = TypeChecker::new();
3302 match checker.check_program(&program) {
3304 Ok(_) => {}
3305 Err(e) => panic!("Type check failed: {}", e),
3306 }
3307 }
3308
3309 #[test]
3310 fn test_roll_inside_match_arm_with_concrete_stack() {
3311 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
3314
3315 let union_def = UnionDef {
3317 name: "Result".to_string(),
3318 variants: vec![
3319 UnionVariant {
3320 name: "Ok".to_string(),
3321 fields: vec![],
3322 source: None,
3323 },
3324 UnionVariant {
3325 name: "Err".to_string(),
3326 fields: vec![],
3327 source: None,
3328 },
3329 ],
3330 source: None,
3331 };
3332
3333 let program = Program {
3339 includes: vec![],
3340 unions: vec![union_def],
3341 words: vec![WordDef {
3342 name: "test".to_string(),
3343 effect: Some(Effect::new(
3344 StackType::Empty
3345 .push(Type::Int)
3346 .push(Type::Int)
3347 .push(Type::Int)
3348 .push(Type::Int)
3349 .push(Type::Union("Result".to_string())),
3350 StackType::Empty
3351 .push(Type::Int)
3352 .push(Type::Int)
3353 .push(Type::Int)
3354 .push(Type::Int),
3355 )),
3356 body: vec![Statement::Match {
3357 arms: vec![
3358 MatchArm {
3359 pattern: Pattern::Variant("Ok".to_string()),
3360 body: vec![
3361 Statement::IntLiteral(3),
3362 Statement::WordCall {
3363 name: "roll".to_string(),
3364 span: None,
3365 },
3366 ],
3367 span: None,
3368 },
3369 MatchArm {
3370 pattern: Pattern::Variant("Err".to_string()),
3371 body: vec![
3372 Statement::WordCall {
3373 name: "rot".to_string(),
3374 span: None,
3375 },
3376 Statement::WordCall {
3377 name: "rot".to_string(),
3378 span: None,
3379 },
3380 ],
3381 span: None,
3382 },
3383 ],
3384 span: None,
3385 }],
3386 source: None,
3387 allowed_lints: vec![],
3388 }],
3389 };
3390
3391 let mut checker = TypeChecker::new();
3392 match checker.check_program(&program) {
3393 Ok(_) => {}
3394 Err(e) => panic!("Type check failed: {}", e),
3395 }
3396 }
3397
3398 #[test]
3399 fn test_roll_with_row_polymorphic_input() {
3400 let program = Program {
3404 includes: vec![],
3405 unions: vec![],
3406 words: vec![WordDef {
3407 name: "test".to_string(),
3408 effect: Some(Effect::new(
3409 StackType::Empty
3410 .push(Type::Var("T".to_string()))
3411 .push(Type::Var("U".to_string()))
3412 .push(Type::Var("V".to_string()))
3413 .push(Type::Var("W".to_string())),
3414 StackType::Empty
3415 .push(Type::Var("U".to_string()))
3416 .push(Type::Var("V".to_string()))
3417 .push(Type::Var("W".to_string()))
3418 .push(Type::Var("T".to_string())),
3419 )),
3420 body: vec![
3421 Statement::IntLiteral(3),
3422 Statement::WordCall {
3423 name: "roll".to_string(),
3424 span: None,
3425 },
3426 ],
3427 source: None,
3428 allowed_lints: vec![],
3429 }],
3430 };
3431
3432 let mut checker = TypeChecker::new();
3433 let result = checker.check_program(&program);
3434 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
3435 }
3436
3437 #[test]
3438 fn test_pick_with_row_polymorphic_input() {
3439 let program = Program {
3443 includes: vec![],
3444 unions: vec![],
3445 words: vec![WordDef {
3446 name: "test".to_string(),
3447 effect: Some(Effect::new(
3448 StackType::Empty
3449 .push(Type::Var("T".to_string()))
3450 .push(Type::Var("U".to_string()))
3451 .push(Type::Var("V".to_string())),
3452 StackType::Empty
3453 .push(Type::Var("T".to_string()))
3454 .push(Type::Var("U".to_string()))
3455 .push(Type::Var("V".to_string()))
3456 .push(Type::Var("T".to_string())),
3457 )),
3458 body: vec![
3459 Statement::IntLiteral(2),
3460 Statement::WordCall {
3461 name: "pick".to_string(),
3462 span: None,
3463 },
3464 ],
3465 source: None,
3466 allowed_lints: vec![],
3467 }],
3468 };
3469
3470 let mut checker = TypeChecker::new();
3471 assert!(checker.check_program(&program).is_ok());
3472 }
3473
3474 #[test]
3475 fn test_valid_union_reference_in_field() {
3476 use crate::ast::{UnionDef, UnionField, UnionVariant};
3477
3478 let program = Program {
3479 includes: vec![],
3480 unions: vec![
3481 UnionDef {
3482 name: "Inner".to_string(),
3483 variants: vec![UnionVariant {
3484 name: "Val".to_string(),
3485 fields: vec![UnionField {
3486 name: "x".to_string(),
3487 type_name: "Int".to_string(),
3488 }],
3489 source: None,
3490 }],
3491 source: None,
3492 },
3493 UnionDef {
3494 name: "Outer".to_string(),
3495 variants: vec![UnionVariant {
3496 name: "Wrap".to_string(),
3497 fields: vec![UnionField {
3498 name: "inner".to_string(),
3499 type_name: "Inner".to_string(), }],
3501 source: None,
3502 }],
3503 source: None,
3504 },
3505 ],
3506 words: vec![],
3507 };
3508
3509 let mut checker = TypeChecker::new();
3510 assert!(
3511 checker.check_program(&program).is_ok(),
3512 "Union reference in field should be valid"
3513 );
3514 }
3515
3516 #[test]
3517 fn test_divergent_recursive_tail_call() {
3518 let program = Program {
3540 includes: vec![],
3541 unions: vec![],
3542 words: vec![WordDef {
3543 name: "store-loop".to_string(),
3544 effect: Some(Effect::new(
3545 StackType::singleton(Type::Channel), StackType::Empty,
3547 )),
3548 body: vec![
3549 Statement::WordCall {
3551 name: "dup".to_string(),
3552 span: None,
3553 },
3554 Statement::WordCall {
3556 name: "chan.receive".to_string(),
3557 span: None,
3558 },
3559 Statement::WordCall {
3561 name: "not".to_string(),
3562 span: None,
3563 },
3564 Statement::If {
3566 then_branch: vec![
3567 Statement::WordCall {
3569 name: "drop".to_string(),
3570 span: None,
3571 },
3572 Statement::WordCall {
3574 name: "store-loop".to_string(), span: None,
3576 },
3577 ],
3578 else_branch: None, span: None,
3580 },
3581 Statement::WordCall {
3583 name: "drop".to_string(),
3584 span: None,
3585 },
3586 Statement::WordCall {
3587 name: "drop".to_string(),
3588 span: None,
3589 },
3590 ],
3591 source: None,
3592 allowed_lints: vec![],
3593 }],
3594 };
3595
3596 let mut checker = TypeChecker::new();
3597 let result = checker.check_program(&program);
3598 assert!(
3599 result.is_ok(),
3600 "Divergent recursive tail call should be accepted: {:?}",
3601 result.err()
3602 );
3603 }
3604
3605 #[test]
3606 fn test_divergent_else_branch() {
3607 let program = Program {
3620 includes: vec![],
3621 unions: vec![],
3622 words: vec![WordDef {
3623 name: "process-loop".to_string(),
3624 effect: Some(Effect::new(
3625 StackType::singleton(Type::Channel), StackType::Empty,
3627 )),
3628 body: vec![
3629 Statement::WordCall {
3630 name: "dup".to_string(),
3631 span: None,
3632 },
3633 Statement::WordCall {
3634 name: "chan.receive".to_string(),
3635 span: None,
3636 },
3637 Statement::If {
3638 then_branch: vec![
3639 Statement::WordCall {
3641 name: "drop".to_string(),
3642 span: None,
3643 },
3644 Statement::WordCall {
3645 name: "drop".to_string(),
3646 span: None,
3647 },
3648 ],
3649 else_branch: Some(vec![
3650 Statement::WordCall {
3652 name: "drop".to_string(),
3653 span: None,
3654 },
3655 Statement::WordCall {
3656 name: "process-loop".to_string(), span: None,
3658 },
3659 ]),
3660 span: None,
3661 },
3662 ],
3663 source: None,
3664 allowed_lints: vec![],
3665 }],
3666 };
3667
3668 let mut checker = TypeChecker::new();
3669 let result = checker.check_program(&program);
3670 assert!(
3671 result.is_ok(),
3672 "Divergent else branch should be accepted: {:?}",
3673 result.err()
3674 );
3675 }
3676
3677 #[test]
3678 fn test_non_tail_call_recursion_not_divergent() {
3679 let program = Program {
3696 includes: vec![],
3697 unions: vec![],
3698 words: vec![WordDef {
3699 name: "bad-loop".to_string(),
3700 effect: Some(Effect::new(
3701 StackType::singleton(Type::Int),
3702 StackType::singleton(Type::Int),
3703 )),
3704 body: vec![
3705 Statement::WordCall {
3706 name: "dup".to_string(),
3707 span: None,
3708 },
3709 Statement::IntLiteral(0),
3710 Statement::WordCall {
3711 name: "i.>".to_string(),
3712 span: None,
3713 },
3714 Statement::If {
3715 then_branch: vec![
3716 Statement::IntLiteral(1),
3717 Statement::WordCall {
3718 name: "i.subtract".to_string(),
3719 span: None,
3720 },
3721 Statement::WordCall {
3722 name: "bad-loop".to_string(), span: None,
3724 },
3725 Statement::IntLiteral(1),
3726 Statement::WordCall {
3727 name: "i.add".to_string(), span: None,
3729 },
3730 ],
3731 else_branch: None,
3732 span: None,
3733 },
3734 ],
3735 source: None,
3736 allowed_lints: vec![],
3737 }],
3738 };
3739
3740 let mut checker = TypeChecker::new();
3741 let result = checker.check_program(&program);
3746 assert!(
3747 result.is_ok(),
3748 "Non-tail recursion should type check normally: {:?}",
3749 result.err()
3750 );
3751 }
3752
3753 #[test]
3754 fn test_call_yield_quotation_error() {
3755 let program = Program {
3759 includes: vec![],
3760 unions: vec![],
3761 words: vec![WordDef {
3762 name: "bad".to_string(),
3763 effect: Some(Effect::new(
3764 StackType::singleton(Type::Var("Ctx".to_string())),
3765 StackType::singleton(Type::Var("Ctx".to_string())),
3766 )),
3767 body: vec![
3768 Statement::IntLiteral(42),
3770 Statement::Quotation {
3771 span: None,
3772 id: 0,
3773 body: vec![Statement::WordCall {
3774 name: "yield".to_string(),
3775 span: None,
3776 }],
3777 },
3778 Statement::WordCall {
3779 name: "call".to_string(),
3780 span: None,
3781 },
3782 ],
3783 source: None,
3784 allowed_lints: vec![],
3785 }],
3786 };
3787
3788 let mut checker = TypeChecker::new();
3789 let result = checker.check_program(&program);
3790 assert!(
3791 result.is_err(),
3792 "Calling yield quotation directly should fail"
3793 );
3794 let err = result.unwrap_err();
3795 assert!(
3796 err.contains("Yield") || err.contains("strand.weave"),
3797 "Error should mention Yield or strand.weave: {}",
3798 err
3799 );
3800 }
3801
3802 #[test]
3803 fn test_strand_weave_yield_quotation_ok() {
3804 let program = Program {
3807 includes: vec![],
3808 unions: vec![],
3809 words: vec![WordDef {
3810 name: "good".to_string(),
3811 effect: Some(Effect::new(
3812 StackType::Empty,
3813 StackType::Empty
3814 .push(Type::Int)
3815 .push(Type::Var("Handle".to_string())),
3816 )),
3817 body: vec![
3818 Statement::IntLiteral(42),
3819 Statement::Quotation {
3820 span: None,
3821 id: 0,
3822 body: vec![Statement::WordCall {
3823 name: "yield".to_string(),
3824 span: None,
3825 }],
3826 },
3827 Statement::WordCall {
3828 name: "strand.weave".to_string(),
3829 span: None,
3830 },
3831 ],
3832 source: None,
3833 allowed_lints: vec![],
3834 }],
3835 };
3836
3837 let mut checker = TypeChecker::new();
3838 let result = checker.check_program(&program);
3839 assert!(
3840 result.is_ok(),
3841 "strand.weave on yield quotation should pass: {:?}",
3842 result.err()
3843 );
3844 }
3845
3846 #[test]
3847 fn test_call_pure_quotation_ok() {
3848 let program = Program {
3851 includes: vec![],
3852 unions: vec![],
3853 words: vec![WordDef {
3854 name: "ok".to_string(),
3855 effect: Some(Effect::new(
3856 StackType::singleton(Type::Int),
3857 StackType::singleton(Type::Int),
3858 )),
3859 body: vec![
3860 Statement::Quotation {
3861 span: None,
3862 id: 0,
3863 body: vec![
3864 Statement::IntLiteral(1),
3865 Statement::WordCall {
3866 name: "i.add".to_string(),
3867 span: None,
3868 },
3869 ],
3870 },
3871 Statement::WordCall {
3872 name: "call".to_string(),
3873 span: None,
3874 },
3875 ],
3876 source: None,
3877 allowed_lints: vec![],
3878 }],
3879 };
3880
3881 let mut checker = TypeChecker::new();
3882 let result = checker.check_program(&program);
3883 assert!(
3884 result.is_ok(),
3885 "Calling pure quotation should pass: {:?}",
3886 result.err()
3887 );
3888 }
3889
3890 #[test]
3896 fn test_pollution_extra_push() {
3897 let program = Program {
3901 includes: vec![],
3902 unions: vec![],
3903 words: vec![WordDef {
3904 name: "test".to_string(),
3905 effect: Some(Effect::new(
3906 StackType::singleton(Type::Int),
3907 StackType::singleton(Type::Int),
3908 )),
3909 body: vec![Statement::IntLiteral(42)],
3910 source: None,
3911 allowed_lints: vec![],
3912 }],
3913 };
3914
3915 let mut checker = TypeChecker::new();
3916 let result = checker.check_program(&program);
3917 assert!(
3918 result.is_err(),
3919 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3920 );
3921 }
3922
3923 #[test]
3924 fn test_pollution_extra_dup() {
3925 let program = Program {
3928 includes: vec![],
3929 unions: vec![],
3930 words: vec![WordDef {
3931 name: "test".to_string(),
3932 effect: Some(Effect::new(
3933 StackType::singleton(Type::Int),
3934 StackType::singleton(Type::Int),
3935 )),
3936 body: vec![Statement::WordCall {
3937 name: "dup".to_string(),
3938 span: None,
3939 }],
3940 source: None,
3941 allowed_lints: vec![],
3942 }],
3943 };
3944
3945 let mut checker = TypeChecker::new();
3946 let result = checker.check_program(&program);
3947 assert!(
3948 result.is_err(),
3949 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3950 );
3951 }
3952
3953 #[test]
3954 fn test_pollution_consumes_extra() {
3955 let program = Program {
3958 includes: vec![],
3959 unions: vec![],
3960 words: vec![WordDef {
3961 name: "test".to_string(),
3962 effect: Some(Effect::new(
3963 StackType::singleton(Type::Int),
3964 StackType::singleton(Type::Int),
3965 )),
3966 body: vec![
3967 Statement::WordCall {
3968 name: "drop".to_string(),
3969 span: None,
3970 },
3971 Statement::WordCall {
3972 name: "drop".to_string(),
3973 span: None,
3974 },
3975 Statement::IntLiteral(42),
3976 ],
3977 source: None,
3978 allowed_lints: vec![],
3979 }],
3980 };
3981
3982 let mut checker = TypeChecker::new();
3983 let result = checker.check_program(&program);
3984 assert!(
3985 result.is_err(),
3986 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3987 );
3988 }
3989
3990 #[test]
3991 fn test_pollution_in_then_branch() {
3992 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::singleton(Type::Bool),
4002 StackType::singleton(Type::Int),
4003 )),
4004 body: vec![Statement::If {
4005 then_branch: vec![
4006 Statement::IntLiteral(1),
4007 Statement::IntLiteral(2), ],
4009 else_branch: Some(vec![Statement::IntLiteral(3)]),
4010 span: None,
4011 }],
4012 source: None,
4013 allowed_lints: vec![],
4014 }],
4015 };
4016
4017 let mut checker = TypeChecker::new();
4018 let result = checker.check_program(&program);
4019 assert!(
4020 result.is_err(),
4021 "Should reject: then branch pushes 2 values, else pushes 1"
4022 );
4023 }
4024
4025 #[test]
4026 fn test_pollution_in_else_branch() {
4027 let program = Program {
4031 includes: vec![],
4032 unions: vec![],
4033 words: vec![WordDef {
4034 name: "test".to_string(),
4035 effect: Some(Effect::new(
4036 StackType::singleton(Type::Bool),
4037 StackType::singleton(Type::Int),
4038 )),
4039 body: vec![Statement::If {
4040 then_branch: vec![Statement::IntLiteral(1)],
4041 else_branch: Some(vec![
4042 Statement::IntLiteral(2),
4043 Statement::IntLiteral(3), ]),
4045 span: None,
4046 }],
4047 source: None,
4048 allowed_lints: vec![],
4049 }],
4050 };
4051
4052 let mut checker = TypeChecker::new();
4053 let result = checker.check_program(&program);
4054 assert!(
4055 result.is_err(),
4056 "Should reject: then branch pushes 1 value, else pushes 2"
4057 );
4058 }
4059
4060 #[test]
4061 fn test_pollution_both_branches_extra() {
4062 let program = Program {
4066 includes: vec![],
4067 unions: vec![],
4068 words: vec![WordDef {
4069 name: "test".to_string(),
4070 effect: Some(Effect::new(
4071 StackType::singleton(Type::Bool),
4072 StackType::singleton(Type::Int),
4073 )),
4074 body: vec![Statement::If {
4075 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
4076 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
4077 span: None,
4078 }],
4079 source: None,
4080 allowed_lints: vec![],
4081 }],
4082 };
4083
4084 let mut checker = TypeChecker::new();
4085 let result = checker.check_program(&program);
4086 assert!(
4087 result.is_err(),
4088 "Should reject: both branches push 2 values, but declared output is 1"
4089 );
4090 }
4091
4092 #[test]
4093 fn test_pollution_branch_consumes_extra() {
4094 let program = Program {
4098 includes: vec![],
4099 unions: vec![],
4100 words: vec![WordDef {
4101 name: "test".to_string(),
4102 effect: Some(Effect::new(
4103 StackType::Empty.push(Type::Bool).push(Type::Int),
4104 StackType::singleton(Type::Int),
4105 )),
4106 body: vec![Statement::If {
4107 then_branch: vec![
4108 Statement::WordCall {
4109 name: "drop".to_string(),
4110 span: None,
4111 },
4112 Statement::WordCall {
4113 name: "drop".to_string(),
4114 span: None,
4115 },
4116 Statement::IntLiteral(1),
4117 ],
4118 else_branch: Some(vec![]),
4119 span: None,
4120 }],
4121 source: None,
4122 allowed_lints: vec![],
4123 }],
4124 };
4125
4126 let mut checker = TypeChecker::new();
4127 let result = checker.check_program(&program);
4128 assert!(
4129 result.is_err(),
4130 "Should reject: then branch consumes Bool (should only have Int after if)"
4131 );
4132 }
4133
4134 #[test]
4135 fn test_pollution_quotation_wrong_arity_output() {
4136 let program = Program {
4140 includes: vec![],
4141 unions: vec![],
4142 words: vec![WordDef {
4143 name: "test".to_string(),
4144 effect: Some(Effect::new(
4145 StackType::singleton(Type::Int),
4146 StackType::singleton(Type::Int),
4147 )),
4148 body: vec![
4149 Statement::Quotation {
4150 span: None,
4151 id: 0,
4152 body: vec![Statement::WordCall {
4153 name: "dup".to_string(),
4154 span: None,
4155 }],
4156 },
4157 Statement::WordCall {
4158 name: "call".to_string(),
4159 span: None,
4160 },
4161 ],
4162 source: None,
4163 allowed_lints: vec![],
4164 }],
4165 };
4166
4167 let mut checker = TypeChecker::new();
4168 let result = checker.check_program(&program);
4169 assert!(
4170 result.is_err(),
4171 "Should reject: quotation [dup] produces 2 values, declared output is 1"
4172 );
4173 }
4174
4175 #[test]
4176 fn test_pollution_quotation_wrong_arity_input() {
4177 let program = Program {
4181 includes: vec![],
4182 unions: vec![],
4183 words: vec![WordDef {
4184 name: "test".to_string(),
4185 effect: Some(Effect::new(
4186 StackType::singleton(Type::Int),
4187 StackType::singleton(Type::Int),
4188 )),
4189 body: vec![
4190 Statement::Quotation {
4191 span: None,
4192 id: 0,
4193 body: vec![
4194 Statement::WordCall {
4195 name: "drop".to_string(),
4196 span: None,
4197 },
4198 Statement::WordCall {
4199 name: "drop".to_string(),
4200 span: None,
4201 },
4202 Statement::IntLiteral(42),
4203 ],
4204 },
4205 Statement::WordCall {
4206 name: "call".to_string(),
4207 span: None,
4208 },
4209 ],
4210 source: None,
4211 allowed_lints: vec![],
4212 }],
4213 };
4214
4215 let mut checker = TypeChecker::new();
4216 let result = checker.check_program(&program);
4217 assert!(
4218 result.is_err(),
4219 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
4220 );
4221 }
4222
4223 #[test]
4224 fn test_missing_effect_provides_helpful_error() {
4225 let program = Program {
4228 includes: vec![],
4229 unions: vec![],
4230 words: vec![WordDef {
4231 name: "myword".to_string(),
4232 effect: None, body: vec![Statement::IntLiteral(42)],
4234 source: None,
4235 allowed_lints: vec![],
4236 }],
4237 };
4238
4239 let mut checker = TypeChecker::new();
4240 let result = checker.check_program(&program);
4241 assert!(result.is_err());
4242 let err = result.unwrap_err();
4243 assert!(err.contains("myword"), "Error should mention word name");
4244 assert!(
4245 err.contains("stack effect"),
4246 "Error should mention stack effect"
4247 );
4248 }
4249
4250 #[test]
4251 fn test_valid_effect_exact_match() {
4252 let program = Program {
4255 includes: vec![],
4256 unions: vec![],
4257 words: vec![WordDef {
4258 name: "test".to_string(),
4259 effect: Some(Effect::new(
4260 StackType::Empty.push(Type::Int).push(Type::Int),
4261 StackType::singleton(Type::Int),
4262 )),
4263 body: vec![Statement::WordCall {
4264 name: "i.add".to_string(),
4265 span: None,
4266 }],
4267 source: None,
4268 allowed_lints: vec![],
4269 }],
4270 };
4271
4272 let mut checker = TypeChecker::new();
4273 let result = checker.check_program(&program);
4274 assert!(result.is_ok(), "Should accept: effect matches exactly");
4275 }
4276
4277 #[test]
4278 fn test_valid_polymorphic_passthrough() {
4279 let program = Program {
4282 includes: vec![],
4283 unions: vec![],
4284 words: vec![WordDef {
4285 name: "test".to_string(),
4286 effect: Some(Effect::new(
4287 StackType::Cons {
4288 rest: Box::new(StackType::RowVar("rest".to_string())),
4289 top: Type::Var("a".to_string()),
4290 },
4291 StackType::Cons {
4292 rest: Box::new(StackType::RowVar("rest".to_string())),
4293 top: Type::Var("a".to_string()),
4294 },
4295 )),
4296 body: vec![], source: None,
4298 allowed_lints: vec![],
4299 }],
4300 };
4301
4302 let mut checker = TypeChecker::new();
4303 let result = checker.check_program(&program);
4304 assert!(result.is_ok(), "Should accept: polymorphic identity");
4305 }
4306
4307 #[test]
4313 fn test_closure_basic_capture() {
4314 let program = Program {
4320 includes: vec![],
4321 unions: vec![],
4322 words: vec![WordDef {
4323 name: "make-adder".to_string(),
4324 effect: Some(Effect::new(
4325 StackType::singleton(Type::Int),
4326 StackType::singleton(Type::Closure {
4327 effect: Box::new(Effect::new(
4328 StackType::RowVar("r".to_string()).push(Type::Int),
4329 StackType::RowVar("r".to_string()).push(Type::Int),
4330 )),
4331 captures: vec![Type::Int], }),
4333 )),
4334 body: vec![Statement::Quotation {
4335 span: None,
4336 id: 0,
4337 body: vec![Statement::WordCall {
4338 name: "i.add".to_string(),
4339 span: None,
4340 }],
4341 }],
4342 source: None,
4343 allowed_lints: vec![],
4344 }],
4345 };
4346
4347 let mut checker = TypeChecker::new();
4348 let result = checker.check_program(&program);
4349 assert!(
4350 result.is_ok(),
4351 "Basic closure capture should work: {:?}",
4352 result.err()
4353 );
4354 }
4355
4356 #[test]
4357 fn test_closure_nested_two_levels() {
4358 let program = Program {
4363 includes: vec![],
4364 unions: vec![],
4365 words: vec![WordDef {
4366 name: "outer".to_string(),
4367 effect: Some(Effect::new(
4368 StackType::Empty,
4369 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4370 StackType::RowVar("r".to_string()),
4371 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
4372 Effect::new(
4373 StackType::RowVar("s".to_string()).push(Type::Int),
4374 StackType::RowVar("s".to_string()).push(Type::Int),
4375 ),
4376 ))),
4377 )))),
4378 )),
4379 body: vec![Statement::Quotation {
4380 span: None,
4381 id: 0,
4382 body: vec![Statement::Quotation {
4383 span: None,
4384 id: 1,
4385 body: vec![
4386 Statement::IntLiteral(1),
4387 Statement::WordCall {
4388 name: "i.add".to_string(),
4389 span: None,
4390 },
4391 ],
4392 }],
4393 }],
4394 source: None,
4395 allowed_lints: vec![],
4396 }],
4397 };
4398
4399 let mut checker = TypeChecker::new();
4400 let result = checker.check_program(&program);
4401 assert!(
4402 result.is_ok(),
4403 "Two-level nested quotations should work: {:?}",
4404 result.err()
4405 );
4406 }
4407
4408 #[test]
4409 fn test_closure_nested_three_levels() {
4410 let inner_effect = Effect::new(
4414 StackType::RowVar("a".to_string()).push(Type::Int),
4415 StackType::RowVar("a".to_string()).push(Type::Int),
4416 );
4417 let middle_effect = Effect::new(
4418 StackType::RowVar("b".to_string()),
4419 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
4420 );
4421 let outer_effect = Effect::new(
4422 StackType::RowVar("c".to_string()),
4423 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
4424 );
4425
4426 let program = Program {
4427 includes: vec![],
4428 unions: vec![],
4429 words: vec![WordDef {
4430 name: "deep".to_string(),
4431 effect: Some(Effect::new(
4432 StackType::Empty,
4433 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
4434 )),
4435 body: vec![Statement::Quotation {
4436 span: None,
4437 id: 0,
4438 body: vec![Statement::Quotation {
4439 span: None,
4440 id: 1,
4441 body: vec![Statement::Quotation {
4442 span: None,
4443 id: 2,
4444 body: vec![
4445 Statement::IntLiteral(1),
4446 Statement::WordCall {
4447 name: "i.add".to_string(),
4448 span: None,
4449 },
4450 ],
4451 }],
4452 }],
4453 }],
4454 source: None,
4455 allowed_lints: vec![],
4456 }],
4457 };
4458
4459 let mut checker = TypeChecker::new();
4460 let result = checker.check_program(&program);
4461 assert!(
4462 result.is_ok(),
4463 "Three-level nested quotations should work: {:?}",
4464 result.err()
4465 );
4466 }
4467
4468 #[test]
4469 fn test_closure_use_after_creation() {
4470 let adder_type = Type::Closure {
4476 effect: Box::new(Effect::new(
4477 StackType::RowVar("r".to_string()).push(Type::Int),
4478 StackType::RowVar("r".to_string()).push(Type::Int),
4479 )),
4480 captures: vec![Type::Int],
4481 };
4482
4483 let program = Program {
4484 includes: vec![],
4485 unions: vec![],
4486 words: vec![
4487 WordDef {
4488 name: "make-adder".to_string(),
4489 effect: Some(Effect::new(
4490 StackType::singleton(Type::Int),
4491 StackType::singleton(adder_type.clone()),
4492 )),
4493 body: vec![Statement::Quotation {
4494 span: None,
4495 id: 0,
4496 body: vec![Statement::WordCall {
4497 name: "i.add".to_string(),
4498 span: None,
4499 }],
4500 }],
4501 source: None,
4502 allowed_lints: vec![],
4503 },
4504 WordDef {
4505 name: "use-adder".to_string(),
4506 effect: Some(Effect::new(
4507 StackType::Empty,
4508 StackType::singleton(Type::Int),
4509 )),
4510 body: vec![
4511 Statement::IntLiteral(5),
4512 Statement::WordCall {
4513 name: "make-adder".to_string(),
4514 span: None,
4515 },
4516 Statement::IntLiteral(10),
4517 Statement::WordCall {
4518 name: "swap".to_string(),
4519 span: None,
4520 },
4521 Statement::WordCall {
4522 name: "call".to_string(),
4523 span: None,
4524 },
4525 ],
4526 source: None,
4527 allowed_lints: vec![],
4528 },
4529 ],
4530 };
4531
4532 let mut checker = TypeChecker::new();
4533 let result = checker.check_program(&program);
4534 assert!(
4535 result.is_ok(),
4536 "Closure usage after creation should work: {:?}",
4537 result.err()
4538 );
4539 }
4540
4541 #[test]
4542 fn test_closure_wrong_call_type() {
4543 let adder_type = Type::Closure {
4547 effect: Box::new(Effect::new(
4548 StackType::RowVar("r".to_string()).push(Type::Int),
4549 StackType::RowVar("r".to_string()).push(Type::Int),
4550 )),
4551 captures: vec![Type::Int],
4552 };
4553
4554 let program = Program {
4555 includes: vec![],
4556 unions: vec![],
4557 words: vec![
4558 WordDef {
4559 name: "make-adder".to_string(),
4560 effect: Some(Effect::new(
4561 StackType::singleton(Type::Int),
4562 StackType::singleton(adder_type.clone()),
4563 )),
4564 body: vec![Statement::Quotation {
4565 span: None,
4566 id: 0,
4567 body: vec![Statement::WordCall {
4568 name: "i.add".to_string(),
4569 span: None,
4570 }],
4571 }],
4572 source: None,
4573 allowed_lints: vec![],
4574 },
4575 WordDef {
4576 name: "bad-use".to_string(),
4577 effect: Some(Effect::new(
4578 StackType::Empty,
4579 StackType::singleton(Type::Int),
4580 )),
4581 body: vec![
4582 Statement::IntLiteral(5),
4583 Statement::WordCall {
4584 name: "make-adder".to_string(),
4585 span: None,
4586 },
4587 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
4589 name: "swap".to_string(),
4590 span: None,
4591 },
4592 Statement::WordCall {
4593 name: "call".to_string(),
4594 span: None,
4595 },
4596 ],
4597 source: None,
4598 allowed_lints: vec![],
4599 },
4600 ],
4601 };
4602
4603 let mut checker = TypeChecker::new();
4604 let result = checker.check_program(&program);
4605 assert!(
4606 result.is_err(),
4607 "Calling Int closure with String should fail"
4608 );
4609 }
4610
4611 #[test]
4612 fn test_closure_multiple_captures() {
4613 let program = Program {
4620 includes: vec![],
4621 unions: vec![],
4622 words: vec![WordDef {
4623 name: "make-between".to_string(),
4624 effect: Some(Effect::new(
4625 StackType::Empty.push(Type::Int).push(Type::Int),
4626 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4627 StackType::RowVar("r".to_string()).push(Type::Int),
4628 StackType::RowVar("r".to_string()).push(Type::Bool),
4629 )))),
4630 )),
4631 body: vec![Statement::Quotation {
4632 span: None,
4633 id: 0,
4634 body: vec![
4635 Statement::WordCall {
4637 name: "i.>=".to_string(),
4638 span: None,
4639 },
4640 ],
4642 }],
4643 source: None,
4644 allowed_lints: vec![],
4645 }],
4646 };
4647
4648 let mut checker = TypeChecker::new();
4649 let result = checker.check_program(&program);
4650 assert!(
4654 result.is_ok() || result.is_err(),
4655 "Multiple captures should be handled (pass or fail gracefully)"
4656 );
4657 }
4658
4659 #[test]
4660 fn test_quotation_type_preserved_through_word() {
4661 let quot_type = Type::Quotation(Box::new(Effect::new(
4664 StackType::RowVar("r".to_string()).push(Type::Int),
4665 StackType::RowVar("r".to_string()).push(Type::Int),
4666 )));
4667
4668 let program = Program {
4669 includes: vec![],
4670 unions: vec![],
4671 words: vec![WordDef {
4672 name: "identity-quot".to_string(),
4673 effect: Some(Effect::new(
4674 StackType::singleton(quot_type.clone()),
4675 StackType::singleton(quot_type.clone()),
4676 )),
4677 body: vec![], source: None,
4679 allowed_lints: vec![],
4680 }],
4681 };
4682
4683 let mut checker = TypeChecker::new();
4684 let result = checker.check_program(&program);
4685 assert!(
4686 result.is_ok(),
4687 "Quotation type should be preserved through identity word: {:?}",
4688 result.err()
4689 );
4690 }
4691
4692 #[test]
4693 fn test_closure_captures_value_for_inner_quotation() {
4694 let closure_effect = Effect::new(
4700 StackType::RowVar("r".to_string()).push(Type::Int),
4701 StackType::RowVar("r".to_string()).push(Type::Int),
4702 );
4703
4704 let program = Program {
4705 includes: vec![],
4706 unions: vec![],
4707 words: vec![WordDef {
4708 name: "make-inner-adder".to_string(),
4709 effect: Some(Effect::new(
4710 StackType::singleton(Type::Int),
4711 StackType::singleton(Type::Closure {
4712 effect: Box::new(closure_effect),
4713 captures: vec![Type::Int],
4714 }),
4715 )),
4716 body: vec![Statement::Quotation {
4717 span: None,
4718 id: 0,
4719 body: vec![
4720 Statement::WordCall {
4722 name: "i.add".to_string(),
4723 span: None,
4724 },
4725 ],
4726 }],
4727 source: None,
4728 allowed_lints: vec![],
4729 }],
4730 };
4731
4732 let mut checker = TypeChecker::new();
4733 let result = checker.check_program(&program);
4734 assert!(
4735 result.is_ok(),
4736 "Closure with capture for inner work should pass: {:?}",
4737 result.err()
4738 );
4739 }
4740
4741 #[test]
4742 fn test_union_type_mismatch_should_fail() {
4743 use crate::ast::{UnionDef, UnionField, UnionVariant};
4746
4747 let mut program = Program {
4748 includes: vec![],
4749 unions: vec![
4750 UnionDef {
4751 name: "UnionA".to_string(),
4752 variants: vec![UnionVariant {
4753 name: "AVal".to_string(),
4754 fields: vec![UnionField {
4755 name: "x".to_string(),
4756 type_name: "Int".to_string(),
4757 }],
4758 source: None,
4759 }],
4760 source: None,
4761 },
4762 UnionDef {
4763 name: "UnionB".to_string(),
4764 variants: vec![UnionVariant {
4765 name: "BVal".to_string(),
4766 fields: vec![UnionField {
4767 name: "y".to_string(),
4768 type_name: "Int".to_string(),
4769 }],
4770 source: None,
4771 }],
4772 source: None,
4773 },
4774 ],
4775 words: vec![
4776 WordDef {
4778 name: "takes-a".to_string(),
4779 effect: Some(Effect::new(
4780 StackType::RowVar("rest".to_string())
4781 .push(Type::Union("UnionA".to_string())),
4782 StackType::RowVar("rest".to_string()),
4783 )),
4784 body: vec![Statement::WordCall {
4785 name: "drop".to_string(),
4786 span: None,
4787 }],
4788 source: None,
4789 allowed_lints: vec![],
4790 },
4791 WordDef {
4794 name: "main".to_string(),
4795 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4796 body: vec![
4797 Statement::IntLiteral(99),
4798 Statement::WordCall {
4799 name: "Make-BVal".to_string(),
4800 span: None,
4801 },
4802 Statement::WordCall {
4803 name: "takes-a".to_string(),
4804 span: None,
4805 },
4806 ],
4807 source: None,
4808 allowed_lints: vec![],
4809 },
4810 ],
4811 };
4812
4813 program.generate_constructors().unwrap();
4815
4816 let mut checker = TypeChecker::new();
4817 let result = checker.check_program(&program);
4818
4819 assert!(
4821 result.is_err(),
4822 "Passing UnionB to function expecting UnionA should fail, but got: {:?}",
4823 result
4824 );
4825 let err = result.unwrap_err();
4826 assert!(
4827 err.contains("Union") || err.contains("mismatch"),
4828 "Error should mention union type mismatch, got: {}",
4829 err
4830 );
4831 }
4832
4833 fn make_word_call(name: &str) -> Statement {
4838 Statement::WordCall {
4839 name: name.to_string(),
4840 span: None,
4841 }
4842 }
4843
4844 #[test]
4845 fn test_aux_basic_round_trip() {
4846 let program = Program {
4848 includes: vec![],
4849 unions: vec![],
4850 words: vec![WordDef {
4851 name: "test".to_string(),
4852 effect: Some(Effect::new(
4853 StackType::singleton(Type::Int),
4854 StackType::singleton(Type::Int),
4855 )),
4856 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4857 source: None,
4858 allowed_lints: vec![],
4859 }],
4860 };
4861
4862 let mut checker = TypeChecker::new();
4863 assert!(checker.check_program(&program).is_ok());
4864 }
4865
4866 #[test]
4867 fn test_aux_preserves_type() {
4868 let program = Program {
4870 includes: vec![],
4871 unions: vec![],
4872 words: vec![WordDef {
4873 name: "test".to_string(),
4874 effect: Some(Effect::new(
4875 StackType::singleton(Type::String),
4876 StackType::singleton(Type::String),
4877 )),
4878 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4879 source: None,
4880 allowed_lints: vec![],
4881 }],
4882 };
4883
4884 let mut checker = TypeChecker::new();
4885 assert!(checker.check_program(&program).is_ok());
4886 }
4887
4888 #[test]
4889 fn test_aux_unbalanced_error() {
4890 let program = Program {
4892 includes: vec![],
4893 unions: vec![],
4894 words: vec![WordDef {
4895 name: "test".to_string(),
4896 effect: Some(Effect::new(
4897 StackType::singleton(Type::Int),
4898 StackType::Empty,
4899 )),
4900 body: vec![make_word_call(">aux")],
4901 source: None,
4902 allowed_lints: vec![],
4903 }],
4904 };
4905
4906 let mut checker = TypeChecker::new();
4907 let result = checker.check_program(&program);
4908 assert!(result.is_err());
4909 let err = result.unwrap_err();
4910 assert!(
4911 err.contains("aux stack is not empty"),
4912 "Expected aux stack balance error, got: {}",
4913 err
4914 );
4915 }
4916
4917 #[test]
4918 fn test_aux_pop_empty_error() {
4919 let program = Program {
4921 includes: vec![],
4922 unions: vec![],
4923 words: vec![WordDef {
4924 name: "test".to_string(),
4925 effect: Some(Effect::new(
4926 StackType::Empty,
4927 StackType::singleton(Type::Int),
4928 )),
4929 body: vec![make_word_call("aux>")],
4930 source: None,
4931 allowed_lints: vec![],
4932 }],
4933 };
4934
4935 let mut checker = TypeChecker::new();
4936 let result = checker.check_program(&program);
4937 assert!(result.is_err());
4938 let err = result.unwrap_err();
4939 assert!(
4940 err.contains("aux stack is empty"),
4941 "Expected aux empty error, got: {}",
4942 err
4943 );
4944 }
4945
4946 #[test]
4947 fn test_aux_multiple_values() {
4948 let program = Program {
4952 includes: vec![],
4953 unions: vec![],
4954 words: vec![WordDef {
4955 name: "test".to_string(),
4956 effect: Some(Effect::new(
4957 StackType::Empty.push(Type::Int).push(Type::String),
4958 StackType::Empty.push(Type::Int).push(Type::String),
4959 )),
4960 body: vec![
4961 make_word_call(">aux"),
4962 make_word_call(">aux"),
4963 make_word_call("aux>"),
4964 make_word_call("aux>"),
4965 ],
4966 source: None,
4967 allowed_lints: vec![],
4968 }],
4969 };
4970
4971 let mut checker = TypeChecker::new();
4972 assert!(checker.check_program(&program).is_ok());
4973 }
4974
4975 #[test]
4976 fn test_aux_max_depths_tracked() {
4977 let program = Program {
4979 includes: vec![],
4980 unions: vec![],
4981 words: vec![WordDef {
4982 name: "test".to_string(),
4983 effect: Some(Effect::new(
4984 StackType::singleton(Type::Int),
4985 StackType::singleton(Type::Int),
4986 )),
4987 body: vec![make_word_call(">aux"), make_word_call("aux>")],
4988 source: None,
4989 allowed_lints: vec![],
4990 }],
4991 };
4992
4993 let mut checker = TypeChecker::new();
4994 checker.check_program(&program).unwrap();
4995 let depths = checker.take_aux_max_depths();
4996 assert_eq!(depths.get("test"), Some(&1));
4997 }
4998
4999 #[test]
5000 fn test_aux_in_match_balanced() {
5001 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
5003
5004 let union_def = UnionDef {
5005 name: "Choice".to_string(),
5006 variants: vec![
5007 UnionVariant {
5008 name: "Left".to_string(),
5009 fields: vec![],
5010 source: None,
5011 },
5012 UnionVariant {
5013 name: "Right".to_string(),
5014 fields: vec![],
5015 source: None,
5016 },
5017 ],
5018 source: None,
5019 };
5020
5021 let program = Program {
5024 includes: vec![],
5025 unions: vec![union_def],
5026 words: vec![WordDef {
5027 name: "test".to_string(),
5028 effect: Some(Effect::new(
5029 StackType::Empty
5030 .push(Type::Int)
5031 .push(Type::Union("Choice".to_string())),
5032 StackType::singleton(Type::Int),
5033 )),
5034 body: vec![
5035 make_word_call("swap"),
5036 make_word_call(">aux"),
5037 Statement::Match {
5038 arms: vec![
5039 MatchArm {
5040 pattern: Pattern::Variant("Left".to_string()),
5041 body: vec![make_word_call("aux>")],
5042 span: None,
5043 },
5044 MatchArm {
5045 pattern: Pattern::Variant("Right".to_string()),
5046 body: vec![make_word_call("aux>")],
5047 span: None,
5048 },
5049 ],
5050 span: None,
5051 },
5052 ],
5053 source: None,
5054 allowed_lints: vec![],
5055 }],
5056 };
5057
5058 let mut checker = TypeChecker::new();
5059 assert!(checker.check_program(&program).is_ok());
5060 }
5061
5062 #[test]
5063 fn test_aux_in_match_unbalanced_error() {
5064 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
5066
5067 let union_def = UnionDef {
5068 name: "Choice".to_string(),
5069 variants: vec![
5070 UnionVariant {
5071 name: "Left".to_string(),
5072 fields: vec![],
5073 source: None,
5074 },
5075 UnionVariant {
5076 name: "Right".to_string(),
5077 fields: vec![],
5078 source: None,
5079 },
5080 ],
5081 source: None,
5082 };
5083
5084 let program = Program {
5087 includes: vec![],
5088 unions: vec![union_def],
5089 words: vec![WordDef {
5090 name: "test".to_string(),
5091 effect: Some(Effect::new(
5092 StackType::Empty
5093 .push(Type::Int)
5094 .push(Type::Union("Choice".to_string())),
5095 StackType::singleton(Type::Int),
5096 )),
5097 body: vec![
5098 make_word_call("swap"),
5099 make_word_call(">aux"),
5100 Statement::Match {
5101 arms: vec![
5102 MatchArm {
5103 pattern: Pattern::Variant("Left".to_string()),
5104 body: vec![make_word_call("aux>")],
5105 span: None,
5106 },
5107 MatchArm {
5108 pattern: Pattern::Variant("Right".to_string()),
5109 body: vec![],
5110 span: None,
5111 },
5112 ],
5113 span: None,
5114 },
5115 ],
5116 source: None,
5117 allowed_lints: vec![],
5118 }],
5119 };
5120
5121 let mut checker = TypeChecker::new();
5122 let result = checker.check_program(&program);
5123 assert!(result.is_err());
5124 let err = result.unwrap_err();
5125 assert!(
5126 err.contains("aux stack"),
5127 "Expected aux stack mismatch error, got: {}",
5128 err
5129 );
5130 }
5131
5132 #[test]
5133 fn test_aux_in_quotation_rejected() {
5134 let program = Program {
5136 includes: vec![],
5137 unions: vec![],
5138 words: vec![WordDef {
5139 name: "test".to_string(),
5140 effect: Some(Effect::new(
5141 StackType::singleton(Type::Int),
5142 StackType::singleton(Type::Quotation(Box::new(Effect::new(
5143 StackType::singleton(Type::Int),
5144 StackType::singleton(Type::Int),
5145 )))),
5146 )),
5147 body: vec![Statement::Quotation {
5148 span: None,
5149 id: 0,
5150 body: vec![make_word_call(">aux"), make_word_call("aux>")],
5151 }],
5152 source: None,
5153 allowed_lints: vec![],
5154 }],
5155 };
5156
5157 let mut checker = TypeChecker::new();
5158 let result = checker.check_program(&program);
5159 assert!(result.is_err());
5160 let err = result.unwrap_err();
5161 assert!(
5162 err.contains("not supported inside quotations"),
5163 "Expected quotation rejection error, got: {}",
5164 err
5165 );
5166 }
5167
5168 #[test]
5173 fn test_dip_basic() {
5174 let program = Program {
5177 includes: vec![],
5178 unions: vec![],
5179 words: vec![WordDef {
5180 name: "test".to_string(),
5181 effect: Some(Effect::new(
5182 StackType::Empty.push(Type::Int).push(Type::Int),
5183 StackType::Empty.push(Type::Int).push(Type::Int),
5184 )),
5185 body: vec![
5186 Statement::Quotation {
5187 id: 0,
5188 body: vec![
5189 Statement::IntLiteral(1),
5190 Statement::WordCall {
5191 name: "i.+".to_string(),
5192 span: None,
5193 },
5194 ],
5195 span: None,
5196 },
5197 Statement::WordCall {
5198 name: "dip".to_string(),
5199 span: None,
5200 },
5201 ],
5202 source: None,
5203 allowed_lints: vec![],
5204 }],
5205 };
5206
5207 let mut checker = TypeChecker::new();
5208 assert!(checker.check_program(&program).is_ok());
5209 }
5210
5211 #[test]
5212 fn test_dip_type_mismatch() {
5213 let program = Program {
5216 includes: vec![],
5217 unions: vec![],
5218 words: vec![WordDef {
5219 name: "test".to_string(),
5220 effect: Some(Effect::new(
5221 StackType::Empty.push(Type::String).push(Type::Int),
5222 StackType::Empty.push(Type::Int).push(Type::Int),
5223 )),
5224 body: vec![
5225 Statement::Quotation {
5226 id: 0,
5227 body: vec![
5228 Statement::IntLiteral(1),
5229 Statement::WordCall {
5230 name: "i.+".to_string(),
5231 span: None,
5232 },
5233 ],
5234 span: None,
5235 },
5236 Statement::WordCall {
5237 name: "dip".to_string(),
5238 span: None,
5239 },
5240 ],
5241 source: None,
5242 allowed_lints: vec![],
5243 }],
5244 };
5245
5246 let mut checker = TypeChecker::new();
5247 assert!(checker.check_program(&program).is_err());
5248 }
5249
5250 #[test]
5251 fn test_keep_basic() {
5252 let program = Program {
5255 includes: vec![],
5256 unions: vec![],
5257 words: vec![WordDef {
5258 name: "test".to_string(),
5259 effect: Some(Effect::new(
5260 StackType::singleton(Type::Int),
5261 StackType::Empty.push(Type::Int).push(Type::Int),
5262 )),
5263 body: vec![
5264 Statement::Quotation {
5265 id: 0,
5266 body: vec![
5267 Statement::WordCall {
5268 name: "dup".to_string(),
5269 span: None,
5270 },
5271 Statement::WordCall {
5272 name: "i.*".to_string(),
5273 span: None,
5274 },
5275 ],
5276 span: None,
5277 },
5278 Statement::WordCall {
5279 name: "keep".to_string(),
5280 span: None,
5281 },
5282 ],
5283 source: None,
5284 allowed_lints: vec![],
5285 }],
5286 };
5287
5288 let mut checker = TypeChecker::new();
5289 assert!(checker.check_program(&program).is_ok());
5290 }
5291
5292 #[test]
5293 fn test_bi_basic() {
5294 let program = Program {
5297 includes: vec![],
5298 unions: vec![],
5299 words: vec![WordDef {
5300 name: "test".to_string(),
5301 effect: Some(Effect::new(
5302 StackType::singleton(Type::Int),
5303 StackType::Empty.push(Type::Int).push(Type::Int),
5304 )),
5305 body: vec![
5306 Statement::Quotation {
5307 id: 0,
5308 body: vec![
5309 Statement::IntLiteral(2),
5310 Statement::WordCall {
5311 name: "i.*".to_string(),
5312 span: None,
5313 },
5314 ],
5315 span: None,
5316 },
5317 Statement::Quotation {
5318 id: 1,
5319 body: vec![
5320 Statement::IntLiteral(3),
5321 Statement::WordCall {
5322 name: "i.*".to_string(),
5323 span: None,
5324 },
5325 ],
5326 span: None,
5327 },
5328 Statement::WordCall {
5329 name: "bi".to_string(),
5330 span: None,
5331 },
5332 ],
5333 source: None,
5334 allowed_lints: vec![],
5335 }],
5336 };
5337
5338 let mut checker = TypeChecker::new();
5339 assert!(checker.check_program(&program).is_ok());
5340 }
5341
5342 #[test]
5343 fn test_keep_type_mismatch() {
5344 let program = Program {
5347 includes: vec![],
5348 unions: vec![],
5349 words: vec![WordDef {
5350 name: "test".to_string(),
5351 effect: Some(Effect::new(
5352 StackType::singleton(Type::String),
5353 StackType::Empty.push(Type::Int).push(Type::String),
5354 )),
5355 body: vec![
5356 Statement::Quotation {
5357 id: 0,
5358 body: vec![
5359 Statement::IntLiteral(1),
5360 Statement::WordCall {
5361 name: "i.+".to_string(),
5362 span: None,
5363 },
5364 ],
5365 span: None,
5366 },
5367 Statement::WordCall {
5368 name: "keep".to_string(),
5369 span: None,
5370 },
5371 ],
5372 source: None,
5373 allowed_lints: vec![],
5374 }],
5375 };
5376
5377 let mut checker = TypeChecker::new();
5378 assert!(checker.check_program(&program).is_err());
5379 }
5380
5381 #[test]
5382 fn test_bi_type_mismatch() {
5383 let program = Program {
5386 includes: vec![],
5387 unions: vec![],
5388 words: vec![WordDef {
5389 name: "test".to_string(),
5390 effect: Some(Effect::new(
5391 StackType::singleton(Type::String),
5392 StackType::Empty.push(Type::Int).push(Type::Int),
5393 )),
5394 body: vec![
5395 Statement::Quotation {
5396 id: 0,
5397 body: vec![Statement::WordCall {
5398 name: "string.length".to_string(),
5399 span: None,
5400 }],
5401 span: None,
5402 },
5403 Statement::Quotation {
5404 id: 1,
5405 body: vec![
5406 Statement::IntLiteral(1),
5407 Statement::WordCall {
5408 name: "i.+".to_string(),
5409 span: None,
5410 },
5411 ],
5412 span: None,
5413 },
5414 Statement::WordCall {
5415 name: "bi".to_string(),
5416 span: None,
5417 },
5418 ],
5419 source: None,
5420 allowed_lints: vec![],
5421 }],
5422 };
5423
5424 let mut checker = TypeChecker::new();
5425 assert!(checker.check_program(&program).is_err());
5426 }
5427
5428 #[test]
5429 fn test_dip_underflow() {
5430 let program = Program {
5433 includes: vec![],
5434 unions: vec![],
5435 words: vec![WordDef {
5436 name: "test".to_string(),
5437 effect: Some(Effect::new(
5438 StackType::Empty,
5439 StackType::singleton(Type::Int),
5440 )),
5441 body: vec![
5442 Statement::Quotation {
5443 id: 0,
5444 body: vec![Statement::IntLiteral(1)],
5445 span: None,
5446 },
5447 Statement::WordCall {
5448 name: "dip".to_string(),
5449 span: None,
5450 },
5451 ],
5452 source: None,
5453 allowed_lints: vec![],
5454 }],
5455 };
5456
5457 let mut checker = TypeChecker::new();
5458 let result = checker.check_program(&program);
5459 assert!(result.is_err());
5460 let err = result.unwrap_err();
5461 assert!(
5462 err.contains("stack underflow"),
5463 "Expected underflow error, got: {}",
5464 err
5465 );
5466 }
5467
5468 #[test]
5469 fn test_dip_preserves_type() {
5470 let program = Program {
5473 includes: vec![],
5474 unions: vec![],
5475 words: vec![WordDef {
5476 name: "test".to_string(),
5477 effect: Some(Effect::new(
5478 StackType::Empty.push(Type::Int).push(Type::String),
5479 StackType::Empty.push(Type::Int).push(Type::String),
5480 )),
5481 body: vec![
5482 Statement::Quotation {
5483 id: 0,
5484 body: vec![
5485 Statement::IntLiteral(1),
5486 Statement::WordCall {
5487 name: "i.+".to_string(),
5488 span: None,
5489 },
5490 ],
5491 span: None,
5492 },
5493 Statement::WordCall {
5494 name: "dip".to_string(),
5495 span: None,
5496 },
5497 ],
5498 source: None,
5499 allowed_lints: vec![],
5500 }],
5501 };
5502
5503 let mut checker = TypeChecker::new();
5504 assert!(checker.check_program(&program).is_ok());
5505 }
5506
5507 #[test]
5508 fn test_keep_underflow() {
5509 let program = Program {
5512 includes: vec![],
5513 unions: vec![],
5514 words: vec![WordDef {
5515 name: "test".to_string(),
5516 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
5517 body: vec![
5518 Statement::Quotation {
5519 id: 0,
5520 body: vec![Statement::WordCall {
5521 name: "drop".to_string(),
5522 span: None,
5523 }],
5524 span: None,
5525 },
5526 Statement::WordCall {
5527 name: "keep".to_string(),
5528 span: None,
5529 },
5530 ],
5531 source: None,
5532 allowed_lints: vec![],
5533 }],
5534 };
5535
5536 let mut checker = TypeChecker::new();
5537 let result = checker.check_program(&program);
5538 assert!(result.is_err());
5539 let err = result.unwrap_err();
5540 assert!(
5541 err.contains("stack underflow") || err.contains("underflow"),
5542 "Expected underflow error, got: {}",
5543 err
5544 );
5545 }
5546
5547 #[test]
5548 fn test_bi_underflow() {
5549 let program = Program {
5552 includes: vec![],
5553 unions: vec![],
5554 words: vec![WordDef {
5555 name: "test".to_string(),
5556 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
5557 body: vec![
5558 Statement::Quotation {
5559 id: 0,
5560 body: vec![Statement::IntLiteral(1)],
5561 span: None,
5562 },
5563 Statement::Quotation {
5564 id: 1,
5565 body: vec![Statement::IntLiteral(2)],
5566 span: None,
5567 },
5568 Statement::WordCall {
5569 name: "bi".to_string(),
5570 span: None,
5571 },
5572 ],
5573 source: None,
5574 allowed_lints: vec![],
5575 }],
5576 };
5577
5578 let mut checker = TypeChecker::new();
5579 let result = checker.check_program(&program);
5580 assert!(result.is_err());
5581 let err = result.unwrap_err();
5582 assert!(
5583 err.contains("stack underflow") || err.contains("underflow"),
5584 "Expected underflow error, got: {}",
5585 err
5586 );
5587 }
5588
5589 #[test]
5590 fn test_bi_polymorphic_quotations() {
5591 let program = Program {
5594 includes: vec![],
5595 unions: vec![],
5596 words: vec![WordDef {
5597 name: "test".to_string(),
5598 effect: Some(Effect::new(
5599 StackType::singleton(Type::Int),
5600 StackType::Empty.push(Type::Int).push(Type::String),
5601 )),
5602 body: vec![
5603 Statement::Quotation {
5604 id: 0,
5605 body: vec![
5606 Statement::IntLiteral(2),
5607 Statement::WordCall {
5608 name: "i.*".to_string(),
5609 span: None,
5610 },
5611 ],
5612 span: None,
5613 },
5614 Statement::Quotation {
5615 id: 1,
5616 body: vec![Statement::WordCall {
5617 name: "int->string".to_string(),
5618 span: None,
5619 }],
5620 span: None,
5621 },
5622 Statement::WordCall {
5623 name: "bi".to_string(),
5624 span: None,
5625 },
5626 ],
5627 source: None,
5628 allowed_lints: vec![],
5629 }],
5630 };
5631
5632 let mut checker = TypeChecker::new();
5633 assert!(checker.check_program(&program).is_ok());
5634 }
5635}