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:
@@ -4,9 +4,7 @@
|
|||||||
* Das Ziel ist, die alte Delphi-Codebase nach Rust zu portieren.
|
* Das Ziel ist, die alte Delphi-Codebase nach Rust zu portieren.
|
||||||
* Die alte Delphi-Codebase ist nicht zu verändern.
|
* Die alte Delphi-Codebase ist nicht zu verändern.
|
||||||
|
|
||||||
Wichtig: zentraler Einstiegspunkt ist
|
Wichtig: zentraler Einstiegspunkt ist Delphi/Myc.Ast.Environment.pasund die dort referenzierten Units.
|
||||||
@Delphi/Myc.Ast.Environment.pas
|
|
||||||
und die dort referenzierten Units.
|
|
||||||
|
|
||||||
### Design
|
### Design
|
||||||
|
|
||||||
@@ -38,3 +36,8 @@ und die dort referenzierten Units.
|
|||||||
|
|
||||||
* Ich kann Rust noch nicht so gut. Erkläre mir, was du machst und welche Konzepte du nutzt. Ich will was dazulernen.
|
* Ich kann Rust noch nicht so gut. Erkläre mir, was du machst und welche Konzepte du nutzt. Ich will was dazulernen.
|
||||||
* Wenn clippy etwas vorschlägt, von dem du glaubst, dass es nicht stabil ist, dann überprüfe die aktuelle Version. Wahrscheinlich ist das Feature mittlerweile im trunk. Wir gehen immer davon aus, dass clippy recht hat.
|
* Wenn clippy etwas vorschlägt, von dem du glaubst, dass es nicht stabil ist, dann überprüfe die aktuelle Version. Wahrscheinlich ist das Feature mittlerweile im trunk. Wir gehen immer davon aus, dass clippy recht hat.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
* Das Projekt enthält eine Testsuite für Skripte. Logik in src/utils/tester.rs. Diese Testes werden automatisch in "cargo test" eingebunden.
|
||||||
|
* Nach Fertigstellung einer Aufgabe: Warnungen sind zu eliminieren. Tests müssen durchlaufen. Clippy auch.
|
||||||
|
|||||||
@@ -155,10 +155,10 @@ impl TypeChecker {
|
|||||||
let ret_ty = body_typed.ty.clone();
|
let ret_ty = body_typed.ty.clone();
|
||||||
|
|
||||||
// 4. Construct function type: fn(any ... any) -> Ret
|
// 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],
|
params: vec![StaticType::Any; param_count as usize],
|
||||||
ret: Box::new(ret_ty),
|
ret: ret_ty,
|
||||||
};
|
}));
|
||||||
|
|
||||||
(BoundKind::Lambda {
|
(BoundKind::Lambda {
|
||||||
param_count,
|
param_count,
|
||||||
@@ -174,10 +174,8 @@ impl TypeChecker {
|
|||||||
typed_args.push(self.check_node(arg, ctx)?);
|
typed_args.push(self.check_node(arg, ctx)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret_ty = match &callee_typed.ty {
|
let arg_types: Vec<_> = typed_args.iter().map(|a| a.ty.clone()).collect();
|
||||||
StaticType::Function { ret, .. } => *ret.clone(),
|
let ret_ty = callee_typed.ty.resolve_call(&arg_types).unwrap_or(StaticType::Any);
|
||||||
_ => StaticType::Any,
|
|
||||||
};
|
|
||||||
|
|
||||||
(BoundKind::Call {
|
(BoundKind::Call {
|
||||||
callee: Box::new(callee_typed),
|
callee: Box::new(callee_typed),
|
||||||
@@ -229,19 +227,11 @@ impl TypeChecker {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::parser::Parser;
|
|
||||||
use crate::ast::compiler::binder::Binder;
|
|
||||||
use crate::ast::types::StaticType;
|
use crate::ast::types::StaticType;
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
fn check_source(source: &str) -> TypedNode {
|
fn check_source(source: &str) -> TypedNode {
|
||||||
let mut parser = Parser::new(source).unwrap();
|
let env = crate::ast::environment::Environment::new();
|
||||||
let untyped = parser.parse_expression().unwrap();
|
env.compile(source).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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -273,14 +263,14 @@ mod tests {
|
|||||||
fn test_inference_lambda_return() {
|
fn test_inference_lambda_return() {
|
||||||
// (fn [a] 10) -> fn(any) -> Int
|
// (fn [a] 10) -> fn(any) -> Int
|
||||||
let typed = check_source("(fn [a] 10)");
|
let typed = check_source("(fn [a] 10)");
|
||||||
if let StaticType::Function { ret, .. } = typed.ty {
|
if let StaticType::Function(sig) = typed.ty {
|
||||||
assert_eq!(*ret, StaticType::Int);
|
assert_eq!(sig.ret, StaticType::Int);
|
||||||
} else { panic!("Expected function type, got {:?}", typed.ty); }
|
} else { panic!("Expected function type, got {:?}", typed.ty); }
|
||||||
|
|
||||||
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
|
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
|
||||||
let typed_nested = check_source("(fn [] (do 1 2.5))");
|
let typed_nested = check_source("(fn [] (do 1 2.5))");
|
||||||
if let StaticType::Function { ret, .. } = typed_nested.ty {
|
if let StaticType::Function(sig) = typed_nested.ty {
|
||||||
assert_eq!(*ret, StaticType::Float);
|
assert_eq!(sig.ret, StaticType::Float);
|
||||||
} else { panic!("Expected function type"); }
|
} 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");
|
assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment");
|
||||||
} else { panic!("Expected block"); }
|
} 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) {
|
fn register_stdlib(&self) {
|
||||||
self.register_native("+", StaticType::Any, |args| {
|
use crate::ast::types::Signature;
|
||||||
let mut acc = 0.0;
|
|
||||||
for arg in args {
|
let plus_ty = StaticType::FunctionOverloads(vec![
|
||||||
if let Value::Int(i) = arg { acc += i as f64; }
|
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int },
|
||||||
else if let Value::Float(f) = arg { acc += f; }
|
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.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] {
|
let mut acc = match args[0] {
|
||||||
Value::Int(i) => i as f64,
|
Value::Int(i) => i as f64,
|
||||||
Value::Float(f) => f,
|
Value::Float(f) => f,
|
||||||
_ => return Value::Void,
|
_ => return Value::Void,
|
||||||
};
|
};
|
||||||
if args.len() == 1 {
|
for arg in &args[1..] {
|
||||||
acc = -acc;
|
if let Value::Int(i) = arg { acc -= *i as f64; }
|
||||||
} else {
|
else if let Value::Float(f) = arg { acc -= f; }
|
||||||
for arg in &args[1..] {
|
}
|
||||||
if let Value::Int(i) = arg { acc -= *i as f64; }
|
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
|
||||||
else if let Value::Float(f) = arg { acc -= f; }
|
});
|
||||||
|
|
||||||
|
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 div_ty = StaticType::FunctionOverloads(vec![
|
||||||
let mut acc = 1.0;
|
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float },
|
||||||
for arg in args {
|
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float },
|
||||||
if let Value::Int(i) = arg { acc *= i as f64; }
|
]);
|
||||||
else if let Value::Float(f) = arg { acc *= f; }
|
self.register_native("/", div_ty, |args| {
|
||||||
}
|
|
||||||
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
|
|
||||||
});
|
|
||||||
|
|
||||||
self.register_native("/", StaticType::Any, |args| {
|
|
||||||
if args.len() != 2 { return Value::Void; }
|
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 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 };
|
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
|
||||||
Value::Float(a / b)
|
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; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
|
(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; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
|
(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)
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum StaticType {
|
pub enum StaticType {
|
||||||
Any,
|
Any,
|
||||||
@@ -82,10 +88,8 @@ pub enum StaticType {
|
|||||||
Keyword,
|
Keyword,
|
||||||
List(Box<StaticType>),
|
List(Box<StaticType>),
|
||||||
Record(Rc<BTreeMap<Keyword, StaticType>>),
|
Record(Rc<BTreeMap<Keyword, StaticType>>),
|
||||||
Function {
|
Function(Box<Signature>),
|
||||||
params: Vec<StaticType>,
|
FunctionOverloads(Vec<Signature>),
|
||||||
ret: Box<StaticType>,
|
|
||||||
},
|
|
||||||
Object(&'static str),
|
Object(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,19 +112,60 @@ impl fmt::Display for StaticType {
|
|||||||
}
|
}
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
},
|
},
|
||||||
StaticType::Function { params, ret } => {
|
StaticType::Function(sig) => {
|
||||||
write!(f, "fn(")?;
|
write!(f, "fn(")?;
|
||||||
for (i, p) in params.iter().enumerate() {
|
for (i, p) in sig.params.iter().enumerate() {
|
||||||
if i > 0 { write!(f, " ")?; }
|
if i > 0 { write!(f, " ")?; }
|
||||||
write!(f, "{}", p)?;
|
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),
|
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 {
|
impl Value {
|
||||||
pub fn is_truthy(&self) -> bool {
|
pub fn is_truthy(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -141,7 +186,7 @@ impl Value {
|
|||||||
Value::Keyword(_) => StaticType::Keyword,
|
Value::Keyword(_) => StaticType::Keyword,
|
||||||
Value::List(_) => StaticType::List(Box::new(StaticType::Any)),
|
Value::List(_) => StaticType::List(Box::new(StaticType::Any)),
|
||||||
Value::Record(_) => StaticType::Record(Rc::new(BTreeMap::new())), // Empty for now
|
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::Object(o) => StaticType::Object(o.type_name()),
|
||||||
Value::Cell(c) => c.borrow().static_type(),
|
Value::Cell(c) => c.borrow().static_type(),
|
||||||
Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any
|
Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any
|
||||||
|
|||||||
Reference in New Issue
Block a user