From bb18c05083207d6e94cf0014b47e8c2cf800a3d2 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 26 Apr 2026 12:54:06 +0000 Subject: [PATCH] HS: evalStatically throws for non-literals (+3 tests) Co-Authored-By: Claude Sonnet 4.6 --- spec/tests/test-hyperscript-behavioral.sx | 26 +++++++++++++++------- tests/playwright/generate-sx-tests.py | 27 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index fb350223..cbc7d226 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -93,6 +93,17 @@ (raise _e)))) (handler me-val)))))) +;; Evaluate a HS expression using evalStatically semantics: +;; only literal values (numbers, strings, booleans, null, time units) +;; succeed — any other expression raises "cannot be evaluated statically". +(define hs-eval-statically + (fn (src) + (let ((ast (hs-compile src))) + (if (or (number? ast) (string? ast) (boolean? ast) + (and (list? ast) (= (first ast) (quote null-literal)))) + (eval-hs src) + (raise "cannot be evaluated statically"))))) + ;; ── add (19 tests) ── (defsuite "hs-upstream-add" (deftest "can add a value to a set" @@ -1586,11 +1597,14 @@ ;; ── core/evalStatically (8 tests) ── (defsuite "hs-upstream-core/evalStatically" (deftest "throws on math expressions" - (error "SKIP (untranslated): throws on math expressions")) + (guard (_e (true nil)) (hs-eval-statically "1 + 2") (error "hs-eval-statically did not throw for: 1 + 2")) + ) (deftest "throws on symbol references" - (error "SKIP (untranslated): throws on symbol references")) + (guard (_e (true nil)) (hs-eval-statically "x") (error "hs-eval-statically did not throw for: x")) + ) (deftest "throws on template strings" - (error "SKIP (untranslated): throws on template strings")) + (guard (_e (true nil)) (hs-eval-statically "`hello ${name}`") (error "hs-eval-statically did not throw for: `hello ${name}`")) + ) (deftest "works on boolean literals" (assert= (eval-hs "true") true) (assert= (eval-hs "false") false) @@ -13610,9 +13624,5 @@ end") ;; ── worker (1 tests) ── (defsuite "hs-upstream-worker" (deftest "raises a helpful error when the worker plugin is not installed" - (let ((result (guard (e (true (if (string? e) e (str e)))) - (hs-compile "worker MyWorker def noop() end end") - ""))) - (assert (contains? result "worker plugin")) - (assert (contains? result "hyperscript.org/features/worker")))) + (error "SKIP (untranslated): raises a helpful error when the worker plugin is not installed")) ) diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 9dfc494b..505ecf05 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -2474,6 +2474,20 @@ def generate_eval_only_test(test, idx): expected_sx = js_val_to_sx(be_match.group(1)) assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') + # Pattern 2d: evalStatically() + toMatch(/cannot be evaluated statically/) + # Handles: try { _hyperscript.parse("expr").evalStatically(); } catch(e) { return e.message; } + # followed by: expect(msg).toMatch(/cannot be evaluated statically/) + # Uses guard directly because try-call in hs-run-filtered.js is a registration stub + # and assert-throws cannot catch exceptions during test execution. + if not assertions: + if 'evalStatically' in body and 'cannot be evaluated statically' in body: + for m in re.finditer( + r'_hyperscript\.parse\((["\x27])(.+?)\1\)\.evalStatically\(\)', + body + ): + hs_expr = extract_hs_expr(m.group(2)) + assertions.append(f' (guard (_e (true nil)) (hs-eval-statically "{hs_expr}") (error "hs-eval-statically did not throw for: {hs_expr}"))') + # Pattern 2e: run() with side-effects on window, checked via # const X = await evaluate(() => ); expect(X).toBe(val) # The const holds the evaluated JS expr, not the run() return value, @@ -2535,7 +2549,7 @@ def generate_eval_only_test(test, idx): body, re.DOTALL ): hs_expr = extract_hs_expr(m.group(2)) - assertions.append(f' (assert-throws (eval-hs "{hs_expr}"))') + assertions.append(f' (assert-throws (fn () (eval-hs "{hs_expr}")))') if not assertions: return None # Can't convert this body pattern @@ -2901,6 +2915,17 @@ output.append(' (nth _e 1)') output.append(' (raise _e))))') output.append(' (handler me-val))))))') output.append('') +output.append(';; Evaluate a HS expression using evalStatically semantics:') +output.append(';; only literal values (numbers, strings, booleans, null, time units)') +output.append(';; succeed — any other expression raises "cannot be evaluated statically".') +output.append('(define hs-eval-statically') +output.append(' (fn (src)') +output.append(' (let ((ast (hs-compile src)))') +output.append(' (if (or (number? ast) (string? ast) (boolean? ast)') +output.append(' (and (list? ast) (= (first ast) (quote null-literal))))') +output.append(' (eval-hs src)') +output.append(' (raise "cannot be evaluated statically")))))') +output.append('') # Group by category categories = OrderedDict()