Refactor type checking for functions and operators

This commit refactors the type checking logic for functions and
operators to improve type safety and expressiveness.

Key changes include:

- Introduced `StaticType::FunctionOverloads` to represent functions with
  multiple possible signatures, enabling better handling of operator
  overloading.
- Updated the `TypeChecker` to correctly infer function types and
  resolve calls with overloaded functions.
- Modified the `Environment` to register standard library functions with
  specific `FunctionOverloads` signatures, replacing the previous
  `StaticType::Any` approach.
- Enhanced the `StaticType::resolve_call` method to intelligently match
  argument types against expected parameters, including handling of
  overloaded functions.
- Added new tests to verify the correctness of type inference for lambda
  returns and operator overloading.
This commit is contained in:
Michael Schimmel
2026-02-19 13:10:28 +01:00
parent a611c50a92
commit 49db73800a
4 changed files with 166 additions and 58 deletions
+53 -8
View File
@@ -71,6 +71,12 @@ pub enum Value {
TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // Internal: For TCO (Boxed to keep Value small)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Signature {
pub params: Vec<StaticType>,
pub ret: StaticType,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum StaticType {
Any,
@@ -82,10 +88,8 @@ pub enum StaticType {
Keyword,
List(Box<StaticType>),
Record(Rc<BTreeMap<Keyword, StaticType>>),
Function {
params: Vec<StaticType>,
ret: Box<StaticType>,
},
Function(Box<Signature>),
FunctionOverloads(Vec<Signature>),
Object(&'static str),
}
@@ -108,19 +112,60 @@ impl fmt::Display for StaticType {
}
write!(f, "}}")
},
StaticType::Function { params, ret } => {
StaticType::Function(sig) => {
write!(f, "fn(")?;
for (i, p) in params.iter().enumerate() {
for (i, p) in sig.params.iter().enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{}", p)?;
}
write!(f, ") -> {}", ret)
write!(f, ") -> {}", sig.ret)
},
StaticType::FunctionOverloads(sigs) => {
write!(f, "overloads({} variants)", sigs.len())
}
StaticType::Object(name) => write!(f, "{}", name),
}
}
}
impl StaticType {
/// Returns true if `other` can be assigned to a location of type `self`.
pub fn is_assignable_from(&self, other: &StaticType) -> bool {
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) {
return true;
}
// Future: Numeric promotion (Int -> Float)
false
}
/// Tries to resolve a call with the given argument types and returns the return type.
pub fn resolve_call(&self, arg_types: &[StaticType]) -> Option<StaticType> {
match self {
StaticType::Any => Some(StaticType::Any),
StaticType::Function(sig) => {
if self.match_params(&sig.params, arg_types) {
Some(sig.ret.clone())
} else {
None
}
}
StaticType::FunctionOverloads(sigs) => {
sigs.iter()
.find(|sig| self.match_params(&sig.params, arg_types))
.map(|sig| sig.ret.clone())
}
_ => None,
}
}
fn match_params(&self, expected: &[StaticType], actual: &[StaticType]) -> bool {
if expected.len() != actual.len() {
return false;
}
expected.iter().zip(actual).all(|(e, a)| e.is_assignable_from(a))
}
}
impl Value {
pub fn is_truthy(&self) -> bool {
match self {
@@ -141,7 +186,7 @@ impl Value {
Value::Keyword(_) => StaticType::Keyword,
Value::List(_) => StaticType::List(Box::new(StaticType::Any)),
Value::Record(_) => StaticType::Record(Rc::new(BTreeMap::new())), // Empty for now
Value::Function { .. } => StaticType::Any, // Dynamic function
Value::Function(_) => StaticType::Any, // Dynamic function
Value::Object(o) => StaticType::Object(o.type_name()),
Value::Cell(c) => c.borrow().static_type(),
Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any