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:
@@ -1,5 +1,5 @@
|
|||||||
;; Closure Scope Test
|
;; Closure Scope Test
|
||||||
;; Benchmark: 700ns
|
;; Benchmark: 800ns
|
||||||
;; Output: 15
|
;; Output: 15
|
||||||
(do
|
(do
|
||||||
(def make-adder (fn [x]
|
(def make-adder (fn [x]
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
;; Complex Data Structure Test
|
;; Complex Data Structure Test
|
||||||
;; Benchmark: 51.8us
|
;; Benchmark: 51.4us
|
||||||
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
||||||
(do
|
(do
|
||||||
(def fib (fn [n]
|
(def fib (fn [n]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 300ns
|
;; Benchmark: 400ns
|
||||||
;; Output: 5
|
;; Output: 5
|
||||||
(do
|
(do
|
||||||
(def y 1)
|
(def y 1)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 400ns
|
;; Benchmark: 300ns
|
||||||
;; Nested Macro and Arithmetic example
|
;; Nested Macro and Arithmetic example
|
||||||
;; Output: 81
|
;; Output: 81
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
;; heavily recursive, benefits significantly from specialization of integer arithmetic
|
;; heavily recursive, benefits significantly from specialization of integer arithmetic
|
||||||
;; and direct function calls (skipping dynamic dispatch).
|
;; and direct function calls (skipping dynamic dispatch).
|
||||||
;; Output: 5
|
;; Output: 5
|
||||||
|
|
||||||
;; Benchmark: 467.7us
|
;; Benchmark: 467.7us
|
||||||
|
|
||||||
(do
|
(do
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 2.8ms
|
;; Benchmark: 2.7ms
|
||||||
;; Output: "done"
|
;; Output: "done"
|
||||||
(do
|
(do
|
||||||
(def count_down (fn [n]
|
(def count_down (fn [n]
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
;; Benchmark: 1.9us
|
||||||
|
;; Demonstration of N-dimensional Tuples, Vectors, and Matrices
|
||||||
|
;; Based on docs/Tupel 1.md
|
||||||
|
;;
|
||||||
|
;; This test verifies that the literals are correctly parsed and represented.
|
||||||
|
;; Type inference is verified via internal compiler unit tests.
|
||||||
|
;;
|
||||||
|
;; Output: [[1 3.14 "text"] [10 20 30 40] [[1 2] [3 4]] [[[1 2] [3 4]] [[5 6] [7 8]]] [[1 2] [3 4 5]] {:x 1, :y 0.3}]
|
||||||
|
|
||||||
|
(do
|
||||||
|
(def tpl [1, 3.14, "text"])
|
||||||
|
(def v [10 20 30 40])
|
||||||
|
(def mat2d [[1 2], [3 4]])
|
||||||
|
(def mat3d [[[1 2] [3 4]] [[5 6] [7 8]]])
|
||||||
|
(def mixed [[1 2] [3 4 5]])
|
||||||
|
(def rec {:x 1, :y 0.3})
|
||||||
|
|
||||||
|
[tpl v mat2d mat3d mixed rec]
|
||||||
|
)
|
||||||
+25
-31
@@ -467,13 +467,12 @@ mod tests {
|
|||||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||||
let expanded = expander.expand(untyped).unwrap();
|
let expanded = expander.expand(untyped).unwrap();
|
||||||
|
|
||||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||||
if let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind {
|
&& let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind {
|
||||||
if let UntypedKind::Def { name, .. } = &result.kind {
|
if let UntypedKind::Def { name, .. } = &result.kind {
|
||||||
assert_eq!(name.context, Some(call.identity.clone()));
|
assert_eq!(name.context, Some(call.identity.clone()));
|
||||||
assert_eq!(name.name.as_ref(), "y");
|
assert_eq!(name.name.as_ref(), "y");
|
||||||
} else { panic!("Expected Def result, got {:?}", result.kind); }
|
} else { panic!("Expected Def result, got {:?}", result.kind); }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,20 +489,18 @@ mod tests {
|
|||||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||||
let expanded = expander.expand(untyped).unwrap();
|
let expanded = expander.expand(untyped).unwrap();
|
||||||
|
|
||||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||||
if let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind {
|
&& let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind
|
||||||
if let UntypedKind::If { cond, then_br, else_br } = &result.kind {
|
&& let UntypedKind::If { cond, then_br, else_br } = &result.kind {
|
||||||
if let UntypedKind::Identifier(sym) = &cond.kind {
|
if let UntypedKind::Identifier(sym) = &cond.kind {
|
||||||
assert_eq!(sym.name.as_ref(), "false");
|
assert_eq!(sym.name.as_ref(), "false");
|
||||||
} else { panic!("Expected identifier 'false', got {:?}", cond.kind); }
|
} else { panic!("Expected identifier 'false', got {:?}", cond.kind); }
|
||||||
assert!(matches!(then_br.kind, UntypedKind::Nop));
|
assert!(matches!(then_br.kind, UntypedKind::Nop));
|
||||||
if let Some(eb) = else_br {
|
if let Some(eb) = else_br {
|
||||||
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
|
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
|
||||||
assert_eq!(*n, 42);
|
assert_eq!(*n, 42);
|
||||||
} else { panic!("Expected 42, got {:?}", eb.kind); }
|
} else { panic!("Expected 42, got {:?}", eb.kind); }
|
||||||
} else { panic!("Else branch missing"); }
|
} else { panic!("Else branch missing"); }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,16 +517,13 @@ mod tests {
|
|||||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||||
let expanded = expander.expand(untyped).unwrap();
|
let expanded = expander.expand(untyped).unwrap();
|
||||||
|
|
||||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
if let UntypedKind::Block { exprs } = &expanded.kind
|
||||||
if let UntypedKind::Def { value, .. } = &exprs[1].kind {
|
&& let UntypedKind::Def { value, .. } = &exprs[1].kind
|
||||||
if let UntypedKind::Expansion { expanded: result, .. } = &value.kind {
|
&& let UntypedKind::Expansion { expanded: result, .. } = &value.kind
|
||||||
if let UntypedKind::Tuple { elements } = &result.kind {
|
&& let UntypedKind::Tuple { elements } = &result.kind {
|
||||||
assert_eq!(elements.len(), 5);
|
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[0].kind { assert_eq!(*n, 0); }
|
||||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); }
|
if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,16 +242,47 @@ impl TypeChecker {
|
|||||||
for e in elements {
|
for e in elements {
|
||||||
typed_elements.push(self.check_node(e, ctx)?);
|
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 } => {
|
BoundKind::Map { entries } => {
|
||||||
let mut typed_entries = Vec::new();
|
let mut typed_entries = Vec::new();
|
||||||
|
let mut fields = std::collections::BTreeMap::new();
|
||||||
for (k, v) in entries {
|
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 } => {
|
BoundKind::Expansion { original_call, bound_expanded } => {
|
||||||
@@ -371,4 +402,54 @@ mod tests {
|
|||||||
// DateTime comparison -> Bool
|
// DateTime comparison -> Bool
|
||||||
assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -102,12 +102,11 @@ impl<'a> Lexer<'a> {
|
|||||||
|
|
||||||
fn skip_whitespace(&mut self) {
|
fn skip_whitespace(&mut self) {
|
||||||
while let Some(&c) = self.peek() {
|
while let Some(&c) = self.peek() {
|
||||||
if c.is_whitespace() || is_invisible(c) {
|
if c.is_whitespace() || is_invisible(c) || c == ',' {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
self.line += 1;
|
self.line += 1;
|
||||||
self.col = 1;
|
self.col = 1;
|
||||||
} else if !is_invisible(c) {
|
} else {
|
||||||
// Only increment column for characters that actually take space
|
|
||||||
self.col += 1;
|
self.col += 1;
|
||||||
}
|
}
|
||||||
self.input.next();
|
self.input.next();
|
||||||
|
|||||||
+87
-5
@@ -89,7 +89,10 @@ pub enum StaticType {
|
|||||||
DateTime,
|
DateTime,
|
||||||
Text,
|
Text,
|
||||||
Keyword,
|
Keyword,
|
||||||
List(Box<StaticType>),
|
List(Box<StaticType>), // Legacy / Dynamic list
|
||||||
|
Tuple(Vec<StaticType>), // Heterogeneous fixed-size
|
||||||
|
Vector(Box<StaticType>, usize), // Homogeneous fixed-size
|
||||||
|
Matrix(Box<StaticType>, Vec<usize>), // Multi-dimensional homogeneous
|
||||||
Record(Rc<BTreeMap<Keyword, StaticType>>),
|
Record(Rc<BTreeMap<Keyword, StaticType>>),
|
||||||
Function(Box<Signature>),
|
Function(Box<Signature>),
|
||||||
FunctionOverloads(Vec<Signature>),
|
FunctionOverloads(Vec<Signature>),
|
||||||
@@ -108,6 +111,23 @@ impl fmt::Display for StaticType {
|
|||||||
StaticType::Text => write!(f, "text"),
|
StaticType::Text => write!(f, "text"),
|
||||||
StaticType::Keyword => write!(f, "keyword"),
|
StaticType::Keyword => write!(f, "keyword"),
|
||||||
StaticType::List(inner) => write!(f, "[{}]", inner),
|
StaticType::List(inner) => write!(f, "[{}]", inner),
|
||||||
|
StaticType::Tuple(elements) => {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, el) in elements.iter().enumerate() {
|
||||||
|
if i > 0 { write!(f, " ")?; }
|
||||||
|
write!(f, "{}", el)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
},
|
||||||
|
StaticType::Vector(inner, len) => write!(f, "vector<{}, {}>", inner, len),
|
||||||
|
StaticType::Matrix(inner, shape) => {
|
||||||
|
write!(f, "matrix<{}, [", inner)?;
|
||||||
|
for (i, s) in shape.iter().enumerate() {
|
||||||
|
if i > 0 { write!(f, " ")?; }
|
||||||
|
write!(f, "{}", s)?;
|
||||||
|
}
|
||||||
|
write!(f, "]>")
|
||||||
|
},
|
||||||
StaticType::Record(fields) => {
|
StaticType::Record(fields) => {
|
||||||
write!(f, "{{")?;
|
write!(f, "{{")?;
|
||||||
for (i, (k, v)) in fields.iter().enumerate() {
|
for (i, (k, v)) in fields.iter().enumerate() {
|
||||||
@@ -138,8 +158,26 @@ impl StaticType {
|
|||||||
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) {
|
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Future: Numeric promotion (Int -> Float)
|
|
||||||
false
|
match (self, other) {
|
||||||
|
// A Vector is a Tuple
|
||||||
|
(StaticType::Tuple(elements), StaticType::Vector(inner, len)) => {
|
||||||
|
if elements.len() != *len { return false; }
|
||||||
|
elements.iter().all(|e| e.is_assignable_from(inner))
|
||||||
|
},
|
||||||
|
// A Matrix is a Vector (of Vectors/Matrices)
|
||||||
|
(StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => {
|
||||||
|
if shape.is_empty() || shape[0] != *len { return false; }
|
||||||
|
if shape.len() == 1 {
|
||||||
|
inner.is_assignable_from(m_inner)
|
||||||
|
} else {
|
||||||
|
// It's a matrix of higher dimension, so inner must be assignable from a sub-matrix
|
||||||
|
let sub_shape = shape[1..].to_vec();
|
||||||
|
inner.is_assignable_from(&StaticType::Matrix(m_inner.clone(), sub_shape))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to resolve a call with the given argument types and returns the return type.
|
/// Tries to resolve a call with the given argument types and returns the return type.
|
||||||
@@ -168,6 +206,17 @@ impl StaticType {
|
|||||||
}
|
}
|
||||||
expected.iter().zip(actual).all(|(e, a)| e.is_assignable_from(a))
|
expected.iter().zip(actual).all(|(e, a)| e.is_assignable_from(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.)
|
||||||
|
pub fn is_scalar_pure(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
StaticType::Int | StaticType::Float | StaticType::Bool | StaticType::DateTime => true,
|
||||||
|
StaticType::Tuple(elements) => elements.iter().all(|e| e.is_scalar_pure()),
|
||||||
|
StaticType::Vector(inner, _) => inner.is_scalar_pure(),
|
||||||
|
StaticType::Matrix(inner, _) => inner.is_scalar_pure(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
@@ -189,8 +238,41 @@ impl Value {
|
|||||||
Value::DateTime(_) => StaticType::DateTime,
|
Value::DateTime(_) => StaticType::DateTime,
|
||||||
Value::Text(_) => StaticType::Text,
|
Value::Text(_) => StaticType::Text,
|
||||||
Value::Keyword(_) => StaticType::Keyword,
|
Value::Keyword(_) => StaticType::Keyword,
|
||||||
Value::List(_) => StaticType::List(Box::new(StaticType::Any)),
|
Value::List(l) => {
|
||||||
Value::Record(_) => StaticType::Record(Rc::new(BTreeMap::new())), // Empty for now
|
if l.is_empty() {
|
||||||
|
return StaticType::Vector(Box::new(StaticType::Any), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let element_types: Vec<_> = l.iter().map(|v| v.static_type()).collect();
|
||||||
|
|
||||||
|
// Check for Homogeneity (Vector)
|
||||||
|
let first_ty = &element_types[0];
|
||||||
|
let all_same = element_types.iter().all(|t| t == first_ty);
|
||||||
|
|
||||||
|
if all_same {
|
||||||
|
match first_ty {
|
||||||
|
StaticType::Vector(inner, len) => {
|
||||||
|
// Possible Matrix
|
||||||
|
StaticType::Matrix(inner.clone(), vec![l.len(), *len])
|
||||||
|
},
|
||||||
|
StaticType::Matrix(inner, shape) => {
|
||||||
|
let mut new_shape = vec![l.len()];
|
||||||
|
new_shape.extend(shape);
|
||||||
|
StaticType::Matrix(inner.clone(), new_shape)
|
||||||
|
},
|
||||||
|
_ => StaticType::Vector(Box::new(first_ty.clone()), l.len())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StaticType::Tuple(element_types)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Record(r) => {
|
||||||
|
let mut fields = BTreeMap::new();
|
||||||
|
for (k, v) in r.iter() {
|
||||||
|
fields.insert(*k, v.static_type());
|
||||||
|
}
|
||||||
|
StaticType::Record(Rc::new(fields))
|
||||||
|
},
|
||||||
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(),
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ mod tests {
|
|||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry = entry.expect("Invalid entry");
|
let entry = entry.expect("Invalid entry");
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.extension().map_or(false, |ext| ext == "myc") {
|
if path.extension().is_some_and(|ext| ext == "myc") {
|
||||||
let content = fs::read_to_string(&path).expect("Could not read file");
|
let content = fs::read_to_string(&path).expect("Could not read file");
|
||||||
|
|
||||||
// Find expected output tag: ;; Output: <value>
|
// Find expected output tag: ;; Output: <value>
|
||||||
|
|||||||
Reference in New Issue
Block a user