;; doesNotUnderstand: tests. (set! st-test-pass 0) (set! st-test-fail 0) (set! st-test-fails (list)) (st-bootstrap-classes!) (define ev (fn (src) (smalltalk-eval src))) (define evp (fn (src) (smalltalk-eval-program src))) ;; ── 1. Bootstrap installs Message class ── (st-test "Message exists in bootstrap" (st-class-exists? "Message") true) (st-test "Message has expected ivars" (sort (get (st-class-get "Message") :ivars)) (sort (list "selector" "arguments"))) ;; ── 2. Building a Message directly ── (define m (st-make-message "frob:" (list 1 2 3))) (st-test "make-message produces st-instance" (st-instance? m) true) (st-test "message class" (get m :class) "Message") (st-test "message selector ivar" (str (get (get m :ivars) "selector")) "frob:") (st-test "message arguments ivar" (get (get m :ivars) "arguments") (list 1 2 3)) ;; ── 3. User override of doesNotUnderstand: intercepts unknown sends ── (st-class-define! "Logger" "Object" (list "log")) (st-class-add-method! "Logger" "log" (st-parse-method "log ^ log")) (st-class-add-method! "Logger" "init" (st-parse-method "init log := nil. ^ self")) (st-class-add-method! "Logger" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage log := aMessage selector. ^ #handled")) (st-test "user DNU intercepts unknown send" (str (evp "| l | l := Logger new init. l frobnicate. ^ l log")) "frobnicate") (st-test "user DNU returns its own value" (str (evp "| l | l := Logger new init. ^ l frobnicate")) "handled") ;; Arguments are captured. (st-class-add-method! "Logger" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage log := aMessage arguments. ^ #handled")) (st-test "user DNU sees args in Message" (evp "| l | l := Logger new init. l zip: 1 zap: 2. ^ l log") (list 1 2)) ;; ── 4. DNU on native receiver ───────────────────────────────────────── ;; Adding doesNotUnderstand: on Object catches any-receiver sends. (st-class-add-method! "Object" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage ^ aMessage selector")) (st-test "Object DNU intercepts on SmallInteger" (str (ev "42 frobnicate")) "frobnicate") (st-test "Object DNU intercepts on String" (str (ev "'hi' bogusmessage")) "bogusmessage") (st-test "Object DNU sees arguments" ;; Re-define Object DNU to return the args array. (begin (st-class-add-method! "Object" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage ^ aMessage arguments")) (ev "42 plop: 1 plop: 2")) (list 1 2)) ;; ── 5. Subclass DNU overrides Object DNU ────────────────────────────── (st-class-define! "Proxy" "Object" (list)) (st-class-add-method! "Proxy" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage ^ #proxyHandled")) (st-test "subclass DNU wins over Object DNU" (str (evp "^ Proxy new whatever")) "proxyHandled") ;; ── 6. Defined methods bypass DNU ───────────────────────────────────── (st-class-add-method! "Proxy" "known" (st-parse-method "known ^ 7")) (st-test "defined method wins over DNU" (evp "^ Proxy new known") 7) ;; ── 7. Block doesNotUnderstand: routes via Object ───────────────────── (st-class-add-method! "Object" "doesNotUnderstand:" (st-parse-method "doesNotUnderstand: aMessage ^ #blockDnu")) (st-test "block unknown selector goes to DNU" (str (ev "[1] frobnicate")) "blockDnu") (list st-test-pass st-test-fail)