From 512193febcac3de26f1cfa25122717c8a6d9e760 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Thu, 26 Feb 2026 21:54:26 +0100 Subject: [PATCH] Add object-oriented records example --- examples/object_records.myc | 50 ++++++++++++++++++++++++++++ src/ast/compiler/optimizer/engine.rs | 5 ++- src/ast/compiler/optimizer/utils.rs | 4 +-- src/integration_test.rs | 41 +++++++++++++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 examples/object_records.myc diff --git a/examples/object_records.myc b/examples/object_records.myc new file mode 100644 index 0000000..0aab3bf --- /dev/null +++ b/examples/object_records.myc @@ -0,0 +1,50 @@ +;; Output: [150 130 "Insufficient funds" 130] + +; --------------------------------------------------------- +; Object-Oriented Records Example +; --------------------------------------------------------- +; This showcase treats a record as an object by storing +; closures that capture private state. + +(do + ; A factory function (Constructor) for a BankAccount object + (def make-account (fn [initial-balance] + (do + ; 'balance' is a captured parameter, acting as private state + (def balance initial-balance) + + { + ; Method: Get current balance + :balance (fn [] balance) + + ; Method: Deposit money + :deposit (fn [amount] + (assign balance (+ balance amount))) + + ; Method: Withdraw money with validation + :withdraw (fn [amount] + (if (>= balance amount) + (assign balance (- balance amount)) + "Insufficient funds")) + } + ))) + + ; 1. Create an instance + (def my-acc (make-account 100)) + + ; 2. Call 'deposit' method + ; Note the double parens: (.deposit my-acc) gets the function, then we call it + (def b1 ((.deposit my-acc) 50)) ; balance is now 150 + + ; 3. Call 'withdraw' method + (def b2 ((.withdraw my-acc) 20)) ; balance is now 130 + + ; 4. Call 'withdraw' with invalid amount + (def error-msg ((.withdraw my-acc) 1000)) + + ; 5. Call 'balance' getter + (def final-balance ((.balance my-acc))) + + ; Return summary of operations + [b1 b2 error-msg final-balance] +) diff --git a/src/ast/compiler/optimizer/engine.rs b/src/ast/compiler/optimizer/engine.rs index 9c2b198..0e73f13 100644 --- a/src/ast/compiler/optimizer/engine.rs +++ b/src/ast/compiler/optimizer/engine.rs @@ -472,9 +472,12 @@ impl Optimizer { for (old_idx, capture_addr) in original_upvalues.iter().enumerate() { let mut inlined_val = None; - if let Some(val) = sub.get_value(capture_addr) { + if !sub.assigned.contains(capture_addr) + && let Some(val) = sub.get_value(capture_addr) + { inlined_val = Some(val.clone()); } + if let Address::Local(slot) = capture_addr { sub.captured_slots.insert(*slot); } diff --git a/src/ast/compiler/optimizer/utils.rs b/src/ast/compiler/optimizer/utils.rs index dce7aa2..ec1dc96 100644 --- a/src/ast/compiler/optimizer/utils.rs +++ b/src/ast/compiler/optimizer/utils.rs @@ -159,9 +159,7 @@ impl UsageInfo { pub fn collect_pattern(&mut self, node: &AnalyzedNode) { match &node.kind { - BoundKind::Define { addr, .. } => { - self.assigned.insert(*addr); - } + BoundKind::Define { .. } => {} BoundKind::Set { addr, .. } => { self.assigned.insert(*addr); } diff --git a/src/integration_test.rs b/src/integration_test.rs index 1af3518..4a6ce4c 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -427,6 +427,47 @@ mod tests { ); } + #[test] + fn test_optimizer_upvalue_inlining_bug_repro() { + let env = Environment::new(); + let source = r#" + (do + (def make-counter (fn [init] + (do + (def val init) + { + :inc (fn [] (assign val (+ val 1))) + :get (fn [] val) + }))) + (def c (make-counter 10)) + ((.inc c)) + ((.get c))) + "#; + let res = env.run_script(source); + assert!(res.is_ok(), "Optimizer bug triggered: {:?}", res.err()); + assert_eq!(format!("{}", res.unwrap()), "11"); + } + + #[test] + fn test_optimizer_destructuring_inlining_and_mutation() { + let env = Environment::new(); + // 1. Test: Destructuring definition should allow inlining if not mutated + let source_inline = "(do (def [x y] [10 20]) (+ x y))"; + let res_inline = env.run_script(source_inline).unwrap(); + assert_eq!(format!("{}", res_inline), "30"); + + // 2. Test: Destructuring definition should NOT be inlined if mutated (Regression Test) + let source_mutation = r#" + (do + (def [a b] [1 2]) + (def f (fn [] (assign a (+ a b)))) + (f) + a) + "#; + let res_mutation = env.run_script(source_mutation).unwrap(); + assert_eq!(format!("{}", res_mutation), "3"); + } + #[test] fn test_record_basics() { let env = Environment::new();