Refactor type inference for collections
The type inference for collections (tuples, vectors, matrices, and records) has been significantly refactored. This includes: - Introducing distinct `StaticType` variants for `Tuple`, `Vector`, and `Matrix`. - Implementing more robust type checking for these collections, allowing for homogeneous and heterogeneous structures. - Enhancing the `is_assignable_from` method to handle type compatibility between these collection types. - Updating `Value::static_type()` to correctly infer the types of literal collections. - Improving the type inference for map literals to create `Record` types. - Adding comprehensive unit tests for tuple, vector, matrix, and record type inference.
This commit is contained in:
+25
-31
@@ -467,13 +467,12 @@ mod tests {
|
||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||
let expanded = expander.expand(untyped).unwrap();
|
||||
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
||||
if let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind {
|
||||
if let UntypedKind::Def { name, .. } = &result.kind {
|
||||
assert_eq!(name.context, Some(call.identity.clone()));
|
||||
assert_eq!(name.name.as_ref(), "y");
|
||||
} else { panic!("Expected Def result, got {:?}", result.kind); }
|
||||
}
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||
&& let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind {
|
||||
if let UntypedKind::Def { name, .. } = &result.kind {
|
||||
assert_eq!(name.context, Some(call.identity.clone()));
|
||||
assert_eq!(name.name.as_ref(), "y");
|
||||
} else { panic!("Expected Def result, got {:?}", result.kind); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,20 +489,18 @@ mod tests {
|
||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||
let expanded = expander.expand(untyped).unwrap();
|
||||
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
||||
if let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind {
|
||||
if let UntypedKind::If { cond, then_br, else_br } = &result.kind {
|
||||
if let UntypedKind::Identifier(sym) = &cond.kind {
|
||||
assert_eq!(sym.name.as_ref(), "false");
|
||||
} else { panic!("Expected identifier 'false', got {:?}", cond.kind); }
|
||||
assert!(matches!(then_br.kind, UntypedKind::Nop));
|
||||
if let Some(eb) = else_br {
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
|
||||
assert_eq!(*n, 42);
|
||||
} else { panic!("Expected 42, got {:?}", eb.kind); }
|
||||
} else { panic!("Else branch missing"); }
|
||||
}
|
||||
}
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||
&& let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind
|
||||
&& let UntypedKind::If { cond, then_br, else_br } = &result.kind {
|
||||
if let UntypedKind::Identifier(sym) = &cond.kind {
|
||||
assert_eq!(sym.name.as_ref(), "false");
|
||||
} else { panic!("Expected identifier 'false', got {:?}", cond.kind); }
|
||||
assert!(matches!(then_br.kind, UntypedKind::Nop));
|
||||
if let Some(eb) = else_br {
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
|
||||
assert_eq!(*n, 42);
|
||||
} else { panic!("Expected 42, got {:?}", eb.kind); }
|
||||
} else { panic!("Else branch missing"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,16 +517,13 @@ mod tests {
|
||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||
let expanded = expander.expand(untyped).unwrap();
|
||||
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
||||
if let UntypedKind::Def { value, .. } = &exprs[1].kind {
|
||||
if let UntypedKind::Expansion { expanded: result, .. } = &value.kind {
|
||||
if let UntypedKind::Tuple { elements } = &result.kind {
|
||||
assert_eq!(elements.len(), 5);
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[0].kind { assert_eq!(*n, 0); }
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||
&& let UntypedKind::Def { value, .. } = &exprs[1].kind
|
||||
&& let UntypedKind::Expansion { expanded: result, .. } = &value.kind
|
||||
&& let UntypedKind::Tuple { elements } = &result.kind {
|
||||
assert_eq!(elements.len(), 5);
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[0].kind { assert_eq!(*n, 0); }
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,16 +242,47 @@ impl TypeChecker {
|
||||
for e in elements {
|
||||
typed_elements.push(self.check_node(e, ctx)?);
|
||||
}
|
||||
// Stability: Just List(Any) for now
|
||||
(BoundKind::Tuple { elements: typed_elements }, StaticType::List(Box::new(StaticType::Any)))
|
||||
|
||||
let ty = if typed_elements.is_empty() {
|
||||
StaticType::Vector(Box::new(StaticType::Any), 0)
|
||||
} else {
|
||||
let first_ty = &typed_elements[0].ty;
|
||||
let all_same = typed_elements.iter().all(|e| e.ty == *first_ty);
|
||||
|
||||
if all_same {
|
||||
match first_ty {
|
||||
StaticType::Vector(inner, len) => {
|
||||
StaticType::Matrix(inner.clone(), vec![typed_elements.len(), *len])
|
||||
},
|
||||
StaticType::Matrix(inner, shape) => {
|
||||
let mut new_shape = vec![typed_elements.len()];
|
||||
new_shape.extend(shape);
|
||||
StaticType::Matrix(inner.clone(), new_shape)
|
||||
},
|
||||
_ => StaticType::Vector(Box::new(first_ty.clone()), typed_elements.len())
|
||||
}
|
||||
} else {
|
||||
StaticType::Tuple(typed_elements.iter().map(|e| e.ty.clone()).collect())
|
||||
}
|
||||
};
|
||||
|
||||
(BoundKind::Tuple { elements: typed_elements }, ty)
|
||||
},
|
||||
|
||||
BoundKind::Map { entries } => {
|
||||
let mut typed_entries = Vec::new();
|
||||
let mut fields = std::collections::BTreeMap::new();
|
||||
for (k, v) in entries {
|
||||
typed_entries.push((self.check_node(k, ctx)?, self.check_node(v, ctx)?));
|
||||
let kt = self.check_node(k, ctx)?;
|
||||
let vt = self.check_node(v, ctx)?;
|
||||
|
||||
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind {
|
||||
fields.insert(*kw, vt.ty.clone());
|
||||
}
|
||||
|
||||
typed_entries.push((kt, vt));
|
||||
}
|
||||
(BoundKind::Map { entries: typed_entries }, StaticType::Record(Rc::new(std::collections::BTreeMap::new())))
|
||||
(BoundKind::Map { entries: typed_entries }, StaticType::Record(Rc::new(fields)))
|
||||
},
|
||||
|
||||
BoundKind::Expansion { original_call, bound_expanded } => {
|
||||
@@ -371,4 +402,54 @@ mod tests {
|
||||
// DateTime comparison -> Bool
|
||||
assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inference_tuple_vector_matrix() {
|
||||
// Heterogeneous -> Tuple
|
||||
assert_eq!(
|
||||
get_ret_type(&check_source("[1 3.14 \"text\"]")),
|
||||
StaticType::Tuple(vec![StaticType::Int, StaticType::Float, StaticType::Text])
|
||||
);
|
||||
|
||||
// Homogeneous -> Vector
|
||||
assert_eq!(
|
||||
get_ret_type(&check_source("[10 20 30]")),
|
||||
StaticType::Vector(Box::new(StaticType::Int), 3)
|
||||
);
|
||||
|
||||
// Nested Homogeneous -> Matrix
|
||||
assert_eq!(
|
||||
get_ret_type(&check_source("[[1 2] [3 4]]")),
|
||||
StaticType::Matrix(Box::new(StaticType::Int), vec![2, 2])
|
||||
);
|
||||
|
||||
// Deep Matrix
|
||||
assert_eq!(
|
||||
get_ret_type(&check_source("[[[1 2]] [[3 4]]]")),
|
||||
StaticType::Matrix(Box::new(StaticType::Int), vec![2, 1, 2])
|
||||
);
|
||||
|
||||
// Shape mismatch -> Tuple of Vectors
|
||||
let mixed = get_ret_type(&check_source("[[1 2] [3 4 5]]"));
|
||||
if let StaticType::Tuple(elements) = mixed {
|
||||
assert_eq!(elements[0], StaticType::Vector(Box::new(StaticType::Int), 2));
|
||||
assert_eq!(elements[1], StaticType::Vector(Box::new(StaticType::Int), 3));
|
||||
} else {
|
||||
panic!("Expected Tuple for shape mismatch, got {:?}", mixed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inference_record() {
|
||||
use crate::ast::types::Keyword;
|
||||
let typed = check_source("{:x 1, :y 0.3}");
|
||||
let ty = get_ret_type(&typed);
|
||||
if let StaticType::Record(fields) = ty {
|
||||
assert_eq!(fields.len(), 2);
|
||||
assert_eq!(fields.get(&Keyword::intern("x")), Some(&StaticType::Int));
|
||||
assert_eq!(fields.get(&Keyword::intern("y")), Some(&StaticType::Float));
|
||||
} else {
|
||||
panic!("Expected Record, got {:?}", ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user