From fdd8e18cc30b6043240a31d9c3d4bf0e6b435a5a Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 09:54:40 +0000 Subject: [PATCH] smalltalk: Object>>becomeForward: + 6 tests --- lib/smalltalk/eval.sx | 18 +++++++++ lib/smalltalk/tests/reflection.sx | 63 +++++++++++++++++++++++++++++++ plans/smalltalk-on-sx.md | 3 +- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/lib/smalltalk/eval.sx b/lib/smalltalk/eval.sx index 0d8e7092..ed97b303 100644 --- a/lib/smalltalk/eval.sx +++ b/lib/smalltalk/eval.sx @@ -565,6 +565,24 @@ (cond ((not (st-class-ref? arg)) false) (else (= target-cls (get arg :name)))))) + ;; Object>>becomeForward: aReceiver — one-way become. The receiver's + ;; class and ivars are mutated in place to match the target. Every + ;; existing reference to the receiver dict sees the new identity. + ;; Note: receiver and target remain distinct dicts (not == in the + ;; SX-identity sense), but receiver behaves as though it were the + ;; target — which is the practical Pharo guarantee. + ((= selector "becomeForward:") + (let ((other (nth args 0))) + (cond + ((not (st-instance? receiver)) + (error "becomeForward: only supported on user instances")) + ((not (st-instance? other)) + (error "becomeForward: target must be a user instance")) + (else + (begin + (dict-set! receiver :class (get other :class)) + (dict-set! receiver :ivars (get other :ivars)) + receiver))))) ((or (= cls "SmallInteger") (= cls "Float")) (st-num-send receiver selector args)) ((or (= cls "String") (= cls "Symbol")) diff --git a/lib/smalltalk/tests/reflection.sx b/lib/smalltalk/tests/reflection.sx index a4a73896..51ff5ca6 100644 --- a/lib/smalltalk/tests/reflection.sx +++ b/lib/smalltalk/tests/reflection.sx @@ -239,3 +239,66 @@ (evp "Cat compile: 'taggedMethod ^ #yes' classified: 'demo'") (str (evp "^ Cat new taggedMethod"))) "yes") + +;; ── 19. Object>>becomeForward: ── +(st-class-define! "Box" "Object" (list "value")) +(st-class-add-method! "Box" "value" (st-parse-method "value ^ value")) +(st-class-add-method! "Box" "value:" (st-parse-method "value: v value := v. ^ self")) +(st-class-add-method! "Box" "kind" (st-parse-method "kind ^ #box")) + +(st-class-define! "Crate" "Object" (list "value")) +(st-class-add-method! "Crate" "value" (st-parse-method "value ^ value")) +(st-class-add-method! "Crate" "value:" (st-parse-method "value: v value := v. ^ self")) +(st-class-add-method! "Crate" "kind" (st-parse-method "kind ^ #crate")) + +(st-test "before becomeForward: instance reports its class" + (str (evp "^ (Box new value: 1) class name")) + "Box") + +(st-test "becomeForward: changes the receiver's class" + (evp + "| a b | + a := Box new value: 1. + b := Crate new value: 99. + a becomeForward: b. + ^ a class name") + "Crate") + +(st-test "becomeForward: routes future sends through new class" + (evp + "| a b | + a := Box new value: 1. + b := Crate new value: 99. + a becomeForward: b. + ^ a kind") + (make-symbol "crate")) + +(st-test "becomeForward: takes target's ivars" + (evp + "| a b | + a := Box new value: 1. + b := Crate new value: 99. + a becomeForward: b. + ^ a value") + 99) + +(st-test "becomeForward: leaves the *target* instance unchanged" + (evp + "| a b | + a := Box new value: 1. + b := Crate new value: 99. + a becomeForward: b. + ^ b kind") + (make-symbol "crate")) + +(st-test "every reference to the receiver sees the new identity" + (evp + "| a alias b | + a := Box new value: 1. + alias := a. + b := Crate new value: 99. + a becomeForward: b. + ^ alias kind") + (make-symbol "crate")) + +(list st-test-pass st-test-fail) diff --git a/plans/smalltalk-on-sx.md b/plans/smalltalk-on-sx.md index 90e908fa..aef5da12 100644 --- a/plans/smalltalk-on-sx.md +++ b/plans/smalltalk-on-sx.md @@ -83,7 +83,7 @@ Core mapping: - [x] `Object>>perform:` / `perform:with:` / `perform:with:with:` / `perform:with:with:with:` / `perform:with:with:with:with:` / `perform:withArguments:`. Universal in `st-primitive-send`; routes back through `st-send` so user methods, primitives, super, and DNU all still apply. Selector arg can be a symbol or string (we `str` it). 10 new tests in `lib/smalltalk/tests/reflection.sx`. - [x] `Object>>respondsTo:`, `Object>>isKindOf:`, `Object>>isMemberOf:`. Universal in `st-primitive-send`. `respondsTo:` searches user method dicts (instance- or class-side based on receiver kind); native primitive selectors aren't enumerated, documented limitation. `isKindOf:` walks `st-class-inherits-from?`; `isMemberOf:` is exact class equality. 26 new tests in `reflection.sx`. - [x] `Behavior>>compile:` — runtime method addition. Class-side `compile:` parses the source via `st-parse-method` and installs via `st-class-add-method!`. Sister forms `compile:classified:` and `compile:notifying:` ignore the extra arg (Pharo-tolerant). Returns the selector as a symbol. Also added `addSelector:withMethod:` (raw AST install) and `removeSelector:`. 9 new tests in `reflection.sx`. -- [ ] `Object>>becomeForward:` (one-way become; rewrites the class field of `aReceiver`) +- [x] `Object>>becomeForward:` — one-way become at the universal `st-primitive-send` layer. Mutates the receiver's `:class` and `:ivars` to match the target via `dict-set!`; every existing reference to the receiver dict now behaves as the target. Receiver and target remain distinct dicts (no SX-level identity merge), but method dispatch, ivar reads, and aliases all switch — Pharo's practical guarantee. 6 tests in `reflection.sx`, including the alias case (`a` and `alias := a` both see the new identity). - [ ] Exceptions: `Exception`, `Error`, `signal`, `signal:`, `on:do:`, `ensure:`, `ifCurtailed:` — built on top of SX `handler-bind`/`raise` ### Phase 5 — collections + numeric tower @@ -108,6 +108,7 @@ Core mapping: _Newest first. Agent appends on every commit._ +- 2026-04-25: `Object>>becomeForward:` + 6 tests. In-place mutation of `:class` and `:ivars` via `dict-set!`; aliases see the new identity. 480/480 total. - 2026-04-25: `Behavior>>compile:` + sisters + 9 tests. Parses source via `st-parse-method`, installs via runtime helpers; also added `addSelector:withMethod:` and `removeSelector:`. 474/474 total. - 2026-04-25: `respondsTo:` / `isKindOf:` / `isMemberOf:` + 26 tests. Universal at `st-primitive-send`. 465/465 total. - 2026-04-25: `Object>>perform:` family + 10 tests. Universal dispatch via `st-send` after `(str (nth args 0))` for the selector. 439/439 total.