Skip to content

test262 built-ins/String — 32 (self-contained worklist) #5902

Closed

Description

@proggeramlug

Self-contained worklist — 32 failing. Snapshot 2026-07-03 full sweep (perry 0.5.1218, node v26.3.0). Everything you need is in this issue; no history to read, no re-measure to start.

String methods on non-generic receivers, normalize, well-formed, symbol coercion. Crate: perry-runtime (string), perry-codegen.

Setup (any box)

git clone https://github.com/PerryTS/perry && cd perry
cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static
git clone --depth 1 https://github.com/tc39/test262 vendor/test262   # checkout test-compat/test262/pinned-sha.txt
# node on PATH (differential oracle). See test-compat/test262/README.md.

Measure/verify: scripts/test262_subset.py --root vendor/test262 --dir built-ins/String --jobs 8 (the .failures.txt sidecar lists failures; diff before/after for zero-regression).

Failure reasons

n reason
4 Uncaught exception: #1.1: Exception === "intostr". Actu
3 Uncaught exception: #1: __instance = new Object(true);
2 Uncaught exception: #1: __instance = new Boolean; __ins
2 Uncaught exception: #1.1: Exception is instance of Type
2 Uncaught exception: Expected a TypeError to be thrown b
1 Uncaught exception: #2: toString=function(){return "__T
1 Uncaught exception: #1: Function.prototype.indicator =
1 TypeError: Cannot assign to read only property 'length'
1 Uncaught exception: #2: var __str__instance = new Strin
1 Uncaught exception: #1: delete String.prototype.toStrin
1 Uncaught exception: #2: delete String.prototype.toStrin
1 Uncaught exception: #1: var x; __instance = {toString:f

All 32 failing cases (path — reason)

built-ins/String/S15.5.1.1_A1_T9.js    Uncaught exception: #2: toString=function(){return "__THIS__";}; __str = String(
built-ins/String/S15.5.3_A2_T2.js    Uncaught exception: #1: Function.prototype.indicator = 1; String.indicator === 1
built-ins/String/S15.5.5.1_A4_T1.js    TypeError: Cannot assign to read only property 'length' of object '#<Object>'
built-ins/String/S15.5.5.1_A5.js    Uncaught exception: #2: var __str__instance = new String("ABC\u0041\u0042\u0043"
built-ins/String/prototype/S15.5.4_A1.js    Uncaught exception: #1: delete String.prototype.toString; String.prototype.toStr
built-ins/String/prototype/S15.5.4_A3.js    Uncaught exception: #2: delete String.prototype.toString; String.prototype.toStr
built-ins/String/prototype/concat/S15.5.4.6_A4_T1.js    Uncaught exception: #1: var x; __instance = {toString:function(){return "one"}};
built-ins/String/prototype/constructor/S15.5.4.1_A1_T2.js    TypeError: String.prototype.valueOf called on incompatible receiver
built-ins/String/prototype/indexOf/S15.5.4.7_A1_T1.js    Uncaught exception: #1: __instance = new Object(true); __instance.indexOf = Stri
built-ins/String/prototype/indexOf/S15.5.4.7_A1_T2.js    Uncaught exception: #1: __instance = new Boolean; __instance.indexOf = String.pr
built-ins/String/prototype/indexOf/S15.5.4.7_A4_T4.js    Uncaught exception: #1.1: Exception === "intostr". Actual: Test262Error: #1: "va
built-ins/String/prototype/indexOf/S15.5.4.7_A4_T5.js    Uncaught exception: #1.1: Exception === "intostr". Actual: Test262Error: #1: "va
built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T1.js    Uncaught exception: #1: __instance = new Object(true); __instance.lastIndexOf = 
built-ins/String/prototype/lastIndexOf/S15.5.4.8_A1_T2.js    Uncaught exception: #1: __instance = new Boolean; __instance.lastIndexOf = Strin
built-ins/String/prototype/lastIndexOf/S15.5.4.8_A4_T2.js    Uncaught exception: #1.1: Exception === "intointeger". Actual: Test262Error: #1:
built-ins/String/prototype/lastIndexOf/S15.5.4.8_A4_T4.js    Uncaught exception: #1.1: Exception === "intostr". Actual: Test262Error: #1: var
built-ins/String/prototype/lastIndexOf/S15.5.4.8_A4_T5.js    Uncaught exception: #1.1: Exception === "intostr". Actual: Test262Error: #1: var
built-ins/String/prototype/localeCompare/15.5.4.9_CE.js    Uncaught exception: String.prototype.localeCompare considers ö (\u006f\u0308) ≠
built-ins/String/prototype/padEnd/observable-operations.js    Uncaught exception: Expected SameValue(«"abc"», «"abcdefdefde"») to be true
built-ins/String/prototype/padStart/observable-operations.js    Uncaught exception: Expected SameValue(«"abc"», «"defdefdeabc"») to be true
built-ins/String/prototype/replace/S15.5.4.11_A1_T1.js    Uncaught exception: #1: __instance = new Object(true); __instance.replace = Stri
built-ins/String/prototype/replace/S15.5.4.11_A1_T10.js    Uncaught exception: #1: var x; var __obj = {toString:function(){return "AB";}}; 
built-ins/String/prototype/replace/S15.5.4.11_A1_T12.js    Uncaught exception: #1.1: Exception === "insearchValue". Actual: Test262Error: #
built-ins/String/prototype/replace/S15.5.4.11_A1_T15.js    Uncaught exception: #1.1: Exception is instance of TypeError. Actual: Test262Err
built-ins/String/prototype/replace/S15.5.4.11_A1_T16.js    Uncaught exception: #1.1: Exception is instance of TypeError. Actual: Test262Err
built-ins/String/prototype/replace/S15.5.4.11_A1_T2.js    Uncaught exception: #1: var x; __instance = new Boolean; __instance.replace = St
built-ins/String/prototype/replace/S15.5.4.11_A1_T5.js    Uncaught exception: #1: function(){return "gnulluna"}().replace(null, Function()
built-ins/String/prototype/replace/S15.5.4.11_A1_T9.js    Uncaught exception: #1: __obj = {valueOf:function(){}, toString:void 0}; functio
built-ins/String/prototype/replace/replaceValue-evaluation-order-regexp-object.js    Uncaught exception: Expected SameValue(«0», «1») to be true
built-ins/String/prototype/split/separator-regexp.js    SyntaxError: Invalid regular expression: /\k<x>/: invalid pattern
built-ins/String/prototype/toString/non-generic.js    Uncaught exception: Expected a TypeError to be thrown but no exception was throw
built-ins/String/prototype/valueOf/non-generic.js    Uncaught exception: Expected a TypeError to be thrown but no exception was throw

Workflow

Pick a coherent single-root subcluster, localize (cargo run --release -- f.ts --trace hir --focus <fn> / --trace llvm), fix, verify listed cases pass AND re-run the slice for zero regressions. Code-only PR (no Cargo.toml/Cargo.lock version bump, no CLAUDE.md Current-Version, no CHANGELOG — maintainer folds metadata). cargo fmt --all -- --check + bash scripts/check_file_size.sh must pass (files < 2000 lines). Reference this issue. Never reference internal infrastructure hostnames.

Activity

  1. added
    parityCompatibility gap with Node.js, ECMAScript, or the supported ecosystem
    on Jul 3, 2026
  2. proggeramlug commented on Jul 4, 2026

    @proggeramlug
    ContributorAuthor

    Localized the biggest subcluster (String.prototype methods borrowed onto non-string objects — obj.indexOf = String.prototype.indexOf; obj.indexOf(true, false) → -1 instead of coercing this; also lastIndexOf/concat).

    Root is at the HIR lowering layer, not runtime dispatch: x.indexOf(a, b) on an any-typed receiver is eagerly folded to Expr::ArrayIndexOf { array: x, ... } (confirmed via --print-hir), so a borrowed String.prototype.indexOf thunk stored as an own property never runs — the array-like engine searches the receiver's indexed elements and returns -1. String.prototype.indexOf.call(obj, ...) works (the .call fold-exclusion routes it through the generic string thunk); only the direct member form obj.indexOf(...) mis-folds.

    The fix belongs in the .indexOf/.lastIndexOf/.concat fold in perry-hir (don't fold to ArrayIndexOf when the receiver has, or may have, an own property of that name). That's a broad optimization change with regression surface across all array indexOf/lastIndexOf/concat call sites, so leaving it for a dedicated maintainer pass rather than rushing it. A runtime-layer classify_own_slot attempt does not help (codegen never reaches the runtime dispatch for these folded calls).

  3. added
    triagedMaintainer reviewed; type, scope, and next step are clear
    on Jul 26, 2026
  4. proggeramlug commented on Aug 5, 2026

    @proggeramlug
    ContributorAuthor

    Localized S15.5.5.1_A4_T1 — it is not a String bug, it is sloppy-mode read-modify-write

    Filing the diagnosis rather than a fix: the root is a missing field on a HIR variant, which is wider than this worklist entry and deserves its own PR.

    The failing line in that test is neither of the plain assignments — it is __str__instance.length++ (and the with(...) length = 0 further down). Plain __str__instance.length = -1 already behaves correctly.

    Characterization (node 26.5.1 vs perry @ 0f9bb6cc5, all sloppy mode):

    case node perry
    new String("globglob").length++ 8 TypeError
    new String("globglob").length += 1 8 TypeError
    new String("globglob").length = -1 8 8 ✅
    Object.freeze({a:1}).a++ 1 TypeError
    Object.freeze({a:1}).a = 2 1 1 ✅
    Object.defineProperty(o,"x",{value:5,writable:false}); o.x++ 5 TypeError
    (function fn(a,b){}).length++ 2 2 ✅
    frozen array a[0]++ 1 1 ✅

    So this is not String-specific — Object.freeze(cfg); cfg.count++ throws on any frozen plain object, which is ordinary real-world code. Only the named-property read-modify-write path is affected; closures and array indices take other paths and are already correct.

    Root cause. The two forms lower to different HIR, and only one of them carries the strict flag:

    o.a = 2   →  PutValueSet { target, key: String("a"), value, receiver, strict: false }
    o.a++     →  PropertyUpdate { object, property: "a", op: Add, prefix: false }
                                                            ^ no `strict` field at all
    

    Expr::PropertyUpdate (perry-hir/src/ir/expr.rs) has no strict-mode information, so its codegen cannot make the sloppy-vs-strict decision and ends up on the runtime setter that throws unconditionally — e.g. the if is_frozen { throw_immutable_write(0, …) } gates in perry-runtime/src/object/field_set_by_name/tail.rs. Per spec these are [[Set]] failures that return false; only strict mode turns that into a TypeError.

    Fix shape. Give PropertyUpdate the same strict flag PutValueSet already carries, populate it in perry-hir/src/lower/expr_misc.rs from the enclosing strictness, and have codegen select the throwing vs. silent setter from it. Mechanically that means the variant plus stable_hash/expr.rs, monomorph/substitute_expr.rs, and the codegen consumer; the ~50 files that mention PropertyUpdate mostly match with .. and are unaffected.

    Note IndexUpdate is the sibling variant and is worth checking for the same gap while in there.

    The with(__str__instance) length = 0; line in the same test is a separate question (whether with is supported at all) and is not covered by the above.

    Found while landing #7470 / #7471.

  5. proggeramlug commented on Aug 18, 2026

    @proggeramlug
    ContributorAuthor

    The final localeCompare canonical-equivalence fix landed in #8349. Closing this String compatibility worklist as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    parityCompatibility gap with Node.js, ECMAScript, or the supported ecosystemtriagedMaintainer reviewed; type, scope, and next step are clear

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions