# CLAUDE.md -- Rust Project Conventions

## Philosophy

Functional, type-driven, domain-driven.

## Architecture

- Modules by domain context.
- One module per evaluation concern (value, heap, env, completion, expression, statement).
- Thin lib.rs that wires contexts together and exposes the Io entry point.

## Types

- Newtypes for domain primitives.
- Sum types for variants.
- No public struct fields; enum variants with named fields are permitted.
- `#[must_use]` on getters and constructors.

## Error Handling

- Single project-wide `Error` enum.
- Display + std::error::Error impls by hand; no thiserror, no anyhow.
- Never panic in library code.

## Style

- Prefer match over if/else, except on bool.
- No `return` keyword.
- No `mut`.
- Combinators over loops.
- Never match on Option<_>; use combinators.
- Prefer combinators on Result<_, _>; match only when arms are complex.
- No unwrap()/expect() anywhere.
- No loop or for.
- No scan.
- No Rc/Arc (exception: comp-cat-rs Stream API requires `Arc<dyn Fn(...)>`).
- No naked `as` casts.
- Exhaustive matches; no `_` wildcard arm on enums.
- Iterators over indexed access.

## Traits

- No dyn Trait (exception: comp-cat-rs effect types use dyn internally).
- Implement standard traits over ad-hoc methods.

## Linting

```toml
[lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
needless_pass_by_value = "warn"
manual_map = "warn"
```

## Verification

- Always run `RUSTFLAGS="-D warnings" cargo clippy --all-targets`.
- Always run `cargo fmt`.

## Testing

- Tests return `Result<(), Error>` and propagate failures with `?`.
- No assert!, assert_eq!, panic!, unreachable!, unwrap, expect.
- Integration tests in tests/ organized by language concern.

## Dependencies

- `comp-cat-rs` foundation.  Use Io at the entry point; manual `(Value, Heap)` threading inside.
- `ecma-syntax-cat` for AST types.
- `ecma-lex-cat` for lexer.
- `ecma-parse-cat` for parser.
- `proptest` dev-dep.
- No path dependencies.

## Documentation

- `///` doc comments on every public item.
- `# Examples` with runnable code blocks.
- Doctests must not use unwrap/expect/unreachable.

## Layer context

This crate is the **engine** in the boa-cat stack:

1. `ecma-syntax-cat` -- AST types.
2. `ecma-lex-cat` -- tokenizer.
3. `ecma-parse-cat` -- parser; consumes tokens, produces Program.
4. **`boa-cat`** -- engine; consumes Program, produces Value.
5. `ecma-runtime-cat` -- built-ins.
6. `tauri-runtime-servocat` -- Tauri bridge.

## Evaluation strategy

- Tree-walking interpreter.
- Persistent heap (`BTreeMap<ObjectId, Object>`) cloned on every "mutation".
- Persistent env (cons list of `(name, Value)`).
- Statements return `(Completion, Heap, Env)`; expressions return `(Value, Heap)`.
- `Completion = Normal(Value) | Break | Continue | Return(Value) | Throw(Value)` -- the spec's abrupt-completion mechanism.
- Functions close over their definition-time env; calls extend it with parameter bindings.
- Arrow functions inherit `this` from the surrounding lexical scope.
- `Fuel` budget bounds evaluation; running out of fuel raises `Error::FuelExhausted`.

## v0.4.0 scope

In v0.3 the engine supports the core surface of ECMAScript:

- All literals (number, string, boolean, null, regex, template, BigInt-as-string).
- Variable resolution + var/let/const declarations (identifier patterns only, no destructuring in v0).
- All binary/unary/logical/conditional/assignment/sequence operators with spec semantics.
- Functions: declarations, expressions, arrow forms; closures; call + return.
- Member access (dot, computed), call, new, optional chaining (treated as plain access).
- Array and object literals (including spread and shorthand properties).
- Statements: block, expression, if, while, do-while, for-classic, for-in, for-of, switch, throw, try/catch/finally, return.

### v0.3 additions

- `Object::with_accessor(key, AccessorPair)` installs an accessor property; `AccessorPair::new(get_fn, set_fn)` carries optional getter/setter halves (`Option<Value>`, accepting `Value::Function` or `Value::Native`).
- Property reads (`obj.x` / `obj["x"]`) dispatch to the accessor's getter when one is installed: getter is invoked with `this = obj`, no args; a getter-less accessor reads as `undefined`.
- Property writes (`obj.x = v` / `obj["x"] = v`) dispatch to the accessor's setter: setter is invoked with `this = obj`, `args = [v]`; the assignment expression evaluates to the RHS regardless of the setter's own return value; a setter-less accessor silently discards the write (non-strict semantics).
- Data and accessor maps are mutually exclusive by key.  Writing a plain data value via `Object::with` evicts any existing accessor; installing an accessor via `with_accessor` evicts any existing data value.
- `Object::accessor(key)` and `Object::accessors()` expose the accessor map for embedder code that needs to inspect it.
- 4 integration tests under `tests/objects.rs::accessor_dispatch` cover the read, write, getter-only, and setter-only paths via the lower-level `evaluate_program_with` API.

### v0.3.1 additions

- Bumps `ecma-parse-cat` to 0.2 so `{ get x() {} }` / `{ set x(v) {} }` / `{ foo() {} }` object-literal syntax in user-provided JS reaches the engine's accessor dispatch.  No engine source changes -- `eval_one_object_member` has handled the `Get` / `Set` / `Method` AST variants since 0.3.0; the parser side was the only blocker.
- 6 new integration tests in `tests/objects.rs` (`getter_invokes_on_read_in_js_literal`, `setter_invokes_on_write_in_js_literal`, `data_init_can_replace_earlier_accessor_in_js_literal`, `accessor_can_replace_earlier_data_init_in_js_literal`, `separate_get_and_set_combine_on_same_key_in_js_literal`, `shorthand_method_in_js_literal`) exercise the JS-literal path end-to-end.  58 total across the suite.
- The lower-level `evaluate_program_with` + `Object::with_accessor` accessor-dispatch tests remain in `tests/objects.rs::accessor_dispatch`; they're still useful for covering pure-Rust accessor installs (the path web-api-cat uses to bridge `document.cookie`).

### v0.4.0 additions (async track chunk 1 of 5-7)

- New `Value::Promise(PromiseId)` variant.  Promises display as `[object Promise]`, coerce to `true` (`ToBoolean`) and `NaN` (`ToNumber`), `typeof` returns `"object"`.
- New `src/promise.rs` module exposes `PromiseState::{Pending(Vec<PromiseHandler>), Resolved(Value), Rejected(Value)}` plus `PromiseHandler` (carries `Option<Value>` `on_resolve` / `on_reject` halves + a chained `PromiseId`).  Both are re-exported from `lib.rs`.
- `Heap` gets a parallel `promises: BTreeMap<PromiseId, PromiseState>` plus `alloc_promise`, `store_promise`, `promise(id)`, and `promise_count()`.  All three store fns now carry `#[allow(clippy::result_large_err)]` since the `Result<Self, Self>` state-threading pattern crossed the 128-byte threshold with the new field.
- `expression::access_property` dispatches `Value::Promise` reads to native `.then` / `.catch` shims (`promise_member` helper).  Other keys read as `undefined` per spec; Promises don't expose data properties.
- `expression::call_function` is now `pub(crate)` so `promise::fire_resolve_handler` / `fire_reject_handler` can invoke user-supplied callbacks.
- `then_impl(args, this, heap, fuel)`:
  - `this = Resolved(v)`: invoke `args[0](v)`, return a new `Resolved(cb_result)`.
  - `this = Rejected(v)`: pass through as a new `Rejected(v)` when `args[1]` is missing / non-callable; invoke `args[1](v)` and return `Resolved(cb_result)` otherwise (recovery).
  - `this = Pending(handlers)`: queue a fresh `PromiseHandler { on_resolve = args[0], on_reject = args[1], chained = new_pending() }` onto the source, return the chained Pending child.  The v0.5 microtask driver settles these later.
  - Throws `TypeError: Promise.prototype.then called on non-promise` when `this` isn't a Promise.
- `catch_impl(args, this, heap, fuel)` is sugar for `then(undefined, args[0])`.
- 8 new integration tests in `tests/promises.rs`: `typeof_promise_is_object`, `then_on_resolved_invokes_callback`, `then_on_rejected_passes_through_value`, `catch_on_resolved_passes_through_value`, `chained_then_propagates_callback_return`, `pending_then_returns_pending_child_with_handler_queued`, `then_without_callback_passes_through_resolution`, `promise_handler_carries_chained_id`.  58 -> 66 total.

### Known v0.4 gaps

- No JS-side `Promise.resolve` / `reject` / `all` / `race` / `Promise()` constructor surface yet -- promises can only be built from Rust via `Heap::alloc_promise`.  Tests bind them into env as `p`.  Coming in ecma-runtime-cat 0.3 once boa-cat 0.5 ships the microtask driver.
- No `await` keyword dispatch in the evaluator yet (the parser already accepts the AST, but the engine treats `await` as `Error::Unsupported`).  Lands in boa-cat 0.5.
- Pending promises stay Pending: handlers queue correctly on `.then` but the v0.5 microtask driver hasn't been written, so callers must build the source promise as Resolved or Rejected from Rust to observe dispatch.
- `ecma-parse-cat = "0.2"` rejects dot-member access to reserved identifiers (e.g. `p.catch`), so the JS-level surface uses `p.then(null, cb)` for the catch path.  Either a parser bump that accepts keywords in member position (matching real ES2024) or a computed `p["catch"](cb)` workaround closes this gap.

### Deferred to v0.5+

- Microtask driver: drain queued `PromiseHandler`s on Pending -> Resolved / Rejected transitions, chaining through their `chained` child promises.  This is the load-bearing piece for `await`.
- `await` evaluator: suspend the calling function until the awaited promise settles, then resume with the resolved value (or throw the rejected one).  Requires the microtask driver plus a generator-style state machine for the calling function.
- Class declarations and expressions (require prototype chain).
- Destructuring patterns in declarations and parameters.
- Modules and import/export.
- yield and generators.
- Tagged templates.
- Labeled break/continue.
- PrivateIdentifier, MetaProperty, ImportExpression, Super.
- `Object.defineProperty` built-in.
- Parse-time arity validation for getters (0 params) and setters (exactly 1 param).
