1use crate::ast::{Program, Statement, WordDef};
7use crate::builtins::builtin_signature;
8use crate::capture_analysis::calculate_captures;
9use crate::types::{
10 Effect, SideEffect, StackType, Type, UnionTypeInfo, VariantFieldInfo, VariantInfo,
11};
12use crate::unification::{Subst, unify_stacks, unify_types};
13use std::collections::HashMap;
14
15pub struct TypeChecker {
16 env: HashMap<String, Effect>,
18 unions: HashMap<String, UnionTypeInfo>,
21 fresh_counter: std::cell::Cell<usize>,
23 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
27 expected_quotation_type: std::cell::RefCell<Option<Type>>,
30 current_word: std::cell::RefCell<Option<String>>,
33 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
37}
38
39impl TypeChecker {
40 pub fn new() -> Self {
41 TypeChecker {
42 env: HashMap::new(),
43 unions: HashMap::new(),
44 fresh_counter: std::cell::Cell::new(0),
45 quotation_types: std::cell::RefCell::new(HashMap::new()),
46 expected_quotation_type: std::cell::RefCell::new(None),
47 current_word: std::cell::RefCell::new(None),
48 statement_top_types: std::cell::RefCell::new(HashMap::new()),
49 }
50 }
51
52 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
54 self.unions.get(name)
55 }
56
57 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
59 &self.unions
60 }
61
62 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
66 for (union_name, union_info) in &self.unions {
67 for variant in &union_info.variants {
68 if variant.name == variant_name {
69 return Some((union_name.as_str(), variant));
70 }
71 }
72 }
73 None
74 }
75
76 pub fn register_external_words(&mut self, words: &[(&str, Option<&Effect>)]) {
81 for (name, effect) in words {
82 if let Some(eff) = effect {
83 self.env.insert(name.to_string(), (*eff).clone());
84 } else {
85 let placeholder = Effect::new(
87 StackType::RowVar("ext_in".to_string()),
88 StackType::RowVar("ext_out".to_string()),
89 );
90 self.env.insert(name.to_string(), placeholder);
91 }
92 }
93 }
94
95 pub fn register_external_unions(&mut self, union_names: &[&str]) {
101 for name in union_names {
102 self.unions.insert(
105 name.to_string(),
106 UnionTypeInfo {
107 name: name.to_string(),
108 variants: vec![],
109 },
110 );
111 }
112 }
113
114 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
120 self.quotation_types.replace(HashMap::new())
121 }
122
123 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
126 self.statement_top_types.replace(HashMap::new())
127 }
128
129 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
132 match stack {
133 StackType::Cons { top, .. } => match top {
134 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
135 _ => None,
136 },
137 _ => None,
138 }
139 }
140
141 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
143 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
144 self.statement_top_types
145 .borrow_mut()
146 .insert((word_name.to_string(), stmt_index), top_type);
147 }
148 }
149
150 fn fresh_var(&self, prefix: &str) -> String {
152 let n = self.fresh_counter.get();
153 self.fresh_counter.set(n + 1);
154 format!("{}${}", prefix, n)
155 }
156
157 fn freshen_effect(&self, effect: &Effect) -> Effect {
159 let mut type_map = HashMap::new();
160 let mut row_map = HashMap::new();
161
162 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
163 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
164
165 let fresh_effects = effect
167 .effects
168 .iter()
169 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
170 .collect();
171
172 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
173 }
174
175 fn freshen_side_effect(
176 &self,
177 effect: &SideEffect,
178 type_map: &mut HashMap<String, String>,
179 row_map: &mut HashMap<String, String>,
180 ) -> SideEffect {
181 match effect {
182 SideEffect::Yield(ty) => {
183 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
184 }
185 }
186 }
187
188 fn freshen_stack(
189 &self,
190 stack: &StackType,
191 type_map: &mut HashMap<String, String>,
192 row_map: &mut HashMap<String, String>,
193 ) -> StackType {
194 match stack {
195 StackType::Empty => StackType::Empty,
196 StackType::RowVar(name) => {
197 let fresh_name = row_map
198 .entry(name.clone())
199 .or_insert_with(|| self.fresh_var(name));
200 StackType::RowVar(fresh_name.clone())
201 }
202 StackType::Cons { rest, top } => {
203 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
204 let fresh_top = self.freshen_type(top, type_map, row_map);
205 StackType::Cons {
206 rest: Box::new(fresh_rest),
207 top: fresh_top,
208 }
209 }
210 }
211 }
212
213 fn freshen_type(
214 &self,
215 ty: &Type,
216 type_map: &mut HashMap<String, String>,
217 row_map: &mut HashMap<String, String>,
218 ) -> Type {
219 match ty {
220 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
221 ty.clone()
222 }
223 Type::Var(name) => {
224 let fresh_name = type_map
225 .entry(name.clone())
226 .or_insert_with(|| self.fresh_var(name));
227 Type::Var(fresh_name.clone())
228 }
229 Type::Quotation(effect) => {
230 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
231 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
232 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
233 }
234 Type::Closure { effect, captures } => {
235 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
236 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
237 let fresh_captures = captures
238 .iter()
239 .map(|t| self.freshen_type(t, type_map, row_map))
240 .collect();
241 Type::Closure {
242 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
243 captures: fresh_captures,
244 }
245 }
246 Type::Union(name) => Type::Union(name.clone()),
248 }
249 }
250
251 fn parse_type_name(&self, name: &str) -> Type {
255 match name {
256 "Int" => Type::Int,
257 "Float" => Type::Float,
258 "Bool" => Type::Bool,
259 "String" => Type::String,
260 "Channel" => Type::Channel,
261 other => Type::Union(other.to_string()),
263 }
264 }
265
266 fn is_valid_type_name(&self, name: &str) -> bool {
271 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
272 || self.unions.contains_key(name)
273 }
274
275 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
279 for union_def in &program.unions {
280 for variant in &union_def.variants {
281 for field in &variant.fields {
282 if !self.is_valid_type_name(&field.type_name) {
283 return Err(format!(
284 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
285 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
286 field.type_name, field.name, variant.name, union_def.name
287 ));
288 }
289 }
290 }
291 }
292 Ok(())
293 }
294
295 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
297 for union_def in &program.unions {
299 let variants = union_def
300 .variants
301 .iter()
302 .map(|v| VariantInfo {
303 name: v.name.clone(),
304 fields: v
305 .fields
306 .iter()
307 .map(|f| VariantFieldInfo {
308 name: f.name.clone(),
309 field_type: self.parse_type_name(&f.type_name),
310 })
311 .collect(),
312 })
313 .collect();
314
315 self.unions.insert(
316 union_def.name.clone(),
317 UnionTypeInfo {
318 name: union_def.name.clone(),
319 variants,
320 },
321 );
322 }
323
324 self.validate_union_field_types(program)?;
326
327 for word in &program.words {
331 if let Some(effect) = &word.effect {
332 self.env.insert(word.name.clone(), effect.clone());
333 } else {
334 let placeholder = Effect::new(
337 StackType::RowVar("input".to_string()),
338 StackType::RowVar("output".to_string()),
339 );
340 self.env.insert(word.name.clone(), placeholder);
341 }
342 }
343
344 for word in &program.words {
346 self.check_word(word)?;
347 }
348
349 Ok(())
350 }
351
352 fn check_word(&self, word: &WordDef) -> Result<(), String> {
354 *self.current_word.borrow_mut() = Some(word.name.clone());
356
357 let result = if let Some(declared_effect) = &word.effect {
359 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
362 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
363 {
364 *self.expected_quotation_type.borrow_mut() = Some(top_type);
365 }
366
367 let (result_stack, _subst, inferred_effects) =
369 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
370
371 *self.expected_quotation_type.borrow_mut() = None;
373
374 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
376 format!(
377 "Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
378 word.name, declared_effect.outputs, result_stack, e
379 )
380 })?;
381
382 for inferred in &inferred_effects {
386 if !self.effect_matches_any(inferred, &declared_effect.effects) {
387 return Err(format!(
388 "Word '{}': body produces effect '{}' but no matching effect is declared.\n\
389 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
390 word.name, inferred
391 ));
392 }
393 }
394
395 for declared in &declared_effect.effects {
398 if !self.effect_matches_any(declared, &inferred_effects) {
399 return Err(format!(
400 "Word '{}': declares effect '{}' but body doesn't produce it.\n\
401 Hint: Remove the effect declaration or ensure the body uses yield.",
402 word.name, declared
403 ));
404 }
405 }
406
407 Ok(())
408 } else {
409 let (_, _, inferred_effects) = self.infer_statements_from(
412 &word.body,
413 &StackType::RowVar("input".to_string()),
414 true,
415 )?;
416
417 if !inferred_effects.is_empty() {
419 let effects_str: Vec<_> =
420 inferred_effects.iter().map(|e| format!("{}", e)).collect();
421 return Err(format!(
422 "Word '{}': body produces effects [{}] but word has no declared effect.\n\
423 Hint: Add a stack effect annotation with '| {}'.",
424 word.name,
425 effects_str.join(", "),
426 effects_str.join(" ")
427 ));
428 }
429 Ok(())
430 };
431
432 *self.current_word.borrow_mut() = None;
434
435 result
436 }
437
438 fn infer_statements_from(
445 &self,
446 statements: &[Statement],
447 start_stack: &StackType,
448 capture_stmt_types: bool,
449 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
450 let mut current_stack = start_stack.clone();
451 let mut accumulated_subst = Subst::empty();
452 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
453 let mut skip_next = false;
454
455 for (i, stmt) in statements.iter().enumerate() {
456 if skip_next {
458 skip_next = false;
459 continue;
460 }
461
462 if let Statement::IntLiteral(n) = stmt
465 && let Some(Statement::WordCall {
466 name: next_word, ..
467 }) = statements.get(i + 1)
468 {
469 if next_word == "pick" {
470 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
471 current_stack = new_stack;
472 accumulated_subst = accumulated_subst.compose(&subst);
473 skip_next = true; continue;
475 } else if next_word == "roll" {
476 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
477 current_stack = new_stack;
478 accumulated_subst = accumulated_subst.compose(&subst);
479 skip_next = true; continue;
481 }
482 }
483
484 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
487 let saved = self.expected_quotation_type.borrow().clone();
489
490 if let Some(Statement::WordCall {
492 name: next_word, ..
493 }) = statements.get(i + 1)
494 {
495 if let Some(next_effect) = self.lookup_word_effect(next_word) {
497 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
500 && matches!(quot_type, Type::Quotation(_))
501 {
502 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
503 }
504 }
505 }
506 Some(saved)
507 } else {
508 None
509 };
510
511 if capture_stmt_types && let Some(word_name) = self.current_word.borrow().as_ref() {
515 self.capture_statement_type(word_name, i, ¤t_stack);
516 }
517
518 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
519 current_stack = new_stack;
520 accumulated_subst = accumulated_subst.compose(&subst);
521
522 for effect in effects {
524 if !accumulated_effects.contains(&effect) {
525 accumulated_effects.push(effect);
526 }
527 }
528
529 if let Some(saved) = saved_expected_type {
531 *self.expected_quotation_type.borrow_mut() = saved;
532 }
533 }
534
535 Ok((current_stack, accumulated_subst, accumulated_effects))
536 }
537
538 fn handle_literal_pick(
549 &self,
550 n: i64,
551 current_stack: StackType,
552 ) -> Result<(StackType, Subst), String> {
553 if n < 0 {
554 return Err(format!("pick: index must be non-negative, got {}", n));
555 }
556
557 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
559
560 Ok((current_stack.push(type_at_n), Subst::empty()))
562 }
563
564 fn handle_literal_roll(
575 &self,
576 n: i64,
577 current_stack: StackType,
578 ) -> Result<(StackType, Subst), String> {
579 if n < 0 {
580 return Err(format!("roll: index must be non-negative, got {}", n));
581 }
582
583 self.rotate_type_to_top(current_stack, n as usize)
588 }
589
590 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
592 let mut current = stack;
593 let mut pos = 0;
594
595 loop {
596 match current {
597 StackType::Cons { rest, top } => {
598 if pos == n {
599 return Ok(top.clone());
600 }
601 pos += 1;
602 current = rest;
603 }
604 StackType::RowVar(name) => {
605 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
615 return Ok(fresh_type);
616 }
617 StackType::Empty => {
618 return Err(format!(
619 "{}: stack underflow - position {} requested but stack has only {} concrete items",
620 op, n, pos
621 ));
622 }
623 }
624 }
625 }
626
627 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
629 if n == 0 {
630 return Ok((stack, Subst::empty()));
632 }
633
634 let mut types_above: Vec<Type> = Vec::new();
636 let mut current = stack;
637 let mut pos = 0;
638
639 loop {
641 match current {
642 StackType::Cons { rest, top } => {
643 if pos == n {
644 let mut result = *rest;
647 for ty in types_above.into_iter().rev() {
649 result = result.push(ty);
650 }
651 result = result.push(top);
653 return Ok((result, Subst::empty()));
654 }
655 types_above.push(top);
656 pos += 1;
657 current = *rest;
658 }
659 StackType::RowVar(name) => {
660 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
673
674 let mut result = StackType::RowVar(name.clone());
676 for ty in types_above.into_iter().rev() {
677 result = result.push(ty);
678 }
679 result = result.push(fresh_type);
680 return Ok((result, Subst::empty()));
681 }
682 StackType::Empty => {
683 return Err(format!(
684 "roll: stack underflow - position {} requested but stack has only {} items",
685 n, pos
686 ));
687 }
688 }
689 }
690 }
691
692 fn infer_statements(&self, statements: &[Statement]) -> Result<Effect, String> {
696 let start = StackType::RowVar("input".to_string());
697 let (result, subst, effects) = self.infer_statements_from(statements, &start, false)?;
699
700 let normalized_start = subst.apply_stack(&start);
703 let normalized_result = subst.apply_stack(&result);
704
705 Ok(Effect::with_effects(
706 normalized_start,
707 normalized_result,
708 effects,
709 ))
710 }
711
712 fn infer_match(
714 &self,
715 arms: &[crate::ast::MatchArm],
716 current_stack: StackType,
717 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
718 if arms.is_empty() {
719 return Err("match expression must have at least one arm".to_string());
720 }
721
722 let (stack_after_match, _matched_type) =
724 self.pop_type(¤t_stack, "match expression")?;
725
726 let mut arm_results: Vec<StackType> = Vec::new();
728 let mut combined_subst = Subst::empty();
729 let mut merged_effects: Vec<SideEffect> = Vec::new();
730
731 for arm in arms {
732 let variant_name = match &arm.pattern {
734 crate::ast::Pattern::Variant(name) => name.as_str(),
735 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
736 };
737
738 let (_union_name, variant_info) = self
740 .find_variant(variant_name)
741 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
742
743 let arm_stack = self.push_variant_fields(
745 &stack_after_match,
746 &arm.pattern,
747 variant_info,
748 variant_name,
749 )?;
750
751 let (arm_result, arm_subst, arm_effects) =
754 self.infer_statements_from(&arm.body, &arm_stack, false)?;
755
756 combined_subst = combined_subst.compose(&arm_subst);
757 arm_results.push(arm_result);
758
759 for effect in arm_effects {
761 if !merged_effects.contains(&effect) {
762 merged_effects.push(effect);
763 }
764 }
765 }
766
767 let mut final_result = arm_results[0].clone();
769 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
770 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
771 format!(
772 "match arms have incompatible stack effects:\n\
773 \x20 arm 0 produces: {}\n\
774 \x20 arm {} produces: {}\n\
775 \x20 All match arms must produce the same stack shape.\n\
776 \x20 Error: {}",
777 final_result, i, arm_result, e
778 )
779 })?;
780 combined_subst = combined_subst.compose(&arm_subst);
781 final_result = arm_subst.apply_stack(&final_result);
782 }
783
784 Ok((final_result, combined_subst, merged_effects))
785 }
786
787 fn push_variant_fields(
789 &self,
790 stack: &StackType,
791 pattern: &crate::ast::Pattern,
792 variant_info: &VariantInfo,
793 variant_name: &str,
794 ) -> Result<StackType, String> {
795 let mut arm_stack = stack.clone();
796 match pattern {
797 crate::ast::Pattern::Variant(_) => {
798 for field in &variant_info.fields {
800 arm_stack = arm_stack.push(field.field_type.clone());
801 }
802 }
803 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
804 for binding in bindings {
806 let field = variant_info
807 .fields
808 .iter()
809 .find(|f| &f.name == binding)
810 .ok_or_else(|| {
811 let available: Vec<_> = variant_info
812 .fields
813 .iter()
814 .map(|f| f.name.as_str())
815 .collect();
816 format!(
817 "Unknown field '{}' in pattern for variant '{}'.\n\
818 Available fields: {}",
819 binding,
820 variant_name,
821 available.join(", ")
822 )
823 })?;
824 arm_stack = arm_stack.push(field.field_type.clone());
825 }
826 }
827 }
828 Ok(arm_stack)
829 }
830
831 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
847 if let Some(current_word) = self.current_word.borrow().as_ref()
848 && let Some(Statement::WordCall { name, .. }) = statements.last()
849 {
850 return name == current_word;
851 }
852 false
853 }
854
855 fn infer_if(
857 &self,
858 then_branch: &[Statement],
859 else_branch: &Option<Vec<Statement>>,
860 current_stack: StackType,
861 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
862 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
864
865 let cond_subst = unify_stacks(
867 &StackType::singleton(Type::Bool),
868 &StackType::singleton(cond_type),
869 )
870 .map_err(|e| format!("if condition must be Bool: {}", e))?;
871
872 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
873
874 let then_diverges = self.is_divergent_branch(then_branch);
876 let else_diverges = else_branch
877 .as_ref()
878 .map(|stmts| self.is_divergent_branch(stmts))
879 .unwrap_or(false);
880
881 let (then_result, then_subst, then_effects) =
884 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
885
886 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
888 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
889 } else {
890 (stack_after_cond.clone(), Subst::empty(), vec![])
891 };
892
893 let mut merged_effects = then_effects;
895 for effect in else_effects {
896 if !merged_effects.contains(&effect) {
897 merged_effects.push(effect);
898 }
899 }
900
901 let (result, branch_subst) = if then_diverges && !else_diverges {
907 (else_result, Subst::empty())
909 } else if else_diverges && !then_diverges {
910 (then_result, Subst::empty())
912 } else {
913 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
915 format!(
916 "if/else branches have incompatible stack effects:\n\
917 \x20 then branch produces: {}\n\
918 \x20 else branch produces: {}\n\
919 \x20 Both branches of an if/else must produce the same stack shape.\n\
920 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
921 \x20 Error: {}",
922 then_result, else_result, e
923 )
924 })?;
925 (branch_subst.apply_stack(&then_result), branch_subst)
926 };
927
928 let total_subst = cond_subst
930 .compose(&then_subst)
931 .compose(&else_subst)
932 .compose(&branch_subst);
933 Ok((result, total_subst, merged_effects))
934 }
935
936 fn infer_quotation(
939 &self,
940 id: usize,
941 body: &[Statement],
942 current_stack: StackType,
943 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
944 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
947 *self.expected_quotation_type.borrow_mut() = None;
948
949 let body_effect = self.infer_statements(body)?;
951
952 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
954
955 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
957
958 self.quotation_types
960 .borrow_mut()
961 .insert(id, quot_type.clone());
962
963 let result_stack = match "_type {
965 Type::Quotation(_) => {
966 current_stack.push(quot_type)
968 }
969 Type::Closure { captures, .. } => {
970 let mut stack = current_stack.clone();
972 for _ in 0..captures.len() {
973 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
974 stack = new_stack;
975 }
976 stack.push(quot_type)
977 }
978 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
979 };
980
981 Ok((result_stack, Subst::empty(), vec![]))
985 }
986
987 fn infer_word_call(
989 &self,
990 name: &str,
991 current_stack: StackType,
992 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
993 if name == "call" {
996 return self.infer_call(current_stack);
997 }
998
999 let effect = self
1001 .lookup_word_effect(name)
1002 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
1003
1004 let fresh_effect = self.freshen_effect(&effect);
1006
1007 let adjusted_stack = if name == "strand.spawn" {
1009 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1010 } else {
1011 current_stack
1012 };
1013
1014 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name)?;
1016
1017 let propagated_effects = fresh_effect.effects.clone();
1021
1022 Ok((result_stack, subst, propagated_effects))
1023 }
1024
1025 fn infer_call(
1031 &self,
1032 current_stack: StackType,
1033 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1034 let (remaining_stack, quot_type) = current_stack
1036 .clone()
1037 .pop()
1038 .ok_or_else(|| "call: stack underflow - expected quotation on stack".to_string())?;
1039
1040 let quot_effect = match "_type {
1042 Type::Quotation(effect) => (**effect).clone(),
1043 Type::Closure { effect, .. } => (**effect).clone(),
1044 Type::Var(_) => {
1045 let effect = self
1048 .lookup_word_effect("call")
1049 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1050 let fresh_effect = self.freshen_effect(&effect);
1051 let (result_stack, subst) =
1052 self.apply_effect(&fresh_effect, current_stack, "call")?;
1053 return Ok((result_stack, subst, vec![]));
1054 }
1055 _ => {
1056 return Err(format!(
1057 "call: expected quotation or closure on stack, got {}",
1058 quot_type
1059 ));
1060 }
1061 };
1062
1063 if quot_effect.has_yield() {
1065 return Err("Cannot call quotation with Yield effect directly.\n\
1066 Quotations that yield values must be wrapped with `strand.weave`.\n\
1067 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1068 .to_string());
1069 }
1070
1071 let fresh_effect = self.freshen_effect("_effect);
1073
1074 let (result_stack, subst) = self.apply_effect(&fresh_effect, remaining_stack, "call")?;
1076
1077 let propagated_effects = fresh_effect.effects.clone();
1079
1080 Ok((result_stack, subst, propagated_effects))
1081 }
1082
1083 fn infer_statement(
1086 &self,
1087 statement: &Statement,
1088 current_stack: StackType,
1089 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1090 match statement {
1091 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1092 Statement::BoolLiteral(_) => {
1093 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1094 }
1095 Statement::StringLiteral(_) => {
1096 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1097 }
1098 Statement::FloatLiteral(_) => {
1099 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1100 }
1101 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1102 Statement::Match { arms } => self.infer_match(arms, current_stack),
1103 Statement::WordCall { name, .. } => self.infer_word_call(name, current_stack),
1104 Statement::If {
1105 then_branch,
1106 else_branch,
1107 } => self.infer_if(then_branch, else_branch, current_stack),
1108 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1109 }
1110 }
1111
1112 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1114 if let Some(effect) = builtin_signature(name) {
1116 return Some(effect);
1117 }
1118
1119 self.env.get(name).cloned()
1121 }
1122
1123 fn apply_effect(
1128 &self,
1129 effect: &Effect,
1130 current_stack: StackType,
1131 operation: &str,
1132 ) -> Result<(StackType, Subst), String> {
1133 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1141 let stack_concrete = Self::count_concrete_types(¤t_stack);
1142
1143 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1144 let is_rigid = row_var_name == "rest";
1157
1158 if is_rigid && effect_concrete > stack_concrete {
1159 let word_name = self.current_word.borrow().clone().unwrap_or_default();
1160 return Err(format!(
1161 "In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1162 word_name, operation, effect_concrete, stack_concrete
1163 ));
1164 }
1165 }
1166
1167 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1169 format!(
1170 "{}: stack type mismatch. Expected {}, got {}: {}",
1171 operation, effect.inputs, current_stack, e
1172 )
1173 })?;
1174
1175 let result_stack = subst.apply_stack(&effect.outputs);
1177
1178 Ok((result_stack, subst))
1179 }
1180
1181 fn count_concrete_types(stack: &StackType) -> usize {
1183 let mut count = 0;
1184 let mut current = stack;
1185 while let StackType::Cons { rest, top: _ } = current {
1186 count += 1;
1187 current = rest;
1188 }
1189 count
1190 }
1191
1192 fn get_row_var_base(stack: &StackType) -> Option<String> {
1194 let mut current = stack;
1195 while let StackType::Cons { rest, top: _ } = current {
1196 current = rest;
1197 }
1198 match current {
1199 StackType::RowVar(name) => Some(name.clone()),
1200 _ => None,
1201 }
1202 }
1203
1204 fn adjust_stack_for_spawn(
1209 &self,
1210 current_stack: StackType,
1211 spawn_effect: &Effect,
1212 ) -> Result<StackType, String> {
1213 let expected_quot_type = match &spawn_effect.inputs {
1216 StackType::Cons { top, rest: _ } => {
1217 if !matches!(top, Type::Quotation(_)) {
1218 return Ok(current_stack); }
1220 top
1221 }
1222 _ => return Ok(current_stack),
1223 };
1224
1225 let (rest_stack, actual_type) = match ¤t_stack {
1227 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1228 _ => return Ok(current_stack), };
1230
1231 if let Type::Quotation(actual_effect) = actual_type {
1233 if !matches!(actual_effect.inputs, StackType::Empty) {
1235 let expected_effect = match expected_quot_type {
1237 Type::Quotation(eff) => eff.as_ref(),
1238 _ => return Ok(current_stack),
1239 };
1240
1241 let captures = calculate_captures(actual_effect, expected_effect)?;
1243
1244 let closure_type = Type::Closure {
1246 effect: Box::new(expected_effect.clone()),
1247 captures: captures.clone(),
1248 };
1249
1250 let mut adjusted_stack = rest_stack;
1253 for _ in &captures {
1254 adjusted_stack = match adjusted_stack {
1255 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1256 _ => {
1257 return Err(format!(
1258 "strand.spawn: not enough values on stack to capture. Need {} values",
1259 captures.len()
1260 ));
1261 }
1262 };
1263 }
1264
1265 return Ok(adjusted_stack.push(closure_type));
1267 }
1268 }
1269
1270 Ok(current_stack)
1271 }
1272
1273 fn analyze_captures(
1299 &self,
1300 body_effect: &Effect,
1301 _current_stack: &StackType,
1302 ) -> Result<Type, String> {
1303 let expected = self.expected_quotation_type.borrow().clone();
1305
1306 match expected {
1307 Some(Type::Closure { effect, .. }) => {
1308 let captures = calculate_captures(body_effect, &effect)?;
1310 Ok(Type::Closure { effect, captures })
1311 }
1312 Some(Type::Quotation(expected_effect)) => {
1313 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1319 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1320
1321 if expected_is_empty && body_needs_inputs {
1322 let captures = calculate_captures(body_effect, &expected_effect)?;
1325 Ok(Type::Closure {
1326 effect: expected_effect,
1327 captures,
1328 })
1329 } else {
1330 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1336 let expected_quot = Type::Quotation(expected_effect.clone());
1337 unify_types(&body_quot, &expected_quot).map_err(|e| {
1338 format!(
1339 "quotation effect mismatch: expected {}, got {}: {}",
1340 expected_effect, body_effect, e
1341 )
1342 })?;
1343
1344 Ok(Type::Quotation(expected_effect))
1346 }
1347 }
1348 _ => {
1349 Ok(Type::Quotation(Box::new(body_effect.clone())))
1351 }
1352 }
1353 }
1354
1355 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1359 declared.iter().any(|decl| match (inferred, decl) {
1360 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1361 })
1362 }
1363
1364 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1366 match stack {
1367 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1368 StackType::Empty => Err(format!(
1369 "{}: stack underflow - expected value on stack but stack is empty",
1370 context
1371 )),
1372 StackType::RowVar(_) => {
1373 Err(format!(
1377 "{}: cannot pop from polymorphic stack without more type information",
1378 context
1379 ))
1380 }
1381 }
1382 }
1383}
1384
1385impl Default for TypeChecker {
1386 fn default() -> Self {
1387 Self::new()
1388 }
1389}
1390
1391#[cfg(test)]
1392mod tests {
1393 use super::*;
1394
1395 #[test]
1396 fn test_simple_literal() {
1397 let program = Program {
1398 includes: vec![],
1399 unions: vec![],
1400 words: vec![WordDef {
1401 name: "test".to_string(),
1402 effect: Some(Effect::new(
1403 StackType::Empty,
1404 StackType::singleton(Type::Int),
1405 )),
1406 body: vec![Statement::IntLiteral(42)],
1407 source: None,
1408 }],
1409 };
1410
1411 let mut checker = TypeChecker::new();
1412 assert!(checker.check_program(&program).is_ok());
1413 }
1414
1415 #[test]
1416 fn test_simple_operation() {
1417 let program = Program {
1419 includes: vec![],
1420 unions: vec![],
1421 words: vec![WordDef {
1422 name: "test".to_string(),
1423 effect: Some(Effect::new(
1424 StackType::Empty.push(Type::Int).push(Type::Int),
1425 StackType::singleton(Type::Int),
1426 )),
1427 body: vec![Statement::WordCall {
1428 name: "i.add".to_string(),
1429 span: None,
1430 }],
1431 source: None,
1432 }],
1433 };
1434
1435 let mut checker = TypeChecker::new();
1436 assert!(checker.check_program(&program).is_ok());
1437 }
1438
1439 #[test]
1440 fn test_type_mismatch() {
1441 let program = Program {
1443 includes: vec![],
1444 unions: vec![],
1445 words: vec![WordDef {
1446 name: "test".to_string(),
1447 effect: Some(Effect::new(
1448 StackType::singleton(Type::String),
1449 StackType::Empty,
1450 )),
1451 body: vec![
1452 Statement::IntLiteral(42), Statement::WordCall {
1454 name: "io.write-line".to_string(),
1455 span: None,
1456 },
1457 ],
1458 source: None,
1459 }],
1460 };
1461
1462 let mut checker = TypeChecker::new();
1463 let result = checker.check_program(&program);
1464 assert!(result.is_err());
1465 assert!(result.unwrap_err().contains("Type mismatch"));
1466 }
1467
1468 #[test]
1469 fn test_polymorphic_dup() {
1470 let program = Program {
1472 includes: vec![],
1473 unions: vec![],
1474 words: vec![WordDef {
1475 name: "my-dup".to_string(),
1476 effect: Some(Effect::new(
1477 StackType::singleton(Type::Int),
1478 StackType::Empty.push(Type::Int).push(Type::Int),
1479 )),
1480 body: vec![Statement::WordCall {
1481 name: "dup".to_string(),
1482 span: None,
1483 }],
1484 source: None,
1485 }],
1486 };
1487
1488 let mut checker = TypeChecker::new();
1489 assert!(checker.check_program(&program).is_ok());
1490 }
1491
1492 #[test]
1493 fn test_conditional_branches() {
1494 let program = Program {
1497 includes: vec![],
1498 unions: vec![],
1499 words: vec![WordDef {
1500 name: "test".to_string(),
1501 effect: Some(Effect::new(
1502 StackType::Empty.push(Type::Int).push(Type::Int),
1503 StackType::singleton(Type::String),
1504 )),
1505 body: vec![
1506 Statement::WordCall {
1507 name: "i.>".to_string(),
1508 span: None,
1509 },
1510 Statement::If {
1511 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1512 else_branch: Some(vec![Statement::StringLiteral(
1513 "not greater".to_string(),
1514 )]),
1515 },
1516 ],
1517 source: None,
1518 }],
1519 };
1520
1521 let mut checker = TypeChecker::new();
1522 assert!(checker.check_program(&program).is_ok());
1523 }
1524
1525 #[test]
1526 fn test_mismatched_branches() {
1527 let program = Program {
1530 includes: vec![],
1531 unions: vec![],
1532 words: vec![WordDef {
1533 name: "test".to_string(),
1534 effect: None,
1535 body: vec![
1536 Statement::BoolLiteral(true),
1537 Statement::If {
1538 then_branch: vec![Statement::IntLiteral(42)],
1539 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1540 },
1541 ],
1542 source: None,
1543 }],
1544 };
1545
1546 let mut checker = TypeChecker::new();
1547 let result = checker.check_program(&program);
1548 assert!(result.is_err());
1549 assert!(result.unwrap_err().contains("incompatible"));
1550 }
1551
1552 #[test]
1553 fn test_user_defined_word_call() {
1554 let program = Program {
1557 includes: vec![],
1558 unions: vec![],
1559 words: vec![
1560 WordDef {
1561 name: "helper".to_string(),
1562 effect: Some(Effect::new(
1563 StackType::singleton(Type::Int),
1564 StackType::singleton(Type::String),
1565 )),
1566 body: vec![Statement::WordCall {
1567 name: "int->string".to_string(),
1568 span: None,
1569 }],
1570 source: None,
1571 },
1572 WordDef {
1573 name: "main".to_string(),
1574 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1575 body: vec![
1576 Statement::IntLiteral(42),
1577 Statement::WordCall {
1578 name: "helper".to_string(),
1579 span: None,
1580 },
1581 Statement::WordCall {
1582 name: "io.write-line".to_string(),
1583 span: None,
1584 },
1585 ],
1586 source: None,
1587 },
1588 ],
1589 };
1590
1591 let mut checker = TypeChecker::new();
1592 assert!(checker.check_program(&program).is_ok());
1593 }
1594
1595 #[test]
1596 fn test_arithmetic_chain() {
1597 let program = Program {
1600 includes: vec![],
1601 unions: vec![],
1602 words: vec![WordDef {
1603 name: "test".to_string(),
1604 effect: Some(Effect::new(
1605 StackType::Empty
1606 .push(Type::Int)
1607 .push(Type::Int)
1608 .push(Type::Int),
1609 StackType::singleton(Type::Int),
1610 )),
1611 body: vec![
1612 Statement::WordCall {
1613 name: "i.add".to_string(),
1614 span: None,
1615 },
1616 Statement::WordCall {
1617 name: "i.multiply".to_string(),
1618 span: None,
1619 },
1620 ],
1621 source: None,
1622 }],
1623 };
1624
1625 let mut checker = TypeChecker::new();
1626 assert!(checker.check_program(&program).is_ok());
1627 }
1628
1629 #[test]
1630 fn test_write_line_type_error() {
1631 let program = Program {
1633 includes: vec![],
1634 unions: vec![],
1635 words: vec![WordDef {
1636 name: "test".to_string(),
1637 effect: Some(Effect::new(
1638 StackType::singleton(Type::Int),
1639 StackType::Empty,
1640 )),
1641 body: vec![Statement::WordCall {
1642 name: "io.write-line".to_string(),
1643 span: None,
1644 }],
1645 source: None,
1646 }],
1647 };
1648
1649 let mut checker = TypeChecker::new();
1650 let result = checker.check_program(&program);
1651 assert!(result.is_err());
1652 assert!(result.unwrap_err().contains("Type mismatch"));
1653 }
1654
1655 #[test]
1656 fn test_stack_underflow_drop() {
1657 let program = Program {
1659 includes: vec![],
1660 unions: vec![],
1661 words: vec![WordDef {
1662 name: "test".to_string(),
1663 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1664 body: vec![Statement::WordCall {
1665 name: "drop".to_string(),
1666 span: None,
1667 }],
1668 source: None,
1669 }],
1670 };
1671
1672 let mut checker = TypeChecker::new();
1673 let result = checker.check_program(&program);
1674 assert!(result.is_err());
1675 assert!(result.unwrap_err().contains("mismatch"));
1676 }
1677
1678 #[test]
1679 fn test_stack_underflow_add() {
1680 let program = Program {
1682 includes: vec![],
1683 unions: vec![],
1684 words: vec![WordDef {
1685 name: "test".to_string(),
1686 effect: Some(Effect::new(
1687 StackType::singleton(Type::Int),
1688 StackType::singleton(Type::Int),
1689 )),
1690 body: vec![Statement::WordCall {
1691 name: "i.add".to_string(),
1692 span: None,
1693 }],
1694 source: None,
1695 }],
1696 };
1697
1698 let mut checker = TypeChecker::new();
1699 let result = checker.check_program(&program);
1700 assert!(result.is_err());
1701 assert!(result.unwrap_err().contains("mismatch"));
1702 }
1703
1704 #[test]
1707 fn test_stack_underflow_rot_issue_169() {
1708 let program = Program {
1712 includes: vec![],
1713 unions: vec![],
1714 words: vec![WordDef {
1715 name: "test".to_string(),
1716 effect: Some(Effect::new(
1717 StackType::RowVar("rest".to_string()),
1718 StackType::RowVar("rest".to_string()),
1719 )),
1720 body: vec![
1721 Statement::IntLiteral(3),
1722 Statement::IntLiteral(4),
1723 Statement::WordCall {
1724 name: "rot".to_string(),
1725 span: None,
1726 },
1727 ],
1728 source: None,
1729 }],
1730 };
1731
1732 let mut checker = TypeChecker::new();
1733 let result = checker.check_program(&program);
1734 assert!(result.is_err(), "rot with 2 values should fail");
1735 let err = result.unwrap_err();
1736 assert!(
1737 err.contains("stack underflow") || err.contains("requires 3"),
1738 "Error should mention underflow: {}",
1739 err
1740 );
1741 }
1742
1743 #[test]
1744 fn test_csp_operations() {
1745 let program = Program {
1752 includes: vec![],
1753 unions: vec![],
1754 words: vec![WordDef {
1755 name: "test".to_string(),
1756 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1757 body: vec![
1758 Statement::WordCall {
1759 name: "chan.make".to_string(),
1760 span: None,
1761 },
1762 Statement::IntLiteral(42),
1763 Statement::WordCall {
1764 name: "swap".to_string(),
1765 span: None,
1766 },
1767 Statement::WordCall {
1768 name: "chan.send".to_string(),
1769 span: None,
1770 },
1771 Statement::WordCall {
1772 name: "drop".to_string(),
1773 span: None,
1774 },
1775 ],
1776 source: None,
1777 }],
1778 };
1779
1780 let mut checker = TypeChecker::new();
1781 assert!(checker.check_program(&program).is_ok());
1782 }
1783
1784 #[test]
1785 fn test_complex_stack_shuffling() {
1786 let program = Program {
1789 includes: vec![],
1790 unions: vec![],
1791 words: vec![WordDef {
1792 name: "test".to_string(),
1793 effect: Some(Effect::new(
1794 StackType::Empty
1795 .push(Type::Int)
1796 .push(Type::Int)
1797 .push(Type::Int),
1798 StackType::singleton(Type::Int),
1799 )),
1800 body: vec![
1801 Statement::WordCall {
1802 name: "rot".to_string(),
1803 span: None,
1804 },
1805 Statement::WordCall {
1806 name: "i.add".to_string(),
1807 span: None,
1808 },
1809 Statement::WordCall {
1810 name: "i.add".to_string(),
1811 span: None,
1812 },
1813 ],
1814 source: None,
1815 }],
1816 };
1817
1818 let mut checker = TypeChecker::new();
1819 assert!(checker.check_program(&program).is_ok());
1820 }
1821
1822 #[test]
1823 fn test_empty_program() {
1824 let program = Program {
1826 includes: vec![],
1827 unions: vec![],
1828 words: vec![],
1829 };
1830
1831 let mut checker = TypeChecker::new();
1832 assert!(checker.check_program(&program).is_ok());
1833 }
1834
1835 #[test]
1836 fn test_word_without_effect_declaration() {
1837 let program = Program {
1839 includes: vec![],
1840 unions: vec![],
1841 words: vec![WordDef {
1842 name: "helper".to_string(),
1843 effect: None,
1844 body: vec![Statement::IntLiteral(42)],
1845 source: None,
1846 }],
1847 };
1848
1849 let mut checker = TypeChecker::new();
1850 assert!(checker.check_program(&program).is_ok());
1851 }
1852
1853 #[test]
1854 fn test_nested_conditionals() {
1855 let program = Program {
1864 includes: vec![],
1865 unions: vec![],
1866 words: vec![WordDef {
1867 name: "test".to_string(),
1868 effect: Some(Effect::new(
1869 StackType::Empty
1870 .push(Type::Int)
1871 .push(Type::Int)
1872 .push(Type::Int)
1873 .push(Type::Int),
1874 StackType::singleton(Type::String),
1875 )),
1876 body: vec![
1877 Statement::WordCall {
1878 name: "i.>".to_string(),
1879 span: None,
1880 },
1881 Statement::If {
1882 then_branch: vec![
1883 Statement::WordCall {
1884 name: "i.>".to_string(),
1885 span: None,
1886 },
1887 Statement::If {
1888 then_branch: vec![Statement::StringLiteral(
1889 "both true".to_string(),
1890 )],
1891 else_branch: Some(vec![Statement::StringLiteral(
1892 "first true".to_string(),
1893 )]),
1894 },
1895 ],
1896 else_branch: Some(vec![
1897 Statement::WordCall {
1898 name: "drop".to_string(),
1899 span: None,
1900 },
1901 Statement::WordCall {
1902 name: "drop".to_string(),
1903 span: None,
1904 },
1905 Statement::StringLiteral("first false".to_string()),
1906 ]),
1907 },
1908 ],
1909 source: None,
1910 }],
1911 };
1912
1913 let mut checker = TypeChecker::new();
1914 match checker.check_program(&program) {
1915 Ok(_) => {}
1916 Err(e) => panic!("Type check failed: {}", e),
1917 }
1918 }
1919
1920 #[test]
1921 fn test_conditional_without_else() {
1922 let program = Program {
1926 includes: vec![],
1927 unions: vec![],
1928 words: vec![WordDef {
1929 name: "test".to_string(),
1930 effect: Some(Effect::new(
1931 StackType::Empty.push(Type::Int).push(Type::Int),
1932 StackType::singleton(Type::Int),
1933 )),
1934 body: vec![
1935 Statement::WordCall {
1936 name: "i.>".to_string(),
1937 span: None,
1938 },
1939 Statement::If {
1940 then_branch: vec![Statement::IntLiteral(100)],
1941 else_branch: None, },
1943 ],
1944 source: None,
1945 }],
1946 };
1947
1948 let mut checker = TypeChecker::new();
1949 let result = checker.check_program(&program);
1950 assert!(result.is_err());
1952 }
1953
1954 #[test]
1955 fn test_multiple_word_chain() {
1956 let program = Program {
1960 includes: vec![],
1961 unions: vec![],
1962 words: vec![
1963 WordDef {
1964 name: "helper1".to_string(),
1965 effect: Some(Effect::new(
1966 StackType::singleton(Type::Int),
1967 StackType::singleton(Type::String),
1968 )),
1969 body: vec![Statement::WordCall {
1970 name: "int->string".to_string(),
1971 span: None,
1972 }],
1973 source: None,
1974 },
1975 WordDef {
1976 name: "helper2".to_string(),
1977 effect: Some(Effect::new(
1978 StackType::singleton(Type::String),
1979 StackType::Empty,
1980 )),
1981 body: vec![Statement::WordCall {
1982 name: "io.write-line".to_string(),
1983 span: None,
1984 }],
1985 source: None,
1986 },
1987 WordDef {
1988 name: "main".to_string(),
1989 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1990 body: vec![
1991 Statement::IntLiteral(42),
1992 Statement::WordCall {
1993 name: "helper1".to_string(),
1994 span: None,
1995 },
1996 Statement::WordCall {
1997 name: "helper2".to_string(),
1998 span: None,
1999 },
2000 ],
2001 source: None,
2002 },
2003 ],
2004 };
2005
2006 let mut checker = TypeChecker::new();
2007 assert!(checker.check_program(&program).is_ok());
2008 }
2009
2010 #[test]
2011 fn test_all_stack_ops() {
2012 let program = Program {
2015 includes: vec![],
2016 unions: vec![],
2017 words: vec![WordDef {
2018 name: "test".to_string(),
2019 effect: Some(Effect::new(
2020 StackType::Empty
2021 .push(Type::Int)
2022 .push(Type::Int)
2023 .push(Type::Int),
2024 StackType::Empty
2025 .push(Type::Int)
2026 .push(Type::Int)
2027 .push(Type::Int)
2028 .push(Type::Int),
2029 )),
2030 body: vec![
2031 Statement::WordCall {
2032 name: "over".to_string(),
2033 span: None,
2034 },
2035 Statement::WordCall {
2036 name: "nip".to_string(),
2037 span: None,
2038 },
2039 Statement::WordCall {
2040 name: "tuck".to_string(),
2041 span: None,
2042 },
2043 ],
2044 source: None,
2045 }],
2046 };
2047
2048 let mut checker = TypeChecker::new();
2049 assert!(checker.check_program(&program).is_ok());
2050 }
2051
2052 #[test]
2053 fn test_mixed_types_complex() {
2054 let program = Program {
2063 includes: vec![],
2064 unions: vec![],
2065 words: vec![WordDef {
2066 name: "test".to_string(),
2067 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2068 body: vec![
2069 Statement::IntLiteral(42),
2070 Statement::WordCall {
2071 name: "int->string".to_string(),
2072 span: None,
2073 },
2074 Statement::IntLiteral(100),
2075 Statement::IntLiteral(200),
2076 Statement::WordCall {
2077 name: "i.>".to_string(),
2078 span: None,
2079 },
2080 Statement::If {
2081 then_branch: vec![Statement::WordCall {
2082 name: "io.write-line".to_string(),
2083 span: None,
2084 }],
2085 else_branch: Some(vec![Statement::WordCall {
2086 name: "io.write-line".to_string(),
2087 span: None,
2088 }]),
2089 },
2090 ],
2091 source: None,
2092 }],
2093 };
2094
2095 let mut checker = TypeChecker::new();
2096 assert!(checker.check_program(&program).is_ok());
2097 }
2098
2099 #[test]
2100 fn test_string_literal() {
2101 let program = Program {
2103 includes: vec![],
2104 unions: vec![],
2105 words: vec![WordDef {
2106 name: "test".to_string(),
2107 effect: Some(Effect::new(
2108 StackType::Empty,
2109 StackType::singleton(Type::String),
2110 )),
2111 body: vec![Statement::StringLiteral("hello".to_string())],
2112 source: None,
2113 }],
2114 };
2115
2116 let mut checker = TypeChecker::new();
2117 assert!(checker.check_program(&program).is_ok());
2118 }
2119
2120 #[test]
2121 fn test_bool_literal() {
2122 let program = Program {
2125 includes: vec![],
2126 unions: vec![],
2127 words: vec![WordDef {
2128 name: "test".to_string(),
2129 effect: Some(Effect::new(
2130 StackType::Empty,
2131 StackType::singleton(Type::Bool),
2132 )),
2133 body: vec![Statement::BoolLiteral(true)],
2134 source: None,
2135 }],
2136 };
2137
2138 let mut checker = TypeChecker::new();
2139 assert!(checker.check_program(&program).is_ok());
2140 }
2141
2142 #[test]
2143 fn test_type_error_in_nested_conditional() {
2144 let program = Program {
2151 includes: vec![],
2152 unions: vec![],
2153 words: vec![WordDef {
2154 name: "test".to_string(),
2155 effect: None,
2156 body: vec![
2157 Statement::IntLiteral(10),
2158 Statement::IntLiteral(20),
2159 Statement::WordCall {
2160 name: "i.>".to_string(),
2161 span: None,
2162 },
2163 Statement::If {
2164 then_branch: vec![
2165 Statement::IntLiteral(42),
2166 Statement::WordCall {
2167 name: "io.write-line".to_string(),
2168 span: None,
2169 },
2170 ],
2171 else_branch: Some(vec![
2172 Statement::StringLiteral("ok".to_string()),
2173 Statement::WordCall {
2174 name: "io.write-line".to_string(),
2175 span: None,
2176 },
2177 ]),
2178 },
2179 ],
2180 source: None,
2181 }],
2182 };
2183
2184 let mut checker = TypeChecker::new();
2185 let result = checker.check_program(&program);
2186 assert!(result.is_err());
2187 assert!(result.unwrap_err().contains("Type mismatch"));
2188 }
2189
2190 #[test]
2191 fn test_read_line_operation() {
2192 let program = Program {
2195 includes: vec![],
2196 unions: vec![],
2197 words: vec![WordDef {
2198 name: "test".to_string(),
2199 effect: Some(Effect::new(
2200 StackType::Empty,
2201 StackType::from_vec(vec![Type::String, Type::Bool]),
2202 )),
2203 body: vec![Statement::WordCall {
2204 name: "io.read-line".to_string(),
2205 span: None,
2206 }],
2207 source: None,
2208 }],
2209 };
2210
2211 let mut checker = TypeChecker::new();
2212 assert!(checker.check_program(&program).is_ok());
2213 }
2214
2215 #[test]
2216 fn test_comparison_operations() {
2217 let program = Program {
2222 includes: vec![],
2223 unions: vec![],
2224 words: vec![WordDef {
2225 name: "test".to_string(),
2226 effect: Some(Effect::new(
2227 StackType::Empty.push(Type::Int).push(Type::Int),
2228 StackType::singleton(Type::Bool),
2229 )),
2230 body: vec![Statement::WordCall {
2231 name: "i.<=".to_string(),
2232 span: None,
2233 }],
2234 source: None,
2235 }],
2236 };
2237
2238 let mut checker = TypeChecker::new();
2239 assert!(checker.check_program(&program).is_ok());
2240 }
2241
2242 #[test]
2243 fn test_recursive_word_definitions() {
2244 let program = Program {
2250 includes: vec![],
2251 unions: vec![],
2252 words: vec![
2253 WordDef {
2254 name: "is-even".to_string(),
2255 effect: Some(Effect::new(
2256 StackType::singleton(Type::Int),
2257 StackType::singleton(Type::Int),
2258 )),
2259 body: vec![
2260 Statement::WordCall {
2261 name: "dup".to_string(),
2262 span: None,
2263 },
2264 Statement::IntLiteral(0),
2265 Statement::WordCall {
2266 name: "i.=".to_string(),
2267 span: None,
2268 },
2269 Statement::If {
2270 then_branch: vec![
2271 Statement::WordCall {
2272 name: "drop".to_string(),
2273 span: None,
2274 },
2275 Statement::IntLiteral(1),
2276 ],
2277 else_branch: Some(vec![
2278 Statement::IntLiteral(1),
2279 Statement::WordCall {
2280 name: "i.subtract".to_string(),
2281 span: None,
2282 },
2283 Statement::WordCall {
2284 name: "is-odd".to_string(),
2285 span: None,
2286 },
2287 ]),
2288 },
2289 ],
2290 source: None,
2291 },
2292 WordDef {
2293 name: "is-odd".to_string(),
2294 effect: Some(Effect::new(
2295 StackType::singleton(Type::Int),
2296 StackType::singleton(Type::Int),
2297 )),
2298 body: vec![
2299 Statement::WordCall {
2300 name: "dup".to_string(),
2301 span: None,
2302 },
2303 Statement::IntLiteral(0),
2304 Statement::WordCall {
2305 name: "i.=".to_string(),
2306 span: None,
2307 },
2308 Statement::If {
2309 then_branch: vec![
2310 Statement::WordCall {
2311 name: "drop".to_string(),
2312 span: None,
2313 },
2314 Statement::IntLiteral(0),
2315 ],
2316 else_branch: Some(vec![
2317 Statement::IntLiteral(1),
2318 Statement::WordCall {
2319 name: "i.subtract".to_string(),
2320 span: None,
2321 },
2322 Statement::WordCall {
2323 name: "is-even".to_string(),
2324 span: None,
2325 },
2326 ]),
2327 },
2328 ],
2329 source: None,
2330 },
2331 ],
2332 };
2333
2334 let mut checker = TypeChecker::new();
2335 assert!(checker.check_program(&program).is_ok());
2336 }
2337
2338 #[test]
2339 fn test_word_calling_word_with_row_polymorphism() {
2340 let program = Program {
2345 includes: vec![],
2346 unions: vec![],
2347 words: vec![
2348 WordDef {
2349 name: "apply-twice".to_string(),
2350 effect: Some(Effect::new(
2351 StackType::singleton(Type::Int),
2352 StackType::singleton(Type::Int),
2353 )),
2354 body: vec![
2355 Statement::WordCall {
2356 name: "dup".to_string(),
2357 span: None,
2358 },
2359 Statement::WordCall {
2360 name: "i.add".to_string(),
2361 span: None,
2362 },
2363 ],
2364 source: None,
2365 },
2366 WordDef {
2367 name: "quad".to_string(),
2368 effect: Some(Effect::new(
2369 StackType::singleton(Type::Int),
2370 StackType::singleton(Type::Int),
2371 )),
2372 body: vec![
2373 Statement::WordCall {
2374 name: "apply-twice".to_string(),
2375 span: None,
2376 },
2377 Statement::WordCall {
2378 name: "apply-twice".to_string(),
2379 span: None,
2380 },
2381 ],
2382 source: None,
2383 },
2384 ],
2385 };
2386
2387 let mut checker = TypeChecker::new();
2388 assert!(checker.check_program(&program).is_ok());
2389 }
2390
2391 #[test]
2392 fn test_deep_stack_types() {
2393 let mut stack_type = StackType::Empty;
2397 for _ in 0..10 {
2398 stack_type = stack_type.push(Type::Int);
2399 }
2400
2401 let program = Program {
2402 includes: vec![],
2403 unions: vec![],
2404 words: vec![WordDef {
2405 name: "test".to_string(),
2406 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2407 body: vec![
2408 Statement::WordCall {
2409 name: "i.add".to_string(),
2410 span: None,
2411 },
2412 Statement::WordCall {
2413 name: "i.add".to_string(),
2414 span: None,
2415 },
2416 Statement::WordCall {
2417 name: "i.add".to_string(),
2418 span: None,
2419 },
2420 Statement::WordCall {
2421 name: "i.add".to_string(),
2422 span: None,
2423 },
2424 Statement::WordCall {
2425 name: "i.add".to_string(),
2426 span: None,
2427 },
2428 Statement::WordCall {
2429 name: "i.add".to_string(),
2430 span: None,
2431 },
2432 Statement::WordCall {
2433 name: "i.add".to_string(),
2434 span: None,
2435 },
2436 Statement::WordCall {
2437 name: "i.add".to_string(),
2438 span: None,
2439 },
2440 Statement::WordCall {
2441 name: "i.add".to_string(),
2442 span: None,
2443 },
2444 ],
2445 source: None,
2446 }],
2447 };
2448
2449 let mut checker = TypeChecker::new();
2450 assert!(checker.check_program(&program).is_ok());
2451 }
2452
2453 #[test]
2454 fn test_simple_quotation() {
2455 let program = Program {
2459 includes: vec![],
2460 unions: vec![],
2461 words: vec![WordDef {
2462 name: "test".to_string(),
2463 effect: Some(Effect::new(
2464 StackType::Empty,
2465 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2466 StackType::RowVar("input".to_string()).push(Type::Int),
2467 StackType::RowVar("input".to_string()).push(Type::Int),
2468 )))),
2469 )),
2470 body: vec![Statement::Quotation {
2471 span: None,
2472 id: 0,
2473 body: vec![
2474 Statement::IntLiteral(1),
2475 Statement::WordCall {
2476 name: "i.add".to_string(),
2477 span: None,
2478 },
2479 ],
2480 }],
2481 source: None,
2482 }],
2483 };
2484
2485 let mut checker = TypeChecker::new();
2486 match checker.check_program(&program) {
2487 Ok(_) => {}
2488 Err(e) => panic!("Type check failed: {}", e),
2489 }
2490 }
2491
2492 #[test]
2493 fn test_empty_quotation() {
2494 let program = Program {
2498 includes: vec![],
2499 unions: vec![],
2500 words: vec![WordDef {
2501 name: "test".to_string(),
2502 effect: Some(Effect::new(
2503 StackType::Empty,
2504 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2505 StackType::RowVar("input".to_string()),
2506 StackType::RowVar("input".to_string()),
2507 )))),
2508 )),
2509 body: vec![Statement::Quotation {
2510 span: None,
2511 id: 1,
2512 body: vec![],
2513 }],
2514 source: None,
2515 }],
2516 };
2517
2518 let mut checker = TypeChecker::new();
2519 assert!(checker.check_program(&program).is_ok());
2520 }
2521
2522 #[test]
2549 fn test_nested_quotation() {
2550 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2554 StackType::RowVar("input".to_string()).push(Type::Int),
2555 StackType::RowVar("input".to_string()).push(Type::Int),
2556 )));
2557
2558 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2559 StackType::RowVar("input".to_string()),
2560 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2561 )));
2562
2563 let program = Program {
2564 includes: vec![],
2565 unions: vec![],
2566 words: vec![WordDef {
2567 name: "test".to_string(),
2568 effect: Some(Effect::new(
2569 StackType::Empty,
2570 StackType::singleton(outer_quot_type),
2571 )),
2572 body: vec![Statement::Quotation {
2573 span: None,
2574 id: 2,
2575 body: vec![Statement::Quotation {
2576 span: None,
2577 id: 3,
2578 body: vec![
2579 Statement::IntLiteral(1),
2580 Statement::WordCall {
2581 name: "i.add".to_string(),
2582 span: None,
2583 },
2584 ],
2585 }],
2586 }],
2587 source: None,
2588 }],
2589 };
2590
2591 let mut checker = TypeChecker::new();
2592 assert!(checker.check_program(&program).is_ok());
2593 }
2594
2595 #[test]
2596 fn test_invalid_field_type_error() {
2597 use crate::ast::{UnionDef, UnionField, UnionVariant};
2598
2599 let program = Program {
2600 includes: vec![],
2601 unions: vec![UnionDef {
2602 name: "Message".to_string(),
2603 variants: vec![UnionVariant {
2604 name: "Get".to_string(),
2605 fields: vec![UnionField {
2606 name: "chan".to_string(),
2607 type_name: "InvalidType".to_string(),
2608 }],
2609 source: None,
2610 }],
2611 source: None,
2612 }],
2613 words: vec![],
2614 };
2615
2616 let mut checker = TypeChecker::new();
2617 let result = checker.check_program(&program);
2618 assert!(result.is_err());
2619 let err = result.unwrap_err();
2620 assert!(err.contains("Unknown type 'InvalidType'"));
2621 assert!(err.contains("chan"));
2622 assert!(err.contains("Get"));
2623 assert!(err.contains("Message"));
2624 }
2625
2626 #[test]
2627 fn test_roll_inside_conditional_with_concrete_stack() {
2628 let program = Program {
2636 includes: vec![],
2637 unions: vec![],
2638 words: vec![WordDef {
2639 name: "test".to_string(),
2640 effect: Some(Effect::new(
2641 StackType::Empty
2642 .push(Type::Int)
2643 .push(Type::Int)
2644 .push(Type::Int)
2645 .push(Type::Int),
2646 StackType::Empty
2647 .push(Type::Int)
2648 .push(Type::Int)
2649 .push(Type::Int)
2650 .push(Type::Int),
2651 )),
2652 body: vec![
2653 Statement::WordCall {
2654 name: "dup".to_string(),
2655 span: None,
2656 },
2657 Statement::IntLiteral(0),
2658 Statement::WordCall {
2659 name: "i.>".to_string(),
2660 span: None,
2661 },
2662 Statement::If {
2663 then_branch: vec![
2664 Statement::IntLiteral(3),
2665 Statement::WordCall {
2666 name: "roll".to_string(),
2667 span: None,
2668 },
2669 ],
2670 else_branch: Some(vec![
2671 Statement::WordCall {
2672 name: "rot".to_string(),
2673 span: None,
2674 },
2675 Statement::WordCall {
2676 name: "rot".to_string(),
2677 span: None,
2678 },
2679 ]),
2680 },
2681 ],
2682 source: None,
2683 }],
2684 };
2685
2686 let mut checker = TypeChecker::new();
2687 match checker.check_program(&program) {
2689 Ok(_) => {}
2690 Err(e) => panic!("Type check failed: {}", e),
2691 }
2692 }
2693
2694 #[test]
2695 fn test_roll_inside_match_arm_with_concrete_stack() {
2696 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2699
2700 let union_def = UnionDef {
2702 name: "Result".to_string(),
2703 variants: vec![
2704 UnionVariant {
2705 name: "Ok".to_string(),
2706 fields: vec![],
2707 source: None,
2708 },
2709 UnionVariant {
2710 name: "Err".to_string(),
2711 fields: vec![],
2712 source: None,
2713 },
2714 ],
2715 source: None,
2716 };
2717
2718 let program = Program {
2724 includes: vec![],
2725 unions: vec![union_def],
2726 words: vec![WordDef {
2727 name: "test".to_string(),
2728 effect: Some(Effect::new(
2729 StackType::Empty
2730 .push(Type::Int)
2731 .push(Type::Int)
2732 .push(Type::Int)
2733 .push(Type::Int)
2734 .push(Type::Union("Result".to_string())),
2735 StackType::Empty
2736 .push(Type::Int)
2737 .push(Type::Int)
2738 .push(Type::Int)
2739 .push(Type::Int),
2740 )),
2741 body: vec![Statement::Match {
2742 arms: vec![
2743 MatchArm {
2744 pattern: Pattern::Variant("Ok".to_string()),
2745 body: vec![
2746 Statement::IntLiteral(3),
2747 Statement::WordCall {
2748 name: "roll".to_string(),
2749 span: None,
2750 },
2751 ],
2752 },
2753 MatchArm {
2754 pattern: Pattern::Variant("Err".to_string()),
2755 body: vec![
2756 Statement::WordCall {
2757 name: "rot".to_string(),
2758 span: None,
2759 },
2760 Statement::WordCall {
2761 name: "rot".to_string(),
2762 span: None,
2763 },
2764 ],
2765 },
2766 ],
2767 }],
2768 source: None,
2769 }],
2770 };
2771
2772 let mut checker = TypeChecker::new();
2773 match checker.check_program(&program) {
2774 Ok(_) => {}
2775 Err(e) => panic!("Type check failed: {}", e),
2776 }
2777 }
2778
2779 #[test]
2780 fn test_roll_with_row_polymorphic_input() {
2781 let program = Program {
2785 includes: vec![],
2786 unions: vec![],
2787 words: vec![WordDef {
2788 name: "test".to_string(),
2789 effect: None, body: vec![
2791 Statement::IntLiteral(3),
2792 Statement::WordCall {
2793 name: "roll".to_string(),
2794 span: None,
2795 },
2796 ],
2797 source: None,
2798 }],
2799 };
2800
2801 let mut checker = TypeChecker::new();
2802 assert!(checker.check_program(&program).is_ok());
2804 }
2805
2806 #[test]
2807 fn test_pick_with_row_polymorphic_input() {
2808 let program = Program {
2812 includes: vec![],
2813 unions: vec![],
2814 words: vec![WordDef {
2815 name: "test".to_string(),
2816 effect: None, body: vec![
2818 Statement::IntLiteral(2),
2819 Statement::WordCall {
2820 name: "pick".to_string(),
2821 span: None,
2822 },
2823 ],
2824 source: None,
2825 }],
2826 };
2827
2828 let mut checker = TypeChecker::new();
2829 assert!(checker.check_program(&program).is_ok());
2831 }
2832
2833 #[test]
2834 fn test_valid_union_reference_in_field() {
2835 use crate::ast::{UnionDef, UnionField, UnionVariant};
2836
2837 let program = Program {
2838 includes: vec![],
2839 unions: vec![
2840 UnionDef {
2841 name: "Inner".to_string(),
2842 variants: vec![UnionVariant {
2843 name: "Val".to_string(),
2844 fields: vec![UnionField {
2845 name: "x".to_string(),
2846 type_name: "Int".to_string(),
2847 }],
2848 source: None,
2849 }],
2850 source: None,
2851 },
2852 UnionDef {
2853 name: "Outer".to_string(),
2854 variants: vec![UnionVariant {
2855 name: "Wrap".to_string(),
2856 fields: vec![UnionField {
2857 name: "inner".to_string(),
2858 type_name: "Inner".to_string(), }],
2860 source: None,
2861 }],
2862 source: None,
2863 },
2864 ],
2865 words: vec![],
2866 };
2867
2868 let mut checker = TypeChecker::new();
2869 assert!(
2870 checker.check_program(&program).is_ok(),
2871 "Union reference in field should be valid"
2872 );
2873 }
2874
2875 #[test]
2876 fn test_divergent_recursive_tail_call() {
2877 let program = Program {
2899 includes: vec![],
2900 unions: vec![],
2901 words: vec![WordDef {
2902 name: "store-loop".to_string(),
2903 effect: Some(Effect::new(
2904 StackType::singleton(Type::Channel), StackType::Empty,
2906 )),
2907 body: vec![
2908 Statement::WordCall {
2910 name: "dup".to_string(),
2911 span: None,
2912 },
2913 Statement::WordCall {
2915 name: "chan.receive".to_string(),
2916 span: None,
2917 },
2918 Statement::WordCall {
2920 name: "not".to_string(),
2921 span: None,
2922 },
2923 Statement::If {
2925 then_branch: vec![
2926 Statement::WordCall {
2928 name: "drop".to_string(),
2929 span: None,
2930 },
2931 Statement::WordCall {
2933 name: "store-loop".to_string(), span: None,
2935 },
2936 ],
2937 else_branch: None, },
2939 Statement::WordCall {
2941 name: "drop".to_string(),
2942 span: None,
2943 },
2944 Statement::WordCall {
2945 name: "drop".to_string(),
2946 span: None,
2947 },
2948 ],
2949 source: None,
2950 }],
2951 };
2952
2953 let mut checker = TypeChecker::new();
2954 let result = checker.check_program(&program);
2955 assert!(
2956 result.is_ok(),
2957 "Divergent recursive tail call should be accepted: {:?}",
2958 result.err()
2959 );
2960 }
2961
2962 #[test]
2963 fn test_divergent_else_branch() {
2964 let program = Program {
2977 includes: vec![],
2978 unions: vec![],
2979 words: vec![WordDef {
2980 name: "process-loop".to_string(),
2981 effect: Some(Effect::new(
2982 StackType::singleton(Type::Channel), StackType::Empty,
2984 )),
2985 body: vec![
2986 Statement::WordCall {
2987 name: "dup".to_string(),
2988 span: None,
2989 },
2990 Statement::WordCall {
2991 name: "chan.receive".to_string(),
2992 span: None,
2993 },
2994 Statement::If {
2995 then_branch: vec![
2996 Statement::WordCall {
2998 name: "drop".to_string(),
2999 span: None,
3000 },
3001 Statement::WordCall {
3002 name: "drop".to_string(),
3003 span: None,
3004 },
3005 ],
3006 else_branch: Some(vec![
3007 Statement::WordCall {
3009 name: "drop".to_string(),
3010 span: None,
3011 },
3012 Statement::WordCall {
3013 name: "process-loop".to_string(), span: None,
3015 },
3016 ]),
3017 },
3018 ],
3019 source: None,
3020 }],
3021 };
3022
3023 let mut checker = TypeChecker::new();
3024 let result = checker.check_program(&program);
3025 assert!(
3026 result.is_ok(),
3027 "Divergent else branch should be accepted: {:?}",
3028 result.err()
3029 );
3030 }
3031
3032 #[test]
3033 fn test_non_tail_call_recursion_not_divergent() {
3034 let program = Program {
3051 includes: vec![],
3052 unions: vec![],
3053 words: vec![WordDef {
3054 name: "bad-loop".to_string(),
3055 effect: Some(Effect::new(
3056 StackType::singleton(Type::Int),
3057 StackType::singleton(Type::Int),
3058 )),
3059 body: vec![
3060 Statement::WordCall {
3061 name: "dup".to_string(),
3062 span: None,
3063 },
3064 Statement::IntLiteral(0),
3065 Statement::WordCall {
3066 name: "i.>".to_string(),
3067 span: None,
3068 },
3069 Statement::If {
3070 then_branch: vec![
3071 Statement::IntLiteral(1),
3072 Statement::WordCall {
3073 name: "i.subtract".to_string(),
3074 span: None,
3075 },
3076 Statement::WordCall {
3077 name: "bad-loop".to_string(), span: None,
3079 },
3080 Statement::IntLiteral(1),
3081 Statement::WordCall {
3082 name: "i.add".to_string(), span: None,
3084 },
3085 ],
3086 else_branch: None,
3087 },
3088 ],
3089 source: None,
3090 }],
3091 };
3092
3093 let mut checker = TypeChecker::new();
3094 let result = checker.check_program(&program);
3099 assert!(
3100 result.is_ok(),
3101 "Non-tail recursion should type check normally: {:?}",
3102 result.err()
3103 );
3104 }
3105
3106 #[test]
3107 fn test_call_yield_quotation_error() {
3108 let program = Program {
3112 includes: vec![],
3113 unions: vec![],
3114 words: vec![WordDef {
3115 name: "bad".to_string(),
3116 effect: Some(Effect::new(
3117 StackType::singleton(Type::Var("Ctx".to_string())),
3118 StackType::singleton(Type::Var("Ctx".to_string())),
3119 )),
3120 body: vec![
3121 Statement::IntLiteral(42),
3123 Statement::Quotation {
3124 span: None,
3125 id: 0,
3126 body: vec![Statement::WordCall {
3127 name: "yield".to_string(),
3128 span: None,
3129 }],
3130 },
3131 Statement::WordCall {
3132 name: "call".to_string(),
3133 span: None,
3134 },
3135 ],
3136 source: None,
3137 }],
3138 };
3139
3140 let mut checker = TypeChecker::new();
3141 let result = checker.check_program(&program);
3142 assert!(
3143 result.is_err(),
3144 "Calling yield quotation directly should fail"
3145 );
3146 let err = result.unwrap_err();
3147 assert!(
3148 err.contains("Yield") || err.contains("strand.weave"),
3149 "Error should mention Yield or strand.weave: {}",
3150 err
3151 );
3152 }
3153
3154 #[test]
3155 fn test_strand_weave_yield_quotation_ok() {
3156 let program = Program {
3159 includes: vec![],
3160 unions: vec![],
3161 words: vec![WordDef {
3162 name: "good".to_string(),
3163 effect: None, body: vec![
3165 Statement::IntLiteral(42),
3166 Statement::Quotation {
3167 span: None,
3168 id: 0,
3169 body: vec![Statement::WordCall {
3170 name: "yield".to_string(),
3171 span: None,
3172 }],
3173 },
3174 Statement::WordCall {
3175 name: "strand.weave".to_string(),
3176 span: None,
3177 },
3178 ],
3179 source: None,
3180 }],
3181 };
3182
3183 let mut checker = TypeChecker::new();
3184 let result = checker.check_program(&program);
3185 assert!(
3186 result.is_ok(),
3187 "strand.weave on yield quotation should pass: {:?}",
3188 result.err()
3189 );
3190 }
3191
3192 #[test]
3193 fn test_call_pure_quotation_ok() {
3194 let program = Program {
3197 includes: vec![],
3198 unions: vec![],
3199 words: vec![WordDef {
3200 name: "ok".to_string(),
3201 effect: Some(Effect::new(
3202 StackType::singleton(Type::Int),
3203 StackType::singleton(Type::Int),
3204 )),
3205 body: vec![
3206 Statement::Quotation {
3207 span: None,
3208 id: 0,
3209 body: vec![
3210 Statement::IntLiteral(1),
3211 Statement::WordCall {
3212 name: "i.add".to_string(),
3213 span: None,
3214 },
3215 ],
3216 },
3217 Statement::WordCall {
3218 name: "call".to_string(),
3219 span: None,
3220 },
3221 ],
3222 source: None,
3223 }],
3224 };
3225
3226 let mut checker = TypeChecker::new();
3227 let result = checker.check_program(&program);
3228 assert!(
3229 result.is_ok(),
3230 "Calling pure quotation should pass: {:?}",
3231 result.err()
3232 );
3233 }
3234
3235 #[test]
3241 fn test_pollution_extra_push() {
3242 let program = Program {
3246 includes: vec![],
3247 unions: vec![],
3248 words: vec![WordDef {
3249 name: "test".to_string(),
3250 effect: Some(Effect::new(
3251 StackType::singleton(Type::Int),
3252 StackType::singleton(Type::Int),
3253 )),
3254 body: vec![Statement::IntLiteral(42)],
3255 source: None,
3256 }],
3257 };
3258
3259 let mut checker = TypeChecker::new();
3260 let result = checker.check_program(&program);
3261 assert!(
3262 result.is_err(),
3263 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3264 );
3265 }
3266
3267 #[test]
3268 fn test_pollution_extra_dup() {
3269 let program = Program {
3272 includes: vec![],
3273 unions: vec![],
3274 words: vec![WordDef {
3275 name: "test".to_string(),
3276 effect: Some(Effect::new(
3277 StackType::singleton(Type::Int),
3278 StackType::singleton(Type::Int),
3279 )),
3280 body: vec![Statement::WordCall {
3281 name: "dup".to_string(),
3282 span: None,
3283 }],
3284 source: None,
3285 }],
3286 };
3287
3288 let mut checker = TypeChecker::new();
3289 let result = checker.check_program(&program);
3290 assert!(
3291 result.is_err(),
3292 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3293 );
3294 }
3295
3296 #[test]
3297 fn test_pollution_consumes_extra() {
3298 let program = Program {
3301 includes: vec![],
3302 unions: vec![],
3303 words: vec![WordDef {
3304 name: "test".to_string(),
3305 effect: Some(Effect::new(
3306 StackType::singleton(Type::Int),
3307 StackType::singleton(Type::Int),
3308 )),
3309 body: vec![
3310 Statement::WordCall {
3311 name: "drop".to_string(),
3312 span: None,
3313 },
3314 Statement::WordCall {
3315 name: "drop".to_string(),
3316 span: None,
3317 },
3318 Statement::IntLiteral(42),
3319 ],
3320 source: None,
3321 }],
3322 };
3323
3324 let mut checker = TypeChecker::new();
3325 let result = checker.check_program(&program);
3326 assert!(
3327 result.is_err(),
3328 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3329 );
3330 }
3331
3332 #[test]
3333 fn test_pollution_in_then_branch() {
3334 let program = Program {
3338 includes: vec![],
3339 unions: vec![],
3340 words: vec![WordDef {
3341 name: "test".to_string(),
3342 effect: Some(Effect::new(
3343 StackType::singleton(Type::Bool),
3344 StackType::singleton(Type::Int),
3345 )),
3346 body: vec![Statement::If {
3347 then_branch: vec![
3348 Statement::IntLiteral(1),
3349 Statement::IntLiteral(2), ],
3351 else_branch: Some(vec![Statement::IntLiteral(3)]),
3352 }],
3353 source: None,
3354 }],
3355 };
3356
3357 let mut checker = TypeChecker::new();
3358 let result = checker.check_program(&program);
3359 assert!(
3360 result.is_err(),
3361 "Should reject: then branch pushes 2 values, else pushes 1"
3362 );
3363 }
3364
3365 #[test]
3366 fn test_pollution_in_else_branch() {
3367 let program = Program {
3371 includes: vec![],
3372 unions: vec![],
3373 words: vec![WordDef {
3374 name: "test".to_string(),
3375 effect: Some(Effect::new(
3376 StackType::singleton(Type::Bool),
3377 StackType::singleton(Type::Int),
3378 )),
3379 body: vec![Statement::If {
3380 then_branch: vec![Statement::IntLiteral(1)],
3381 else_branch: Some(vec![
3382 Statement::IntLiteral(2),
3383 Statement::IntLiteral(3), ]),
3385 }],
3386 source: None,
3387 }],
3388 };
3389
3390 let mut checker = TypeChecker::new();
3391 let result = checker.check_program(&program);
3392 assert!(
3393 result.is_err(),
3394 "Should reject: then branch pushes 1 value, else pushes 2"
3395 );
3396 }
3397
3398 #[test]
3399 fn test_pollution_both_branches_extra() {
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::singleton(Type::Bool),
3410 StackType::singleton(Type::Int),
3411 )),
3412 body: vec![Statement::If {
3413 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3414 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3415 }],
3416 source: None,
3417 }],
3418 };
3419
3420 let mut checker = TypeChecker::new();
3421 let result = checker.check_program(&program);
3422 assert!(
3423 result.is_err(),
3424 "Should reject: both branches push 2 values, but declared output is 1"
3425 );
3426 }
3427
3428 #[test]
3429 fn test_pollution_branch_consumes_extra() {
3430 let program = Program {
3434 includes: vec![],
3435 unions: vec![],
3436 words: vec![WordDef {
3437 name: "test".to_string(),
3438 effect: Some(Effect::new(
3439 StackType::Empty.push(Type::Bool).push(Type::Int),
3440 StackType::singleton(Type::Int),
3441 )),
3442 body: vec![Statement::If {
3443 then_branch: vec![
3444 Statement::WordCall {
3445 name: "drop".to_string(),
3446 span: None,
3447 },
3448 Statement::WordCall {
3449 name: "drop".to_string(),
3450 span: None,
3451 },
3452 Statement::IntLiteral(1),
3453 ],
3454 else_branch: Some(vec![]),
3455 }],
3456 source: None,
3457 }],
3458 };
3459
3460 let mut checker = TypeChecker::new();
3461 let result = checker.check_program(&program);
3462 assert!(
3463 result.is_err(),
3464 "Should reject: then branch consumes Bool (should only have Int after if)"
3465 );
3466 }
3467
3468 #[test]
3469 fn test_pollution_quotation_wrong_arity_output() {
3470 let program = Program {
3474 includes: vec![],
3475 unions: vec![],
3476 words: vec![WordDef {
3477 name: "test".to_string(),
3478 effect: Some(Effect::new(
3479 StackType::singleton(Type::Int),
3480 StackType::singleton(Type::Int),
3481 )),
3482 body: vec![
3483 Statement::Quotation {
3484 span: None,
3485 id: 0,
3486 body: vec![Statement::WordCall {
3487 name: "dup".to_string(),
3488 span: None,
3489 }],
3490 },
3491 Statement::WordCall {
3492 name: "call".to_string(),
3493 span: None,
3494 },
3495 ],
3496 source: None,
3497 }],
3498 };
3499
3500 let mut checker = TypeChecker::new();
3501 let result = checker.check_program(&program);
3502 assert!(
3503 result.is_err(),
3504 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3505 );
3506 }
3507
3508 #[test]
3509 fn test_pollution_quotation_wrong_arity_input() {
3510 let program = Program {
3514 includes: vec![],
3515 unions: vec![],
3516 words: vec![WordDef {
3517 name: "test".to_string(),
3518 effect: Some(Effect::new(
3519 StackType::singleton(Type::Int),
3520 StackType::singleton(Type::Int),
3521 )),
3522 body: vec![
3523 Statement::Quotation {
3524 span: None,
3525 id: 0,
3526 body: vec![
3527 Statement::WordCall {
3528 name: "drop".to_string(),
3529 span: None,
3530 },
3531 Statement::WordCall {
3532 name: "drop".to_string(),
3533 span: None,
3534 },
3535 Statement::IntLiteral(42),
3536 ],
3537 },
3538 Statement::WordCall {
3539 name: "call".to_string(),
3540 span: None,
3541 },
3542 ],
3543 source: None,
3544 }],
3545 };
3546
3547 let mut checker = TypeChecker::new();
3548 let result = checker.check_program(&program);
3549 assert!(
3550 result.is_err(),
3551 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3552 );
3553 }
3554
3555 #[test]
3556 fn test_no_effect_annotation_pollution() {
3557 let program = Program {
3561 includes: vec![],
3562 unions: vec![],
3563 words: vec![WordDef {
3564 name: "test".to_string(),
3565 effect: None, body: vec![Statement::IntLiteral(42)],
3567 source: None,
3568 }],
3569 };
3570
3571 let mut checker = TypeChecker::new();
3572 let result = checker.check_program(&program);
3573 assert!(
3574 result.is_ok(),
3575 "Should accept: no effect annotation means effect is inferred"
3576 );
3577 }
3578
3579 #[test]
3580 fn test_valid_effect_exact_match() {
3581 let program = Program {
3584 includes: vec![],
3585 unions: vec![],
3586 words: vec![WordDef {
3587 name: "test".to_string(),
3588 effect: Some(Effect::new(
3589 StackType::Empty.push(Type::Int).push(Type::Int),
3590 StackType::singleton(Type::Int),
3591 )),
3592 body: vec![Statement::WordCall {
3593 name: "i.add".to_string(),
3594 span: None,
3595 }],
3596 source: None,
3597 }],
3598 };
3599
3600 let mut checker = TypeChecker::new();
3601 let result = checker.check_program(&program);
3602 assert!(result.is_ok(), "Should accept: effect matches exactly");
3603 }
3604
3605 #[test]
3606 fn test_valid_polymorphic_passthrough() {
3607 let program = Program {
3610 includes: vec![],
3611 unions: vec![],
3612 words: vec![WordDef {
3613 name: "test".to_string(),
3614 effect: Some(Effect::new(
3615 StackType::Cons {
3616 rest: Box::new(StackType::RowVar("rest".to_string())),
3617 top: Type::Var("a".to_string()),
3618 },
3619 StackType::Cons {
3620 rest: Box::new(StackType::RowVar("rest".to_string())),
3621 top: Type::Var("a".to_string()),
3622 },
3623 )),
3624 body: vec![], source: None,
3626 }],
3627 };
3628
3629 let mut checker = TypeChecker::new();
3630 let result = checker.check_program(&program);
3631 assert!(result.is_ok(), "Should accept: polymorphic identity");
3632 }
3633
3634 #[test]
3640 fn test_closure_basic_capture() {
3641 let program = Program {
3647 includes: vec![],
3648 unions: vec![],
3649 words: vec![WordDef {
3650 name: "make-adder".to_string(),
3651 effect: Some(Effect::new(
3652 StackType::singleton(Type::Int),
3653 StackType::singleton(Type::Closure {
3654 effect: Box::new(Effect::new(
3655 StackType::RowVar("r".to_string()).push(Type::Int),
3656 StackType::RowVar("r".to_string()).push(Type::Int),
3657 )),
3658 captures: vec![Type::Int], }),
3660 )),
3661 body: vec![Statement::Quotation {
3662 span: None,
3663 id: 0,
3664 body: vec![Statement::WordCall {
3665 name: "i.add".to_string(),
3666 span: None,
3667 }],
3668 }],
3669 source: None,
3670 }],
3671 };
3672
3673 let mut checker = TypeChecker::new();
3674 let result = checker.check_program(&program);
3675 assert!(
3676 result.is_ok(),
3677 "Basic closure capture should work: {:?}",
3678 result.err()
3679 );
3680 }
3681
3682 #[test]
3683 fn test_closure_nested_two_levels() {
3684 let program = Program {
3689 includes: vec![],
3690 unions: vec![],
3691 words: vec![WordDef {
3692 name: "outer".to_string(),
3693 effect: Some(Effect::new(
3694 StackType::Empty,
3695 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3696 StackType::RowVar("r".to_string()),
3697 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3698 Effect::new(
3699 StackType::RowVar("s".to_string()).push(Type::Int),
3700 StackType::RowVar("s".to_string()).push(Type::Int),
3701 ),
3702 ))),
3703 )))),
3704 )),
3705 body: vec![Statement::Quotation {
3706 span: None,
3707 id: 0,
3708 body: vec![Statement::Quotation {
3709 span: None,
3710 id: 1,
3711 body: vec![
3712 Statement::IntLiteral(1),
3713 Statement::WordCall {
3714 name: "i.add".to_string(),
3715 span: None,
3716 },
3717 ],
3718 }],
3719 }],
3720 source: None,
3721 }],
3722 };
3723
3724 let mut checker = TypeChecker::new();
3725 let result = checker.check_program(&program);
3726 assert!(
3727 result.is_ok(),
3728 "Two-level nested quotations should work: {:?}",
3729 result.err()
3730 );
3731 }
3732
3733 #[test]
3734 fn test_closure_nested_three_levels() {
3735 let inner_effect = Effect::new(
3739 StackType::RowVar("a".to_string()).push(Type::Int),
3740 StackType::RowVar("a".to_string()).push(Type::Int),
3741 );
3742 let middle_effect = Effect::new(
3743 StackType::RowVar("b".to_string()),
3744 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3745 );
3746 let outer_effect = Effect::new(
3747 StackType::RowVar("c".to_string()),
3748 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3749 );
3750
3751 let program = Program {
3752 includes: vec![],
3753 unions: vec![],
3754 words: vec![WordDef {
3755 name: "deep".to_string(),
3756 effect: Some(Effect::new(
3757 StackType::Empty,
3758 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3759 )),
3760 body: vec![Statement::Quotation {
3761 span: None,
3762 id: 0,
3763 body: vec![Statement::Quotation {
3764 span: None,
3765 id: 1,
3766 body: vec![Statement::Quotation {
3767 span: None,
3768 id: 2,
3769 body: vec![
3770 Statement::IntLiteral(1),
3771 Statement::WordCall {
3772 name: "i.add".to_string(),
3773 span: None,
3774 },
3775 ],
3776 }],
3777 }],
3778 }],
3779 source: None,
3780 }],
3781 };
3782
3783 let mut checker = TypeChecker::new();
3784 let result = checker.check_program(&program);
3785 assert!(
3786 result.is_ok(),
3787 "Three-level nested quotations should work: {:?}",
3788 result.err()
3789 );
3790 }
3791
3792 #[test]
3793 fn test_closure_use_after_creation() {
3794 let adder_type = Type::Closure {
3800 effect: Box::new(Effect::new(
3801 StackType::RowVar("r".to_string()).push(Type::Int),
3802 StackType::RowVar("r".to_string()).push(Type::Int),
3803 )),
3804 captures: vec![Type::Int],
3805 };
3806
3807 let program = Program {
3808 includes: vec![],
3809 unions: vec![],
3810 words: vec![
3811 WordDef {
3812 name: "make-adder".to_string(),
3813 effect: Some(Effect::new(
3814 StackType::singleton(Type::Int),
3815 StackType::singleton(adder_type.clone()),
3816 )),
3817 body: vec![Statement::Quotation {
3818 span: None,
3819 id: 0,
3820 body: vec![Statement::WordCall {
3821 name: "i.add".to_string(),
3822 span: None,
3823 }],
3824 }],
3825 source: None,
3826 },
3827 WordDef {
3828 name: "use-adder".to_string(),
3829 effect: Some(Effect::new(
3830 StackType::Empty,
3831 StackType::singleton(Type::Int),
3832 )),
3833 body: vec![
3834 Statement::IntLiteral(5),
3835 Statement::WordCall {
3836 name: "make-adder".to_string(),
3837 span: None,
3838 },
3839 Statement::IntLiteral(10),
3840 Statement::WordCall {
3841 name: "swap".to_string(),
3842 span: None,
3843 },
3844 Statement::WordCall {
3845 name: "call".to_string(),
3846 span: None,
3847 },
3848 ],
3849 source: None,
3850 },
3851 ],
3852 };
3853
3854 let mut checker = TypeChecker::new();
3855 let result = checker.check_program(&program);
3856 assert!(
3857 result.is_ok(),
3858 "Closure usage after creation should work: {:?}",
3859 result.err()
3860 );
3861 }
3862
3863 #[test]
3864 fn test_closure_wrong_call_type() {
3865 let adder_type = Type::Closure {
3869 effect: Box::new(Effect::new(
3870 StackType::RowVar("r".to_string()).push(Type::Int),
3871 StackType::RowVar("r".to_string()).push(Type::Int),
3872 )),
3873 captures: vec![Type::Int],
3874 };
3875
3876 let program = Program {
3877 includes: vec![],
3878 unions: vec![],
3879 words: vec![
3880 WordDef {
3881 name: "make-adder".to_string(),
3882 effect: Some(Effect::new(
3883 StackType::singleton(Type::Int),
3884 StackType::singleton(adder_type.clone()),
3885 )),
3886 body: vec![Statement::Quotation {
3887 span: None,
3888 id: 0,
3889 body: vec![Statement::WordCall {
3890 name: "i.add".to_string(),
3891 span: None,
3892 }],
3893 }],
3894 source: None,
3895 },
3896 WordDef {
3897 name: "bad-use".to_string(),
3898 effect: Some(Effect::new(
3899 StackType::Empty,
3900 StackType::singleton(Type::Int),
3901 )),
3902 body: vec![
3903 Statement::IntLiteral(5),
3904 Statement::WordCall {
3905 name: "make-adder".to_string(),
3906 span: None,
3907 },
3908 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
3910 name: "swap".to_string(),
3911 span: None,
3912 },
3913 Statement::WordCall {
3914 name: "call".to_string(),
3915 span: None,
3916 },
3917 ],
3918 source: None,
3919 },
3920 ],
3921 };
3922
3923 let mut checker = TypeChecker::new();
3924 let result = checker.check_program(&program);
3925 assert!(
3926 result.is_err(),
3927 "Calling Int closure with String should fail"
3928 );
3929 }
3930
3931 #[test]
3932 fn test_closure_multiple_captures() {
3933 let program = Program {
3940 includes: vec![],
3941 unions: vec![],
3942 words: vec![WordDef {
3943 name: "make-between".to_string(),
3944 effect: Some(Effect::new(
3945 StackType::Empty.push(Type::Int).push(Type::Int),
3946 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3947 StackType::RowVar("r".to_string()).push(Type::Int),
3948 StackType::RowVar("r".to_string()).push(Type::Bool),
3949 )))),
3950 )),
3951 body: vec![Statement::Quotation {
3952 span: None,
3953 id: 0,
3954 body: vec![
3955 Statement::WordCall {
3957 name: "i.>=".to_string(),
3958 span: None,
3959 },
3960 ],
3962 }],
3963 source: None,
3964 }],
3965 };
3966
3967 let mut checker = TypeChecker::new();
3968 let result = checker.check_program(&program);
3969 assert!(
3973 result.is_ok() || result.is_err(),
3974 "Multiple captures should be handled (pass or fail gracefully)"
3975 );
3976 }
3977
3978 #[test]
3979 fn test_quotation_in_times_loop() {
3980 let program = Program {
3985 includes: vec![],
3986 unions: vec![],
3987 words: vec![WordDef {
3988 name: "do-nothing-n-times".to_string(),
3989 effect: Some(Effect::new(
3990 StackType::singleton(Type::Int),
3991 StackType::Empty,
3992 )),
3993 body: vec![
3994 Statement::Quotation {
3995 span: None,
3996 id: 0,
3997 body: vec![], },
3999 Statement::WordCall {
4000 name: "swap".to_string(),
4001 span: None,
4002 },
4003 Statement::WordCall {
4004 name: "times".to_string(),
4005 span: None,
4006 },
4007 ],
4008 source: None,
4009 }],
4010 };
4011
4012 let mut checker = TypeChecker::new();
4013 let result = checker.check_program(&program);
4014 assert!(
4015 result.is_ok(),
4016 "Stack-neutral quotation in times loop should work: {:?}",
4017 result.err()
4018 );
4019 }
4020
4021 #[test]
4022 fn test_quotation_type_preserved_through_word() {
4023 let quot_type = Type::Quotation(Box::new(Effect::new(
4026 StackType::RowVar("r".to_string()).push(Type::Int),
4027 StackType::RowVar("r".to_string()).push(Type::Int),
4028 )));
4029
4030 let program = Program {
4031 includes: vec![],
4032 unions: vec![],
4033 words: vec![WordDef {
4034 name: "identity-quot".to_string(),
4035 effect: Some(Effect::new(
4036 StackType::singleton(quot_type.clone()),
4037 StackType::singleton(quot_type.clone()),
4038 )),
4039 body: vec![], source: None,
4041 }],
4042 };
4043
4044 let mut checker = TypeChecker::new();
4045 let result = checker.check_program(&program);
4046 assert!(
4047 result.is_ok(),
4048 "Quotation type should be preserved through identity word: {:?}",
4049 result.err()
4050 );
4051 }
4052
4053 #[test]
4054 fn test_closure_captures_value_for_inner_quotation() {
4055 let closure_effect = Effect::new(
4061 StackType::RowVar("r".to_string()).push(Type::Int),
4062 StackType::RowVar("r".to_string()).push(Type::Int),
4063 );
4064
4065 let program = Program {
4066 includes: vec![],
4067 unions: vec![],
4068 words: vec![WordDef {
4069 name: "make-inner-adder".to_string(),
4070 effect: Some(Effect::new(
4071 StackType::singleton(Type::Int),
4072 StackType::singleton(Type::Closure {
4073 effect: Box::new(closure_effect),
4074 captures: vec![Type::Int],
4075 }),
4076 )),
4077 body: vec![Statement::Quotation {
4078 span: None,
4079 id: 0,
4080 body: vec![
4081 Statement::WordCall {
4083 name: "i.add".to_string(),
4084 span: None,
4085 },
4086 ],
4087 }],
4088 source: None,
4089 }],
4090 };
4091
4092 let mut checker = TypeChecker::new();
4093 let result = checker.check_program(&program);
4094 assert!(
4095 result.is_ok(),
4096 "Closure with capture for inner work should pass: {:?}",
4097 result.err()
4098 );
4099 }
4100
4101 #[test]
4107 fn test_times_rejects_quotation_that_pushes() {
4108 let program = Program {
4112 includes: vec![],
4113 unions: vec![],
4114 words: vec![WordDef {
4115 name: "bad-times".to_string(),
4116 effect: Some(Effect::new(
4117 StackType::singleton(Type::Int),
4118 StackType::Empty,
4119 )),
4120 body: vec![
4121 Statement::Quotation {
4122 span: None,
4123 id: 0,
4124 body: vec![Statement::IntLiteral(1)], },
4126 Statement::WordCall {
4127 name: "swap".to_string(),
4128 span: None,
4129 },
4130 Statement::WordCall {
4131 name: "times".to_string(),
4132 span: None,
4133 },
4134 ],
4135 source: None,
4136 }],
4137 };
4138
4139 let mut checker = TypeChecker::new();
4140 let result = checker.check_program(&program);
4141 assert!(
4142 result.is_err(),
4143 "times should reject quotation that pushes extra values"
4144 );
4145 }
4146
4147 #[test]
4148 fn test_times_rejects_quotation_that_consumes() {
4149 let program = Program {
4153 includes: vec![],
4154 unions: vec![],
4155 words: vec![WordDef {
4156 name: "bad-times".to_string(),
4157 effect: Some(Effect::new(
4158 StackType::Empty.push(Type::Int).push(Type::Int),
4159 StackType::Empty,
4160 )),
4161 body: vec![
4162 Statement::Quotation {
4163 span: None,
4164 id: 0,
4165 body: vec![Statement::WordCall {
4166 name: "drop".to_string(),
4167 span: None,
4168 }], },
4170 Statement::WordCall {
4171 name: "swap".to_string(),
4172 span: None,
4173 },
4174 Statement::WordCall {
4175 name: "times".to_string(),
4176 span: None,
4177 },
4178 ],
4179 source: None,
4180 }],
4181 };
4182
4183 let mut checker = TypeChecker::new();
4184 let result = checker.check_program(&program);
4185 assert!(
4186 result.is_err(),
4187 "times should reject quotation that consumes values"
4188 );
4189 }
4190
4191 #[test]
4192 fn test_while_cond_must_return_bool() {
4193 let program = Program {
4197 includes: vec![],
4198 unions: vec![],
4199 words: vec![WordDef {
4200 name: "bad-while".to_string(),
4201 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4202 body: vec![
4203 Statement::Quotation {
4204 span: None,
4205 id: 0,
4206 body: vec![Statement::IntLiteral(1)], },
4208 Statement::Quotation {
4209 span: None,
4210 id: 1,
4211 body: vec![],
4212 },
4213 Statement::WordCall {
4214 name: "while".to_string(),
4215 span: None,
4216 },
4217 ],
4218 source: None,
4219 }],
4220 };
4221
4222 let mut checker = TypeChecker::new();
4223 let result = checker.check_program(&program);
4224 assert!(
4225 result.is_err(),
4226 "while should reject cond quotation that doesn't return Bool"
4227 );
4228 }
4229
4230 #[test]
4231 fn test_while_body_must_be_stack_neutral() {
4232 let program = Program {
4237 includes: vec![],
4238 unions: vec![],
4239 words: vec![WordDef {
4240 name: "bad-while".to_string(),
4241 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4242 body: vec![
4243 Statement::Quotation {
4244 span: None,
4245 id: 0,
4246 body: vec![Statement::BoolLiteral(true)],
4247 },
4248 Statement::Quotation {
4249 span: None,
4250 id: 1,
4251 body: vec![Statement::IntLiteral(1)], },
4253 Statement::WordCall {
4254 name: "while".to_string(),
4255 span: None,
4256 },
4257 ],
4258 source: None,
4259 }],
4260 };
4261
4262 let mut checker = TypeChecker::new();
4263 let result = checker.check_program(&program);
4264 assert!(
4265 result.is_err(),
4266 "while body that pushes values should be rejected"
4267 );
4268 let err = result.unwrap_err();
4269 assert!(
4270 err.contains("quotation effect mismatch"),
4271 "Error should mention quotation effect mismatch, got: {}",
4272 err
4273 );
4274 }
4275
4276 #[test]
4277 fn test_until_cond_must_return_bool() {
4278 let program = Program {
4283 includes: vec![],
4284 unions: vec![],
4285 words: vec![WordDef {
4286 name: "bad-until".to_string(),
4287 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4288 body: vec![
4289 Statement::Quotation {
4290 span: None,
4291 id: 0,
4292 body: vec![],
4293 },
4294 Statement::Quotation {
4295 span: None,
4296 id: 1,
4297 body: vec![Statement::IntLiteral(1)], },
4299 Statement::WordCall {
4300 name: "until".to_string(),
4301 span: None,
4302 },
4303 ],
4304 source: None,
4305 }],
4306 };
4307
4308 let mut checker = TypeChecker::new();
4309 let result = checker.check_program(&program);
4310 assert!(
4311 result.is_err(),
4312 "until cond returning Int instead of Bool should be rejected"
4313 );
4314 let err = result.unwrap_err();
4315 assert!(
4316 err.contains("quotation effect mismatch") || err.contains("Type mismatch"),
4317 "Error should mention type mismatch, got: {}",
4318 err
4319 );
4320 }
4321
4322 #[test]
4323 fn test_until_body_must_be_stack_neutral() {
4324 let program = Program {
4328 includes: vec![],
4329 unions: vec![],
4330 words: vec![WordDef {
4331 name: "bad-until".to_string(),
4332 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
4333 body: vec![
4334 Statement::Quotation {
4335 span: None,
4336 id: 0,
4337 body: vec![Statement::WordCall {
4338 name: "drop".to_string(),
4339 span: None,
4340 }], },
4342 Statement::Quotation {
4343 span: None,
4344 id: 1,
4345 body: vec![Statement::BoolLiteral(true)],
4346 },
4347 Statement::WordCall {
4348 name: "until".to_string(),
4349 span: None,
4350 },
4351 ],
4352 source: None,
4353 }],
4354 };
4355
4356 let mut checker = TypeChecker::new();
4357 let result = checker.check_program(&program);
4358 assert!(
4359 result.is_err(),
4360 "until should reject body quotation that consumes values"
4361 );
4362 }
4363
4364 #[test]
4365 fn test_valid_while_loop() {
4366 let program = Program {
4370 includes: vec![],
4371 unions: vec![],
4372 words: vec![WordDef {
4373 name: "count-while".to_string(),
4374 effect: Some(Effect::new(
4375 StackType::singleton(Type::Int),
4376 StackType::singleton(Type::Int),
4377 )),
4378 body: vec![
4379 Statement::Quotation {
4380 span: None,
4381 id: 0,
4382 body: vec![
4383 Statement::WordCall {
4384 name: "dup".to_string(),
4385 span: None,
4386 },
4387 Statement::IntLiteral(0),
4388 Statement::WordCall {
4389 name: "i.>".to_string(),
4390 span: None,
4391 },
4392 ],
4393 },
4394 Statement::Quotation {
4395 span: None,
4396 id: 1,
4397 body: vec![
4398 Statement::IntLiteral(1),
4399 Statement::WordCall {
4400 name: "i.-".to_string(),
4401 span: None,
4402 },
4403 ],
4404 },
4405 Statement::WordCall {
4406 name: "while".to_string(),
4407 span: None,
4408 },
4409 ],
4410 source: None,
4411 }],
4412 };
4413
4414 let mut checker = TypeChecker::new();
4415 let result = checker.check_program(&program);
4416 assert!(
4417 result.is_ok(),
4418 "Valid while loop should pass: {:?}",
4419 result.err()
4420 );
4421 }
4422
4423 #[test]
4424 fn test_valid_until_loop() {
4425 let program = Program {
4429 includes: vec![],
4430 unions: vec![],
4431 words: vec![WordDef {
4432 name: "count-until".to_string(),
4433 effect: Some(Effect::new(
4434 StackType::singleton(Type::Int),
4435 StackType::singleton(Type::Int),
4436 )),
4437 body: vec![
4438 Statement::Quotation {
4439 span: None,
4440 id: 0,
4441 body: vec![
4442 Statement::IntLiteral(1),
4443 Statement::WordCall {
4444 name: "i.-".to_string(),
4445 span: None,
4446 },
4447 ],
4448 },
4449 Statement::Quotation {
4450 span: None,
4451 id: 1,
4452 body: vec![
4453 Statement::WordCall {
4454 name: "dup".to_string(),
4455 span: None,
4456 },
4457 Statement::IntLiteral(0),
4458 Statement::WordCall {
4459 name: "i.<=".to_string(),
4460 span: None,
4461 },
4462 ],
4463 },
4464 Statement::WordCall {
4465 name: "until".to_string(),
4466 span: None,
4467 },
4468 ],
4469 source: None,
4470 }],
4471 };
4472
4473 let mut checker = TypeChecker::new();
4474 let result = checker.check_program(&program);
4475 assert!(
4476 result.is_ok(),
4477 "Valid until loop should pass: {:?}",
4478 result.err()
4479 );
4480 }
4481}