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.
(define _hs-wrap-body
(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 (list (quote it) nil) (list (quote event) nil))
(list (list (quote event) nil))
(list (quote let)
(list (list (quote _ret) sx))
(list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret))))))
@@ -5679,7 +5681,7 @@
(dom-append (dom-body) _el-pDiv)
))
(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"
(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())))
if me_val_match:
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):
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_num_match.group(1)}) {expected_sx})')
else:
@@ -1917,6 +1925,13 @@ def generate_eval_only_test(test, idx):
args_str)
if me_m:
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)
if not lm:
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('(define _hs-wrap-body')
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 (list (quote it) nil) (list (quote event) nil))')
output.append(' (list (list (quote event) nil))')
output.append(' (list (quote let)')
output.append(' (list (list (quote _ret) sx))')
output.append(' (list (quote if) (list (quote nil?) (quote _ret)) (quote it) (quote _ret))))))')