Refactor: Implement Record Layouts and Optimized Field Access
Introduces a new `RecordLayout` system for efficient and type-safe record handling. Key changes: - `RecordLayout` struct with `O(1)` field lookup using FMap optimization. - Field accessors (`.name`) are now first-class callable values. - Parser recognizes dot-prefixed identifiers as field accessors. - Binder and TypeChecker handle field accessors. - Optimizer transforms field accessor calls into specialized `GetField` nodes. - VM executes `GetField` efficiently by directly accessing record values. - Adds a new `records.myc` example showcasing record features. - Improves comparison logic for records to use pointer equality for layout.
This commit is contained in:
@@ -426,4 +426,85 @@ mod tests {
|
||||
"Definitions should be removed by DCE"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_basics() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
((fn [user] [(.name user) (.age user)])
|
||||
{:name "Alice" :age 30})
|
||||
"#;
|
||||
let res = env.run_script(source).unwrap();
|
||||
assert_eq!(format!("{}", res), "[\"Alice\" 30]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_optimized_access() {
|
||||
let env = Environment::new();
|
||||
let source = "(.price {:id 1 :price 99.5})";
|
||||
|
||||
// 1. Check result
|
||||
let res = env.run_script(source).unwrap();
|
||||
assert_eq!(format!("{}", res), "99.5");
|
||||
|
||||
// 2. Verify optimization to GET_FIELD
|
||||
let dump = env.dump_ast(source).unwrap();
|
||||
assert!(dump.contains("GetField: .price"), "Should be optimized to GetField. Dump:\n{}", dump);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_class_field_accessor() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def get-name .name)
|
||||
; Dynamic call to field accessor
|
||||
(get-name {:name "Alice"}))
|
||||
"#;
|
||||
let res = env.run_script(source).unwrap();
|
||||
assert_eq!(format!("{}", res), "\"Alice\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_constant_folding() {
|
||||
let env = Environment::new();
|
||||
// Optimizer should fold (.x {:x 10}) into 10
|
||||
let source = "(.x {:x 10 :y 20})";
|
||||
let dump = env.dump_ast(source).unwrap();
|
||||
assert!(dump.contains("Constant: 10"), "Should be constant folded. Dump:\n{}", dump);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_errors() {
|
||||
let env = Environment::new();
|
||||
|
||||
// Both cases are reported by the TypeChecker since it can't resolve the call
|
||||
// for a missing field or a non-record argument.
|
||||
|
||||
// 1. Missing field
|
||||
let res_missing = env.run_script("(.missing {:a 1})");
|
||||
assert!(res_missing.is_err());
|
||||
assert!(res_missing.unwrap_err().contains("Invalid arguments"));
|
||||
|
||||
// 2. Not a record
|
||||
let res_not_rec = env.run_script("(.name 123)");
|
||||
assert!(res_not_rec.is_err());
|
||||
assert!(res_not_rec.unwrap_err().contains("Invalid arguments"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_layout_interning() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def r1 {:a 1 :b 2})
|
||||
(def r2 {:a 10 :b 20})
|
||||
; Identical layouts result in identical types
|
||||
(= r1 r2))
|
||||
"#;
|
||||
// This will be false because values differ, but let's just check if it compiles and runs.
|
||||
// To really test interning, we'd need a way to check if layouts are the same Arc.
|
||||
let res = env.run_script(source).unwrap();
|
||||
assert_eq!(format!("{}", res), "false");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user