All checks were successful
ci / fast (linux) (push) Successful in 7m9s
Create comprehensive implementation-ready story files for the remaining Epic 3 (JavaScript Engine Maturity) stories and update sprint status from backlog to ready-for-dev: - 3.5: Built-in Completeness (Array/String/Object) - 3.6: Built-in Completeness (Date/RegExp/Map/Set) - 3.7: WeakRef, FinalizationRegistry & Strict Mode Edge Cases - 3.8: DOM Bindings via web_api - 3.9: Event Dispatch Completeness - 3.10: Web API Exposure (fetch, Math, setInterval, rAF) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
407 lines
28 KiB
Markdown
407 lines
28 KiB
Markdown
# Story 3.6: Built-in Completeness -- Date/RegExp/Map/Set
|
|
|
|
Status: ready-for-dev
|
|
|
|
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
|
|
|
## Story
|
|
|
|
As a web developer using JavaScript,
|
|
I want Date, RegExp, Map, Set, WeakMap, and WeakSet to work completely,
|
|
So that date handling, pattern matching, and collection types function correctly.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Date construction and methods:** `new Date()`, `new Date(value)`, `new Date(year, month, ...)`, `Date.now()`, `Date.parse()`, `Date.UTC()` all construct dates correctly. Getter methods (`getFullYear`, `getMonth`, `getDate`, `getDay`, `getHours`, `getMinutes`, `getSeconds`, `getMilliseconds`, `getTime`, `getTimezoneOffset`) and their UTC variants return correct values. Setter methods (`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`) modify dates correctly. Conversion methods (`toISOString`, `toJSON`, `toDateString`, `toTimeString`, `toString`, `valueOf`) produce spec-compliant output. Invalid dates return `NaN` from `getTime()` and `"Invalid Date"` from `toString()`. Per ECMAScript §21.4.
|
|
|
|
2. **RegExp completeness:** Extend existing RegExp with: function replacers in `String.prototype.replace()`, named capture groups `(?<name>...)` with `$<name>` substitution (requires `regress` crate support check), `RegExp.prototype[Symbol.match]`, `[Symbol.replace]`, `[Symbol.search]`, `[Symbol.split]` well-known symbol methods. Per ECMAScript §22.2.
|
|
|
|
3. **Map complete:** `new Map(iterable?)` constructor. Methods: `.get(key)`, `.set(key, value)`, `.has(key)`, `.delete(key)`, `.clear()`, `.forEach(callback, thisArg)`. Property: `.size`. Iterators: `.entries()`, `.keys()`, `.values()`, `[Symbol.iterator]`. Keys use SameValueZero equality (`NaN === NaN`, `-0 === +0`). Insertion order preserved. Per ECMAScript §24.1.
|
|
|
|
4. **Set complete:** `new Set(iterable?)` constructor. Methods: `.add(value)`, `.has(value)`, `.delete(value)`, `.clear()`, `.forEach(callback, thisArg)`. Property: `.size`. Iterators: `.entries()`, `.keys()`, `.values()`, `[Symbol.iterator]`. Values use SameValueZero equality. Insertion order preserved. Per ECMAScript §24.2.
|
|
|
|
5. **WeakSet complete:** `new WeakSet(iterable?)` constructor. Methods: `.add(value)`, `.has(value)`, `.delete(value)`. Object-only keys (TypeError for primitives). Per ECMAScript §24.4.
|
|
|
|
6. **WeakMap enhancements:** Constructor accepts iterable of `[key, value]` pairs: `new WeakMap(iterable?)`. Existing `get`/`set`/`has`/`delete` methods already implemented -- verify function keys work correctly (current limitation: function keys may throw TypeError).
|
|
|
|
7. **Callback error propagation:** `Map.forEach`, `Set.forEach`, and `Array.from(map/set)` correctly propagate thrown errors from callbacks without corrupting collection internal state.
|
|
|
|
8. **Test262 tests promoted:** All relevant vendored and full-suite Test262 tests for Date, RegExp, Map, Set, WeakMap, WeakSet are promoted. `docs/JavaScript_Implementation_Checklist.md` and `docs/js_feature_matrix.md` updated. `just ci` passes.
|
|
|
|
## What NOT to Implement
|
|
|
|
- **No `Intl.DateTimeFormat`** -- locale-sensitive Date formatting (`toLocaleDateString`, `toLocaleTimeString`, `toLocaleString`) return English-only fixed format. Full Intl support out of scope.
|
|
- **No timezone database** -- timezone offset uses the host system's local timezone via `chrono` or `std::time`. Named timezone support (IANA) deferred.
|
|
- **No RegExp lookbehind** -- `(?<=...)` and `(?<!...)` depend on `regress` crate support. If `regress` doesn't support them, document as known limitation.
|
|
- **No RegExp `d` flag (hasIndices)** -- ES2022 match indices deferred unless `regress` provides match positions (it likely does via `Match::start()`/`Match::end()` -- investigate).
|
|
- **No `RegExp.prototype.compile()`** -- deprecated legacy method, skip.
|
|
- **No `Map`/`Set` subclassing** -- `class MyMap extends Map` and `Symbol.species` deferred.
|
|
- **No garbage collection for WeakMap/WeakSet** -- entries remain until explicit deletion. Documenting as known limitation (Rust's `Rc<RefCell<>>` prevents true weak references without architectural changes).
|
|
|
|
## Files to Modify
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `crates/js_vm/src/value.rs` | Add `PrimitiveValue::Date(f64)` variant for Date internal time value. Add `map_data: Option<OrderedMap>` and `set_data: Option<OrderedSet>` and `weak_set_data: Option<HashSet<usize>>` fields to `JsObjectData`. |
|
|
| `crates/js_vm/src/interpreter/date_builtins.rs` | **New file** -- Date constructor, all Date prototype methods (getters, setters, conversion), Date static methods (now, parse, UTC). |
|
|
| `crates/js_vm/src/interpreter/map_builtins.rs` | **New file** -- Map constructor, prototype methods (get, set, has, delete, clear, forEach, entries, keys, values, size), setup function. |
|
|
| `crates/js_vm/src/interpreter/set_builtins.rs` | **New file** -- Set constructor, prototype methods (add, has, delete, clear, forEach, entries, keys, values, size), setup function. |
|
|
| `crates/js_vm/src/interpreter/weakmap_builtins.rs` | Add iterable constructor support. Fix function-as-key limitation. |
|
|
| `crates/js_vm/src/interpreter/weakset_builtins.rs` | **New file** -- WeakSet constructor (with iterable), add, has, delete. Same pointer-identity pattern as WeakMap. |
|
|
| `crates/js_vm/src/interpreter/regexp_builtins.rs` | Add Symbol.match/replace/search/split well-known symbol methods. |
|
|
| `crates/js_vm/src/interpreter/builtins.rs` | Add function replacer support to `String.prototype.replace()`. Add named capture group `$<name>` substitution. |
|
|
| `crates/js_vm/src/interpreter/mod.rs` | Add `setup_date_builtins()`, `setup_map_builtins()`, `setup_set_builtins()`, `setup_weakset_builtins()` calls. |
|
|
| `crates/js_vm/src/interpreter/expressions/calls.rs` | Add Map/Set/WeakSet method dispatch alongside existing WeakMap dispatch. |
|
|
| `crates/js_vm/src/interpreter/tests/date_tests.rs` | **New file** -- Date unit tests. |
|
|
| `crates/js_vm/src/interpreter/tests/map_tests.rs` | **New file** -- Map unit tests. |
|
|
| `crates/js_vm/src/interpreter/tests/set_tests.rs` | **New file** -- Set unit tests. |
|
|
| `crates/js_vm/src/interpreter/tests/weakset_tests.rs` | **New file** -- WeakSet unit tests. |
|
|
| `crates/js_vm/src/interpreter/tests/regexp_tests.rs` | Add tests for function replacers, Symbol methods. |
|
|
| `tests/external/js262/js262_manifest.toml` | Promote passing tests. |
|
|
| `docs/JavaScript_Implementation_Checklist.md` | Check off Phase 20 (Map, Set, WeakSet) and Phase 22 (Date) items. |
|
|
| `docs/old/js_feature_matrix.md` | Update built-in coverage. |
|
|
|
|
## Tasks / Subtasks
|
|
|
|
- [ ] Task 1: Map implementation (AC: #3, #7)
|
|
- [ ] 1.1 Add ordered map storage to `crates/js_vm/src/value.rs`:
|
|
- Add `map_data: Option<IndexMap<MapKey, JsValue>>` field to `JsObjectData`
|
|
- `MapKey` enum: wraps `JsValue` with custom `Eq`/`Hash` using SameValueZero semantics
|
|
- SameValueZero: `NaN == NaN` (true), `+0 == -0` (true), otherwise strict equality
|
|
- For object keys: use `Rc::as_ptr()` identity (same as WeakMap)
|
|
- For `NaN`: canonical hash/eq so all NaN variants match
|
|
- **Use `indexmap` crate** (already a workspace dependency) for insertion-order preservation
|
|
- Add `init_map()`, `is_map()` helpers on `JsObject`
|
|
- [ ] 1.2 Create `crates/js_vm/src/interpreter/map_builtins.rs`:
|
|
- `setup_map_builtins(env: &mut Environment)` -- registers `Map` constructor and prototype
|
|
- Constructor: `new Map()` creates empty map. `new Map(iterable)` iterates entries and calls `.set()` for each `[key, value]` pair.
|
|
- **TypeError** if called without `new`
|
|
- [ ] 1.3 Implement Map prototype methods:
|
|
- `.set(key, value)` -- insert/update, return `this` for chaining (ECMAScript §24.1.3.9)
|
|
- `.get(key)` -- return value or `undefined` (ECMAScript §24.1.3.6)
|
|
- `.has(key)` -- return boolean (ECMAScript §24.1.3.7)
|
|
- `.delete(key)` -- remove and return boolean (ECMAScript §24.1.3.3)
|
|
- `.clear()` -- remove all entries (ECMAScript §24.1.3.2)
|
|
- `.size` -- getter returning entry count (ECMAScript §24.1.3.10)
|
|
- [ ] 1.4 Implement Map callback and iterator methods:
|
|
- `.forEach(callback, thisArg)` -- call `callback(value, key, map)` for each entry in insertion order. Propagate errors from callback without corrupting map state.
|
|
- `.entries()` -- return iterator yielding `[key, value]` arrays (ECMAScript §24.1.3.4)
|
|
- `.keys()` -- return iterator yielding keys (ECMAScript §24.1.3.8)
|
|
- `.values()` -- return iterator yielding values (ECMAScript §24.1.3.11)
|
|
- `Map.prototype[Symbol.iterator]` = `Map.prototype.entries` (ECMAScript §24.1.3.12)
|
|
- Iterators reuse generator/iterator protocol from Story 3.2
|
|
- [ ] 1.5 Wire Map dispatch in `crates/js_vm/src/interpreter/expressions/calls.rs`:
|
|
- Follow WeakMap dispatch pattern (check `obj.is_map()`, then fast-path method resolution)
|
|
- [ ] 1.6 Add `setup_map_builtins()` call in `crates/js_vm/src/interpreter/mod.rs`
|
|
- [ ] 1.7 Add unit tests in `crates/js_vm/src/interpreter/tests/map_tests.rs`:
|
|
- Basic CRUD operations, constructor with iterable, size, chaining
|
|
- NaN key equality (`map.set(NaN, 1); map.get(NaN) === 1`)
|
|
- `-0` and `+0` treated as same key
|
|
- Object key identity (two `{}` are different keys)
|
|
- Insertion order preservation in iteration
|
|
- forEach error propagation
|
|
- Iterator protocol compliance
|
|
|
|
- [ ] Task 2: Set implementation (AC: #4, #7)
|
|
- [ ] 2.1 Add ordered set storage to `crates/js_vm/src/value.rs`:
|
|
- Add `set_data: Option<IndexSet<MapKey>>` field to `JsObjectData`
|
|
- Reuse `MapKey` from Map implementation (same SameValueZero equality)
|
|
- **Use `indexmap::IndexSet`** for insertion-order preservation
|
|
- Add `init_set()`, `is_set()` helpers on `JsObject`
|
|
- [ ] 2.2 Create `crates/js_vm/src/interpreter/set_builtins.rs`:
|
|
- `setup_set_builtins(env: &mut Environment)` -- registers `Set` constructor and prototype
|
|
- Constructor: `new Set()` creates empty set. `new Set(iterable)` iterates and calls `.add()` for each value.
|
|
- **TypeError** if called without `new`
|
|
- [ ] 2.3 Implement Set prototype methods:
|
|
- `.add(value)` -- insert value, return `this` for chaining (ECMAScript §24.2.3.1)
|
|
- `.has(value)` -- return boolean (ECMAScript §24.2.3.5)
|
|
- `.delete(value)` -- remove and return boolean (ECMAScript §24.2.3.4)
|
|
- `.clear()` -- remove all values (ECMAScript §24.2.3.2)
|
|
- `.size` -- getter returning count (ECMAScript §24.2.3.9)
|
|
- [ ] 2.4 Implement Set callback and iterator methods:
|
|
- `.forEach(callback, thisArg)` -- call `callback(value, value, set)` (note: value passed as both args per spec). Propagate errors.
|
|
- `.entries()` -- return iterator yielding `[value, value]` (ECMAScript §24.2.3.5)
|
|
- `.keys()` -- alias for `.values()` (ECMAScript §24.2.3.8)
|
|
- `.values()` -- return iterator yielding values (ECMAScript §24.2.3.10)
|
|
- `Set.prototype[Symbol.iterator]` = `Set.prototype.values` (ECMAScript §24.2.3.11)
|
|
- [ ] 2.5 Wire Set dispatch in `crates/js_vm/src/interpreter/expressions/calls.rs`
|
|
- [ ] 2.6 Add `setup_set_builtins()` call in `crates/js_vm/src/interpreter/mod.rs`
|
|
- [ ] 2.7 Add unit tests in `crates/js_vm/src/interpreter/tests/set_tests.rs`:
|
|
- Basic add/has/delete/clear, constructor with iterable, size
|
|
- NaN equality, `-0`/`+0` treated as same, object identity
|
|
- Insertion order preservation
|
|
- forEach with `(value, value, set)` args
|
|
- Deduplication (adding same value twice keeps one entry)
|
|
|
|
- [ ] Task 3: WeakSet implementation (AC: #5)
|
|
- [ ] 3.1 Add weak set storage to `crates/js_vm/src/value.rs`:
|
|
- Add `weak_set_data: Option<HashSet<usize>>` field to `JsObjectData`
|
|
- Use `Rc::as_ptr()` identity (same pattern as WeakMap)
|
|
- Add `init_weakset()`, `is_weakset()` helpers on `JsObject`
|
|
- [ ] 3.2 Create `crates/js_vm/src/interpreter/weakset_builtins.rs`:
|
|
- `setup_weakset_builtins(env: &mut Environment)`
|
|
- Constructor: `new WeakSet()` / `new WeakSet(iterable)` -- iterable items must be objects
|
|
- [ ] 3.3 Implement WeakSet prototype methods:
|
|
- `.add(value)` -- TypeError if not object. Add by pointer identity. Return `this`.
|
|
- `.has(value)` -- return boolean
|
|
- `.delete(value)` -- remove and return boolean
|
|
- [ ] 3.4 Wire WeakSet dispatch alongside WeakMap in `calls.rs`
|
|
- [ ] 3.5 Add `setup_weakset_builtins()` call in `mod.rs`
|
|
- [ ] 3.6 Add unit tests in `crates/js_vm/src/interpreter/tests/weakset_tests.rs`:
|
|
- Basic add/has/delete, TypeError on primitive values
|
|
- Constructor with iterable of objects
|
|
- Object identity semantics
|
|
|
|
- [ ] Task 4: WeakMap enhancements (AC: #6)
|
|
- [ ] 4.1 Add iterable constructor to WeakMap in `weakmap_builtins.rs`:
|
|
- `new WeakMap(iterable)` -- iterate `[key, value]` pairs, key must be object
|
|
- [ ] 4.2 Fix function-as-key limitation:
|
|
- `JsValue::Function` should be accepted as a WeakMap/WeakSet key (functions are objects per spec)
|
|
- Use function pointer identity for keying
|
|
- [ ] 4.3 Add unit tests for iterable constructor and function keys
|
|
|
|
- [ ] Task 5: Date implementation (AC: #1)
|
|
- [ ] 5.1 Add Date internal value storage to `crates/js_vm/src/value.rs`:
|
|
- Add `PrimitiveValue::Date(f64)` variant -- stores milliseconds since epoch (or `NaN` for invalid)
|
|
- Add `init_date(time_value: f64)`, `is_date()`, `date_value() -> f64` helpers on `JsObject`
|
|
- [ ] 5.2 Create `crates/js_vm/src/interpreter/date_builtins.rs`:
|
|
- `setup_date_builtins(env: &mut Environment)` -- registers `Date` constructor and prototype
|
|
- [ ] 5.3 Implement Date constructors (ECMAScript §21.4.2):
|
|
- `Date()` (no new) -- return current date as string
|
|
- `new Date()` -- current time in milliseconds (`std::time::SystemTime::now()`)
|
|
- `new Date(value)` -- if number, use as milliseconds. If string, parse (ISO 8601 subset)
|
|
- `new Date(year, month, date?, hours?, minutes?, seconds?, ms?)` -- component constructor
|
|
- Month is 0-indexed (0=January), year 0-99 maps to 1900-1999
|
|
- [ ] 5.4 Implement Date static methods:
|
|
- `Date.now()` -- return current time in ms (ECMAScript §21.4.3.1)
|
|
- `Date.parse(string)` -- parse ISO 8601 date string, return ms or NaN (ECMAScript §21.4.3.2)
|
|
- `Date.UTC(year, month, ...)` -- like component constructor but in UTC (ECMAScript §21.4.3.4)
|
|
- [ ] 5.5 Implement Date prototype getter methods (ECMAScript §21.4.4):
|
|
- Local time getters: `getFullYear()`, `getMonth()`, `getDate()`, `getDay()`, `getHours()`, `getMinutes()`, `getSeconds()`, `getMilliseconds()`
|
|
- `getTime()` -- return internal time value (ms since epoch)
|
|
- `getTimezoneOffset()` -- return local timezone offset in minutes
|
|
- UTC getters: `getUTCFullYear()`, `getUTCMonth()`, `getUTCDate()`, `getUTCDay()`, `getUTCHours()`, `getUTCMinutes()`, `getUTCSeconds()`, `getUTCMilliseconds()`
|
|
- All getters return `NaN` if internal time value is `NaN` (Invalid Date)
|
|
- [ ] 5.6 Implement Date prototype setter methods:
|
|
- `setTime(time)`, `setMilliseconds(ms)`, `setSeconds(sec, ms?)`, `setMinutes(min, sec?, ms?)`, `setHours(hour, min?, sec?, ms?)`, `setDate(date)`, `setMonth(month, date?)`, `setFullYear(year, month?, date?)`
|
|
- UTC setters: `setUTCFullYear()`, `setUTCMonth()`, `setUTCDate()`, `setUTCHours()`, `setUTCMinutes()`, `setUTCSeconds()`, `setUTCMilliseconds()`
|
|
- Each setter recalculates the internal time value and returns it
|
|
- [ ] 5.7 Implement Date conversion methods:
|
|
- `toString()` -- e.g. `"Sun Mar 16 2026 12:30:00 GMT-0400 (Eastern Daylight Time)"` (simplified format acceptable)
|
|
- `toDateString()` -- e.g. `"Sun Mar 16 2026"`
|
|
- `toTimeString()` -- e.g. `"12:30:00 GMT-0400"`
|
|
- `toISOString()` -- e.g. `"2026-03-16T16:30:00.000Z"` (always UTC, throws RangeError if invalid)
|
|
- `toJSON()` -- calls `toISOString()`, returns null if invalid (ECMAScript §21.4.4.37)
|
|
- `toUTCString()` -- e.g. `"Sun, 16 Mar 2026 16:30:00 GMT"`
|
|
- `valueOf()` -- return `getTime()` (enables numeric comparison with `<`, `>`)
|
|
- `[Symbol.toPrimitive](hint)` -- "default"/"number" returns valueOf, "string" returns toString
|
|
- [ ] 5.8 Implement Date internal helpers:
|
|
- `ms_to_components(time_value: f64) -> DateComponents` -- decompose ms to year/month/day/hour/min/sec/ms in local time
|
|
- `ms_to_utc_components(time_value: f64) -> DateComponents` -- same but UTC
|
|
- `components_to_ms(year, month, day, hour, min, sec, ms) -> f64` -- recompose to ms
|
|
- `local_tz_offset_ms() -> f64` -- get system local timezone offset
|
|
- Use `std::time::SystemTime` for `Date.now()`. Use manual arithmetic for component decomposition (avoid adding `chrono` dependency -- it's large).
|
|
- **Date math reference:** ECMAScript §21.4.1 defines `Day(t)`, `TimeWithinDay(t)`, `DaysInYear(y)`, `DayFromYear(y)`, `TimeFromYear(y)`, `YearFromTime(t)`, `InLeapYear(t)`, `MonthFromTime(t)`, `DateFromTime(t)`, `WeekDay(t)`, `LocalTime(t)`, `UTC(t)`, `MakeTime()`, `MakeDay()`, `MakeDate()`, `TimeClip()`. Implement these helper functions.
|
|
- [ ] 5.9 Wire Date dispatch in `calls.rs` and add `setup_date_builtins()` in `mod.rs`
|
|
- [ ] 5.10 Add unit tests in `crates/js_vm/src/interpreter/tests/date_tests.rs`:
|
|
- All constructor forms (no args, ms, string, components)
|
|
- Invalid date detection (`new Date("invalid").getTime() === NaN`)
|
|
- All getters for a known date (e.g., `new Date(2024, 0, 15, 10, 30, 45, 500)`)
|
|
- All setters modifying a date and reading back
|
|
- `toISOString()` output format
|
|
- `Date.now()` returns a reasonable value
|
|
- `Date.parse()` for ISO strings
|
|
- `Date.UTC()` constructor
|
|
- Comparison operators work via `valueOf()`
|
|
- Edge cases: leap years, month overflow, year 0-99 mapping, negative timestamps
|
|
|
|
- [ ] Task 6: RegExp enhancements (AC: #2)
|
|
- [ ] 6.1 Add function replacer support to `String.prototype.replace()` in `builtins.rs`:
|
|
- When second argument is a function, call it with `(match, ...captures, index, fullString)`
|
|
- Return value of function becomes replacement string
|
|
- Works with both string and regex patterns
|
|
- For regex with `/g` flag: call function for each match
|
|
- [ ] 6.2 Add `$<name>` named capture group substitution in `String.prototype.replace()`:
|
|
- Check if `regress` crate supports named capture groups first
|
|
- If supported: extract group name, look up in match captures
|
|
- If not supported: document as known limitation, skip
|
|
- [ ] 6.3 Add RegExp Symbol method implementations in `regexp_builtins.rs`:
|
|
- `RegExp.prototype[Symbol.match](string)` -- same behavior as current `String.prototype.match()` delegation (ECMAScript §22.2.6.8)
|
|
- `RegExp.prototype[Symbol.replace](string, replaceValue)` -- (ECMAScript §22.2.6.10)
|
|
- `RegExp.prototype[Symbol.search](string)` -- (ECMAScript §22.2.6.11)
|
|
- `RegExp.prototype[Symbol.split](string, limit)` -- (ECMAScript §22.2.6.12)
|
|
- Wire these as Symbol-keyed properties on RegExp.prototype
|
|
- [ ] 6.4 Add unit tests for function replacers and Symbol methods in `regexp_tests.rs`
|
|
|
|
- [ ] Task 7: Testing and validation (AC: #8)
|
|
- [ ] 7.1 Run vendored Test262 suite and promote passing tests:
|
|
- `cargo test -p rust_browser --test js262_harness js262_suite_matches_manifest_expectations -- --nocapture`
|
|
- `just js262-status promote --id <test-id>` for each newly passing test
|
|
- [ ] 7.2 Run full Test262 suite and triage:
|
|
- `just test262-full`
|
|
- `just triage-test262-full`
|
|
- [ ] 7.3 Run all existing JS test suites to verify no regressions:
|
|
- `cargo test -p js_vm` (all unit tests)
|
|
- `cargo test -p rust_browser --test js_tests`
|
|
- `cargo test -p rust_browser --test js_dom_tests`
|
|
- `cargo test -p rust_browser --test js_events`
|
|
- `cargo test -p rust_browser --test js_scheduling`
|
|
- `cargo test -p rust_browser --test js_async`
|
|
- `cargo test -p rust_browser --test js_modules`
|
|
- [ ] 7.4 Update `docs/JavaScript_Implementation_Checklist.md`:
|
|
- Check off Phase 20 items (Map, Set, WeakSet)
|
|
- Check off Phase 22 items (Date)
|
|
- Update RegExp section with new features
|
|
- [ ] 7.5 Update `docs/old/js_feature_matrix.md` with expanded coverage
|
|
- [ ] 7.6 Run `just ci` -- full validation pass
|
|
|
|
## Dev Notes
|
|
|
|
### Key Architecture Decisions
|
|
|
|
**Map/Set use `indexmap` crate (already a dependency).** `IndexMap` and `IndexSet` preserve insertion order, which is required by the spec. Do NOT use `HashMap`/`HashSet` -- they don't preserve order. The `indexmap` crate is already in `[workspace.dependencies]`.
|
|
|
|
**Date stores `f64` milliseconds since epoch.** This is the internal `[[DateValue]]` per ECMAScript §21.4.1. Use `PrimitiveValue::Date(f64)` on the JsObject, following the same pattern as `PrimitiveValue::RegExp(RegExpData)`. `NaN` represents an Invalid Date.
|
|
|
|
**No `chrono` dependency for Date.** Implement ECMAScript date math helpers directly per §21.4.1. The spec defines all the conversion functions (`Day(t)`, `YearFromTime(t)`, `MonthFromTime(t)`, etc.) using simple arithmetic. `std::time::SystemTime::now()` provides milliseconds for `Date.now()`. This avoids adding a large dependency.
|
|
|
|
**MapKey wrapper for SameValueZero equality.** Map and Set keys need custom equality: `NaN === NaN` (unlike `===`), `+0 === -0` (like `===`). Create a `MapKey` wrapper around `JsValue` that implements `Hash` and `Eq` with these semantics. For object keys, hash/eq by pointer identity (`Rc::as_ptr()`).
|
|
|
|
**WeakSet follows WeakMap pattern exactly.** WeakMap already uses `HashMap<usize, JsValue>` keyed by `Rc::as_ptr()`. WeakSet is simply `HashSet<usize>` with the same pattern. Both lack true weak reference semantics (no GC integration), which is a documented limitation.
|
|
|
|
### Implementation Patterns from Existing Code
|
|
|
|
**WeakMap dispatch pattern** (in `calls.rs:401-447`):
|
|
```rust
|
|
// Check if object is a special type, dispatch method before prototype lookup
|
|
if obj.is_weakmap() {
|
|
match method_name.as_str() {
|
|
"get" => { /* ... */ }
|
|
"set" => { /* ... */ }
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
Map, Set, WeakSet, and Date should follow this same dispatch pattern.
|
|
|
|
**Builtin setup pattern** (in `mod.rs`):
|
|
```rust
|
|
regexp_builtins::setup_regexp_builtins(self.env);
|
|
weakmap_builtins::setup_weakmap_builtins(self.env);
|
|
// Add:
|
|
map_builtins::setup_map_builtins(self.env);
|
|
set_builtins::setup_set_builtins(self.env);
|
|
weakset_builtins::setup_weakset_builtins(self.env);
|
|
date_builtins::setup_date_builtins(self.env);
|
|
```
|
|
|
|
**PrimitiveValue pattern** (in `value.rs`):
|
|
```rust
|
|
pub enum PrimitiveValue {
|
|
Number(f64),
|
|
String(String),
|
|
Boolean(bool),
|
|
RegExp(RegExpData),
|
|
// Add: Date(f64),
|
|
}
|
|
```
|
|
|
|
### Critical Implementation Details
|
|
|
|
**Date math is pure arithmetic, not library calls.** ECMAScript §21.4.1 defines:
|
|
- `msPerDay = 86_400_000`
|
|
- `Day(t) = floor(t / msPerDay)`
|
|
- `TimeWithinDay(t) = t % msPerDay` (use `t.rem_euclid(msPerDay)` for negative timestamps)
|
|
- Leap year calculation, month/day decomposition, etc. -- all spec-defined formulas
|
|
- For local time: `LocalTime(t) = t + local_tz_offset_ms`. Get offset via `libc::localtime_r` or by comparing `SystemTime` epoch math.
|
|
|
|
**Map/Set forEach must handle mutation during iteration.** Per spec, entries added during forEach are visited; entries deleted before being visited are skipped. `IndexMap`'s index-based iteration handles this naturally if you iterate by index rather than by iterator.
|
|
|
|
**Constructor `new` enforcement.** Map, Set, WeakSet, Date must throw TypeError when called without `new`. Check the `new_target` or use a flag in the NativeFunction setup. Follow the pattern used by existing constructors.
|
|
|
|
**Map.prototype.size is a getter, not a data property.** It must be defined via `Object.defineProperty` with a getter function (or the internal equivalent once property descriptors are available from Story 3.5). If property descriptors aren't landed yet, implement as a regular method call -- the dispatch in `calls.rs` can handle `.size` specially.
|
|
|
|
### Dependency on Story 3.5
|
|
|
|
**Property descriptors (Story 3.5 Task 1)** are NOT required for this story's core functionality. Map/Set/Date can be implemented using the existing `.set()`/`.get()` API. However:
|
|
- `.size` as a getter property ideally needs property descriptors. Workaround: handle in method dispatch.
|
|
- `Object.freeze` on Map/Set is a Story 3.5 concern, not this story.
|
|
- Iterator methods depend on Story 3.2 (generators/iterator protocol) which is already done.
|
|
|
|
**This story can proceed independently of Story 3.5.**
|
|
|
|
### Previous Story Intelligence
|
|
|
|
From Story 3.5 (Array/String/Object):
|
|
- Property descriptor changes are high-risk foundational work -- this story should NOT depend on them
|
|
- Iterator protocol from Story 3.2 is available and working
|
|
- `indexmap` is already a workspace dependency -- confirmed available
|
|
|
|
From Story 3.4 (ES modules) review patterns:
|
|
- Don't create stub/dead code -- wire everything into actual execution paths
|
|
- Integration tests must test the right code paths
|
|
- Run `just ci` after each task
|
|
|
|
### Risk Assessment
|
|
|
|
**HIGH: Date math complexity.** ECMAScript date arithmetic with local time conversions, leap years, month boundaries, and pre-epoch negative timestamps is intricate. Many edge cases (Feb 29, month overflow, year 0-99 mapping). Must implement per-spec formulas carefully.
|
|
|
|
**MEDIUM: MapKey hash/eq for arbitrary JsValue types.** Hashing JsValues (especially objects by pointer) requires careful `Hash`/`Eq` implementation. NaN handling must be special-cased. Consider that `JsValue` may not derive `Hash` natively -- `MapKey` wrapper handles this.
|
|
|
|
**MEDIUM: Date.parse() string parsing.** ISO 8601 parsing has many edge cases. Implement a focused subset: `YYYY-MM-DDTHH:mm:ss.sssZ` and common variants. Don't try to handle all date string formats browsers support (those are implementation-defined).
|
|
|
|
**LOW: Map/Set core operations.** Straightforward CRUD with `IndexMap`/`IndexSet`. Well-defined spec behavior.
|
|
|
|
**LOW: WeakSet.** Near-identical to WeakMap with simpler API.
|
|
|
|
### Phased Implementation Strategy
|
|
|
|
**Phase A -- Map + Set (Tasks 1-2):** Implement together -- they share `MapKey` infrastructure and follow identical patterns. Can start immediately.
|
|
|
|
**Phase B -- WeakSet + WeakMap fixes (Tasks 3-4):** Quick additions. WeakSet mirrors WeakMap. Fix function-as-key for both.
|
|
|
|
**Phase C -- Date (Task 5):** Largest task. Independent of Map/Set. Implement date math helpers first, then constructors, then methods.
|
|
|
|
**Phase D -- RegExp enhancements (Task 6):** Independent of other tasks. Function replacers are the highest-value addition.
|
|
|
|
**Phase E -- Testing + Validation (Task 7):** After all implementations complete.
|
|
|
|
### Project Structure Notes
|
|
|
|
- All implementation in `crates/js_vm/src/interpreter/` (Layer 1) -- no layer violations
|
|
- New files: `date_builtins.rs`, `map_builtins.rs`, `set_builtins.rs`, `weakset_builtins.rs` (4 new)
|
|
- New test files: `date_tests.rs`, `map_tests.rs`, `set_tests.rs`, `weakset_tests.rs` (4 new)
|
|
- Value type changes in `crates/js_vm/src/value.rs` (Layer 1)
|
|
- No `unsafe` code needed
|
|
- No new external dependencies (indexmap already available)
|
|
|
|
### References
|
|
|
|
- [ECMAScript §21.4 -- Date Objects](https://tc39.es/ecma262/#sec-date-objects) -- Date constructor, methods, math
|
|
- [ECMAScript §21.4.1 -- Time Values](https://tc39.es/ecma262/#sec-time-values-and-time-range) -- Date math helper definitions
|
|
- [ECMAScript §22.2 -- RegExp Objects](https://tc39.es/ecma262/#sec-regexp-regular-expression-objects) -- RegExp built-in
|
|
- [ECMAScript §24.1 -- Map Objects](https://tc39.es/ecma262/#sec-map-objects) -- Map constructor and prototype
|
|
- [ECMAScript §24.2 -- Set Objects](https://tc39.es/ecma262/#sec-set-objects) -- Set constructor and prototype
|
|
- [ECMAScript §24.3 -- WeakMap Objects](https://tc39.es/ecma262/#sec-weakmap-objects) -- WeakMap
|
|
- [ECMAScript §24.4 -- WeakSet Objects](https://tc39.es/ecma262/#sec-weakset-objects) -- WeakSet
|
|
- [Source: crates/js_vm/src/interpreter/regexp_builtins.rs] -- Existing RegExp implementation (421 lines)
|
|
- [Source: crates/js_vm/src/interpreter/weakmap_builtins.rs] -- Existing WeakMap implementation
|
|
- [Source: crates/js_vm/src/interpreter/expressions/calls.rs:401-447] -- WeakMap method dispatch pattern
|
|
- [Source: crates/js_vm/src/interpreter/mod.rs:507-521] -- Existing builtin setup calls
|
|
- [Source: crates/js_vm/src/value.rs:602-622] -- JsValue enum and PrimitiveValue
|
|
- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.6] -- Story requirements
|
|
- [Source: _bmad-output/planning-artifacts/architecture.md#JS Feature Implementation Order] -- Implementation order pattern
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
{{agent_model_name_version}}
|
|
|
|
### Debug Log References
|
|
|
|
### Completion Notes List
|
|
|
|
### File List
|