Files
rust_browser/_bmad-output/implementation-artifacts/3-5-built-in-completeness-array-string-object.md
Zachary D. Rowitsch fb64ca1d34
All checks were successful
ci / fast (linux) (push) Successful in 7m9s
Create story files for Epic 3 stories 3.5-3.10
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>
2026-03-16 08:06:03 -04:00

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

  1. Array methods complete: splice, flat, flatMap, fill, copyWithin, at, from, of, entries/keys/values, toReversed, toSorted, toSpliced, with, toLocaleString all 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.

  2. String methods complete: padStart, padEnd, replaceAll, matchAll, at, codePointAt, normalize, String.fromCharCode, String.fromCodePoint, String.raw all behave per their ECMAScript specifications. Existing methods (28 already implemented) continue to pass.

  3. 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.isPrototypeOf all behave per their ECMAScript specifications. Existing methods (11 already implemented) continue to pass.

  4. Callback error propagation: Methods accepting callbacks (map, filter, reduce, sort, flat, flatMap, from, etc.) correctly propagate thrown errors without corrupting internal state.

  5. Property descriptor system: Object.defineProperty enforces writable, enumerable, configurable flags. Accessor properties (get/set) work. Object.freeze/Object.seal/Object.preventExtensions enforce their respective constraints on subsequent operations.

  6. Iterator protocol for Array: Array.prototype.entries(), .keys(), .values(), and Array.prototype[Symbol.iterator] return spec-compliant iterators usable with for...of (depends on generator/iterator protocol from Story 3.2).

  7. Test262 tests promoted: All relevant vendored and full-suite Test262 tests for Array, String, and Object built-ins are promoted from known_fail to pass. docs/JavaScript_Implementation_Checklist.md and docs/js_feature_matrix.md are updated. just ci passes.

What NOT to Implement

  • No Intl / locale-sensitive methods -- toLocaleString, toLocaleLowerCase, toLocaleUpperCase, localeCompare are out of scope (would require ICU/locale data).
  • No String.prototype.normalize with full Unicode normalization -- stub returning input unchanged is acceptable if full NFC/NFD/NFKC/NFKD is too complex.
  • No Proxy or Reflect -- 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 PropertyDescriptor to crates/js_vm/src/value.rs:
      • PropertyDescriptor { value: Option<JsValue>, writable: Option<bool>, enumerable: Option<bool>, configurable: Option<bool>, get: Option<JsValue>, set: Option<JsValue> }
      • PropertyFlags bitflags: 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 JsObject property storage to support descriptors:
      • Current: HashMap<String, JsValue> for properties
      • New: HashMap<String, Property> where Property { value: JsValue, flags: PropertyFlags, getter: Option<JsValue>, setter: Option<JsValue> }
      • CRITICAL: Existing .set() and .get() must continue working unchanged -- all existing properties default to WRITABLE | ENUMERABLE | CONFIGURABLE
      • Update .get() to call getter function if property is accessor
      • Update .set() to call setter function if property is accessor, respect WRITABLE flag
      • Update .delete() to respect CONFIGURABLE flag
    • 1.3 Add internal [[DefineOwnProperty]] helper on JsObject:
      • Follows ECMAScript §10.1.6.3 -- validates descriptor, merges with existing, rejects invalid changes to non-configurable properties
      • Used by Object.defineProperty and property assignment
    • 1.4 Update Object.keys(), Object.values(), Object.entries() to filter by ENUMERABLE flag
    • 1.5 Update for...in enumeration to respect ENUMERABLE flag
    • 1.6 Add unit tests for property descriptor behavior: writable rejection, enumerable filtering, configurable deletion prevention, accessor get/set invocation
  • Task 2: Object static methods (AC: #3, #5)

    • 2.1 Create crates/js_vm/src/interpreter/object_builtins.rs with:
      • 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
    • 2.3 Implement Object.defineProperties(obj, props) (ECMAScript §20.1.2.3):
      • For each enumerable own property of props, call defineProperty
    • 2.4 Implement Object.getOwnPropertyDescriptor(obj, prop) (ECMAScript §20.1.2.6):
      • Return descriptor object { value, writable, enumerable, configurable } or { get, set, enumerable, configurable }
    • 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: bool flag to JsObject (defaults to true)
      • Check flag when adding new properties
    • 2.11 Implement Object.is(a, b) (ECMAScript §20.1.2.9):
      • SameValue: NaN === NaN is true, +0 === -0 is false
    • 2.12 Implement Object.fromEntries(iterable) (ECMAScript §20.1.2.9):
      • Accept array of [key, value] pairs, create object
    • 2.13 Implement Object.hasOwn(obj, prop) (ECMAScript §20.1.2.16):
      • Static version of hasOwnProperty (ES2022)
    • 2.14 Implement Object.prototype.isPrototypeOf(obj) (ECMAScript §20.1.3.6):
      • Walk prototype chain of obj checking for this
    • 2.15 Wire all Object static methods into setup_builtins() in crates/js_vm/src/interpreter/mod.rs
    • 2.16 Add unit tests in crates/js_vm/src/interpreter/tests/object_tests.rs
  • Task 3: Array methods -- non-callback (AC: #1)

    • 3.1 Implement Array.prototype.splice(start, deleteCount, ...items) in eval_array_method() (ECMAScript §22.1.3.26):
      • Remove deleteCount elements starting at start, insert items in their place
      • Return array of deleted elements
      • Adjust .length and shift elements as needed
    • 3.2 Implement Array.prototype.fill(value, start, end) (ECMAScript §22.1.3.10):
      • Fill elements from start to end with value
      • Returns the modified array
    • 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) in eval_string_method()
    • 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
    • 3.6 Implement Array.prototype.toReversed() (ECMAScript §22.1.3.30):
      • Return new reversed array (non-mutating copy of reverse())
    • 3.7 Implement Array.prototype.toSorted(compareFn) (ECMAScript §22.1.3.31):
      • Return new sorted array (non-mutating copy of sort())
    • 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 index replaced by value
    • 3.10 Add unit tests in crates/js_vm/src/interpreter/tests/array_tests.rs
  • Task 4: Array methods -- callback and static (AC: #1, #4, #6)

    • 4.1 Implement Array.prototype.flatMap(callback, thisArg) in eval_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 .length property)
      • Handle iterables (objects with Symbol.iterator) -- use iterator protocol from Story 3.2
      • Handle strings (iterate characters)
      • Apply mapFn to each element if provided
      • Wire as static method on Array constructor in setup_builtins()
    • 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)
    • 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] pairs
      • keys() yields indices
      • values() yields values
      • Wire Array.prototype[Symbol.iterator] = Array.prototype.values
    • 4.5 Verify callback error propagation in flatMap and Array.from with mapFn
    • 4.6 Add unit tests in crates/js_vm/src/interpreter/tests/array_tests.rs
  • Task 5: String methods (AC: #2)

    • 5.1 Implement String.prototype.padStart(targetLength, padString) in eval_string_method() (ECMAScript §21.1.3.16):
      • Pad start of string to reach targetLength; padString defaults to " "
    • 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 searchValue is string: replace all occurrences (NOT regex)
      • If searchValue is regex: must have global flag, otherwise TypeError
      • Reuse existing replace logic from eval_string_method() but apply globally for string pattern
    • 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
  • 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 -- --nocapture
      • just js262-status promote --id <test-id> for each newly passing test
    • 6.2 Run full Test262 suite and triage built-in tests:
      • just test262-full
      • just 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_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
    • 6.4 Update docs/JavaScript_Implementation_Checklist.md:
      • Check off all newly implemented built-in methods
    • 6.5 Update docs/old/js_feature_matrix.md with expanded built-in coverage
    • 6.6 Run just ci -- full validation pass

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 to WRITABLE | ENUMERABLE | CONFIGURABLE
  • All existing .get() calls must transparently handle both data and accessor properties
  • mark_all_non_enumerable() already exists -- it should now set the ENUMERABLE flag to false
  • Run just ci after 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 of execute_module() were caught in review. Similarly, ensure built-in method tests actually exercise the new code paths.
  • Run just ci frequently: 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 JsObject instances with next() method returning { value, done }
  • Symbol.iterator is used for for...of dispatch
  • 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.rs for Object static methods
  • Property descriptor changes in crates/js_vm/src/value.rs (Layer 1)
  • No unsafe code needed
  • No new external dependencies

References

Dev Agent Record

Agent Model Used

{{agent_model_name_version}}

Debug Log References

Completion Notes List

File List