HS: possessive expression via its (+1 test)

Two generator changes: (a) `parse_run_locals` for Pattern 2
(`var R = await run(...)`) now recognises `result: <literal>` in the
opts dict and binds it to `it` so `run("its foo", {result: {foo: "foo"}})`
produces `(eval-hs-locals "its foo" (list (list (quote it) {:foo "foo"})))`.
Also adds the same extraction to Pattern 1 (`expect(run(...)).toBe(...)`).
(b) `_hs-wrap-body` emitted by the generator no longer shadows `it` to
nil — it only binds `event` — so eval-hs-locals's outer `it` binding is
visible inside the wrapped body. `eval-hs` still binds `it` nil at its
own `fn` wrapper so nothing regresses.

Suite hs-upstream-expressions/possessiveExpression: 22/23 → 23/23.
Smoke 0-195: 162/195 unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 07:01:34 +00:00
parent a15c1d2cfb
commit f0c4127870
2 changed files with 22 additions and 3 deletions

View File

@@ -27,8 +27,10 @@
;; bare expressions (e.g. `foo.foo`) get the expression value back. ;; bare expressions (e.g. `foo.foo`) get the expression value back.
(define _hs-wrap-body (define _hs-wrap-body
(fn (sx) (fn (sx)
;; Wrap body to capture return via `it`. `event` default is always nil.
;; `it` is NOT shadowed here — callers (eval-hs-locals) may pre-bind it.
(list (quote let) (list (quote let)
(list (list (quote it) nil) (list (quote event) nil)) (list (list (quote event) nil))
(list (quote let) (list (quote let)
(list (list (quote _ret) sx)) (list (list (quote _ret) sx))
(list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret)))))) (list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret))))))
@@ -5679,7 +5681,7 @@
(dom-append (dom-body) _el-pDiv) (dom-append (dom-body) _el-pDiv)
)) ))
(deftest "can access its properties" (deftest "can access its properties"
(assert= (eval-hs "its foo") "foo") (assert= (eval-hs-locals "its foo" (list (list (quote it) {:foo "foo"}))) "foo")
) )
(deftest "can access multiple basic attributes" (deftest "can access multiple basic attributes"
(hs-cleanup!) (hs-cleanup!)

View File

@@ -1760,6 +1760,14 @@ def generate_eval_only_test(test, idx):
extra.append((m2.group(1), js_val_to_sx(m2.group(2).strip()))) extra.append((m2.group(1), js_val_to_sx(m2.group(2).strip())))
if me_val_match: if me_val_match:
extra.append(('me', js_val_to_sx(me_val_match.group(1)))) extra.append(('me', js_val_to_sx(me_val_match.group(1))))
# `result: X` (or `it: X`) binds `it` — upstream `run("expr", { result: ... })`
# uses the value as the implicit `it` for possessive expressions like `its foo`.
result_match = re.search(
r'\b(?:result|it):\s*(\[[^\]]*\]|\{[^}]*(?:\{[^}]*\}[^}]*)?\}|"[^"]*"|\'[^\']*\'|[\w.]+)',
opts_str,
)
if result_match:
extra.append(('it', js_val_to_sx(result_match.group(1))))
if me_num_match and not (window_setups or extra): if me_num_match and not (window_setups or extra):
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_num_match.group(1)}) {expected_sx})') assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_num_match.group(1)}) {expected_sx})')
else: else:
@@ -1917,6 +1925,13 @@ def generate_eval_only_test(test, idx):
args_str) args_str)
if me_m: if me_m:
pairs.append(('me', js_val_to_sx(me_m.group(1)))) pairs.append(('me', js_val_to_sx(me_m.group(1))))
# `result: <literal>` binds `it` — upstream `run("its X", {result: obj})`
# passes `obj` as the implicit `it` for possessive expressions.
result_m = re.search(
r'\bresult:\s*(\{[^}]*(?:\{[^}]*\}[^}]*)?\}|\[[^\]]*\]|"[^"]*"|\'[^\']*\'|\d+(?:\.\d+)?)',
args_str)
if result_m:
pairs.append(('it', js_val_to_sx(result_m.group(1))))
lm = re.search(r'locals:\s*\{', args_str) lm = re.search(r'locals:\s*\{', args_str)
if not lm: if not lm:
return pairs return pairs
@@ -2453,8 +2468,10 @@ output.append(';; mutate `it` (e.g. `pick first 3 of arr; set $test to it`) get
output.append(';; bare expressions (e.g. `foo.foo`) get the expression value back.') output.append(';; bare expressions (e.g. `foo.foo`) get the expression value back.')
output.append('(define _hs-wrap-body') output.append('(define _hs-wrap-body')
output.append(' (fn (sx)') output.append(' (fn (sx)')
output.append(' ;; Wrap body to capture return via `it`. `event` default is always nil.')
output.append(' ;; `it` is NOT shadowed here — callers (eval-hs-locals) may pre-bind it.')
output.append(' (list (quote let)') output.append(' (list (quote let)')
output.append(' (list (list (quote it) nil) (list (quote event) nil))') output.append(' (list (list (quote event) nil))')
output.append(' (list (quote let)') output.append(' (list (quote let)')
output.append(' (list (list (quote _ret) sx))') output.append(' (list (list (quote _ret) sx))')
output.append(' (list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret))))))') output.append(' (list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret))))))')