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
+88 -26
View File
@@ -88,50 +88,112 @@ impl Environment {
}
fn register_stdlib(&self) {
self.register_native("+", StaticType::Any, |args| {
let mut acc = 0.0;
for arg in args {
if let Value::Int(i) = arg { acc += i as f64; }
else if let Value::Float(f) = arg { acc += f; }
use crate::ast::types::Signature;
let plus_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text },
]);
self.register_native("+", plus_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
(Value::Float(a), Value::Float(b)) => Value::Float(a + b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b),
(Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64),
(Value::Text(a), Value::Text(b)) => {
let mut res = a.to_string();
res.push_str(b);
Value::Text(Rc::from(res))
}
_ => Value::Void,
}
} else {
let mut acc = 0.0;
for arg in args {
if let Value::Int(i) = arg { acc += i as f64; }
else if let Value::Float(f) = arg { acc += f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
self.register_native("-", StaticType::Any, |args| {
let minus_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
Signature { params: vec![StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float], ret: StaticType::Float },
]);
self.register_native("-", minus_ty, |args| {
if args.is_empty() { return Value::Void; }
if args.len() == 1 {
return match args[0] {
Value::Int(i) => Value::Int(-i),
Value::Float(f) => Value::Float(-f),
_ => Value::Void,
};
}
if args.len() == 2 {
return match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a - b),
(Value::Float(a), Value::Float(b)) => Value::Float(a - b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b),
(Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64),
_ => Value::Void,
};
}
let mut acc = match args[0] {
Value::Int(i) => i as f64,
Value::Float(f) => f,
_ => return Value::Void,
};
if args.len() == 1 {
acc = -acc;
} else {
for arg in &args[1..] {
if let Value::Int(i) = arg { acc -= *i as f64; }
else if let Value::Float(f) = arg { acc -= f; }
for arg in &args[1..] {
if let Value::Int(i) = arg { acc -= *i as f64; }
else if let Value::Float(f) = arg { acc -= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
let mult_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
self.register_native("*", mult_ty, |args| {
if args.len() == 2 {
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
(Value::Float(a), Value::Float(b)) => Value::Float(a * b),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b),
(Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64),
_ => Value::Void,
}
} else {
let mut acc = 1.0;
for arg in args {
if let Value::Int(i) = arg { acc *= i as f64; }
else if let Value::Float(f) = arg { acc *= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
self.register_native("*", StaticType::Any, |args| {
let mut acc = 1.0;
for arg in args {
if let Value::Int(i) = arg { acc *= i as f64; }
else if let Value::Float(f) = arg { acc *= f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
self.register_native("/", StaticType::Any, |args| {
let div_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
]);
self.register_native("/", div_ty, |args| {
if args.len() != 2 { return Value::Void; }
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
Value::Float(a / b)
});
self.register_native(">", StaticType::Any, |args| {
let cmp_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool },
]);
self.register_native(">", cmp_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
@@ -142,7 +204,7 @@ impl Environment {
}
});
self.register_native("<", StaticType::Any, |args| {
self.register_native("<", cmp_ty, |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),