1use crate::ast::{Program, Statement, WordDef};
7use crate::builtins::builtin_signature;
8use crate::call_graph::CallGraph;
9use crate::capture_analysis::calculate_captures;
10use crate::types::{
11 Effect, SideEffect, StackType, Type, UnionTypeInfo, VariantFieldInfo, VariantInfo,
12};
13use crate::unification::{Subst, unify_stacks, unify_types};
14use std::collections::HashMap;
15
16fn format_line_prefix(line: usize) -> String {
19 format!("at line {}: ", line + 1)
20}
21
22pub struct TypeChecker {
23 env: HashMap<String, Effect>,
25 unions: HashMap<String, UnionTypeInfo>,
28 fresh_counter: std::cell::Cell<usize>,
30 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
34 expected_quotation_type: std::cell::RefCell<Option<Type>>,
37 current_word: std::cell::RefCell<Option<(String, Option<usize>)>>,
41 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
45 call_graph: Option<CallGraph>,
48}
49
50impl TypeChecker {
51 pub fn new() -> Self {
52 TypeChecker {
53 env: HashMap::new(),
54 unions: HashMap::new(),
55 fresh_counter: std::cell::Cell::new(0),
56 quotation_types: std::cell::RefCell::new(HashMap::new()),
57 expected_quotation_type: std::cell::RefCell::new(None),
58 current_word: std::cell::RefCell::new(None),
59 statement_top_types: std::cell::RefCell::new(HashMap::new()),
60 call_graph: None,
61 }
62 }
63
64 pub fn set_call_graph(&mut self, call_graph: CallGraph) {
69 self.call_graph = Some(call_graph);
70 }
71
72 fn line_prefix(&self) -> String {
74 self.current_word
75 .borrow()
76 .as_ref()
77 .and_then(|(_, line)| line.map(format_line_prefix))
78 .unwrap_or_default()
79 }
80
81 pub fn get_union(&self, name: &str) -> Option<&UnionTypeInfo> {
83 self.unions.get(name)
84 }
85
86 pub fn get_unions(&self) -> &HashMap<String, UnionTypeInfo> {
88 &self.unions
89 }
90
91 fn find_variant(&self, variant_name: &str) -> Option<(&str, &VariantInfo)> {
95 for (union_name, union_info) in &self.unions {
96 for variant in &union_info.variants {
97 if variant.name == variant_name {
98 return Some((union_name.as_str(), variant));
99 }
100 }
101 }
102 None
103 }
104
105 pub fn register_external_words(&mut self, words: &[(&str, &Effect)]) {
109 for (name, effect) in words {
110 self.env.insert(name.to_string(), (*effect).clone());
111 }
112 }
113
114 pub fn register_external_unions(&mut self, union_names: &[&str]) {
120 for name in union_names {
121 self.unions.insert(
124 name.to_string(),
125 UnionTypeInfo {
126 name: name.to_string(),
127 variants: vec![],
128 },
129 );
130 }
131 }
132
133 pub fn take_quotation_types(&self) -> HashMap<usize, Type> {
139 self.quotation_types.replace(HashMap::new())
140 }
141
142 pub fn take_statement_top_types(&self) -> HashMap<(String, usize), Type> {
145 self.statement_top_types.replace(HashMap::new())
146 }
147
148 fn get_trivially_copyable_top(stack: &StackType) -> Option<Type> {
151 match stack {
152 StackType::Cons { top, .. } => match top {
153 Type::Int | Type::Float | Type::Bool => Some(top.clone()),
154 _ => None,
155 },
156 _ => None,
157 }
158 }
159
160 fn capture_statement_type(&self, word_name: &str, stmt_index: usize, stack: &StackType) {
162 if let Some(top_type) = Self::get_trivially_copyable_top(stack) {
163 self.statement_top_types
164 .borrow_mut()
165 .insert((word_name.to_string(), stmt_index), top_type);
166 }
167 }
168
169 fn fresh_var(&self, prefix: &str) -> String {
171 let n = self.fresh_counter.get();
172 self.fresh_counter.set(n + 1);
173 format!("{}${}", prefix, n)
174 }
175
176 fn freshen_effect(&self, effect: &Effect) -> Effect {
178 let mut type_map = HashMap::new();
179 let mut row_map = HashMap::new();
180
181 let fresh_inputs = self.freshen_stack(&effect.inputs, &mut type_map, &mut row_map);
182 let fresh_outputs = self.freshen_stack(&effect.outputs, &mut type_map, &mut row_map);
183
184 let fresh_effects = effect
186 .effects
187 .iter()
188 .map(|e| self.freshen_side_effect(e, &mut type_map, &mut row_map))
189 .collect();
190
191 Effect::with_effects(fresh_inputs, fresh_outputs, fresh_effects)
192 }
193
194 fn freshen_side_effect(
195 &self,
196 effect: &SideEffect,
197 type_map: &mut HashMap<String, String>,
198 row_map: &mut HashMap<String, String>,
199 ) -> SideEffect {
200 match effect {
201 SideEffect::Yield(ty) => {
202 SideEffect::Yield(Box::new(self.freshen_type(ty, type_map, row_map)))
203 }
204 }
205 }
206
207 fn freshen_stack(
208 &self,
209 stack: &StackType,
210 type_map: &mut HashMap<String, String>,
211 row_map: &mut HashMap<String, String>,
212 ) -> StackType {
213 match stack {
214 StackType::Empty => StackType::Empty,
215 StackType::RowVar(name) => {
216 let fresh_name = row_map
217 .entry(name.clone())
218 .or_insert_with(|| self.fresh_var(name));
219 StackType::RowVar(fresh_name.clone())
220 }
221 StackType::Cons { rest, top } => {
222 let fresh_rest = self.freshen_stack(rest, type_map, row_map);
223 let fresh_top = self.freshen_type(top, type_map, row_map);
224 StackType::Cons {
225 rest: Box::new(fresh_rest),
226 top: fresh_top,
227 }
228 }
229 }
230 }
231
232 fn freshen_type(
233 &self,
234 ty: &Type,
235 type_map: &mut HashMap<String, String>,
236 row_map: &mut HashMap<String, String>,
237 ) -> Type {
238 match ty {
239 Type::Int | Type::Float | Type::Bool | Type::String | Type::Symbol | Type::Channel => {
240 ty.clone()
241 }
242 Type::Var(name) => {
243 let fresh_name = type_map
244 .entry(name.clone())
245 .or_insert_with(|| self.fresh_var(name));
246 Type::Var(fresh_name.clone())
247 }
248 Type::Quotation(effect) => {
249 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
250 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
251 Type::Quotation(Box::new(Effect::new(fresh_inputs, fresh_outputs)))
252 }
253 Type::Closure { effect, captures } => {
254 let fresh_inputs = self.freshen_stack(&effect.inputs, type_map, row_map);
255 let fresh_outputs = self.freshen_stack(&effect.outputs, type_map, row_map);
256 let fresh_captures = captures
257 .iter()
258 .map(|t| self.freshen_type(t, type_map, row_map))
259 .collect();
260 Type::Closure {
261 effect: Box::new(Effect::new(fresh_inputs, fresh_outputs)),
262 captures: fresh_captures,
263 }
264 }
265 Type::Union(name) => Type::Union(name.clone()),
267 }
268 }
269
270 fn parse_type_name(&self, name: &str) -> Type {
274 match name {
275 "Int" => Type::Int,
276 "Float" => Type::Float,
277 "Bool" => Type::Bool,
278 "String" => Type::String,
279 "Channel" => Type::Channel,
280 other => Type::Union(other.to_string()),
282 }
283 }
284
285 fn is_valid_type_name(&self, name: &str) -> bool {
290 matches!(name, "Int" | "Float" | "Bool" | "String" | "Channel")
291 || self.unions.contains_key(name)
292 }
293
294 fn validate_union_field_types(&self, program: &Program) -> Result<(), String> {
298 for union_def in &program.unions {
299 for variant in &union_def.variants {
300 for field in &variant.fields {
301 if !self.is_valid_type_name(&field.type_name) {
302 return Err(format!(
303 "Unknown type '{}' in field '{}' of variant '{}' in union '{}'. \
304 Valid types are: Int, Float, Bool, String, Channel, or a defined union name.",
305 field.type_name, field.name, variant.name, union_def.name
306 ));
307 }
308 }
309 }
310 }
311 Ok(())
312 }
313
314 pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
316 for union_def in &program.unions {
318 let variants = union_def
319 .variants
320 .iter()
321 .map(|v| VariantInfo {
322 name: v.name.clone(),
323 fields: v
324 .fields
325 .iter()
326 .map(|f| VariantFieldInfo {
327 name: f.name.clone(),
328 field_type: self.parse_type_name(&f.type_name),
329 })
330 .collect(),
331 })
332 .collect();
333
334 self.unions.insert(
335 union_def.name.clone(),
336 UnionTypeInfo {
337 name: union_def.name.clone(),
338 variants,
339 },
340 );
341 }
342
343 self.validate_union_field_types(program)?;
345
346 for word in &program.words {
349 if let Some(effect) = &word.effect {
350 self.env.insert(word.name.clone(), effect.clone());
351 } else {
352 return Err(format!(
353 "Word '{}' is missing a stack effect declaration.\n\
354 All words must declare their stack effect, e.g.: : {} ( -- ) ... ;",
355 word.name, word.name
356 ));
357 }
358 }
359
360 for word in &program.words {
362 self.check_word(word)?;
363 }
364
365 Ok(())
366 }
367
368 fn check_word(&self, word: &WordDef) -> Result<(), String> {
370 let line = word.source.as_ref().map(|s| s.start_line);
372 *self.current_word.borrow_mut() = Some((word.name.clone(), line));
373
374 let declared_effect = word.effect.as_ref().expect("word must have effect");
376
377 if let Some((_rest, top_type)) = declared_effect.outputs.clone().pop()
380 && matches!(top_type, Type::Quotation(_) | Type::Closure { .. })
381 {
382 *self.expected_quotation_type.borrow_mut() = Some(top_type);
383 }
384
385 let (result_stack, _subst, inferred_effects) =
387 self.infer_statements_from(&word.body, &declared_effect.inputs, true)?;
388
389 *self.expected_quotation_type.borrow_mut() = None;
391
392 let line_info = line.map(format_line_prefix).unwrap_or_default();
394 unify_stacks(&declared_effect.outputs, &result_stack).map_err(|e| {
395 format!(
396 "{}Word '{}': declared output stack ({}) doesn't match inferred ({}): {}",
397 line_info, word.name, declared_effect.outputs, result_stack, e
398 )
399 })?;
400
401 for inferred in &inferred_effects {
405 if !self.effect_matches_any(inferred, &declared_effect.effects) {
406 return Err(format!(
407 "{}Word '{}': body produces effect '{}' but no matching effect is declared.\n\
408 Hint: Add '| Yield <type>' to the word's stack effect declaration.",
409 line_info, word.name, inferred
410 ));
411 }
412 }
413
414 for declared in &declared_effect.effects {
417 if !self.effect_matches_any(declared, &inferred_effects) {
418 return Err(format!(
419 "{}Word '{}': declares effect '{}' but body doesn't produce it.\n\
420 Hint: Remove the effect declaration or ensure the body uses yield.",
421 line_info, word.name, declared
422 ));
423 }
424 }
425
426 *self.current_word.borrow_mut() = None;
428
429 Ok(())
430 }
431
432 fn infer_statements_from(
439 &self,
440 statements: &[Statement],
441 start_stack: &StackType,
442 capture_stmt_types: bool,
443 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
444 let mut current_stack = start_stack.clone();
445 let mut accumulated_subst = Subst::empty();
446 let mut accumulated_effects: Vec<SideEffect> = Vec::new();
447 let mut skip_next = false;
448
449 for (i, stmt) in statements.iter().enumerate() {
450 if skip_next {
452 skip_next = false;
453 continue;
454 }
455
456 if let Statement::IntLiteral(n) = stmt
459 && let Some(Statement::WordCall {
460 name: next_word, ..
461 }) = statements.get(i + 1)
462 {
463 if next_word == "pick" {
464 let (new_stack, subst) = self.handle_literal_pick(*n, current_stack.clone())?;
465 current_stack = new_stack;
466 accumulated_subst = accumulated_subst.compose(&subst);
467 skip_next = true; continue;
469 } else if next_word == "roll" {
470 let (new_stack, subst) = self.handle_literal_roll(*n, current_stack.clone())?;
471 current_stack = new_stack;
472 accumulated_subst = accumulated_subst.compose(&subst);
473 skip_next = true; continue;
475 }
476 }
477
478 let saved_expected_type = if matches!(stmt, Statement::Quotation { .. }) {
481 let saved = self.expected_quotation_type.borrow().clone();
483
484 if let Some(Statement::WordCall {
486 name: next_word, ..
487 }) = statements.get(i + 1)
488 {
489 if let Some(next_effect) = self.lookup_word_effect(next_word) {
491 if let Some((_rest, quot_type)) = next_effect.inputs.clone().pop()
494 && matches!(quot_type, Type::Quotation(_))
495 {
496 *self.expected_quotation_type.borrow_mut() = Some(quot_type);
497 }
498 }
499 }
500 Some(saved)
501 } else {
502 None
503 };
504
505 if capture_stmt_types && let Some((word_name, _)) = self.current_word.borrow().as_ref()
509 {
510 self.capture_statement_type(word_name, i, ¤t_stack);
511 }
512
513 let (new_stack, subst, effects) = self.infer_statement(stmt, current_stack)?;
514 current_stack = new_stack;
515 accumulated_subst = accumulated_subst.compose(&subst);
516
517 for effect in effects {
519 if !accumulated_effects.contains(&effect) {
520 accumulated_effects.push(effect);
521 }
522 }
523
524 if let Some(saved) = saved_expected_type {
526 *self.expected_quotation_type.borrow_mut() = saved;
527 }
528 }
529
530 Ok((current_stack, accumulated_subst, accumulated_effects))
531 }
532
533 fn handle_literal_pick(
544 &self,
545 n: i64,
546 current_stack: StackType,
547 ) -> Result<(StackType, Subst), String> {
548 if n < 0 {
549 return Err(format!("pick: index must be non-negative, got {}", n));
550 }
551
552 let type_at_n = self.get_type_at_position(¤t_stack, n as usize, "pick")?;
554
555 Ok((current_stack.push(type_at_n), Subst::empty()))
557 }
558
559 fn handle_literal_roll(
570 &self,
571 n: i64,
572 current_stack: StackType,
573 ) -> Result<(StackType, Subst), String> {
574 if n < 0 {
575 return Err(format!("roll: index must be non-negative, got {}", n));
576 }
577
578 self.rotate_type_to_top(current_stack, n as usize)
583 }
584
585 fn get_type_at_position(&self, stack: &StackType, n: usize, op: &str) -> Result<Type, String> {
587 let mut current = stack;
588 let mut pos = 0;
589
590 loop {
591 match current {
592 StackType::Cons { rest, top } => {
593 if pos == n {
594 return Ok(top.clone());
595 }
596 pos += 1;
597 current = rest;
598 }
599 StackType::RowVar(name) => {
600 let fresh_type = Type::Var(self.fresh_var(&format!("{}_{}", op, name)));
610 return Ok(fresh_type);
611 }
612 StackType::Empty => {
613 return Err(format!(
614 "{}{}: stack underflow - position {} requested but stack has only {} concrete items",
615 self.line_prefix(),
616 op,
617 n,
618 pos
619 ));
620 }
621 }
622 }
623 }
624
625 fn rotate_type_to_top(&self, stack: StackType, n: usize) -> Result<(StackType, Subst), String> {
627 if n == 0 {
628 return Ok((stack, Subst::empty()));
630 }
631
632 let mut types_above: Vec<Type> = Vec::new();
634 let mut current = stack;
635 let mut pos = 0;
636
637 loop {
639 match current {
640 StackType::Cons { rest, top } => {
641 if pos == n {
642 let mut result = *rest;
645 for ty in types_above.into_iter().rev() {
647 result = result.push(ty);
648 }
649 result = result.push(top);
651 return Ok((result, Subst::empty()));
652 }
653 types_above.push(top);
654 pos += 1;
655 current = *rest;
656 }
657 StackType::RowVar(name) => {
658 let fresh_type = Type::Var(self.fresh_var(&format!("roll_{}", name)));
671
672 let mut result = StackType::RowVar(name.clone());
674 for ty in types_above.into_iter().rev() {
675 result = result.push(ty);
676 }
677 result = result.push(fresh_type);
678 return Ok((result, Subst::empty()));
679 }
680 StackType::Empty => {
681 return Err(format!(
682 "{}roll: stack underflow - position {} requested but stack has only {} items",
683 self.line_prefix(),
684 n,
685 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 match_span: &Option<crate::ast::Span>,
717 current_stack: StackType,
718 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
719 if arms.is_empty() {
720 return Err("match expression must have at least one arm".to_string());
721 }
722
723 let (stack_after_match, _matched_type) =
725 self.pop_type(¤t_stack, "match expression")?;
726
727 let mut arm_results: Vec<StackType> = Vec::new();
729 let mut combined_subst = Subst::empty();
730 let mut merged_effects: Vec<SideEffect> = Vec::new();
731
732 for arm in arms {
733 let variant_name = match &arm.pattern {
735 crate::ast::Pattern::Variant(name) => name.as_str(),
736 crate::ast::Pattern::VariantWithBindings { name, .. } => name.as_str(),
737 };
738
739 let (_union_name, variant_info) = self
741 .find_variant(variant_name)
742 .ok_or_else(|| format!("Unknown variant '{}' in match pattern", variant_name))?;
743
744 let arm_stack = self.push_variant_fields(
746 &stack_after_match,
747 &arm.pattern,
748 variant_info,
749 variant_name,
750 )?;
751
752 let (arm_result, arm_subst, arm_effects) =
755 self.infer_statements_from(&arm.body, &arm_stack, false)?;
756
757 combined_subst = combined_subst.compose(&arm_subst);
758 arm_results.push(arm_result);
759
760 for effect in arm_effects {
762 if !merged_effects.contains(&effect) {
763 merged_effects.push(effect);
764 }
765 }
766 }
767
768 let mut final_result = arm_results[0].clone();
770 for (i, arm_result) in arm_results.iter().enumerate().skip(1) {
771 let match_line = match_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
773 let arm0_line = arms[0].span.as_ref().map(|s| s.line + 1).unwrap_or(0);
774 let arm_i_line = arms[i].span.as_ref().map(|s| s.line + 1).unwrap_or(0);
775
776 let arm_subst = unify_stacks(&final_result, arm_result).map_err(|e| {
777 if match_line > 0 && arm0_line > 0 && arm_i_line > 0 {
778 format!(
779 "at line {}: match arms have incompatible stack effects:\n\
780 \x20 arm 0 (line {}) produces: {}\n\
781 \x20 arm {} (line {}) produces: {}\n\
782 \x20 All match arms must produce the same stack shape.\n\
783 \x20 Error: {}",
784 match_line, arm0_line, final_result, i, arm_i_line, arm_result, e
785 )
786 } else {
787 format!(
788 "match arms have incompatible stack effects:\n\
789 \x20 arm 0 produces: {}\n\
790 \x20 arm {} produces: {}\n\
791 \x20 All match arms must produce the same stack shape.\n\
792 \x20 Error: {}",
793 final_result, i, arm_result, e
794 )
795 }
796 })?;
797 combined_subst = combined_subst.compose(&arm_subst);
798 final_result = arm_subst.apply_stack(&final_result);
799 }
800
801 Ok((final_result, combined_subst, merged_effects))
802 }
803
804 fn push_variant_fields(
806 &self,
807 stack: &StackType,
808 pattern: &crate::ast::Pattern,
809 variant_info: &VariantInfo,
810 variant_name: &str,
811 ) -> Result<StackType, String> {
812 let mut arm_stack = stack.clone();
813 match pattern {
814 crate::ast::Pattern::Variant(_) => {
815 for field in &variant_info.fields {
817 arm_stack = arm_stack.push(field.field_type.clone());
818 }
819 }
820 crate::ast::Pattern::VariantWithBindings { bindings, .. } => {
821 for binding in bindings {
823 let field = variant_info
824 .fields
825 .iter()
826 .find(|f| &f.name == binding)
827 .ok_or_else(|| {
828 let available: Vec<_> = variant_info
829 .fields
830 .iter()
831 .map(|f| f.name.as_str())
832 .collect();
833 format!(
834 "Unknown field '{}' in pattern for variant '{}'.\n\
835 Available fields: {}",
836 binding,
837 variant_name,
838 available.join(", ")
839 )
840 })?;
841 arm_stack = arm_stack.push(field.field_type.clone());
842 }
843 }
844 }
845 Ok(arm_stack)
846 }
847
848 fn is_divergent_branch(&self, statements: &[Statement]) -> bool {
869 let Some((current_word_name, _)) = self.current_word.borrow().as_ref().cloned() else {
870 return false;
871 };
872 let Some(Statement::WordCall { name, .. }) = statements.last() else {
873 return false;
874 };
875
876 if name == ¤t_word_name {
878 return true;
879 }
880
881 if let Some(ref graph) = self.call_graph
883 && graph.are_mutually_recursive(¤t_word_name, name)
884 {
885 return true;
886 }
887
888 false
889 }
890
891 fn infer_if(
893 &self,
894 then_branch: &[Statement],
895 else_branch: &Option<Vec<Statement>>,
896 if_span: &Option<crate::ast::Span>,
897 current_stack: StackType,
898 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
899 let (stack_after_cond, cond_type) = self.pop_type(¤t_stack, "if condition")?;
901
902 let cond_subst = unify_stacks(
904 &StackType::singleton(Type::Bool),
905 &StackType::singleton(cond_type),
906 )
907 .map_err(|e| format!("if condition must be Bool: {}", e))?;
908
909 let stack_after_cond = cond_subst.apply_stack(&stack_after_cond);
910
911 let then_diverges = self.is_divergent_branch(then_branch);
913 let else_diverges = else_branch
914 .as_ref()
915 .map(|stmts| self.is_divergent_branch(stmts))
916 .unwrap_or(false);
917
918 let (then_result, then_subst, then_effects) =
921 self.infer_statements_from(then_branch, &stack_after_cond, false)?;
922
923 let (else_result, else_subst, else_effects) = if let Some(else_stmts) = else_branch {
925 self.infer_statements_from(else_stmts, &stack_after_cond, false)?
926 } else {
927 (stack_after_cond.clone(), Subst::empty(), vec![])
928 };
929
930 let mut merged_effects = then_effects;
932 for effect in else_effects {
933 if !merged_effects.contains(&effect) {
934 merged_effects.push(effect);
935 }
936 }
937
938 let (result, branch_subst) = if then_diverges && !else_diverges {
944 (else_result, Subst::empty())
946 } else if else_diverges && !then_diverges {
947 (then_result, Subst::empty())
949 } else {
950 let if_line = if_span.as_ref().map(|s| s.line + 1).unwrap_or(0);
952 let branch_subst = unify_stacks(&then_result, &else_result).map_err(|e| {
953 if if_line > 0 {
954 format!(
955 "at line {}: if/else branches have incompatible stack effects:\n\
956 \x20 then branch produces: {}\n\
957 \x20 else branch produces: {}\n\
958 \x20 Both branches of an if/else must produce the same stack shape.\n\
959 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
960 \x20 Error: {}",
961 if_line, then_result, else_result, e
962 )
963 } else {
964 format!(
965 "if/else branches have incompatible stack effects:\n\
966 \x20 then branch produces: {}\n\
967 \x20 else branch produces: {}\n\
968 \x20 Both branches of an if/else must produce the same stack shape.\n\
969 \x20 Hint: Make sure both branches push/pop the same number of values.\n\
970 \x20 Error: {}",
971 then_result, else_result, e
972 )
973 }
974 })?;
975 (branch_subst.apply_stack(&then_result), branch_subst)
976 };
977
978 let total_subst = cond_subst
980 .compose(&then_subst)
981 .compose(&else_subst)
982 .compose(&branch_subst);
983 Ok((result, total_subst, merged_effects))
984 }
985
986 fn infer_quotation(
989 &self,
990 id: usize,
991 body: &[Statement],
992 current_stack: StackType,
993 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
994 let expected_for_this_quotation = self.expected_quotation_type.borrow().clone();
997 *self.expected_quotation_type.borrow_mut() = None;
998
999 let body_effect = self.infer_statements(body)?;
1001
1002 *self.expected_quotation_type.borrow_mut() = expected_for_this_quotation;
1004
1005 let quot_type = self.analyze_captures(&body_effect, ¤t_stack)?;
1007
1008 self.quotation_types
1010 .borrow_mut()
1011 .insert(id, quot_type.clone());
1012
1013 let result_stack = match "_type {
1015 Type::Quotation(_) => {
1016 current_stack.push(quot_type)
1018 }
1019 Type::Closure { captures, .. } => {
1020 let mut stack = current_stack.clone();
1022 for _ in 0..captures.len() {
1023 let (new_stack, _value) = self.pop_type(&stack, "closure capture")?;
1024 stack = new_stack;
1025 }
1026 stack.push(quot_type)
1027 }
1028 _ => unreachable!("analyze_captures only returns Quotation or Closure"),
1029 };
1030
1031 Ok((result_stack, Subst::empty(), vec![]))
1035 }
1036
1037 fn infer_word_call(
1039 &self,
1040 name: &str,
1041 span: &Option<crate::ast::Span>,
1042 current_stack: StackType,
1043 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1044 if name == "call" {
1047 return self.infer_call(span, current_stack);
1048 }
1049
1050 let effect = self
1052 .lookup_word_effect(name)
1053 .ok_or_else(|| format!("Unknown word: '{}'", name))?;
1054
1055 let fresh_effect = self.freshen_effect(&effect);
1057
1058 let adjusted_stack = if name == "strand.spawn" {
1060 self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
1061 } else {
1062 current_stack
1063 };
1064
1065 let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name, span)?;
1067
1068 let propagated_effects = fresh_effect.effects.clone();
1072
1073 Ok((result_stack, subst, propagated_effects))
1074 }
1075
1076 fn infer_call(
1082 &self,
1083 span: &Option<crate::ast::Span>,
1084 current_stack: StackType,
1085 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1086 let line_prefix = self.line_prefix();
1088 let (remaining_stack, quot_type) = current_stack.clone().pop().ok_or_else(|| {
1089 format!(
1090 "{}call: stack underflow - expected quotation on stack",
1091 line_prefix
1092 )
1093 })?;
1094
1095 let quot_effect = match "_type {
1097 Type::Quotation(effect) => (**effect).clone(),
1098 Type::Closure { effect, .. } => (**effect).clone(),
1099 Type::Var(_) => {
1100 let effect = self
1103 .lookup_word_effect("call")
1104 .ok_or_else(|| "Unknown word: 'call'".to_string())?;
1105 let fresh_effect = self.freshen_effect(&effect);
1106 let (result_stack, subst) =
1107 self.apply_effect(&fresh_effect, current_stack, "call", span)?;
1108 return Ok((result_stack, subst, vec![]));
1109 }
1110 _ => {
1111 return Err(format!(
1112 "call: expected quotation or closure on stack, got {}",
1113 quot_type
1114 ));
1115 }
1116 };
1117
1118 if quot_effect.has_yield() {
1120 return Err("Cannot call quotation with Yield effect directly.\n\
1121 Quotations that yield values must be wrapped with `strand.weave`.\n\
1122 Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
1123 .to_string());
1124 }
1125
1126 let fresh_effect = self.freshen_effect("_effect);
1128
1129 let (result_stack, subst) =
1131 self.apply_effect(&fresh_effect, remaining_stack, "call", span)?;
1132
1133 let propagated_effects = fresh_effect.effects.clone();
1135
1136 Ok((result_stack, subst, propagated_effects))
1137 }
1138
1139 fn infer_statement(
1142 &self,
1143 statement: &Statement,
1144 current_stack: StackType,
1145 ) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
1146 match statement {
1147 Statement::IntLiteral(_) => Ok((current_stack.push(Type::Int), Subst::empty(), vec![])),
1148 Statement::BoolLiteral(_) => {
1149 Ok((current_stack.push(Type::Bool), Subst::empty(), vec![]))
1150 }
1151 Statement::StringLiteral(_) => {
1152 Ok((current_stack.push(Type::String), Subst::empty(), vec![]))
1153 }
1154 Statement::FloatLiteral(_) => {
1155 Ok((current_stack.push(Type::Float), Subst::empty(), vec![]))
1156 }
1157 Statement::Symbol(_) => Ok((current_stack.push(Type::Symbol), Subst::empty(), vec![])),
1158 Statement::Match { arms, span } => self.infer_match(arms, span, current_stack),
1159 Statement::WordCall { name, span } => self.infer_word_call(name, span, current_stack),
1160 Statement::If {
1161 then_branch,
1162 else_branch,
1163 span,
1164 } => self.infer_if(then_branch, else_branch, span, current_stack),
1165 Statement::Quotation { id, body, .. } => self.infer_quotation(*id, body, current_stack),
1166 }
1167 }
1168
1169 fn lookup_word_effect(&self, name: &str) -> Option<Effect> {
1171 if let Some(effect) = builtin_signature(name) {
1173 return Some(effect);
1174 }
1175
1176 self.env.get(name).cloned()
1178 }
1179
1180 fn apply_effect(
1185 &self,
1186 effect: &Effect,
1187 current_stack: StackType,
1188 operation: &str,
1189 span: &Option<crate::ast::Span>,
1190 ) -> Result<(StackType, Subst), String> {
1191 let effect_concrete = Self::count_concrete_types(&effect.inputs);
1199 let stack_concrete = Self::count_concrete_types(¤t_stack);
1200
1201 if let Some(row_var_name) = Self::get_row_var_base(¤t_stack) {
1202 let is_rigid = row_var_name == "rest";
1215
1216 if is_rigid && effect_concrete > stack_concrete {
1217 let word_name = self
1218 .current_word
1219 .borrow()
1220 .as_ref()
1221 .map(|(n, _)| n.clone())
1222 .unwrap_or_else(|| "unknown".to_string());
1223 return Err(format!(
1224 "{}In '{}': {}: stack underflow - requires {} value(s), only {} provided",
1225 self.line_prefix(),
1226 word_name,
1227 operation,
1228 effect_concrete,
1229 stack_concrete
1230 ));
1231 }
1232 }
1233
1234 let subst = unify_stacks(&effect.inputs, ¤t_stack).map_err(|e| {
1236 let line_info = span
1237 .as_ref()
1238 .map(|s| format_line_prefix(s.line))
1239 .unwrap_or_default();
1240 format!(
1241 "{}{}: stack type mismatch. Expected {}, got {}: {}",
1242 line_info, operation, effect.inputs, current_stack, e
1243 )
1244 })?;
1245
1246 let result_stack = subst.apply_stack(&effect.outputs);
1248
1249 Ok((result_stack, subst))
1250 }
1251
1252 fn count_concrete_types(stack: &StackType) -> usize {
1254 let mut count = 0;
1255 let mut current = stack;
1256 while let StackType::Cons { rest, top: _ } = current {
1257 count += 1;
1258 current = rest;
1259 }
1260 count
1261 }
1262
1263 fn get_row_var_base(stack: &StackType) -> Option<String> {
1265 let mut current = stack;
1266 while let StackType::Cons { rest, top: _ } = current {
1267 current = rest;
1268 }
1269 match current {
1270 StackType::RowVar(name) => Some(name.clone()),
1271 _ => None,
1272 }
1273 }
1274
1275 fn adjust_stack_for_spawn(
1280 &self,
1281 current_stack: StackType,
1282 spawn_effect: &Effect,
1283 ) -> Result<StackType, String> {
1284 let expected_quot_type = match &spawn_effect.inputs {
1287 StackType::Cons { top, rest: _ } => {
1288 if !matches!(top, Type::Quotation(_)) {
1289 return Ok(current_stack); }
1291 top
1292 }
1293 _ => return Ok(current_stack),
1294 };
1295
1296 let (rest_stack, actual_type) = match ¤t_stack {
1298 StackType::Cons { rest, top } => (rest.as_ref().clone(), top),
1299 _ => return Ok(current_stack), };
1301
1302 if let Type::Quotation(actual_effect) = actual_type {
1304 if !matches!(actual_effect.inputs, StackType::Empty) {
1306 let expected_effect = match expected_quot_type {
1308 Type::Quotation(eff) => eff.as_ref(),
1309 _ => return Ok(current_stack),
1310 };
1311
1312 let captures = calculate_captures(actual_effect, expected_effect)?;
1314
1315 let closure_type = Type::Closure {
1317 effect: Box::new(expected_effect.clone()),
1318 captures: captures.clone(),
1319 };
1320
1321 let mut adjusted_stack = rest_stack;
1324 for _ in &captures {
1325 adjusted_stack = match adjusted_stack {
1326 StackType::Cons { rest, .. } => rest.as_ref().clone(),
1327 _ => {
1328 return Err(format!(
1329 "strand.spawn: not enough values on stack to capture. Need {} values",
1330 captures.len()
1331 ));
1332 }
1333 };
1334 }
1335
1336 return Ok(adjusted_stack.push(closure_type));
1338 }
1339 }
1340
1341 Ok(current_stack)
1342 }
1343
1344 fn analyze_captures(
1370 &self,
1371 body_effect: &Effect,
1372 _current_stack: &StackType,
1373 ) -> Result<Type, String> {
1374 let expected = self.expected_quotation_type.borrow().clone();
1376
1377 match expected {
1378 Some(Type::Closure { effect, .. }) => {
1379 let captures = calculate_captures(body_effect, &effect)?;
1381 Ok(Type::Closure { effect, captures })
1382 }
1383 Some(Type::Quotation(expected_effect)) => {
1384 let expected_is_empty = matches!(expected_effect.inputs, StackType::Empty);
1390 let body_needs_inputs = !matches!(body_effect.inputs, StackType::Empty);
1391
1392 if expected_is_empty && body_needs_inputs {
1393 let captures = calculate_captures(body_effect, &expected_effect)?;
1396 Ok(Type::Closure {
1397 effect: expected_effect,
1398 captures,
1399 })
1400 } else {
1401 let body_quot = Type::Quotation(Box::new(body_effect.clone()));
1407 let expected_quot = Type::Quotation(expected_effect.clone());
1408 unify_types(&body_quot, &expected_quot).map_err(|e| {
1409 format!(
1410 "quotation effect mismatch: expected {}, got {}: {}",
1411 expected_effect, body_effect, e
1412 )
1413 })?;
1414
1415 Ok(Type::Quotation(expected_effect))
1417 }
1418 }
1419 _ => {
1420 Ok(Type::Quotation(Box::new(body_effect.clone())))
1422 }
1423 }
1424 }
1425
1426 fn effect_matches_any(&self, inferred: &SideEffect, declared: &[SideEffect]) -> bool {
1430 declared.iter().any(|decl| match (inferred, decl) {
1431 (SideEffect::Yield(_), SideEffect::Yield(_)) => true,
1432 })
1433 }
1434
1435 fn pop_type(&self, stack: &StackType, context: &str) -> Result<(StackType, Type), String> {
1437 match stack {
1438 StackType::Cons { rest, top } => Ok(((**rest).clone(), top.clone())),
1439 StackType::Empty => Err(format!(
1440 "{}: stack underflow - expected value on stack but stack is empty",
1441 context
1442 )),
1443 StackType::RowVar(_) => {
1444 Err(format!(
1448 "{}: cannot pop from polymorphic stack without more type information",
1449 context
1450 ))
1451 }
1452 }
1453 }
1454}
1455
1456impl Default for TypeChecker {
1457 fn default() -> Self {
1458 Self::new()
1459 }
1460}
1461
1462#[cfg(test)]
1463mod tests {
1464 use super::*;
1465
1466 #[test]
1467 fn test_simple_literal() {
1468 let program = Program {
1469 includes: vec![],
1470 unions: vec![],
1471 words: vec![WordDef {
1472 name: "test".to_string(),
1473 effect: Some(Effect::new(
1474 StackType::Empty,
1475 StackType::singleton(Type::Int),
1476 )),
1477 body: vec![Statement::IntLiteral(42)],
1478 source: None,
1479 allowed_lints: vec![],
1480 }],
1481 };
1482
1483 let mut checker = TypeChecker::new();
1484 assert!(checker.check_program(&program).is_ok());
1485 }
1486
1487 #[test]
1488 fn test_simple_operation() {
1489 let program = Program {
1491 includes: vec![],
1492 unions: vec![],
1493 words: vec![WordDef {
1494 name: "test".to_string(),
1495 effect: Some(Effect::new(
1496 StackType::Empty.push(Type::Int).push(Type::Int),
1497 StackType::singleton(Type::Int),
1498 )),
1499 body: vec![Statement::WordCall {
1500 name: "i.add".to_string(),
1501 span: None,
1502 }],
1503 source: None,
1504 allowed_lints: vec![],
1505 }],
1506 };
1507
1508 let mut checker = TypeChecker::new();
1509 assert!(checker.check_program(&program).is_ok());
1510 }
1511
1512 #[test]
1513 fn test_type_mismatch() {
1514 let program = Program {
1516 includes: vec![],
1517 unions: vec![],
1518 words: vec![WordDef {
1519 name: "test".to_string(),
1520 effect: Some(Effect::new(
1521 StackType::singleton(Type::String),
1522 StackType::Empty,
1523 )),
1524 body: vec![
1525 Statement::IntLiteral(42), Statement::WordCall {
1527 name: "io.write-line".to_string(),
1528 span: None,
1529 },
1530 ],
1531 source: None,
1532 allowed_lints: vec![],
1533 }],
1534 };
1535
1536 let mut checker = TypeChecker::new();
1537 let result = checker.check_program(&program);
1538 assert!(result.is_err());
1539 assert!(result.unwrap_err().contains("Type mismatch"));
1540 }
1541
1542 #[test]
1543 fn test_polymorphic_dup() {
1544 let program = Program {
1546 includes: vec![],
1547 unions: vec![],
1548 words: vec![WordDef {
1549 name: "my-dup".to_string(),
1550 effect: Some(Effect::new(
1551 StackType::singleton(Type::Int),
1552 StackType::Empty.push(Type::Int).push(Type::Int),
1553 )),
1554 body: vec![Statement::WordCall {
1555 name: "dup".to_string(),
1556 span: None,
1557 }],
1558 source: None,
1559 allowed_lints: vec![],
1560 }],
1561 };
1562
1563 let mut checker = TypeChecker::new();
1564 assert!(checker.check_program(&program).is_ok());
1565 }
1566
1567 #[test]
1568 fn test_conditional_branches() {
1569 let program = Program {
1572 includes: vec![],
1573 unions: vec![],
1574 words: vec![WordDef {
1575 name: "test".to_string(),
1576 effect: Some(Effect::new(
1577 StackType::Empty.push(Type::Int).push(Type::Int),
1578 StackType::singleton(Type::String),
1579 )),
1580 body: vec![
1581 Statement::WordCall {
1582 name: "i.>".to_string(),
1583 span: None,
1584 },
1585 Statement::If {
1586 then_branch: vec![Statement::StringLiteral("greater".to_string())],
1587 else_branch: Some(vec![Statement::StringLiteral(
1588 "not greater".to_string(),
1589 )]),
1590 span: None,
1591 },
1592 ],
1593 source: None,
1594 allowed_lints: vec![],
1595 }],
1596 };
1597
1598 let mut checker = TypeChecker::new();
1599 assert!(checker.check_program(&program).is_ok());
1600 }
1601
1602 #[test]
1603 fn test_mismatched_branches() {
1604 let program = Program {
1607 includes: vec![],
1608 unions: vec![],
1609 words: vec![WordDef {
1610 name: "test".to_string(),
1611 effect: Some(Effect::new(
1612 StackType::Empty,
1613 StackType::singleton(Type::Int),
1614 )),
1615 body: vec![
1616 Statement::BoolLiteral(true),
1617 Statement::If {
1618 then_branch: vec![Statement::IntLiteral(42)],
1619 else_branch: Some(vec![Statement::StringLiteral("string".to_string())]),
1620 span: None,
1621 },
1622 ],
1623 source: None,
1624 allowed_lints: vec![],
1625 }],
1626 };
1627
1628 let mut checker = TypeChecker::new();
1629 let result = checker.check_program(&program);
1630 assert!(result.is_err());
1631 assert!(result.unwrap_err().contains("incompatible"));
1632 }
1633
1634 #[test]
1635 fn test_user_defined_word_call() {
1636 let program = Program {
1639 includes: vec![],
1640 unions: vec![],
1641 words: vec![
1642 WordDef {
1643 name: "helper".to_string(),
1644 effect: Some(Effect::new(
1645 StackType::singleton(Type::Int),
1646 StackType::singleton(Type::String),
1647 )),
1648 body: vec![Statement::WordCall {
1649 name: "int->string".to_string(),
1650 span: None,
1651 }],
1652 source: None,
1653 allowed_lints: vec![],
1654 },
1655 WordDef {
1656 name: "main".to_string(),
1657 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1658 body: vec![
1659 Statement::IntLiteral(42),
1660 Statement::WordCall {
1661 name: "helper".to_string(),
1662 span: None,
1663 },
1664 Statement::WordCall {
1665 name: "io.write-line".to_string(),
1666 span: None,
1667 },
1668 ],
1669 source: None,
1670 allowed_lints: vec![],
1671 },
1672 ],
1673 };
1674
1675 let mut checker = TypeChecker::new();
1676 assert!(checker.check_program(&program).is_ok());
1677 }
1678
1679 #[test]
1680 fn test_arithmetic_chain() {
1681 let program = Program {
1684 includes: vec![],
1685 unions: vec![],
1686 words: vec![WordDef {
1687 name: "test".to_string(),
1688 effect: Some(Effect::new(
1689 StackType::Empty
1690 .push(Type::Int)
1691 .push(Type::Int)
1692 .push(Type::Int),
1693 StackType::singleton(Type::Int),
1694 )),
1695 body: vec![
1696 Statement::WordCall {
1697 name: "i.add".to_string(),
1698 span: None,
1699 },
1700 Statement::WordCall {
1701 name: "i.multiply".to_string(),
1702 span: None,
1703 },
1704 ],
1705 source: None,
1706 allowed_lints: vec![],
1707 }],
1708 };
1709
1710 let mut checker = TypeChecker::new();
1711 assert!(checker.check_program(&program).is_ok());
1712 }
1713
1714 #[test]
1715 fn test_write_line_type_error() {
1716 let program = Program {
1718 includes: vec![],
1719 unions: vec![],
1720 words: vec![WordDef {
1721 name: "test".to_string(),
1722 effect: Some(Effect::new(
1723 StackType::singleton(Type::Int),
1724 StackType::Empty,
1725 )),
1726 body: vec![Statement::WordCall {
1727 name: "io.write-line".to_string(),
1728 span: None,
1729 }],
1730 source: None,
1731 allowed_lints: vec![],
1732 }],
1733 };
1734
1735 let mut checker = TypeChecker::new();
1736 let result = checker.check_program(&program);
1737 assert!(result.is_err());
1738 assert!(result.unwrap_err().contains("Type mismatch"));
1739 }
1740
1741 #[test]
1742 fn test_stack_underflow_drop() {
1743 let program = Program {
1745 includes: vec![],
1746 unions: vec![],
1747 words: vec![WordDef {
1748 name: "test".to_string(),
1749 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1750 body: vec![Statement::WordCall {
1751 name: "drop".to_string(),
1752 span: None,
1753 }],
1754 source: None,
1755 allowed_lints: vec![],
1756 }],
1757 };
1758
1759 let mut checker = TypeChecker::new();
1760 let result = checker.check_program(&program);
1761 assert!(result.is_err());
1762 assert!(result.unwrap_err().contains("mismatch"));
1763 }
1764
1765 #[test]
1766 fn test_stack_underflow_add() {
1767 let program = Program {
1769 includes: vec![],
1770 unions: vec![],
1771 words: vec![WordDef {
1772 name: "test".to_string(),
1773 effect: Some(Effect::new(
1774 StackType::singleton(Type::Int),
1775 StackType::singleton(Type::Int),
1776 )),
1777 body: vec![Statement::WordCall {
1778 name: "i.add".to_string(),
1779 span: None,
1780 }],
1781 source: None,
1782 allowed_lints: vec![],
1783 }],
1784 };
1785
1786 let mut checker = TypeChecker::new();
1787 let result = checker.check_program(&program);
1788 assert!(result.is_err());
1789 assert!(result.unwrap_err().contains("mismatch"));
1790 }
1791
1792 #[test]
1795 fn test_stack_underflow_rot_issue_169() {
1796 let program = Program {
1800 includes: vec![],
1801 unions: vec![],
1802 words: vec![WordDef {
1803 name: "test".to_string(),
1804 effect: Some(Effect::new(
1805 StackType::RowVar("rest".to_string()),
1806 StackType::RowVar("rest".to_string()),
1807 )),
1808 body: vec![
1809 Statement::IntLiteral(3),
1810 Statement::IntLiteral(4),
1811 Statement::WordCall {
1812 name: "rot".to_string(),
1813 span: None,
1814 },
1815 ],
1816 source: None,
1817 allowed_lints: vec![],
1818 }],
1819 };
1820
1821 let mut checker = TypeChecker::new();
1822 let result = checker.check_program(&program);
1823 assert!(result.is_err(), "rot with 2 values should fail");
1824 let err = result.unwrap_err();
1825 assert!(
1826 err.contains("stack underflow") || err.contains("requires 3"),
1827 "Error should mention underflow: {}",
1828 err
1829 );
1830 }
1831
1832 #[test]
1833 fn test_csp_operations() {
1834 let program = Program {
1841 includes: vec![],
1842 unions: vec![],
1843 words: vec![WordDef {
1844 name: "test".to_string(),
1845 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
1846 body: vec![
1847 Statement::WordCall {
1848 name: "chan.make".to_string(),
1849 span: None,
1850 },
1851 Statement::IntLiteral(42),
1852 Statement::WordCall {
1853 name: "swap".to_string(),
1854 span: None,
1855 },
1856 Statement::WordCall {
1857 name: "chan.send".to_string(),
1858 span: None,
1859 },
1860 Statement::WordCall {
1861 name: "drop".to_string(),
1862 span: None,
1863 },
1864 ],
1865 source: None,
1866 allowed_lints: vec![],
1867 }],
1868 };
1869
1870 let mut checker = TypeChecker::new();
1871 assert!(checker.check_program(&program).is_ok());
1872 }
1873
1874 #[test]
1875 fn test_complex_stack_shuffling() {
1876 let program = Program {
1879 includes: vec![],
1880 unions: vec![],
1881 words: vec![WordDef {
1882 name: "test".to_string(),
1883 effect: Some(Effect::new(
1884 StackType::Empty
1885 .push(Type::Int)
1886 .push(Type::Int)
1887 .push(Type::Int),
1888 StackType::singleton(Type::Int),
1889 )),
1890 body: vec![
1891 Statement::WordCall {
1892 name: "rot".to_string(),
1893 span: None,
1894 },
1895 Statement::WordCall {
1896 name: "i.add".to_string(),
1897 span: None,
1898 },
1899 Statement::WordCall {
1900 name: "i.add".to_string(),
1901 span: None,
1902 },
1903 ],
1904 source: None,
1905 allowed_lints: vec![],
1906 }],
1907 };
1908
1909 let mut checker = TypeChecker::new();
1910 assert!(checker.check_program(&program).is_ok());
1911 }
1912
1913 #[test]
1914 fn test_empty_program() {
1915 let program = Program {
1917 includes: vec![],
1918 unions: vec![],
1919 words: vec![],
1920 };
1921
1922 let mut checker = TypeChecker::new();
1923 assert!(checker.check_program(&program).is_ok());
1924 }
1925
1926 #[test]
1927 fn test_word_without_effect_declaration() {
1928 let program = Program {
1930 includes: vec![],
1931 unions: vec![],
1932 words: vec![WordDef {
1933 name: "helper".to_string(),
1934 effect: None,
1935 body: vec![Statement::IntLiteral(42)],
1936 source: None,
1937 allowed_lints: vec![],
1938 }],
1939 };
1940
1941 let mut checker = TypeChecker::new();
1942 let result = checker.check_program(&program);
1943 assert!(result.is_err());
1944 assert!(
1945 result
1946 .unwrap_err()
1947 .contains("missing a stack effect declaration")
1948 );
1949 }
1950
1951 #[test]
1952 fn test_nested_conditionals() {
1953 let program = Program {
1962 includes: vec![],
1963 unions: vec![],
1964 words: vec![WordDef {
1965 name: "test".to_string(),
1966 effect: Some(Effect::new(
1967 StackType::Empty
1968 .push(Type::Int)
1969 .push(Type::Int)
1970 .push(Type::Int)
1971 .push(Type::Int),
1972 StackType::singleton(Type::String),
1973 )),
1974 body: vec![
1975 Statement::WordCall {
1976 name: "i.>".to_string(),
1977 span: None,
1978 },
1979 Statement::If {
1980 then_branch: vec![
1981 Statement::WordCall {
1982 name: "i.>".to_string(),
1983 span: None,
1984 },
1985 Statement::If {
1986 then_branch: vec![Statement::StringLiteral(
1987 "both true".to_string(),
1988 )],
1989 else_branch: Some(vec![Statement::StringLiteral(
1990 "first true".to_string(),
1991 )]),
1992 span: None,
1993 },
1994 ],
1995 else_branch: Some(vec![
1996 Statement::WordCall {
1997 name: "drop".to_string(),
1998 span: None,
1999 },
2000 Statement::WordCall {
2001 name: "drop".to_string(),
2002 span: None,
2003 },
2004 Statement::StringLiteral("first false".to_string()),
2005 ]),
2006 span: None,
2007 },
2008 ],
2009 source: None,
2010 allowed_lints: vec![],
2011 }],
2012 };
2013
2014 let mut checker = TypeChecker::new();
2015 match checker.check_program(&program) {
2016 Ok(_) => {}
2017 Err(e) => panic!("Type check failed: {}", e),
2018 }
2019 }
2020
2021 #[test]
2022 fn test_conditional_without_else() {
2023 let program = Program {
2027 includes: vec![],
2028 unions: vec![],
2029 words: vec![WordDef {
2030 name: "test".to_string(),
2031 effect: Some(Effect::new(
2032 StackType::Empty.push(Type::Int).push(Type::Int),
2033 StackType::singleton(Type::Int),
2034 )),
2035 body: vec![
2036 Statement::WordCall {
2037 name: "i.>".to_string(),
2038 span: None,
2039 },
2040 Statement::If {
2041 then_branch: vec![Statement::IntLiteral(100)],
2042 else_branch: None, span: None,
2044 },
2045 ],
2046 source: None,
2047 allowed_lints: vec![],
2048 }],
2049 };
2050
2051 let mut checker = TypeChecker::new();
2052 let result = checker.check_program(&program);
2053 assert!(result.is_err());
2055 }
2056
2057 #[test]
2058 fn test_multiple_word_chain() {
2059 let program = Program {
2063 includes: vec![],
2064 unions: vec![],
2065 words: vec![
2066 WordDef {
2067 name: "helper1".to_string(),
2068 effect: Some(Effect::new(
2069 StackType::singleton(Type::Int),
2070 StackType::singleton(Type::String),
2071 )),
2072 body: vec![Statement::WordCall {
2073 name: "int->string".to_string(),
2074 span: None,
2075 }],
2076 source: None,
2077 allowed_lints: vec![],
2078 },
2079 WordDef {
2080 name: "helper2".to_string(),
2081 effect: Some(Effect::new(
2082 StackType::singleton(Type::String),
2083 StackType::Empty,
2084 )),
2085 body: vec![Statement::WordCall {
2086 name: "io.write-line".to_string(),
2087 span: None,
2088 }],
2089 source: None,
2090 allowed_lints: vec![],
2091 },
2092 WordDef {
2093 name: "main".to_string(),
2094 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2095 body: vec![
2096 Statement::IntLiteral(42),
2097 Statement::WordCall {
2098 name: "helper1".to_string(),
2099 span: None,
2100 },
2101 Statement::WordCall {
2102 name: "helper2".to_string(),
2103 span: None,
2104 },
2105 ],
2106 source: None,
2107 allowed_lints: vec![],
2108 },
2109 ],
2110 };
2111
2112 let mut checker = TypeChecker::new();
2113 assert!(checker.check_program(&program).is_ok());
2114 }
2115
2116 #[test]
2117 fn test_all_stack_ops() {
2118 let program = Program {
2121 includes: vec![],
2122 unions: vec![],
2123 words: vec![WordDef {
2124 name: "test".to_string(),
2125 effect: Some(Effect::new(
2126 StackType::Empty
2127 .push(Type::Int)
2128 .push(Type::Int)
2129 .push(Type::Int),
2130 StackType::Empty
2131 .push(Type::Int)
2132 .push(Type::Int)
2133 .push(Type::Int)
2134 .push(Type::Int),
2135 )),
2136 body: vec![
2137 Statement::WordCall {
2138 name: "over".to_string(),
2139 span: None,
2140 },
2141 Statement::WordCall {
2142 name: "nip".to_string(),
2143 span: None,
2144 },
2145 Statement::WordCall {
2146 name: "tuck".to_string(),
2147 span: None,
2148 },
2149 ],
2150 source: None,
2151 allowed_lints: vec![],
2152 }],
2153 };
2154
2155 let mut checker = TypeChecker::new();
2156 assert!(checker.check_program(&program).is_ok());
2157 }
2158
2159 #[test]
2160 fn test_mixed_types_complex() {
2161 let program = Program {
2170 includes: vec![],
2171 unions: vec![],
2172 words: vec![WordDef {
2173 name: "test".to_string(),
2174 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2175 body: vec![
2176 Statement::IntLiteral(42),
2177 Statement::WordCall {
2178 name: "int->string".to_string(),
2179 span: None,
2180 },
2181 Statement::IntLiteral(100),
2182 Statement::IntLiteral(200),
2183 Statement::WordCall {
2184 name: "i.>".to_string(),
2185 span: None,
2186 },
2187 Statement::If {
2188 then_branch: vec![Statement::WordCall {
2189 name: "io.write-line".to_string(),
2190 span: None,
2191 }],
2192 else_branch: Some(vec![Statement::WordCall {
2193 name: "io.write-line".to_string(),
2194 span: None,
2195 }]),
2196 span: None,
2197 },
2198 ],
2199 source: None,
2200 allowed_lints: vec![],
2201 }],
2202 };
2203
2204 let mut checker = TypeChecker::new();
2205 assert!(checker.check_program(&program).is_ok());
2206 }
2207
2208 #[test]
2209 fn test_string_literal() {
2210 let program = Program {
2212 includes: vec![],
2213 unions: vec![],
2214 words: vec![WordDef {
2215 name: "test".to_string(),
2216 effect: Some(Effect::new(
2217 StackType::Empty,
2218 StackType::singleton(Type::String),
2219 )),
2220 body: vec![Statement::StringLiteral("hello".to_string())],
2221 source: None,
2222 allowed_lints: vec![],
2223 }],
2224 };
2225
2226 let mut checker = TypeChecker::new();
2227 assert!(checker.check_program(&program).is_ok());
2228 }
2229
2230 #[test]
2231 fn test_bool_literal() {
2232 let program = Program {
2235 includes: vec![],
2236 unions: vec![],
2237 words: vec![WordDef {
2238 name: "test".to_string(),
2239 effect: Some(Effect::new(
2240 StackType::Empty,
2241 StackType::singleton(Type::Bool),
2242 )),
2243 body: vec![Statement::BoolLiteral(true)],
2244 source: None,
2245 allowed_lints: vec![],
2246 }],
2247 };
2248
2249 let mut checker = TypeChecker::new();
2250 assert!(checker.check_program(&program).is_ok());
2251 }
2252
2253 #[test]
2254 fn test_type_error_in_nested_conditional() {
2255 let program = Program {
2262 includes: vec![],
2263 unions: vec![],
2264 words: vec![WordDef {
2265 name: "test".to_string(),
2266 effect: Some(Effect::new(StackType::Empty, StackType::Empty)),
2267 body: vec![
2268 Statement::IntLiteral(10),
2269 Statement::IntLiteral(20),
2270 Statement::WordCall {
2271 name: "i.>".to_string(),
2272 span: None,
2273 },
2274 Statement::If {
2275 then_branch: vec![
2276 Statement::IntLiteral(42),
2277 Statement::WordCall {
2278 name: "io.write-line".to_string(),
2279 span: None,
2280 },
2281 ],
2282 else_branch: Some(vec![
2283 Statement::StringLiteral("ok".to_string()),
2284 Statement::WordCall {
2285 name: "io.write-line".to_string(),
2286 span: None,
2287 },
2288 ]),
2289 span: None,
2290 },
2291 ],
2292 source: None,
2293 allowed_lints: vec![],
2294 }],
2295 };
2296
2297 let mut checker = TypeChecker::new();
2298 let result = checker.check_program(&program);
2299 assert!(result.is_err());
2300 assert!(result.unwrap_err().contains("Type mismatch"));
2301 }
2302
2303 #[test]
2304 fn test_read_line_operation() {
2305 let program = Program {
2308 includes: vec![],
2309 unions: vec![],
2310 words: vec![WordDef {
2311 name: "test".to_string(),
2312 effect: Some(Effect::new(
2313 StackType::Empty,
2314 StackType::from_vec(vec![Type::String, Type::Bool]),
2315 )),
2316 body: vec![Statement::WordCall {
2317 name: "io.read-line".to_string(),
2318 span: None,
2319 }],
2320 source: None,
2321 allowed_lints: vec![],
2322 }],
2323 };
2324
2325 let mut checker = TypeChecker::new();
2326 assert!(checker.check_program(&program).is_ok());
2327 }
2328
2329 #[test]
2330 fn test_comparison_operations() {
2331 let program = Program {
2336 includes: vec![],
2337 unions: vec![],
2338 words: vec![WordDef {
2339 name: "test".to_string(),
2340 effect: Some(Effect::new(
2341 StackType::Empty.push(Type::Int).push(Type::Int),
2342 StackType::singleton(Type::Bool),
2343 )),
2344 body: vec![Statement::WordCall {
2345 name: "i.<=".to_string(),
2346 span: None,
2347 }],
2348 source: None,
2349 allowed_lints: vec![],
2350 }],
2351 };
2352
2353 let mut checker = TypeChecker::new();
2354 assert!(checker.check_program(&program).is_ok());
2355 }
2356
2357 #[test]
2358 fn test_recursive_word_definitions() {
2359 let program = Program {
2365 includes: vec![],
2366 unions: vec![],
2367 words: vec![
2368 WordDef {
2369 name: "is-even".to_string(),
2370 effect: Some(Effect::new(
2371 StackType::singleton(Type::Int),
2372 StackType::singleton(Type::Int),
2373 )),
2374 body: vec![
2375 Statement::WordCall {
2376 name: "dup".to_string(),
2377 span: None,
2378 },
2379 Statement::IntLiteral(0),
2380 Statement::WordCall {
2381 name: "i.=".to_string(),
2382 span: None,
2383 },
2384 Statement::If {
2385 then_branch: vec![
2386 Statement::WordCall {
2387 name: "drop".to_string(),
2388 span: None,
2389 },
2390 Statement::IntLiteral(1),
2391 ],
2392 else_branch: Some(vec![
2393 Statement::IntLiteral(1),
2394 Statement::WordCall {
2395 name: "i.subtract".to_string(),
2396 span: None,
2397 },
2398 Statement::WordCall {
2399 name: "is-odd".to_string(),
2400 span: None,
2401 },
2402 ]),
2403 span: None,
2404 },
2405 ],
2406 source: None,
2407 allowed_lints: vec![],
2408 },
2409 WordDef {
2410 name: "is-odd".to_string(),
2411 effect: Some(Effect::new(
2412 StackType::singleton(Type::Int),
2413 StackType::singleton(Type::Int),
2414 )),
2415 body: vec![
2416 Statement::WordCall {
2417 name: "dup".to_string(),
2418 span: None,
2419 },
2420 Statement::IntLiteral(0),
2421 Statement::WordCall {
2422 name: "i.=".to_string(),
2423 span: None,
2424 },
2425 Statement::If {
2426 then_branch: vec![
2427 Statement::WordCall {
2428 name: "drop".to_string(),
2429 span: None,
2430 },
2431 Statement::IntLiteral(0),
2432 ],
2433 else_branch: Some(vec![
2434 Statement::IntLiteral(1),
2435 Statement::WordCall {
2436 name: "i.subtract".to_string(),
2437 span: None,
2438 },
2439 Statement::WordCall {
2440 name: "is-even".to_string(),
2441 span: None,
2442 },
2443 ]),
2444 span: None,
2445 },
2446 ],
2447 source: None,
2448 allowed_lints: vec![],
2449 },
2450 ],
2451 };
2452
2453 let mut checker = TypeChecker::new();
2454 assert!(checker.check_program(&program).is_ok());
2455 }
2456
2457 #[test]
2458 fn test_word_calling_word_with_row_polymorphism() {
2459 let program = Program {
2464 includes: vec![],
2465 unions: vec![],
2466 words: vec![
2467 WordDef {
2468 name: "apply-twice".to_string(),
2469 effect: Some(Effect::new(
2470 StackType::singleton(Type::Int),
2471 StackType::singleton(Type::Int),
2472 )),
2473 body: vec![
2474 Statement::WordCall {
2475 name: "dup".to_string(),
2476 span: None,
2477 },
2478 Statement::WordCall {
2479 name: "i.add".to_string(),
2480 span: None,
2481 },
2482 ],
2483 source: None,
2484 allowed_lints: vec![],
2485 },
2486 WordDef {
2487 name: "quad".to_string(),
2488 effect: Some(Effect::new(
2489 StackType::singleton(Type::Int),
2490 StackType::singleton(Type::Int),
2491 )),
2492 body: vec![
2493 Statement::WordCall {
2494 name: "apply-twice".to_string(),
2495 span: None,
2496 },
2497 Statement::WordCall {
2498 name: "apply-twice".to_string(),
2499 span: None,
2500 },
2501 ],
2502 source: None,
2503 allowed_lints: vec![],
2504 },
2505 ],
2506 };
2507
2508 let mut checker = TypeChecker::new();
2509 assert!(checker.check_program(&program).is_ok());
2510 }
2511
2512 #[test]
2513 fn test_deep_stack_types() {
2514 let mut stack_type = StackType::Empty;
2518 for _ in 0..10 {
2519 stack_type = stack_type.push(Type::Int);
2520 }
2521
2522 let program = Program {
2523 includes: vec![],
2524 unions: vec![],
2525 words: vec![WordDef {
2526 name: "test".to_string(),
2527 effect: Some(Effect::new(stack_type, StackType::singleton(Type::Int))),
2528 body: vec![
2529 Statement::WordCall {
2530 name: "i.add".to_string(),
2531 span: None,
2532 },
2533 Statement::WordCall {
2534 name: "i.add".to_string(),
2535 span: None,
2536 },
2537 Statement::WordCall {
2538 name: "i.add".to_string(),
2539 span: None,
2540 },
2541 Statement::WordCall {
2542 name: "i.add".to_string(),
2543 span: None,
2544 },
2545 Statement::WordCall {
2546 name: "i.add".to_string(),
2547 span: None,
2548 },
2549 Statement::WordCall {
2550 name: "i.add".to_string(),
2551 span: None,
2552 },
2553 Statement::WordCall {
2554 name: "i.add".to_string(),
2555 span: None,
2556 },
2557 Statement::WordCall {
2558 name: "i.add".to_string(),
2559 span: None,
2560 },
2561 Statement::WordCall {
2562 name: "i.add".to_string(),
2563 span: None,
2564 },
2565 ],
2566 source: None,
2567 allowed_lints: vec![],
2568 }],
2569 };
2570
2571 let mut checker = TypeChecker::new();
2572 assert!(checker.check_program(&program).is_ok());
2573 }
2574
2575 #[test]
2576 fn test_simple_quotation() {
2577 let program = Program {
2581 includes: vec![],
2582 unions: vec![],
2583 words: vec![WordDef {
2584 name: "test".to_string(),
2585 effect: Some(Effect::new(
2586 StackType::Empty,
2587 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2588 StackType::RowVar("input".to_string()).push(Type::Int),
2589 StackType::RowVar("input".to_string()).push(Type::Int),
2590 )))),
2591 )),
2592 body: vec![Statement::Quotation {
2593 span: None,
2594 id: 0,
2595 body: vec![
2596 Statement::IntLiteral(1),
2597 Statement::WordCall {
2598 name: "i.add".to_string(),
2599 span: None,
2600 },
2601 ],
2602 }],
2603 source: None,
2604 allowed_lints: vec![],
2605 }],
2606 };
2607
2608 let mut checker = TypeChecker::new();
2609 match checker.check_program(&program) {
2610 Ok(_) => {}
2611 Err(e) => panic!("Type check failed: {}", e),
2612 }
2613 }
2614
2615 #[test]
2616 fn test_empty_quotation() {
2617 let program = Program {
2621 includes: vec![],
2622 unions: vec![],
2623 words: vec![WordDef {
2624 name: "test".to_string(),
2625 effect: Some(Effect::new(
2626 StackType::Empty,
2627 StackType::singleton(Type::Quotation(Box::new(Effect::new(
2628 StackType::RowVar("input".to_string()),
2629 StackType::RowVar("input".to_string()),
2630 )))),
2631 )),
2632 body: vec![Statement::Quotation {
2633 span: None,
2634 id: 1,
2635 body: vec![],
2636 }],
2637 source: None,
2638 allowed_lints: vec![],
2639 }],
2640 };
2641
2642 let mut checker = TypeChecker::new();
2643 assert!(checker.check_program(&program).is_ok());
2644 }
2645
2646 #[test]
2647 fn test_nested_quotation() {
2648 let inner_quot_type = Type::Quotation(Box::new(Effect::new(
2652 StackType::RowVar("input".to_string()).push(Type::Int),
2653 StackType::RowVar("input".to_string()).push(Type::Int),
2654 )));
2655
2656 let outer_quot_type = Type::Quotation(Box::new(Effect::new(
2657 StackType::RowVar("input".to_string()),
2658 StackType::RowVar("input".to_string()).push(inner_quot_type.clone()),
2659 )));
2660
2661 let program = Program {
2662 includes: vec![],
2663 unions: vec![],
2664 words: vec![WordDef {
2665 name: "test".to_string(),
2666 effect: Some(Effect::new(
2667 StackType::Empty,
2668 StackType::singleton(outer_quot_type),
2669 )),
2670 body: vec![Statement::Quotation {
2671 span: None,
2672 id: 2,
2673 body: vec![Statement::Quotation {
2674 span: None,
2675 id: 3,
2676 body: vec![
2677 Statement::IntLiteral(1),
2678 Statement::WordCall {
2679 name: "i.add".to_string(),
2680 span: None,
2681 },
2682 ],
2683 }],
2684 }],
2685 source: None,
2686 allowed_lints: vec![],
2687 }],
2688 };
2689
2690 let mut checker = TypeChecker::new();
2691 assert!(checker.check_program(&program).is_ok());
2692 }
2693
2694 #[test]
2695 fn test_invalid_field_type_error() {
2696 use crate::ast::{UnionDef, UnionField, UnionVariant};
2697
2698 let program = Program {
2699 includes: vec![],
2700 unions: vec![UnionDef {
2701 name: "Message".to_string(),
2702 variants: vec![UnionVariant {
2703 name: "Get".to_string(),
2704 fields: vec![UnionField {
2705 name: "chan".to_string(),
2706 type_name: "InvalidType".to_string(),
2707 }],
2708 source: None,
2709 }],
2710 source: None,
2711 }],
2712 words: vec![],
2713 };
2714
2715 let mut checker = TypeChecker::new();
2716 let result = checker.check_program(&program);
2717 assert!(result.is_err());
2718 let err = result.unwrap_err();
2719 assert!(err.contains("Unknown type 'InvalidType'"));
2720 assert!(err.contains("chan"));
2721 assert!(err.contains("Get"));
2722 assert!(err.contains("Message"));
2723 }
2724
2725 #[test]
2726 fn test_roll_inside_conditional_with_concrete_stack() {
2727 let program = Program {
2735 includes: vec![],
2736 unions: vec![],
2737 words: vec![WordDef {
2738 name: "test".to_string(),
2739 effect: Some(Effect::new(
2740 StackType::Empty
2741 .push(Type::Int)
2742 .push(Type::Int)
2743 .push(Type::Int)
2744 .push(Type::Int),
2745 StackType::Empty
2746 .push(Type::Int)
2747 .push(Type::Int)
2748 .push(Type::Int)
2749 .push(Type::Int),
2750 )),
2751 body: vec![
2752 Statement::WordCall {
2753 name: "dup".to_string(),
2754 span: None,
2755 },
2756 Statement::IntLiteral(0),
2757 Statement::WordCall {
2758 name: "i.>".to_string(),
2759 span: None,
2760 },
2761 Statement::If {
2762 then_branch: vec![
2763 Statement::IntLiteral(3),
2764 Statement::WordCall {
2765 name: "roll".to_string(),
2766 span: None,
2767 },
2768 ],
2769 else_branch: Some(vec![
2770 Statement::WordCall {
2771 name: "rot".to_string(),
2772 span: None,
2773 },
2774 Statement::WordCall {
2775 name: "rot".to_string(),
2776 span: None,
2777 },
2778 ]),
2779 span: None,
2780 },
2781 ],
2782 source: None,
2783 allowed_lints: vec![],
2784 }],
2785 };
2786
2787 let mut checker = TypeChecker::new();
2788 match checker.check_program(&program) {
2790 Ok(_) => {}
2791 Err(e) => panic!("Type check failed: {}", e),
2792 }
2793 }
2794
2795 #[test]
2796 fn test_roll_inside_match_arm_with_concrete_stack() {
2797 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
2800
2801 let union_def = UnionDef {
2803 name: "Result".to_string(),
2804 variants: vec![
2805 UnionVariant {
2806 name: "Ok".to_string(),
2807 fields: vec![],
2808 source: None,
2809 },
2810 UnionVariant {
2811 name: "Err".to_string(),
2812 fields: vec![],
2813 source: None,
2814 },
2815 ],
2816 source: None,
2817 };
2818
2819 let program = Program {
2825 includes: vec![],
2826 unions: vec![union_def],
2827 words: vec![WordDef {
2828 name: "test".to_string(),
2829 effect: Some(Effect::new(
2830 StackType::Empty
2831 .push(Type::Int)
2832 .push(Type::Int)
2833 .push(Type::Int)
2834 .push(Type::Int)
2835 .push(Type::Union("Result".to_string())),
2836 StackType::Empty
2837 .push(Type::Int)
2838 .push(Type::Int)
2839 .push(Type::Int)
2840 .push(Type::Int),
2841 )),
2842 body: vec![Statement::Match {
2843 arms: vec![
2844 MatchArm {
2845 pattern: Pattern::Variant("Ok".to_string()),
2846 body: vec![
2847 Statement::IntLiteral(3),
2848 Statement::WordCall {
2849 name: "roll".to_string(),
2850 span: None,
2851 },
2852 ],
2853 span: None,
2854 },
2855 MatchArm {
2856 pattern: Pattern::Variant("Err".to_string()),
2857 body: vec![
2858 Statement::WordCall {
2859 name: "rot".to_string(),
2860 span: None,
2861 },
2862 Statement::WordCall {
2863 name: "rot".to_string(),
2864 span: None,
2865 },
2866 ],
2867 span: None,
2868 },
2869 ],
2870 span: None,
2871 }],
2872 source: None,
2873 allowed_lints: vec![],
2874 }],
2875 };
2876
2877 let mut checker = TypeChecker::new();
2878 match checker.check_program(&program) {
2879 Ok(_) => {}
2880 Err(e) => panic!("Type check failed: {}", e),
2881 }
2882 }
2883
2884 #[test]
2885 fn test_roll_with_row_polymorphic_input() {
2886 let program = Program {
2890 includes: vec![],
2891 unions: vec![],
2892 words: vec![WordDef {
2893 name: "test".to_string(),
2894 effect: Some(Effect::new(
2895 StackType::Empty
2896 .push(Type::Var("T".to_string()))
2897 .push(Type::Var("U".to_string()))
2898 .push(Type::Var("V".to_string()))
2899 .push(Type::Var("W".to_string())),
2900 StackType::Empty
2901 .push(Type::Var("U".to_string()))
2902 .push(Type::Var("V".to_string()))
2903 .push(Type::Var("W".to_string()))
2904 .push(Type::Var("T".to_string())),
2905 )),
2906 body: vec![
2907 Statement::IntLiteral(3),
2908 Statement::WordCall {
2909 name: "roll".to_string(),
2910 span: None,
2911 },
2912 ],
2913 source: None,
2914 allowed_lints: vec![],
2915 }],
2916 };
2917
2918 let mut checker = TypeChecker::new();
2919 let result = checker.check_program(&program);
2920 assert!(result.is_ok(), "roll test failed: {:?}", result.err());
2921 }
2922
2923 #[test]
2924 fn test_pick_with_row_polymorphic_input() {
2925 let program = Program {
2929 includes: vec![],
2930 unions: vec![],
2931 words: vec![WordDef {
2932 name: "test".to_string(),
2933 effect: Some(Effect::new(
2934 StackType::Empty
2935 .push(Type::Var("T".to_string()))
2936 .push(Type::Var("U".to_string()))
2937 .push(Type::Var("V".to_string())),
2938 StackType::Empty
2939 .push(Type::Var("T".to_string()))
2940 .push(Type::Var("U".to_string()))
2941 .push(Type::Var("V".to_string()))
2942 .push(Type::Var("T".to_string())),
2943 )),
2944 body: vec![
2945 Statement::IntLiteral(2),
2946 Statement::WordCall {
2947 name: "pick".to_string(),
2948 span: None,
2949 },
2950 ],
2951 source: None,
2952 allowed_lints: vec![],
2953 }],
2954 };
2955
2956 let mut checker = TypeChecker::new();
2957 assert!(checker.check_program(&program).is_ok());
2958 }
2959
2960 #[test]
2961 fn test_valid_union_reference_in_field() {
2962 use crate::ast::{UnionDef, UnionField, UnionVariant};
2963
2964 let program = Program {
2965 includes: vec![],
2966 unions: vec![
2967 UnionDef {
2968 name: "Inner".to_string(),
2969 variants: vec![UnionVariant {
2970 name: "Val".to_string(),
2971 fields: vec![UnionField {
2972 name: "x".to_string(),
2973 type_name: "Int".to_string(),
2974 }],
2975 source: None,
2976 }],
2977 source: None,
2978 },
2979 UnionDef {
2980 name: "Outer".to_string(),
2981 variants: vec![UnionVariant {
2982 name: "Wrap".to_string(),
2983 fields: vec![UnionField {
2984 name: "inner".to_string(),
2985 type_name: "Inner".to_string(), }],
2987 source: None,
2988 }],
2989 source: None,
2990 },
2991 ],
2992 words: vec![],
2993 };
2994
2995 let mut checker = TypeChecker::new();
2996 assert!(
2997 checker.check_program(&program).is_ok(),
2998 "Union reference in field should be valid"
2999 );
3000 }
3001
3002 #[test]
3003 fn test_divergent_recursive_tail_call() {
3004 let program = Program {
3026 includes: vec![],
3027 unions: vec![],
3028 words: vec![WordDef {
3029 name: "store-loop".to_string(),
3030 effect: Some(Effect::new(
3031 StackType::singleton(Type::Channel), StackType::Empty,
3033 )),
3034 body: vec![
3035 Statement::WordCall {
3037 name: "dup".to_string(),
3038 span: None,
3039 },
3040 Statement::WordCall {
3042 name: "chan.receive".to_string(),
3043 span: None,
3044 },
3045 Statement::WordCall {
3047 name: "not".to_string(),
3048 span: None,
3049 },
3050 Statement::If {
3052 then_branch: vec![
3053 Statement::WordCall {
3055 name: "drop".to_string(),
3056 span: None,
3057 },
3058 Statement::WordCall {
3060 name: "store-loop".to_string(), span: None,
3062 },
3063 ],
3064 else_branch: None, span: None,
3066 },
3067 Statement::WordCall {
3069 name: "drop".to_string(),
3070 span: None,
3071 },
3072 Statement::WordCall {
3073 name: "drop".to_string(),
3074 span: None,
3075 },
3076 ],
3077 source: None,
3078 allowed_lints: vec![],
3079 }],
3080 };
3081
3082 let mut checker = TypeChecker::new();
3083 let result = checker.check_program(&program);
3084 assert!(
3085 result.is_ok(),
3086 "Divergent recursive tail call should be accepted: {:?}",
3087 result.err()
3088 );
3089 }
3090
3091 #[test]
3092 fn test_divergent_else_branch() {
3093 let program = Program {
3106 includes: vec![],
3107 unions: vec![],
3108 words: vec![WordDef {
3109 name: "process-loop".to_string(),
3110 effect: Some(Effect::new(
3111 StackType::singleton(Type::Channel), StackType::Empty,
3113 )),
3114 body: vec![
3115 Statement::WordCall {
3116 name: "dup".to_string(),
3117 span: None,
3118 },
3119 Statement::WordCall {
3120 name: "chan.receive".to_string(),
3121 span: None,
3122 },
3123 Statement::If {
3124 then_branch: vec![
3125 Statement::WordCall {
3127 name: "drop".to_string(),
3128 span: None,
3129 },
3130 Statement::WordCall {
3131 name: "drop".to_string(),
3132 span: None,
3133 },
3134 ],
3135 else_branch: Some(vec![
3136 Statement::WordCall {
3138 name: "drop".to_string(),
3139 span: None,
3140 },
3141 Statement::WordCall {
3142 name: "process-loop".to_string(), span: None,
3144 },
3145 ]),
3146 span: None,
3147 },
3148 ],
3149 source: None,
3150 allowed_lints: vec![],
3151 }],
3152 };
3153
3154 let mut checker = TypeChecker::new();
3155 let result = checker.check_program(&program);
3156 assert!(
3157 result.is_ok(),
3158 "Divergent else branch should be accepted: {:?}",
3159 result.err()
3160 );
3161 }
3162
3163 #[test]
3164 fn test_non_tail_call_recursion_not_divergent() {
3165 let program = Program {
3182 includes: vec![],
3183 unions: vec![],
3184 words: vec![WordDef {
3185 name: "bad-loop".to_string(),
3186 effect: Some(Effect::new(
3187 StackType::singleton(Type::Int),
3188 StackType::singleton(Type::Int),
3189 )),
3190 body: vec![
3191 Statement::WordCall {
3192 name: "dup".to_string(),
3193 span: None,
3194 },
3195 Statement::IntLiteral(0),
3196 Statement::WordCall {
3197 name: "i.>".to_string(),
3198 span: None,
3199 },
3200 Statement::If {
3201 then_branch: vec![
3202 Statement::IntLiteral(1),
3203 Statement::WordCall {
3204 name: "i.subtract".to_string(),
3205 span: None,
3206 },
3207 Statement::WordCall {
3208 name: "bad-loop".to_string(), span: None,
3210 },
3211 Statement::IntLiteral(1),
3212 Statement::WordCall {
3213 name: "i.add".to_string(), span: None,
3215 },
3216 ],
3217 else_branch: None,
3218 span: None,
3219 },
3220 ],
3221 source: None,
3222 allowed_lints: vec![],
3223 }],
3224 };
3225
3226 let mut checker = TypeChecker::new();
3227 let result = checker.check_program(&program);
3232 assert!(
3233 result.is_ok(),
3234 "Non-tail recursion should type check normally: {:?}",
3235 result.err()
3236 );
3237 }
3238
3239 #[test]
3240 fn test_call_yield_quotation_error() {
3241 let program = Program {
3245 includes: vec![],
3246 unions: vec![],
3247 words: vec![WordDef {
3248 name: "bad".to_string(),
3249 effect: Some(Effect::new(
3250 StackType::singleton(Type::Var("Ctx".to_string())),
3251 StackType::singleton(Type::Var("Ctx".to_string())),
3252 )),
3253 body: vec![
3254 Statement::IntLiteral(42),
3256 Statement::Quotation {
3257 span: None,
3258 id: 0,
3259 body: vec![Statement::WordCall {
3260 name: "yield".to_string(),
3261 span: None,
3262 }],
3263 },
3264 Statement::WordCall {
3265 name: "call".to_string(),
3266 span: None,
3267 },
3268 ],
3269 source: None,
3270 allowed_lints: vec![],
3271 }],
3272 };
3273
3274 let mut checker = TypeChecker::new();
3275 let result = checker.check_program(&program);
3276 assert!(
3277 result.is_err(),
3278 "Calling yield quotation directly should fail"
3279 );
3280 let err = result.unwrap_err();
3281 assert!(
3282 err.contains("Yield") || err.contains("strand.weave"),
3283 "Error should mention Yield or strand.weave: {}",
3284 err
3285 );
3286 }
3287
3288 #[test]
3289 fn test_strand_weave_yield_quotation_ok() {
3290 let program = Program {
3293 includes: vec![],
3294 unions: vec![],
3295 words: vec![WordDef {
3296 name: "good".to_string(),
3297 effect: Some(Effect::new(
3298 StackType::Empty,
3299 StackType::Empty
3300 .push(Type::Int)
3301 .push(Type::Var("Handle".to_string())),
3302 )),
3303 body: vec![
3304 Statement::IntLiteral(42),
3305 Statement::Quotation {
3306 span: None,
3307 id: 0,
3308 body: vec![Statement::WordCall {
3309 name: "yield".to_string(),
3310 span: None,
3311 }],
3312 },
3313 Statement::WordCall {
3314 name: "strand.weave".to_string(),
3315 span: None,
3316 },
3317 ],
3318 source: None,
3319 allowed_lints: vec![],
3320 }],
3321 };
3322
3323 let mut checker = TypeChecker::new();
3324 let result = checker.check_program(&program);
3325 assert!(
3326 result.is_ok(),
3327 "strand.weave on yield quotation should pass: {:?}",
3328 result.err()
3329 );
3330 }
3331
3332 #[test]
3333 fn test_call_pure_quotation_ok() {
3334 let program = Program {
3337 includes: vec![],
3338 unions: vec![],
3339 words: vec![WordDef {
3340 name: "ok".to_string(),
3341 effect: Some(Effect::new(
3342 StackType::singleton(Type::Int),
3343 StackType::singleton(Type::Int),
3344 )),
3345 body: vec![
3346 Statement::Quotation {
3347 span: None,
3348 id: 0,
3349 body: vec![
3350 Statement::IntLiteral(1),
3351 Statement::WordCall {
3352 name: "i.add".to_string(),
3353 span: None,
3354 },
3355 ],
3356 },
3357 Statement::WordCall {
3358 name: "call".to_string(),
3359 span: None,
3360 },
3361 ],
3362 source: None,
3363 allowed_lints: vec![],
3364 }],
3365 };
3366
3367 let mut checker = TypeChecker::new();
3368 let result = checker.check_program(&program);
3369 assert!(
3370 result.is_ok(),
3371 "Calling pure quotation should pass: {:?}",
3372 result.err()
3373 );
3374 }
3375
3376 #[test]
3382 fn test_pollution_extra_push() {
3383 let program = Program {
3387 includes: vec![],
3388 unions: vec![],
3389 words: vec![WordDef {
3390 name: "test".to_string(),
3391 effect: Some(Effect::new(
3392 StackType::singleton(Type::Int),
3393 StackType::singleton(Type::Int),
3394 )),
3395 body: vec![Statement::IntLiteral(42)],
3396 source: None,
3397 allowed_lints: vec![],
3398 }],
3399 };
3400
3401 let mut checker = TypeChecker::new();
3402 let result = checker.check_program(&program);
3403 assert!(
3404 result.is_err(),
3405 "Should reject: declares ( Int -- Int ) but leaves 2 values on stack"
3406 );
3407 }
3408
3409 #[test]
3410 fn test_pollution_extra_dup() {
3411 let program = Program {
3414 includes: vec![],
3415 unions: vec![],
3416 words: vec![WordDef {
3417 name: "test".to_string(),
3418 effect: Some(Effect::new(
3419 StackType::singleton(Type::Int),
3420 StackType::singleton(Type::Int),
3421 )),
3422 body: vec![Statement::WordCall {
3423 name: "dup".to_string(),
3424 span: None,
3425 }],
3426 source: None,
3427 allowed_lints: vec![],
3428 }],
3429 };
3430
3431 let mut checker = TypeChecker::new();
3432 let result = checker.check_program(&program);
3433 assert!(
3434 result.is_err(),
3435 "Should reject: declares ( Int -- Int ) but dup produces 2 values"
3436 );
3437 }
3438
3439 #[test]
3440 fn test_pollution_consumes_extra() {
3441 let program = Program {
3444 includes: vec![],
3445 unions: vec![],
3446 words: vec![WordDef {
3447 name: "test".to_string(),
3448 effect: Some(Effect::new(
3449 StackType::singleton(Type::Int),
3450 StackType::singleton(Type::Int),
3451 )),
3452 body: vec![
3453 Statement::WordCall {
3454 name: "drop".to_string(),
3455 span: None,
3456 },
3457 Statement::WordCall {
3458 name: "drop".to_string(),
3459 span: None,
3460 },
3461 Statement::IntLiteral(42),
3462 ],
3463 source: None,
3464 allowed_lints: vec![],
3465 }],
3466 };
3467
3468 let mut checker = TypeChecker::new();
3469 let result = checker.check_program(&program);
3470 assert!(
3471 result.is_err(),
3472 "Should reject: declares ( Int -- Int ) but consumes 2 values"
3473 );
3474 }
3475
3476 #[test]
3477 fn test_pollution_in_then_branch() {
3478 let program = Program {
3482 includes: vec![],
3483 unions: vec![],
3484 words: vec![WordDef {
3485 name: "test".to_string(),
3486 effect: Some(Effect::new(
3487 StackType::singleton(Type::Bool),
3488 StackType::singleton(Type::Int),
3489 )),
3490 body: vec![Statement::If {
3491 then_branch: vec![
3492 Statement::IntLiteral(1),
3493 Statement::IntLiteral(2), ],
3495 else_branch: Some(vec![Statement::IntLiteral(3)]),
3496 span: None,
3497 }],
3498 source: None,
3499 allowed_lints: vec![],
3500 }],
3501 };
3502
3503 let mut checker = TypeChecker::new();
3504 let result = checker.check_program(&program);
3505 assert!(
3506 result.is_err(),
3507 "Should reject: then branch pushes 2 values, else pushes 1"
3508 );
3509 }
3510
3511 #[test]
3512 fn test_pollution_in_else_branch() {
3513 let program = Program {
3517 includes: vec![],
3518 unions: vec![],
3519 words: vec![WordDef {
3520 name: "test".to_string(),
3521 effect: Some(Effect::new(
3522 StackType::singleton(Type::Bool),
3523 StackType::singleton(Type::Int),
3524 )),
3525 body: vec![Statement::If {
3526 then_branch: vec![Statement::IntLiteral(1)],
3527 else_branch: Some(vec![
3528 Statement::IntLiteral(2),
3529 Statement::IntLiteral(3), ]),
3531 span: None,
3532 }],
3533 source: None,
3534 allowed_lints: vec![],
3535 }],
3536 };
3537
3538 let mut checker = TypeChecker::new();
3539 let result = checker.check_program(&program);
3540 assert!(
3541 result.is_err(),
3542 "Should reject: then branch pushes 1 value, else pushes 2"
3543 );
3544 }
3545
3546 #[test]
3547 fn test_pollution_both_branches_extra() {
3548 let program = Program {
3552 includes: vec![],
3553 unions: vec![],
3554 words: vec![WordDef {
3555 name: "test".to_string(),
3556 effect: Some(Effect::new(
3557 StackType::singleton(Type::Bool),
3558 StackType::singleton(Type::Int),
3559 )),
3560 body: vec![Statement::If {
3561 then_branch: vec![Statement::IntLiteral(1), Statement::IntLiteral(2)],
3562 else_branch: Some(vec![Statement::IntLiteral(3), Statement::IntLiteral(4)]),
3563 span: None,
3564 }],
3565 source: None,
3566 allowed_lints: vec![],
3567 }],
3568 };
3569
3570 let mut checker = TypeChecker::new();
3571 let result = checker.check_program(&program);
3572 assert!(
3573 result.is_err(),
3574 "Should reject: both branches push 2 values, but declared output is 1"
3575 );
3576 }
3577
3578 #[test]
3579 fn test_pollution_branch_consumes_extra() {
3580 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::Bool).push(Type::Int),
3590 StackType::singleton(Type::Int),
3591 )),
3592 body: vec![Statement::If {
3593 then_branch: vec![
3594 Statement::WordCall {
3595 name: "drop".to_string(),
3596 span: None,
3597 },
3598 Statement::WordCall {
3599 name: "drop".to_string(),
3600 span: None,
3601 },
3602 Statement::IntLiteral(1),
3603 ],
3604 else_branch: Some(vec![]),
3605 span: None,
3606 }],
3607 source: None,
3608 allowed_lints: vec![],
3609 }],
3610 };
3611
3612 let mut checker = TypeChecker::new();
3613 let result = checker.check_program(&program);
3614 assert!(
3615 result.is_err(),
3616 "Should reject: then branch consumes Bool (should only have Int after if)"
3617 );
3618 }
3619
3620 #[test]
3621 fn test_pollution_quotation_wrong_arity_output() {
3622 let program = Program {
3626 includes: vec![],
3627 unions: vec![],
3628 words: vec![WordDef {
3629 name: "test".to_string(),
3630 effect: Some(Effect::new(
3631 StackType::singleton(Type::Int),
3632 StackType::singleton(Type::Int),
3633 )),
3634 body: vec![
3635 Statement::Quotation {
3636 span: None,
3637 id: 0,
3638 body: vec![Statement::WordCall {
3639 name: "dup".to_string(),
3640 span: None,
3641 }],
3642 },
3643 Statement::WordCall {
3644 name: "call".to_string(),
3645 span: None,
3646 },
3647 ],
3648 source: None,
3649 allowed_lints: vec![],
3650 }],
3651 };
3652
3653 let mut checker = TypeChecker::new();
3654 let result = checker.check_program(&program);
3655 assert!(
3656 result.is_err(),
3657 "Should reject: quotation [dup] produces 2 values, declared output is 1"
3658 );
3659 }
3660
3661 #[test]
3662 fn test_pollution_quotation_wrong_arity_input() {
3663 let program = Program {
3667 includes: vec![],
3668 unions: vec![],
3669 words: vec![WordDef {
3670 name: "test".to_string(),
3671 effect: Some(Effect::new(
3672 StackType::singleton(Type::Int),
3673 StackType::singleton(Type::Int),
3674 )),
3675 body: vec![
3676 Statement::Quotation {
3677 span: None,
3678 id: 0,
3679 body: vec![
3680 Statement::WordCall {
3681 name: "drop".to_string(),
3682 span: None,
3683 },
3684 Statement::WordCall {
3685 name: "drop".to_string(),
3686 span: None,
3687 },
3688 Statement::IntLiteral(42),
3689 ],
3690 },
3691 Statement::WordCall {
3692 name: "call".to_string(),
3693 span: None,
3694 },
3695 ],
3696 source: None,
3697 allowed_lints: vec![],
3698 }],
3699 };
3700
3701 let mut checker = TypeChecker::new();
3702 let result = checker.check_program(&program);
3703 assert!(
3704 result.is_err(),
3705 "Should reject: quotation [drop drop 42] consumes 2 values, only 1 available"
3706 );
3707 }
3708
3709 #[test]
3710 fn test_missing_effect_provides_helpful_error() {
3711 let program = Program {
3714 includes: vec![],
3715 unions: vec![],
3716 words: vec![WordDef {
3717 name: "myword".to_string(),
3718 effect: None, body: vec![Statement::IntLiteral(42)],
3720 source: None,
3721 allowed_lints: vec![],
3722 }],
3723 };
3724
3725 let mut checker = TypeChecker::new();
3726 let result = checker.check_program(&program);
3727 assert!(result.is_err());
3728 let err = result.unwrap_err();
3729 assert!(err.contains("myword"), "Error should mention word name");
3730 assert!(
3731 err.contains("stack effect"),
3732 "Error should mention stack effect"
3733 );
3734 }
3735
3736 #[test]
3737 fn test_valid_effect_exact_match() {
3738 let program = Program {
3741 includes: vec![],
3742 unions: vec![],
3743 words: vec![WordDef {
3744 name: "test".to_string(),
3745 effect: Some(Effect::new(
3746 StackType::Empty.push(Type::Int).push(Type::Int),
3747 StackType::singleton(Type::Int),
3748 )),
3749 body: vec![Statement::WordCall {
3750 name: "i.add".to_string(),
3751 span: None,
3752 }],
3753 source: None,
3754 allowed_lints: vec![],
3755 }],
3756 };
3757
3758 let mut checker = TypeChecker::new();
3759 let result = checker.check_program(&program);
3760 assert!(result.is_ok(), "Should accept: effect matches exactly");
3761 }
3762
3763 #[test]
3764 fn test_valid_polymorphic_passthrough() {
3765 let program = Program {
3768 includes: vec![],
3769 unions: vec![],
3770 words: vec![WordDef {
3771 name: "test".to_string(),
3772 effect: Some(Effect::new(
3773 StackType::Cons {
3774 rest: Box::new(StackType::RowVar("rest".to_string())),
3775 top: Type::Var("a".to_string()),
3776 },
3777 StackType::Cons {
3778 rest: Box::new(StackType::RowVar("rest".to_string())),
3779 top: Type::Var("a".to_string()),
3780 },
3781 )),
3782 body: vec![], source: None,
3784 allowed_lints: vec![],
3785 }],
3786 };
3787
3788 let mut checker = TypeChecker::new();
3789 let result = checker.check_program(&program);
3790 assert!(result.is_ok(), "Should accept: polymorphic identity");
3791 }
3792
3793 #[test]
3799 fn test_closure_basic_capture() {
3800 let program = Program {
3806 includes: vec![],
3807 unions: vec![],
3808 words: vec![WordDef {
3809 name: "make-adder".to_string(),
3810 effect: Some(Effect::new(
3811 StackType::singleton(Type::Int),
3812 StackType::singleton(Type::Closure {
3813 effect: Box::new(Effect::new(
3814 StackType::RowVar("r".to_string()).push(Type::Int),
3815 StackType::RowVar("r".to_string()).push(Type::Int),
3816 )),
3817 captures: vec![Type::Int], }),
3819 )),
3820 body: vec![Statement::Quotation {
3821 span: None,
3822 id: 0,
3823 body: vec![Statement::WordCall {
3824 name: "i.add".to_string(),
3825 span: None,
3826 }],
3827 }],
3828 source: None,
3829 allowed_lints: vec![],
3830 }],
3831 };
3832
3833 let mut checker = TypeChecker::new();
3834 let result = checker.check_program(&program);
3835 assert!(
3836 result.is_ok(),
3837 "Basic closure capture should work: {:?}",
3838 result.err()
3839 );
3840 }
3841
3842 #[test]
3843 fn test_closure_nested_two_levels() {
3844 let program = Program {
3849 includes: vec![],
3850 unions: vec![],
3851 words: vec![WordDef {
3852 name: "outer".to_string(),
3853 effect: Some(Effect::new(
3854 StackType::Empty,
3855 StackType::singleton(Type::Quotation(Box::new(Effect::new(
3856 StackType::RowVar("r".to_string()),
3857 StackType::RowVar("r".to_string()).push(Type::Quotation(Box::new(
3858 Effect::new(
3859 StackType::RowVar("s".to_string()).push(Type::Int),
3860 StackType::RowVar("s".to_string()).push(Type::Int),
3861 ),
3862 ))),
3863 )))),
3864 )),
3865 body: vec![Statement::Quotation {
3866 span: None,
3867 id: 0,
3868 body: vec![Statement::Quotation {
3869 span: None,
3870 id: 1,
3871 body: vec![
3872 Statement::IntLiteral(1),
3873 Statement::WordCall {
3874 name: "i.add".to_string(),
3875 span: None,
3876 },
3877 ],
3878 }],
3879 }],
3880 source: None,
3881 allowed_lints: vec![],
3882 }],
3883 };
3884
3885 let mut checker = TypeChecker::new();
3886 let result = checker.check_program(&program);
3887 assert!(
3888 result.is_ok(),
3889 "Two-level nested quotations should work: {:?}",
3890 result.err()
3891 );
3892 }
3893
3894 #[test]
3895 fn test_closure_nested_three_levels() {
3896 let inner_effect = Effect::new(
3900 StackType::RowVar("a".to_string()).push(Type::Int),
3901 StackType::RowVar("a".to_string()).push(Type::Int),
3902 );
3903 let middle_effect = Effect::new(
3904 StackType::RowVar("b".to_string()),
3905 StackType::RowVar("b".to_string()).push(Type::Quotation(Box::new(inner_effect))),
3906 );
3907 let outer_effect = Effect::new(
3908 StackType::RowVar("c".to_string()),
3909 StackType::RowVar("c".to_string()).push(Type::Quotation(Box::new(middle_effect))),
3910 );
3911
3912 let program = Program {
3913 includes: vec![],
3914 unions: vec![],
3915 words: vec![WordDef {
3916 name: "deep".to_string(),
3917 effect: Some(Effect::new(
3918 StackType::Empty,
3919 StackType::singleton(Type::Quotation(Box::new(outer_effect))),
3920 )),
3921 body: vec![Statement::Quotation {
3922 span: None,
3923 id: 0,
3924 body: vec![Statement::Quotation {
3925 span: None,
3926 id: 1,
3927 body: vec![Statement::Quotation {
3928 span: None,
3929 id: 2,
3930 body: vec![
3931 Statement::IntLiteral(1),
3932 Statement::WordCall {
3933 name: "i.add".to_string(),
3934 span: None,
3935 },
3936 ],
3937 }],
3938 }],
3939 }],
3940 source: None,
3941 allowed_lints: vec![],
3942 }],
3943 };
3944
3945 let mut checker = TypeChecker::new();
3946 let result = checker.check_program(&program);
3947 assert!(
3948 result.is_ok(),
3949 "Three-level nested quotations should work: {:?}",
3950 result.err()
3951 );
3952 }
3953
3954 #[test]
3955 fn test_closure_use_after_creation() {
3956 let adder_type = Type::Closure {
3962 effect: Box::new(Effect::new(
3963 StackType::RowVar("r".to_string()).push(Type::Int),
3964 StackType::RowVar("r".to_string()).push(Type::Int),
3965 )),
3966 captures: vec![Type::Int],
3967 };
3968
3969 let program = Program {
3970 includes: vec![],
3971 unions: vec![],
3972 words: vec![
3973 WordDef {
3974 name: "make-adder".to_string(),
3975 effect: Some(Effect::new(
3976 StackType::singleton(Type::Int),
3977 StackType::singleton(adder_type.clone()),
3978 )),
3979 body: vec![Statement::Quotation {
3980 span: None,
3981 id: 0,
3982 body: vec![Statement::WordCall {
3983 name: "i.add".to_string(),
3984 span: None,
3985 }],
3986 }],
3987 source: None,
3988 allowed_lints: vec![],
3989 },
3990 WordDef {
3991 name: "use-adder".to_string(),
3992 effect: Some(Effect::new(
3993 StackType::Empty,
3994 StackType::singleton(Type::Int),
3995 )),
3996 body: vec![
3997 Statement::IntLiteral(5),
3998 Statement::WordCall {
3999 name: "make-adder".to_string(),
4000 span: None,
4001 },
4002 Statement::IntLiteral(10),
4003 Statement::WordCall {
4004 name: "swap".to_string(),
4005 span: None,
4006 },
4007 Statement::WordCall {
4008 name: "call".to_string(),
4009 span: None,
4010 },
4011 ],
4012 source: None,
4013 allowed_lints: vec![],
4014 },
4015 ],
4016 };
4017
4018 let mut checker = TypeChecker::new();
4019 let result = checker.check_program(&program);
4020 assert!(
4021 result.is_ok(),
4022 "Closure usage after creation should work: {:?}",
4023 result.err()
4024 );
4025 }
4026
4027 #[test]
4028 fn test_closure_wrong_call_type() {
4029 let adder_type = Type::Closure {
4033 effect: Box::new(Effect::new(
4034 StackType::RowVar("r".to_string()).push(Type::Int),
4035 StackType::RowVar("r".to_string()).push(Type::Int),
4036 )),
4037 captures: vec![Type::Int],
4038 };
4039
4040 let program = Program {
4041 includes: vec![],
4042 unions: vec![],
4043 words: vec![
4044 WordDef {
4045 name: "make-adder".to_string(),
4046 effect: Some(Effect::new(
4047 StackType::singleton(Type::Int),
4048 StackType::singleton(adder_type.clone()),
4049 )),
4050 body: vec![Statement::Quotation {
4051 span: None,
4052 id: 0,
4053 body: vec![Statement::WordCall {
4054 name: "i.add".to_string(),
4055 span: None,
4056 }],
4057 }],
4058 source: None,
4059 allowed_lints: vec![],
4060 },
4061 WordDef {
4062 name: "bad-use".to_string(),
4063 effect: Some(Effect::new(
4064 StackType::Empty,
4065 StackType::singleton(Type::Int),
4066 )),
4067 body: vec![
4068 Statement::IntLiteral(5),
4069 Statement::WordCall {
4070 name: "make-adder".to_string(),
4071 span: None,
4072 },
4073 Statement::StringLiteral("hello".to_string()), Statement::WordCall {
4075 name: "swap".to_string(),
4076 span: None,
4077 },
4078 Statement::WordCall {
4079 name: "call".to_string(),
4080 span: None,
4081 },
4082 ],
4083 source: None,
4084 allowed_lints: vec![],
4085 },
4086 ],
4087 };
4088
4089 let mut checker = TypeChecker::new();
4090 let result = checker.check_program(&program);
4091 assert!(
4092 result.is_err(),
4093 "Calling Int closure with String should fail"
4094 );
4095 }
4096
4097 #[test]
4098 fn test_closure_multiple_captures() {
4099 let program = Program {
4106 includes: vec![],
4107 unions: vec![],
4108 words: vec![WordDef {
4109 name: "make-between".to_string(),
4110 effect: Some(Effect::new(
4111 StackType::Empty.push(Type::Int).push(Type::Int),
4112 StackType::singleton(Type::Quotation(Box::new(Effect::new(
4113 StackType::RowVar("r".to_string()).push(Type::Int),
4114 StackType::RowVar("r".to_string()).push(Type::Bool),
4115 )))),
4116 )),
4117 body: vec![Statement::Quotation {
4118 span: None,
4119 id: 0,
4120 body: vec![
4121 Statement::WordCall {
4123 name: "i.>=".to_string(),
4124 span: None,
4125 },
4126 ],
4128 }],
4129 source: None,
4130 allowed_lints: vec![],
4131 }],
4132 };
4133
4134 let mut checker = TypeChecker::new();
4135 let result = checker.check_program(&program);
4136 assert!(
4140 result.is_ok() || result.is_err(),
4141 "Multiple captures should be handled (pass or fail gracefully)"
4142 );
4143 }
4144
4145 #[test]
4146 fn test_quotation_type_preserved_through_word() {
4147 let quot_type = Type::Quotation(Box::new(Effect::new(
4150 StackType::RowVar("r".to_string()).push(Type::Int),
4151 StackType::RowVar("r".to_string()).push(Type::Int),
4152 )));
4153
4154 let program = Program {
4155 includes: vec![],
4156 unions: vec![],
4157 words: vec![WordDef {
4158 name: "identity-quot".to_string(),
4159 effect: Some(Effect::new(
4160 StackType::singleton(quot_type.clone()),
4161 StackType::singleton(quot_type.clone()),
4162 )),
4163 body: vec![], source: None,
4165 allowed_lints: vec![],
4166 }],
4167 };
4168
4169 let mut checker = TypeChecker::new();
4170 let result = checker.check_program(&program);
4171 assert!(
4172 result.is_ok(),
4173 "Quotation type should be preserved through identity word: {:?}",
4174 result.err()
4175 );
4176 }
4177
4178 #[test]
4179 fn test_closure_captures_value_for_inner_quotation() {
4180 let closure_effect = Effect::new(
4186 StackType::RowVar("r".to_string()).push(Type::Int),
4187 StackType::RowVar("r".to_string()).push(Type::Int),
4188 );
4189
4190 let program = Program {
4191 includes: vec![],
4192 unions: vec![],
4193 words: vec![WordDef {
4194 name: "make-inner-adder".to_string(),
4195 effect: Some(Effect::new(
4196 StackType::singleton(Type::Int),
4197 StackType::singleton(Type::Closure {
4198 effect: Box::new(closure_effect),
4199 captures: vec![Type::Int],
4200 }),
4201 )),
4202 body: vec![Statement::Quotation {
4203 span: None,
4204 id: 0,
4205 body: vec![
4206 Statement::WordCall {
4208 name: "i.add".to_string(),
4209 span: None,
4210 },
4211 ],
4212 }],
4213 source: None,
4214 allowed_lints: vec![],
4215 }],
4216 };
4217
4218 let mut checker = TypeChecker::new();
4219 let result = checker.check_program(&program);
4220 assert!(
4221 result.is_ok(),
4222 "Closure with capture for inner work should pass: {:?}",
4223 result.err()
4224 );
4225 }
4226}