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>
23 KiB
Story 3.5: Built-in Completeness -- Array/String/Object
Status: ready-for-dev
Story
As a web developer using JavaScript, I want all standard Array, String, and Object methods to be available, So that common JavaScript patterns and library code work correctly.
Acceptance Criteria
-
Array methods complete:
splice,flat,flatMap,fill,copyWithin,at,from,of,entries/keys/values,toReversed,toSorted,toSpliced,with,toLocaleStringall behave per their ECMAScript specifications. Existing methods (push,pop,shift,unshift,slice,concat,join,indexOf,lastIndexOf,includes,reverse,toString,sort,forEach,map,filter,find,findIndex,some,every,reduce,reduceRight,isArray) continue to pass. -
String methods complete:
padStart,padEnd,replaceAll,matchAll,at,codePointAt,normalize,String.fromCharCode,String.fromCodePoint,String.rawall behave per their ECMAScript specifications. Existing methods (28 already implemented) continue to pass. -
Object methods complete:
Object.defineProperty,Object.defineProperties,Object.getOwnPropertyDescriptor,Object.getOwnPropertyDescriptors,Object.getOwnPropertyNames,Object.setPrototypeOf,Object.freeze,Object.isFrozen,Object.seal,Object.isSealed,Object.preventExtensions,Object.isExtensible,Object.is,Object.fromEntries,Object.hasOwn,Object.prototype.isPrototypeOfall behave per their ECMAScript specifications. Existing methods (11 already implemented) continue to pass. -
Callback error propagation: Methods accepting callbacks (
map,filter,reduce,sort,flat,flatMap,from, etc.) correctly propagate thrown errors without corrupting internal state. -
Property descriptor system:
Object.definePropertyenforceswritable,enumerable,configurableflags. Accessor properties (get/set) work.Object.freeze/Object.seal/Object.preventExtensionsenforce their respective constraints on subsequent operations. -
Iterator protocol for Array:
Array.prototype.entries(),.keys(),.values(), andArray.prototype[Symbol.iterator]return spec-compliant iterators usable withfor...of(depends on generator/iterator protocol from Story 3.2). -
Test262 tests promoted: All relevant vendored and full-suite Test262 tests for Array, String, and Object built-ins are promoted from
known_failtopass.docs/JavaScript_Implementation_Checklist.mdanddocs/js_feature_matrix.mdare updated.just cipasses.
What NOT to Implement
- No
Intl/ locale-sensitive methods --toLocaleString,toLocaleLowerCase,toLocaleUpperCase,localeCompareare out of scope (would require ICU/locale data). - No
String.prototype.normalizewith full Unicode normalization -- stub returning input unchanged is acceptable if full NFC/NFD/NFKC/NFKD is too complex. - No
ProxyorReflect-- property descriptor enforcement is internal, not through Proxy traps. - No typed arrays --
Int8Array,Uint8Array, etc. are separate built-ins for a future story. - No
structuredClone-- not a built-in method on these prototypes. - No
Array.prototype.group/groupToMap-- Stage 3, not yet standard at time of implementation.
Files to Modify
| File | Change |
|---|---|
crates/js_vm/src/interpreter/builtins.rs |
Add splice, fill, copyWithin, at, flat, toReversed, toSorted, toSpliced, with, toLocaleString to eval_array_method(). Add padStart, padEnd, replaceAll, at, codePointAt to eval_string_method(). |
crates/js_vm/src/interpreter/array_builtins.rs |
Add flatMap to eval_array_callback_method(). Add Array.from(iterable, mapFn) support. |
crates/js_vm/src/interpreter/mod.rs |
Extend setup_builtins() with new Object static methods and prototype methods. Add String.fromCharCode, String.fromCodePoint, String.raw as static methods on String constructor. Add Array.of as static method. Wire entries()/keys()/values() iterator methods. |
crates/js_vm/src/interpreter/primitive_builtins.rs |
Add String static methods (fromCharCode, fromCodePoint, raw) if String constructor setup lives here. |
crates/js_vm/src/interpreter/object_builtins.rs |
New file -- Object.defineProperty, Object.defineProperties, Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.setPrototypeOf, Object.freeze/isFrozen, Object.seal/isSealed, Object.preventExtensions/isExtensible, Object.is, Object.fromEntries, Object.hasOwn, Object.prototype.isPrototypeOf. |
crates/js_vm/src/value.rs |
Add property descriptor support: PropertyDescriptor struct (value, writable, enumerable, configurable, get, set), PropertyFlags bitflags, update JsObject property storage to support descriptors. |
crates/js_vm/src/interpreter/bytecode_exec.rs |
Update property access to respect descriptors (getter/setter calls, writable checks). |
crates/js_vm/src/interpreter/tests/array_tests.rs |
Add tests for all new Array methods. |
crates/js_vm/src/interpreter/tests/string_tests.rs |
Add tests for all new String methods. |
crates/js_vm/src/interpreter/tests/object_tests.rs |
Add tests for all new Object methods and property descriptors. |
tests/external/js262/js262_manifest.toml |
Promote passing tests. |
docs/JavaScript_Implementation_Checklist.md |
Check off completed built-in items. |
docs/old/js_feature_matrix.md |
Update built-in coverage. |
Cargo.toml |
No new dependencies expected. |
Tasks / Subtasks
-
Task 1: Property descriptor infrastructure (AC: #5)
- 1.1 Add
PropertyDescriptortocrates/js_vm/src/value.rs:PropertyDescriptor { value: Option<JsValue>, writable: Option<bool>, enumerable: Option<bool>, configurable: Option<bool>, get: Option<JsValue>, set: Option<JsValue> }PropertyFlagsbitflags:WRITABLE,ENUMERABLE,CONFIGURABLE, defaults to all-true for existing properties- Accessor vs data descriptor detection: accessor has get/set, data has value/writable
- 1.2 Update
JsObjectproperty storage to support descriptors:- Current:
HashMap<String, JsValue>for properties - New:
HashMap<String, Property>whereProperty { value: JsValue, flags: PropertyFlags, getter: Option<JsValue>, setter: Option<JsValue> } - CRITICAL: Existing
.set()and.get()must continue working unchanged -- all existing properties default toWRITABLE | ENUMERABLE | CONFIGURABLE - Update
.get()to call getter function if property is accessor - Update
.set()to call setter function if property is accessor, respectWRITABLEflag - Update
.delete()to respectCONFIGURABLEflag
- Current:
- 1.3 Add internal
[[DefineOwnProperty]]helper onJsObject:- Follows ECMAScript §10.1.6.3 -- validates descriptor, merges with existing, rejects invalid changes to non-configurable properties
- Used by
Object.definePropertyand property assignment
- 1.4 Update
Object.keys(),Object.values(),Object.entries()to filter byENUMERABLEflag - 1.5 Update
for...inenumeration to respectENUMERABLEflag - 1.6 Add unit tests for property descriptor behavior: writable rejection, enumerable filtering, configurable deletion prevention, accessor get/set invocation
- 1.1 Add
-
Task 2: Object static methods (AC: #3, #5)
- 2.1 Create
crates/js_vm/src/interpreter/object_builtins.rswith:eval_object_static_method(method: &str, args: &[JsValue]) -> Option<Result<JsValue, RuntimeError>>
- 2.2 Implement
Object.defineProperty(obj, prop, descriptor)(ECMAScript §20.1.2.4):- Parse descriptor object into
PropertyDescriptor - Call internal
[[DefineOwnProperty]] - Return the object
- Parse descriptor object into
- 2.3 Implement
Object.defineProperties(obj, props)(ECMAScript §20.1.2.3):- For each enumerable own property of
props, calldefineProperty
- For each enumerable own property of
- 2.4 Implement
Object.getOwnPropertyDescriptor(obj, prop)(ECMAScript §20.1.2.6):- Return descriptor object
{ value, writable, enumerable, configurable }or{ get, set, enumerable, configurable }
- Return descriptor object
- 2.5 Implement
Object.getOwnPropertyDescriptors(obj)(ECMAScript §20.1.2.8):- Return object mapping all own properties to their descriptors
- 2.6 Implement
Object.getOwnPropertyNames(obj)(ECMAScript §20.1.2.7):- Return all own string property names (enumerable and non-enumerable)
- 2.7 Implement
Object.setPrototypeOf(obj, proto)(ECMAScript §20.1.2.23):- Set prototype, check for circular prototype chains, return obj
- 2.8 Implement
Object.freeze(obj)/Object.isFrozen(obj)(ECMAScript §20.1.2.5/§20.1.2.11):- Freeze: set all properties non-writable + non-configurable, mark object non-extensible
- isFrozen: check all conditions
- 2.9 Implement
Object.seal(obj)/Object.isSealed(obj)(ECMAScript §20.1.2.20/§20.1.2.12):- Seal: set all properties non-configurable, mark object non-extensible
- isSealed: check conditions
- 2.10 Implement
Object.preventExtensions(obj)/Object.isExtensible(obj)(ECMAScript §20.1.2.15/§20.1.2.10):- Add
extensible: boolflag toJsObject(defaults to true) - Check flag when adding new properties
- Add
- 2.11 Implement
Object.is(a, b)(ECMAScript §20.1.2.9):- SameValue:
NaN === NaNis true,+0 === -0is false
- SameValue:
- 2.12 Implement
Object.fromEntries(iterable)(ECMAScript §20.1.2.9):- Accept array of
[key, value]pairs, create object
- Accept array of
- 2.13 Implement
Object.hasOwn(obj, prop)(ECMAScript §20.1.2.16):- Static version of
hasOwnProperty(ES2022)
- Static version of
- 2.14 Implement
Object.prototype.isPrototypeOf(obj)(ECMAScript §20.1.3.6):- Walk prototype chain of
objchecking forthis
- Walk prototype chain of
- 2.15 Wire all Object static methods into
setup_builtins()incrates/js_vm/src/interpreter/mod.rs - 2.16 Add unit tests in
crates/js_vm/src/interpreter/tests/object_tests.rs
- 2.1 Create
-
Task 3: Array methods -- non-callback (AC: #1)
- 3.1 Implement
Array.prototype.splice(start, deleteCount, ...items)ineval_array_method()(ECMAScript §22.1.3.26):- Remove
deleteCountelements starting atstart, insertitemsin their place - Return array of deleted elements
- Adjust
.lengthand shift elements as needed
- Remove
- 3.2 Implement
Array.prototype.fill(value, start, end)(ECMAScript §22.1.3.10):- Fill elements from
starttoendwithvalue - Returns the modified array
- Fill elements from
- 3.3 Implement
Array.prototype.copyWithin(target, start, end)(ECMAScript §22.1.3.5):- Copy elements within array, handle overlapping regions
- 3.4 Implement
Array.prototype.at(index)(ECMAScript §22.1.3.2):- Negative indices count from end (
-1= last element) - Also add to
String.prototype.at(index)(ECMAScript §21.1.3.1) ineval_string_method()
- Negative indices count from end (
- 3.5 Implement
Array.prototype.flat(depth)(ECMAScript §22.1.3.13):- Flatten nested arrays up to
depth(default 1, Infinity for full flatten) - Recursive flattening with depth tracking
- Flatten nested arrays up to
- 3.6 Implement
Array.prototype.toReversed()(ECMAScript §22.1.3.30):- Return new reversed array (non-mutating copy of
reverse())
- Return new reversed array (non-mutating copy of
- 3.7 Implement
Array.prototype.toSorted(compareFn)(ECMAScript §22.1.3.31):- Return new sorted array (non-mutating copy of
sort())
- Return new sorted array (non-mutating copy of
- 3.8 Implement
Array.prototype.toSpliced(start, deleteCount, ...items)(ECMAScript §22.1.3.32):- Return new array with splice applied (non-mutating)
- 3.9 Implement
Array.prototype.with(index, value)(ECMAScript §22.1.3.36):- Return new array with element at
indexreplaced byvalue
- Return new array with element at
- 3.10 Add unit tests in
crates/js_vm/src/interpreter/tests/array_tests.rs
- 3.1 Implement
-
Task 4: Array methods -- callback and static (AC: #1, #4, #6)
- 4.1 Implement
Array.prototype.flatMap(callback, thisArg)ineval_array_callback_method()(ECMAScript §22.1.3.14):- Map each element, then flatten one level
- Callback receives
(element, index, array)
- 4.2 Implement
Array.from(arrayLike, mapFn, thisArg)(ECMAScript §22.1.2.1):- Handle array-like objects (with
.lengthproperty) - Handle iterables (objects with
Symbol.iterator) -- use iterator protocol from Story 3.2 - Handle strings (iterate characters)
- Apply
mapFnto each element if provided - Wire as static method on
Arrayconstructor insetup_builtins()
- Handle array-like objects (with
- 4.3 Implement
Array.of(...items)(ECMAScript §22.1.2.3):- Create array from arguments (unlike
Array(n)which creates sparse array of length n)
- Create array from arguments (unlike
- 4.4 Implement
Array.prototype.entries(),.keys(),.values()(ECMAScript §22.1.3.6/§22.1.3.15/§22.1.3.33):- Return iterator objects (reuse iterator protocol from Story 3.2 generators)
entries()yields[index, value]pairskeys()yields indicesvalues()yields values- Wire
Array.prototype[Symbol.iterator]=Array.prototype.values
- 4.5 Verify callback error propagation in
flatMapandArray.fromwithmapFn - 4.6 Add unit tests in
crates/js_vm/src/interpreter/tests/array_tests.rs
- 4.1 Implement
-
Task 5: String methods (AC: #2)
- 5.1 Implement
String.prototype.padStart(targetLength, padString)ineval_string_method()(ECMAScript §21.1.3.16):- Pad start of string to reach
targetLength; padString defaults to " "
- Pad start of string to reach
- 5.2 Implement
String.prototype.padEnd(targetLength, padString)(ECMAScript §21.1.3.17):- Pad end of string
- 5.3 Implement
String.prototype.replaceAll(searchValue, replaceValue)(ECMAScript §21.1.3.19):- If
searchValueis string: replace all occurrences (NOT regex) - If
searchValueis regex: must have global flag, otherwise TypeError - Reuse existing
replacelogic fromeval_string_method()but apply globally for string pattern
- If
- 5.4 Implement
String.prototype.matchAll(regexp)(ECMAScript §21.1.3.11):- Returns iterator of all matches (requires global or sticky flag)
- Each match includes capture groups, index, input
- Return iterator object (use iterator protocol from Story 3.2)
- 5.5 Implement
String.prototype.at(index)(ECMAScript §21.1.3.1):- Negative indices count from end
- Returns single-character string or undefined
- 5.6 Implement
String.prototype.codePointAt(index)(ECMAScript §21.1.3.3):- Returns full Unicode code point (handles surrogate pairs)
- 5.7 Implement
String.fromCharCode(...codes)(ECMAScript §21.1.2.1):- Static method on String constructor
- Creates string from UTF-16 code units
- 5.8 Implement
String.fromCodePoint(...codes)(ECMAScript §21.1.2.2):- Static method on String constructor
- Creates string from Unicode code points (handles > 0xFFFF)
- 5.9 Implement
String.raw(template, ...substitutions)(ECMAScript §21.1.2.4):- Template tag function that returns raw string without escape processing
- 5.10 Implement
String.prototype.normalize(form)-- STUB ONLY:- Accept "NFC", "NFD", "NFKC", "NFKD" forms
- Return input string unchanged (full Unicode normalization deferred)
- Log warning on first call
- 5.11 Add unit tests in
crates/js_vm/src/interpreter/tests/string_tests.rs
- 5.1 Implement
-
Task 6: Testing and validation (AC: #7)
- 6.1 Run vendored Test262 suite and promote passing tests:
cargo test -p rust_browser --test js262_harness js262_suite_matches_manifest_expectations -- --nocapturejust js262-status promote --id <test-id>for each newly passing test
- 6.2 Run full Test262 suite and triage built-in tests:
just test262-fulljust triage-test262-full
- 6.3 Run all existing JS test suites to verify no regressions:
cargo test -p js_vm(unit tests including existing array/string/object tests)cargo test -p rust_browser --test js_testscargo test -p rust_browser --test js_dom_testscargo test -p rust_browser --test js_eventscargo test -p rust_browser --test js_schedulingcargo test -p rust_browser --test js_asynccargo test -p rust_browser --test js_modules
- 6.4 Update
docs/JavaScript_Implementation_Checklist.md:- Check off all newly implemented built-in methods
- 6.5 Update
docs/old/js_feature_matrix.mdwith expanded built-in coverage - 6.6 Run
just ci-- full validation pass
- 6.1 Run vendored Test262 suite and promote passing tests:
Dev Notes
Key Architecture Decisions
Property descriptor system is the foundation. Task 1 must land first -- it changes the internal property storage model in JsObject. All subsequent Object methods (defineProperty, freeze, seal, etc.) depend on it. Array and String methods do NOT depend on property descriptors, so Tasks 3-5 can proceed in parallel after Task 1.
No new crate dependencies. All built-in methods are pure Rust using existing infrastructure. The regress crate (already a dependency) handles regex for matchAll/replaceAll. The url crate is not needed here.
Iterator methods reuse Story 3.2 generator protocol. Array.prototype.entries(), .keys(), .values(), and String.prototype.matchAll() return iterator objects. These should reuse the IteratorRecord / GeneratorObject infrastructure from Story 3.2 (generators and iterator protocol). Check crates/js_vm/src/interpreter/ for existing iterator patterns.
Implementation Patterns from Existing Code
Array method dispatch (in builtins.rs:eval_array_method()):
// Non-callback methods return Option<Result<JsValue, RuntimeError>>
// Return None if method not found -> falls through to prototype chain
"splice" => { /* implementation */ Some(Ok(result)) }
Callback array methods (in array_builtins.rs:eval_array_callback_method()):
// Callback methods need &mut self (interpreter) for function invocation
// Use self.invoke_array_callback(&callback, &[elem, idx, arr], this_arg)?
Object static methods (in mod.rs:setup_builtins()):
// Object static methods are NativeFunction values on the Object constructor
let obj_constructor = JsValue::NativeFunction {
name: "Object".into(),
func: /* ... */
};
// Then obj_constructor.set("keys", JsValue::NativeFunction { ... });
Critical Compatibility Concerns
Changing JsObject property storage is high-risk. The JsObject type is used everywhere in the VM. Changing from HashMap<String, JsValue> to HashMap<String, Property> must be done carefully:
- All existing
.set()calls must default toWRITABLE | ENUMERABLE | CONFIGURABLE - All existing
.get()calls must transparently handle both data and accessor properties mark_all_non_enumerable()already exists -- it should now set theENUMERABLEflag to false- Run
just ciafter property descriptor changes before proceeding
ES2023 immutable array methods (toReversed, toSorted, toSpliced, with) return new arrays. They must NOT modify the original array. This is a common LLM mistake -- double-check each implementation.
Array.from with iterables depends on the iterator protocol from Story 3.2. Verify that Symbol.iterator dispatch works correctly for strings, arrays, and custom iterables before implementing.
String.prototype.replaceAll with regex requires the global flag -- throw TypeError if regex without /g flag is passed. This is a common spec compliance detail.
Previous Story Intelligence
From Story 3.4 (ES modules) review findings:
- Opcode stubs as dead code: Avoid creating infrastructure that isn't wired into execution. If adding opcodes, make sure they're actually emitted by the compiler and handled by the VM.
- Integration tests must test the right function: Tests that call
execute_script()instead ofexecute_module()were caught in review. Similarly, ensure built-in method tests actually exercise the new code paths. - Run
just cifrequently: The 3-4 story had 3 rounds of code review. Run CI after each task, not just at the end.
From Story 3.2 (generators/iterator protocol):
- Iterator objects are
JsObjectinstances withnext()method returning{ value, done } Symbol.iteratoris used forfor...ofdispatch- Array iterator methods should follow this same pattern
Risk Assessment
HIGH: Property descriptor changes to JsObject. This is a foundational change affecting all property access in the VM. Must maintain backwards compatibility with existing .set()/.get() API. Risk of subtle breakage in existing tests.
MEDIUM: Array.from with iterator protocol. Depends on Story 3.2's iterator infrastructure. If Symbol.iterator dispatch has issues, this will surface them.
MEDIUM: String.matchAll returning iterator. Combines regex engine (regress crate) with iterator protocol. Each match object needs index, input, and capture groups.
LOW: Simple methods (at, padStart, padEnd, fill, splice). Well-defined, self-contained. Straightforward to implement and test.
Phased Implementation Strategy
Phase A -- Property Descriptors (Task 1): Foundation for everything in Object methods. Must land first and pass just ci before proceeding.
Phase B -- Object Methods (Task 2): Depends on Phase A. All Object static methods.
Phase C -- Array Non-Callback (Task 3): Independent of Phases A/B. Can start in parallel with Phase B.
Phase D -- Array Callback + Static (Task 4): Depends on iterator protocol (Story 3.2). Can start after Phase C.
Phase E -- String Methods (Task 5): Largely independent. Can start in parallel with Phases C/D.
Phase F -- Testing + Validation (Task 6): After all methods implemented.
Project Structure Notes
- All implementation in
crates/js_vm/src/interpreter/(Layer 1) -- no layer violations - New file:
crates/js_vm/src/interpreter/object_builtins.rsfor Object static methods - Property descriptor changes in
crates/js_vm/src/value.rs(Layer 1) - No
unsafecode needed - No new external dependencies
References
- ECMAScript §22.1 -- Array Objects -- Array prototype methods
- ECMAScript §21.1 -- String Objects -- String prototype methods
- ECMAScript §20.1 -- Object Objects -- Object constructor and prototype
- ECMAScript §10.1.6.3 -- OrdinaryDefineOwnProperty -- Property descriptor semantics
- ECMAScript §6.2.6 -- Property Descriptor -- Descriptor type specification
- [Source: _bmad-output/planning-artifacts/architecture.md#JS Feature Implementation Order] -- Parser -> pipeline -> tests -> docs
- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.5] -- Story requirements and acceptance criteria
- [Source: crates/js_vm/src/interpreter/builtins.rs] -- Existing Array/String method dispatch
- [Source: crates/js_vm/src/interpreter/array_builtins.rs] -- Existing callback-based Array methods
- [Source: crates/js_vm/src/interpreter/mod.rs:90-494] -- setup_builtins() initialization
Dev Agent Record
Agent Model Used
{{agent_model_name_version}}