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:
@@ -155,10 +155,10 @@ impl TypeChecker {
|
||||
let ret_ty = body_typed.ty.clone();
|
||||
|
||||
// 4. Construct function type: fn(any ... any) -> Ret
|
||||
let fn_ty = StaticType::Function {
|
||||
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
|
||||
params: vec![StaticType::Any; param_count as usize],
|
||||
ret: Box::new(ret_ty),
|
||||
};
|
||||
ret: ret_ty,
|
||||
}));
|
||||
|
||||
(BoundKind::Lambda {
|
||||
param_count,
|
||||
@@ -174,10 +174,8 @@ impl TypeChecker {
|
||||
typed_args.push(self.check_node(arg, ctx)?);
|
||||
}
|
||||
|
||||
let ret_ty = match &callee_typed.ty {
|
||||
StaticType::Function { ret, .. } => *ret.clone(),
|
||||
_ => StaticType::Any,
|
||||
};
|
||||
let arg_types: Vec<_> = typed_args.iter().map(|a| a.ty.clone()).collect();
|
||||
let ret_ty = callee_typed.ty.resolve_call(&arg_types).unwrap_or(StaticType::Any);
|
||||
|
||||
(BoundKind::Call {
|
||||
callee: Box::new(callee_typed),
|
||||
@@ -229,19 +227,11 @@ impl TypeChecker {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::compiler::binder::Binder;
|
||||
use crate::ast::types::StaticType;
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn check_source(source: &str) -> TypedNode {
|
||||
let mut parser = Parser::new(source).unwrap();
|
||||
let untyped = parser.parse_expression().unwrap();
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let bound = Binder::bind_root(globals, &untyped).unwrap();
|
||||
let global_types = Rc::new(RefCell::new(HashMap::new()));
|
||||
let checker = TypeChecker::new(global_types);
|
||||
checker.check(bound).unwrap()
|
||||
let env = crate::ast::environment::Environment::new();
|
||||
env.compile(source).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -273,14 +263,14 @@ mod tests {
|
||||
fn test_inference_lambda_return() {
|
||||
// (fn [a] 10) -> fn(any) -> Int
|
||||
let typed = check_source("(fn [a] 10)");
|
||||
if let StaticType::Function { ret, .. } = typed.ty {
|
||||
assert_eq!(*ret, StaticType::Int);
|
||||
if let StaticType::Function(sig) = typed.ty {
|
||||
assert_eq!(sig.ret, StaticType::Int);
|
||||
} else { panic!("Expected function type, got {:?}", typed.ty); }
|
||||
|
||||
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
|
||||
let typed_nested = check_source("(fn [] (do 1 2.5))");
|
||||
if let StaticType::Function { ret, .. } = typed_nested.ty {
|
||||
assert_eq!(*ret, StaticType::Float);
|
||||
if let StaticType::Function(sig) = typed_nested.ty {
|
||||
assert_eq!(sig.ret, StaticType::Float);
|
||||
} else { panic!("Expected function type"); }
|
||||
}
|
||||
|
||||
@@ -293,4 +283,12 @@ mod tests {
|
||||
assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment");
|
||||
} else { panic!("Expected block"); }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_operator_overloading_inference() {
|
||||
assert_eq!(check_source("(+ 1 2)").ty, StaticType::Int);
|
||||
assert_eq!(check_source("(+ 1.0 2.0)").ty, StaticType::Float);
|
||||
assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text);
|
||||
assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float);
|
||||
}
|
||||
}
|
||||
|
||||
+88
-26
@@ -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),
|
||||
|
||||
+53
-8
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user