use crate::types::{SideEffect, StackType, Type};
use crate::unification::Subst;
use super::TypeChecker;
impl TypeChecker {
pub(super) fn infer_word_call(
&self,
name: &str,
span: &Option<crate::ast::Span>,
current_stack: StackType,
) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
let is_sugar = matches!(
name,
"+" | "-" | "*" | "/" | "%" | "=" | "<" | ">" | "<=" | ">=" | "<>"
);
if is_sugar {
if let Some(resolved) = self.resolve_arithmetic_sugar(name, ¤t_stack) {
if let Some(s) = span {
self.resolved_sugar
.borrow_mut()
.insert((s.line, s.column), resolved.clone());
}
return self.infer_word_call(&resolved, span, current_stack);
}
let line_prefix = self.line_prefix();
let (top_desc, second_desc) = {
let top = current_stack.clone().pop().map(|(_, t)| format!("{}", t));
let second = current_stack
.clone()
.pop()
.and_then(|(r, _)| r.pop().map(|(_, t)| format!("{}", t)));
(
top.unwrap_or_else(|| "empty".to_string()),
second.unwrap_or_else(|| "empty".to_string()),
)
};
let (type_options, suggestion) = match name {
"+" => (
"Int+Int, Float+Float, or String+String",
"Use `i.+`, `f.+`, or `string.concat`.",
),
"=" => (
"Int+Int, Float+Float, or String+String (equality)",
"Use `i.=`, `f.=`, or `string.equal?`.",
),
"%" => (
"Int+Int only — float modulo is not supported",
"Use `i.%` for integer modulo.",
),
_ => (
"Int+Int or Float+Float",
"Use the `i.` or `f.` prefixed variant.",
),
};
return Err(format!(
"{}`{}` requires matching types ({}), got ({}, {}). {}",
line_prefix, name, type_options, second_desc, top_desc, suggestion,
));
}
if name == ">aux" {
return self.infer_to_aux(span, current_stack);
}
if name == "aux>" {
return self.infer_from_aux(span, current_stack);
}
if name == "call" {
return self.infer_call(span, current_stack);
}
if name == "dip" {
return self.infer_dip(span, current_stack);
}
if name == "keep" {
return self.infer_keep(span, current_stack);
}
if name == "bi" {
return self.infer_bi(span, current_stack);
}
let effect = self
.lookup_word_effect(name)
.ok_or_else(|| format!("Unknown word: '{}'", name))?;
let fresh_effect = self.freshen_effect(&effect);
let adjusted_stack = if name == "strand.spawn" {
self.adjust_stack_for_spawn(current_stack, &fresh_effect)?
} else {
current_stack
};
let (result_stack, subst) = self.apply_effect(&fresh_effect, adjusted_stack, name, span)?;
let propagated_effects = fresh_effect.effects.clone();
Ok((result_stack, subst, propagated_effects))
}
pub(super) fn infer_to_aux(
&self,
_span: &Option<crate::ast::Span>,
current_stack: StackType,
) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
let (rest, top_type) = self.pop_type(¤t_stack, ">aux")?;
let mut aux = self.current_aux_stack.borrow_mut();
*aux = aux.clone().push(top_type);
let depth = Self::stack_depth(&aux);
let quot_stack = self.quotation_id_stack.borrow();
if let Some("_id) = quot_stack.last() {
let mut depths = self.quotation_aux_depths.borrow_mut();
let entry = depths.entry(quot_id).or_insert(0);
if depth > *entry {
*entry = depth;
}
} else if let Some((word_name, _)) = self.current_word.borrow().as_ref() {
let mut depths = self.aux_max_depths.borrow_mut();
let entry = depths.entry(word_name.clone()).or_insert(0);
if depth > *entry {
*entry = depth;
}
}
Ok((rest, Subst::empty(), vec![]))
}
pub(super) fn infer_from_aux(
&self,
_span: &Option<crate::ast::Span>,
current_stack: StackType,
) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
let mut aux = self.current_aux_stack.borrow_mut();
match aux.clone().pop() {
Some((rest, top_type)) => {
*aux = rest;
Ok((current_stack.push(top_type), Subst::empty(), vec![]))
}
None => {
let line_info = self.line_prefix();
Err(format!(
"{}aux>: aux stack is empty. Every aux> must be paired with a preceding >aux.",
line_info
))
}
}
}
pub(super) fn infer_call(
&self,
span: &Option<crate::ast::Span>,
current_stack: StackType,
) -> Result<(StackType, Subst, Vec<SideEffect>), String> {
let line_prefix = self.line_prefix();
let (remaining_stack, quot_type) = current_stack.clone().pop().ok_or_else(|| {
format!(
"{}call: stack underflow - expected quotation on stack",
line_prefix
)
})?;
let quot_effect = match "_type {
Type::Quotation(effect) => (**effect).clone(),
Type::Closure { effect, .. } => (**effect).clone(),
Type::Var(_) => {
let effect = self
.lookup_word_effect("call")
.ok_or_else(|| "Unknown word: 'call'".to_string())?;
let fresh_effect = self.freshen_effect(&effect);
let (result_stack, subst) =
self.apply_effect(&fresh_effect, current_stack, "call", span)?;
return Ok((result_stack, subst, vec![]));
}
_ => {
return Err(format!(
"call: expected quotation or closure on stack, got {}",
quot_type
));
}
};
if quot_effect.has_yield() {
return Err("Cannot call quotation with Yield effect directly.\n\
Quotations that yield values must be wrapped with `strand.weave`.\n\
Example: `[ yielding-code ] strand.weave` instead of `[ yielding-code ] call`"
.to_string());
}
let fresh_effect = self.freshen_effect("_effect);
let (result_stack, subst) =
self.apply_effect(&fresh_effect, remaining_stack, "call", span)?;
let propagated_effects = fresh_effect.effects.clone();
Ok((result_stack, subst, propagated_effects))
}
pub(super) fn resolve_arithmetic_sugar(&self, name: &str, stack: &StackType) -> Option<String> {
let is_binary = matches!(
name,
"+" | "-" | "*" | "/" | "%" | "=" | "<" | ">" | "<=" | ">=" | "<>"
);
if !is_binary {
return None;
}
let (rest, top) = stack.clone().pop()?;
let (_, second) = rest.pop()?;
match (name, &second, &top) {
("+", Type::Int, Type::Int) => Some("i.+".to_string()),
("-", Type::Int, Type::Int) => Some("i.-".to_string()),
("*", Type::Int, Type::Int) => Some("i.*".to_string()),
("/", Type::Int, Type::Int) => Some("i./".to_string()),
("%", Type::Int, Type::Int) => Some("i.%".to_string()),
("=", Type::Int, Type::Int) => Some("i.=".to_string()),
("<", Type::Int, Type::Int) => Some("i.<".to_string()),
(">", Type::Int, Type::Int) => Some("i.>".to_string()),
("<=", Type::Int, Type::Int) => Some("i.<=".to_string()),
(">=", Type::Int, Type::Int) => Some("i.>=".to_string()),
("<>", Type::Int, Type::Int) => Some("i.<>".to_string()),
("+", Type::Float, Type::Float) => Some("f.+".to_string()),
("-", Type::Float, Type::Float) => Some("f.-".to_string()),
("*", Type::Float, Type::Float) => Some("f.*".to_string()),
("/", Type::Float, Type::Float) => Some("f./".to_string()),
("=", Type::Float, Type::Float) => Some("f.=".to_string()),
("<", Type::Float, Type::Float) => Some("f.<".to_string()),
(">", Type::Float, Type::Float) => Some("f.>".to_string()),
("<=", Type::Float, Type::Float) => Some("f.<=".to_string()),
(">=", Type::Float, Type::Float) => Some("f.>=".to_string()),
("<>", Type::Float, Type::Float) => Some("f.<>".to_string()),
("+", Type::String, Type::String) => Some("string.concat".to_string()),
("=", Type::String, Type::String) => Some("string.equal?".to_string()),
_ => None,
}
}
}