Introduce numeric and record widening
This commit introduces implicit numeric widening from Int to Float and a corresponding record promotion mechanism. When pushing values into a series, if the series' element type is a TypeVar, and the pushed value is of a different numeric type (e.g., Int and Float), the TypeVar will be unified with the wider type (Float). This ensures that operations on series elements handle type differences gracefully. The record promotion handles cases where two records are involved, and their fields have compatible types, either identical or through numeric widening. This allows for more flexible type inference in complex structures.
This commit is contained in:
@@ -65,6 +65,140 @@ fn test_series_typevar_resolved_through_while_upvalue() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Numeric promotion (Int → Float widening) ────────────────────────────────
|
||||
|
||||
/// Push int first, then float: series element type should widen to Float.
|
||||
/// The int value must be readable back as a float (no truncation).
|
||||
#[test]
|
||||
fn test_series_promote_int_then_float() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s 1)
|
||||
(push s 2.5)
|
||||
(+ (s 0) (s 1))
|
||||
)
|
||||
"#;
|
||||
let result = env.run_script(source);
|
||||
match result {
|
||||
Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {}
|
||||
other => panic!("Expected Float(3.5), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Push float first, then int: same widening, other direction.
|
||||
#[test]
|
||||
fn test_series_promote_float_then_int() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s 2.5)
|
||||
(push s 1)
|
||||
(+ (s 0) (s 1))
|
||||
)
|
||||
"#;
|
||||
let result = env.run_script(source);
|
||||
match result {
|
||||
Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {}
|
||||
other => panic!("Expected Float(3.5), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// `if` branches with int/float: the join type must be Float, not Any.
|
||||
/// A Float series is expected — the pushed value must be accessible as float.
|
||||
#[test]
|
||||
fn test_series_promote_if_int_float_branch() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s (if true 1 2.5))
|
||||
(push s (if false 1 2.5))
|
||||
(+ (s 0) (s 1))
|
||||
)
|
||||
"#;
|
||||
let result = env.run_script(source);
|
||||
match result {
|
||||
Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {}
|
||||
other => panic!("Expected Float(3.5), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Int variable pushed into a float-inferred series must be promoted.
|
||||
#[test]
|
||||
fn test_series_promote_int_variable_into_float_series() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(def n 3)
|
||||
(push s 1.5)
|
||||
(push s n)
|
||||
(+ (s 0) (s 1))
|
||||
)
|
||||
"#;
|
||||
let result = env.run_script(source);
|
||||
match result {
|
||||
Ok(Value::Float(v)) if (v - 4.5).abs() < 1e-9 => {}
|
||||
other => panic!("Expected Float(4.5), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Record field promotion: pushing records where one field is int, another float.
|
||||
/// The field type must widen to Float.
|
||||
#[test]
|
||||
fn test_series_promote_record_field_int_float() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s {:v 1})
|
||||
(push s {:v 2.5})
|
||||
(def v (.v s))
|
||||
(+ (v 0) (v 1))
|
||||
)
|
||||
"#;
|
||||
let result = env.run_script(source);
|
||||
match result {
|
||||
Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {}
|
||||
other => panic!("Expected Float(3.5), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanity check: Bool → Int is NOT a valid promotion (different semantic type).
|
||||
#[test]
|
||||
fn test_series_no_promotion_bool_int() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s true)
|
||||
(push s 1)
|
||||
(s 0)
|
||||
)
|
||||
"#;
|
||||
let result = env.compile(source).into_result();
|
||||
assert!(result.is_err(), "Bool->Int promotion must not be allowed");
|
||||
}
|
||||
|
||||
/// Sanity check: Float → Bool is NOT a valid promotion.
|
||||
#[test]
|
||||
fn test_series_no_promotion_float_bool() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def s (series 5))
|
||||
(push s 1.5)
|
||||
(push s true)
|
||||
(s 0)
|
||||
)
|
||||
"#;
|
||||
let result = env.compile(source).into_result();
|
||||
assert!(result.is_err(), "Float->Bool promotion must not be allowed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_structural_equality_order() {
|
||||
let env = Environment::new();
|
||||
|
||||
Reference in New Issue
Block a user