# F8 — evalStatically (+3)

**Suite:** `hs-upstream-core/evalStatically`  
**Target:** 3 failing (untranslated) out of 8. 5 already pass.

## 1. Current state

5 passing tests use `(eval-hs expr)` and check the return value for literals: booleans, null, numbers, plain strings, time expressions. These call `_hyperscript.evaluate(src)` and return the result.

3 failing tests are named:
- `throws on math expressions`
- `throws on symbol references`  
- `throws on template strings`

All are `SKIP (untranslated)` — no test body has been generated.

## 2. What upstream checks

From `test/core/evalStatically.js`, the `throwErrors` mode:

```js
expect(() => _hyperscript.evaluate("1 + 2")).toThrow();
expect(() => _hyperscript.evaluate("x")).toThrow();
expect(() => _hyperscript.evaluate(`"hello ${name}"`)).toThrow();
```

`_hyperscript.evaluate(src)` in strict static mode throws when the expression is not a pure literal — math operators, symbol references, and template string interpolation all involve runtime evaluation that can't be statically resolved.

The "static" constraint: only literals that can be evaluated without any runtime context or side effects are allowed. `1 + 2` is not static (it's a math op). `x` is not static (symbol lookup). `"hello ${name}"` is not static (interpolation).

## 3. What `eval-hs` currently does

`eval-hs` in our harness calls `(hs-compile-and-run src)` or equivalent. It does NOT currently have a "static mode" — it runs everything with the full runtime.

We need a new harness helper `eval-hs-static-error` that:
1. Calls `(hs-compile src)` with a flag that makes it throw on non-literal expressions
2. Returns the caught error message, or raises if no error was thrown

## 4. Implementation options

### Option A — Static analysis pass (accurate)

Before evaluation, walk the AST and reject any node that isn't a literal:
- Number literal ✓
- String literal (no interpolation) ✓
- Boolean literal ✓  
- Null literal ✓
- Time expression (`200ms`, `2s`) ✓
- Everything else → throw `"expression is not static"`

This is a pre-eval AST check, not a runtime change. Lives in `lib/hyperscript/compiler.sx` as `hs-check-static`.

### Option B — Generator translation (simpler)

The 3 tests are untranslated. All three just verify that `_hyperscript.evaluate(expr)` throws. In our SX harness we can test this with a `guard` form:

```lisp
(deftest "throws on math expressions"
  (let ((result (guard (e (true true))
                  (eval-hs "1 + 2")
                  false)))
    (assert result)))
```

But this only works if `eval-hs` actually throws on math expressions. Currently it doesn't — `eval-hs "1 + 2"` returns `3`. So we'd need the static analysis anyway to make the test pass.

### Chosen approach: Option A

Add `hs-static-check` to the compiler: a fast AST walker that throws on any non-literal node. Wire it as an optional mode. The test harness calls `eval-hs-static` which runs with static-check enabled.

Actually, reading the upstream more carefully: `_hyperscript.evaluate` already throws in static mode without additional flags — the "evaluate" API is documented as static-only. Our `eval-hs` in the passing tests works because booleans/numbers/strings/time ARE static. `1 + 2`, `x`, and template strings are NOT static and should throw.

So the fix is: make `hs-compile-and-run` (or whatever backs `eval-hs`) reject non-literal AST nodes. The 5 passing tests will continue to pass (they use literals). The 3 failing tests will get translated using `eval-hs-error` or a guard pattern.

## 5. Non-literal AST node types to reject

| Expression | AST node type | Reject? |
|-----------|--------------|---------|
| `1`, `3.14` | number literal | ✓ allow |
| `"hello"`, `'world'` | string literal (no interpolation) | ✓ allow |
| `true`, `false` | boolean literal | ✓ allow |
| `null` | null literal | ✓ allow |
| `200ms`, `2s` | time literal | ✓ allow |
| `1 + 2` | math operator | ✗ throw |
| `x` | symbol reference | ✗ throw |
| `"hello ${name}"` | template string | ✗ throw |

## 6. Implementation checklist

1. In `lib/hyperscript/compiler.sx`, add `hs-static?` predicate: returns true only for literal AST node types.
2. In the `eval-hs` path (wherever `hs-compile-and-run` is called for the evaluate API), call `hs-static?` on the parsed AST and throw `"expression is not statically evaluable"` if false.
3. Replace 3 `SKIP` bodies in `spec/tests/test-hyperscript-behavioral.sx`:
   ```lisp
   (deftest "throws on math expressions"
     (assert (string? (eval-hs-error "1 + 2"))))
   (deftest "throws on symbol references"
     (assert (string? (eval-hs-error "x"))))
   (deftest "throws on template strings"
     (assert (string? (eval-hs-error "\"hello ${name}\""))))
   ```
4. Run `hs_test_run suite="hs-upstream-core/evalStatically"` — expect 8/8.
5. Run smoke 0–195 — verify the 5 passing tests still pass.
6. Commit: `HS: evalStatically — static literal check, 3 tests (+3)`

## 7. Risk

Low-medium. The main risk is that `eval-hs` is used in many tests for non-static expressions and adding a static check to the shared path would break them. The fix must be gated — either a separate `eval-hs-static` helper or a flag parameter. The passing tests must not be affected.
