From 1c4ac47450ce106ad59eac46bc722c8fd859fa0d Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 09:06:40 +0000 Subject: [PATCH] smalltalk: respondsTo:/isKindOf:/isMemberOf: + 26 tests --- lib/smalltalk/eval.sx | 26 +++++++++++++++++ lib/smalltalk/tests/reflection.sx | 48 +++++++++++++++++++++++++++++++ plans/smalltalk-on-sx.md | 3 +- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/lib/smalltalk/eval.sx b/lib/smalltalk/eval.sx index 2234c21f..6f191e82 100644 --- a/lib/smalltalk/eval.sx +++ b/lib/smalltalk/eval.sx @@ -539,6 +539,32 @@ (= selector "perform:with:with:with:") (= selector "perform:with:with:with:with:")) (st-send receiver (str (nth args 0)) (slice args 1 (len args)))) + ;; respondsTo: aSymbol — searches user method dicts only. Native + ;; primitive selectors aren't enumerated, so e.g. `42 respondsTo: + ;; #+` returns false. (The send still works because dispatch falls + ;; through to st-num-send.) Documented limitation. + ((= selector "respondsTo:") + (let + ((sel-str (str (nth args 0))) + (target-cls (if (st-class-ref? receiver) (get receiver :name) cls)) + (class-side? (st-class-ref? receiver))) + (not (= (st-method-lookup target-cls sel-str class-side?) nil)))) + ;; isKindOf: aClass — true iff the receiver's class chain reaches it. + ((= selector "isKindOf:") + (let + ((arg (nth args 0)) + (target-cls (if (st-class-ref? receiver) "Metaclass" cls))) + (cond + ((not (st-class-ref? arg)) false) + (else (st-class-inherits-from? target-cls (get arg :name)))))) + ;; isMemberOf: aClass — exact class match. + ((= selector "isMemberOf:") + (let + ((arg (nth args 0)) + (target-cls (if (st-class-ref? receiver) "Metaclass" cls))) + (cond + ((not (st-class-ref? arg)) false) + (else (= target-cls (get arg :name)))))) ((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 692664a1..a2e27339 100644 --- a/lib/smalltalk/tests/reflection.sx +++ b/lib/smalltalk/tests/reflection.sx @@ -141,4 +141,52 @@ ;; perform: routes through ordinary dispatch, so super, DNU, primitives ;; all still apply naturally. No special test for that — it's free. +;; ── 15. isKindOf: walks the class chain ── +(st-test "42 isKindOf: SmallInteger" (ev "42 isKindOf: SmallInteger") true) +(st-test "42 isKindOf: Integer" (ev "42 isKindOf: Integer") true) +(st-test "42 isKindOf: Number" (ev "42 isKindOf: Number") true) +(st-test "42 isKindOf: Magnitude" (ev "42 isKindOf: Magnitude") true) +(st-test "42 isKindOf: Object" (ev "42 isKindOf: Object") true) +(st-test "42 isKindOf: String" (ev "42 isKindOf: String") false) +(st-test "3.14 isKindOf: Float" (ev "3.14 isKindOf: Float") true) +(st-test "3.14 isKindOf: Number" (ev "3.14 isKindOf: Number") true) + +(st-test "'hi' isKindOf: String" (ev "'hi' isKindOf: String") true) +(st-test "'hi' isKindOf: ArrayedCollection" + (ev "'hi' isKindOf: ArrayedCollection") true) +(st-test "true isKindOf: Boolean" (ev "true isKindOf: Boolean") true) +(st-test "nil isKindOf: UndefinedObject" + (ev "nil isKindOf: UndefinedObject") true) + +;; User-class chain. +(st-test "Cat new isKindOf: Cat" (evp "^ Cat new isKindOf: Cat") true) +(st-test "Cat new isKindOf: Object" (evp "^ Cat new isKindOf: Object") true) +(st-test "Cat new isKindOf: Boolean" + (evp "^ Cat new isKindOf: Boolean") false) +(st-test "Kitten new isKindOf: Cat" + (evp "^ Kitten new isKindOf: Cat") true) + +;; ── 16. isMemberOf: requires exact class match ── +(st-test "42 isMemberOf: SmallInteger" (ev "42 isMemberOf: SmallInteger") true) +(st-test "42 isMemberOf: Integer" (ev "42 isMemberOf: Integer") false) +(st-test "42 isMemberOf: Number" (ev "42 isMemberOf: Number") false) +(st-test "Cat new isMemberOf: Cat" + (evp "^ Cat new isMemberOf: Cat") true) +(st-test "Cat new isMemberOf: Kitten" + (evp "^ Cat new isMemberOf: Kitten") false) + +;; ── 17. respondsTo: — user method dictionary search ── +(st-test "Cat respondsTo: #miaow" + (evp "^ Cat new respondsTo: #miaow") true) +(st-test "Cat respondsTo: inherited (only own/super in dict)" + (evp "^ Kitten new respondsTo: #miaow") true) +(st-test "Cat respondsTo: missing" + (evp "^ Cat new respondsTo: #noSuchSelector") false) +(st-test "respondsTo: on class-ref searches class side" + (evp "^ Cat respondsTo: #named:") true) + +;; Non-symbol arg coerces via str — also accepts strings. +(st-test "respondsTo: with string arg" + (evp "^ Cat new respondsTo: 'miaow'") true) + (list st-test-pass st-test-fail) diff --git a/plans/smalltalk-on-sx.md b/plans/smalltalk-on-sx.md index a41148ce..4b8a7b37 100644 --- a/plans/smalltalk-on-sx.md +++ b/plans/smalltalk-on-sx.md @@ -81,7 +81,7 @@ Core mapping: ### Phase 4 — reflection + MOP - [x] `Object>>class`, `class>>name`, `class>>superclass`, `class>>methodDict`, `class>>selectors`. `class` is universal in `st-primitive-send` (returns `Metaclass` for class-refs, the receiver's class otherwise). Class-side dispatch gains `methodDict`/`classMethodDict` (raw dict), `selectors`/`classSelectors` (Array of symbols), `instanceVariableNames` (own), `allInstVarNames` (inherited + own). 26 tests in `lib/smalltalk/tests/reflection.sx`. - [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`. -- [ ] `Object>>respondsTo:`, `Object>>isKindOf:`, `Object>>isMemberOf:` +- [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`. - [ ] `Behavior>>compile:` — runtime method addition - [ ] `Object>>becomeForward:` (one-way become; rewrites the class field of `aReceiver`) - [ ] Exceptions: `Exception`, `Error`, `signal`, `signal:`, `on:do:`, `ensure:`, `ifCurtailed:` — built on top of SX `handler-bind`/`raise` @@ -108,6 +108,7 @@ Core mapping: _Newest first. Agent appends on every commit._ +- 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. - 2026-04-25: Phase 4 reflection accessors (`lib/smalltalk/tests/reflection.sx`, 26 tests). Universal `Object>>class`, plus `methodDict`/`selectors`/`instanceVariableNames`/`allInstVarNames`/`classMethodDict`/`classSelectors` on class-refs. 429/429 total. - 2026-04-25: conformance.sh + scoreboard.{json,md} (`lib/smalltalk/conformance.sh`, `lib/smalltalk/scoreboard.json`, `lib/smalltalk/scoreboard.md`). Single-pass runner over `test.sh -v`; baseline at 5 programs / 39 corpus tests / 403 total. **Phase 3 complete.**