From 69bea5ddbba8317cf0efed83aff1261c9ea4aeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 31 Jul 2026 09:39:29 +0200 Subject: [PATCH 1/3] fix(runtime): run ToPropertyKey for every `in`-operator key type (#6944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per spec, `RelationalExpression in ShiftExpression` is ToPropertyKey(lval) for every key type, but js_object_has_property only coerced number keys. An object key was compared as a raw pointer and never matched, and its Symbol.toPrimitive / toString / valueOf never ran — observable even when the property is absent. Widen the coercion to every non-string, non-symbol key (strings and symbols are already property keys; coercion is identity and allocates nothing, so they keep the fast path). The receiver stays rooted across the GC-capable coercion via RuntimeHandleScope, mirroring js_object_get_property_key / js_object_set_property_key (#6935/#6941). New gap test test_gap_in_operator_to_property_key.ts covers object keys (toString / valueOf fallback / Symbol.toPrimitive), exactly-once coercion on absent properties, object-to-Symbol coercion, primitive keys (boolean/null/undefined/bigint), int32-vs-f64 numeric agreement, and proxy has-trap receiving the coerced key — byte-identical to node 26.5.1. --- CLAUDE.md | 2 +- Cargo.lock | 152 +++++++++--------- Cargo.toml | 2 +- .../src/object/field_get_set/has_property.rs | 38 ++--- .../test_gap_in_operator_to_property_key.ts | 95 +++++++++++ 5 files changed, 192 insertions(+), 97 deletions(-) create mode 100644 test-files/test_gap_in_operator_to_property_key.ts diff --git a/CLAUDE.md b/CLAUDE.md index 355e1e5b8d..a642c4bad7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1265 +**Current Version:** 0.5.1266 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 09786f5717..8f7e7875d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5503,7 +5503,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "base64", @@ -5563,14 +5563,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "cc", "libc", @@ -5578,7 +5578,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "log", @@ -5592,7 +5592,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-hir", @@ -5600,7 +5600,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-hir", @@ -5608,7 +5608,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-dispatch", @@ -5617,7 +5617,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-hir", @@ -5625,7 +5625,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "base64", @@ -5637,7 +5637,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-hir", @@ -5645,7 +5645,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "async-trait", @@ -5674,14 +5674,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "serde", "serde_json", @@ -5689,7 +5689,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1265" +version = "0.5.1266" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5700,7 +5700,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "clap", @@ -5715,7 +5715,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "block2", "objc2", @@ -5725,7 +5725,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "argon2", "perry-ffi", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "reqwest", @@ -5742,7 +5742,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bcrypt", "perry-ffi", @@ -5750,7 +5750,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "rusqlite", @@ -5758,7 +5758,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "scraper", @@ -5766,7 +5766,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "perry-runtime", @@ -5774,7 +5774,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "chrono", "cron", @@ -5784,7 +5784,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "chrono", "perry-ffi", @@ -5792,7 +5792,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "rust_decimal", @@ -5800,7 +5800,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "serde_json", @@ -5808,7 +5808,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5816,7 +5816,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "perry-runtime", @@ -5824,14 +5824,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bytes", "http-body-util", @@ -5849,7 +5849,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bytes", "lazy_static", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bytes", "h2", @@ -5886,7 +5886,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "lazy_static", "perry-ffi", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "jsonwebtoken", @@ -5907,7 +5907,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "lru", "perry-ffi", @@ -5915,7 +5915,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "chrono", "perry-ffi", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bson", "futures-util", @@ -5935,7 +5935,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "chrono", "perry-ffi", @@ -5945,7 +5945,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "nanoid", "perry-ffi", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "bytes", "perry-ffi", @@ -5967,7 +5967,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -5986,7 +5986,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "lettre", "perry-ffi", @@ -5996,7 +5996,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "printpdf", @@ -6004,7 +6004,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "sqlx", @@ -6013,7 +6013,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "governor", "perry-ffi", @@ -6021,7 +6021,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "fast_image_resize", "image", @@ -6031,14 +6031,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "lazy_static", "perry-ffi", @@ -6047,7 +6047,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "perry-runtime", @@ -6056,7 +6056,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "uuid", @@ -6064,7 +6064,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ffi", "regex", @@ -6074,7 +6074,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "futures-util", "lazy_static", @@ -6087,7 +6087,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "brotli", "flate2", @@ -6097,7 +6097,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "dashmap", "once_cell", @@ -6106,7 +6106,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-api-manifest", @@ -6124,7 +6124,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-diagnostics", @@ -6136,7 +6136,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "base64", @@ -6177,14 +6177,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6279,14 +6279,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "perry-hir", @@ -6295,14 +6295,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "itoa", @@ -6319,7 +6319,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "rand 0.10.1", "serde", @@ -6329,7 +6329,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6352,7 +6352,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "block2", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "block2", @@ -6383,7 +6383,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1265" +version = "0.5.1266" [[package]] name = "perry-ui-test" @@ -6394,11 +6394,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1265" +version = "0.5.1266" [[package]] name = "perry-ui-tvos" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "block2", @@ -6414,7 +6414,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "block2", @@ -6430,7 +6430,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "block2", "libc", @@ -6443,7 +6443,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "base64", "libc", @@ -6460,14 +6460,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "anyhow", "base64", @@ -6483,7 +6483,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1265" +version = "0.5.1266" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 7358b3c628..19b4d94ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -292,7 +292,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1265" +version = "0.5.1266" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-runtime/src/object/field_get_set/has_property.rs b/crates/perry-runtime/src/object/field_get_set/has_property.rs index 5e83c68688..36985b9ad8 100644 --- a/crates/perry-runtime/src/object/field_get_set/has_property.rs +++ b/crates/perry-runtime/src/object/field_get_set/has_property.rs @@ -191,33 +191,33 @@ pub extern "C" fn js_object_has_property(obj: f64, key: f64) -> f64 { let obj_val = JSValue::from_bits(obj.to_bits()); - // `in` runs ToPropertyKey on the key. Object property names are strings, so a - // NUMBER key must be coerced to its string form before the lookup — `307 in - // {307: …}` is `"307" in {…}` and must be true. Without this the string-only - // lookup below never matched a numeric key against a numeric-string property, - // so `307 in obj` was false while `"307" in obj` was true. Next.js's - // `isRedirectError` does `Number(digest.at(-2)) in RedirectStatusCode` (a - // `{307: …, 308: …}` map), so a `redirect()` thrown from a Server Component - // was not recognized as a redirect — Next treated it as a real error and a - // concurrently-rendered sibling's `session.user` read (guarded by that same - // redirect on the happy path) surfaced as a fatal 500 instead of a 307. - // (Symbols and strings pass through unchanged; a proxy/handle receiver is - // handled below with the coerced key.) + // `in` runs ToPropertyKey on the key for EVERY key type: per spec, + // `RelationalExpression in ShiftExpression` is `ToPropertyKey(lval)`, so an + // object key must have its `Symbol.toPrimitive` / `toString` / `valueOf` + // invoked — exactly once, and even when the property is absent, because the + // coercion is observable (#6944). Only strings (heap or SSO) and symbols + // are already property keys; for them the coercion is identity and + // allocates nothing, so it is skipped. Everything else goes through + // `js_to_property_key` before the lookup: numbers (`307 in {307: …}` is + // `"307" in {…}` — Next.js's `isRedirectError` does `Number(digest.at(-2)) + // in RedirectStatusCode`), booleans, null, undefined, BigInt, and objects. + // (A proxy/handle receiver is handled below with the coerced key.) // - // #6935: that coercion ALLOCATES the stringified key, so it can trigger a - // GC that evacuates the receiver — and `obj` / `obj_val` are raw locals - // captured above. (Only the number arm coerces, so no user JS runs here, - // but an allocation-triggered evacuation moves the receiver just the same.) + // #6935: that coercion ALLOCATES the stringified key, and for an object key + // it also runs user JS, so it can trigger a GC that evacuates the receiver + // — and `obj` / `obj_val` are raw locals captured above. Root the receiver + // across the coercion and read it back through the handle, mirroring + // `js_object_get_property_key` / `js_object_set_property_key`. let (obj, obj_val, key) = { let kv = JSValue::from_bits(key.to_bits()); - if kv.is_number() { + if kv.is_any_string() || unsafe { crate::symbol::js_is_symbol(key) } != 0 { + (obj, obj_val, key) + } else { let scope = crate::gc::RuntimeHandleScope::new(); let obj_handle = scope.root_heap_word_u64(obj.to_bits()); let key = unsafe { crate::object::js_to_property_key(key) }; let obj = f64::from_bits(obj_handle.get_heap_word_u64()); (obj, JSValue::from_bits(obj.to_bits()), key) - } else { - (obj, obj_val, key) } }; let key_val = JSValue::from_bits(key.to_bits()); diff --git a/test-files/test_gap_in_operator_to_property_key.ts b/test-files/test_gap_in_operator_to_property_key.ts new file mode 100644 index 0000000000..c6e7cf64df --- /dev/null +++ b/test-files/test_gap_in_operator_to_property_key.ts @@ -0,0 +1,95 @@ +// #6944: the `in` operator must run ToPropertyKey on its left operand for +// EVERY key type, not just numbers. Per spec, `RelationalExpression in +// ShiftExpression` is `ToPropertyKey(lval)`, so an object key has its +// `Symbol.toPrimitive` / `toString` / `valueOf` invoked — exactly once, and +// even when the property is absent (the coercion is observable). Perry only +// coerced number keys, so an object key was compared as a raw pointer and +// never matched. + +const obj: any = { here: 1 }; + +// object key with toString +const k: any = { toString(): string { return "here"; } }; +console.log("object toString key :", (k as any) in obj); +console.log("string key :", "here" in obj); + +// the coercion runs exactly once, even when the property is ABSENT +let calls = 0; +const absent: any = { + toString(): string { + calls++; + return "nope"; + }, +}; +console.log("absent object key :", (absent as any) in obj); +console.log("coercion call count :", calls); + +// Symbol.toPrimitive takes priority over toString/valueOf +const symPrim: any = { + [Symbol.toPrimitive](hint: string): string { + return "via-" + hint; + }, + toString(): string { + return "via-toString"; + }, +}; +const obj2: any = { "via-string": 1, "via-toString": 2 }; +console.log("Symbol.toPrimitive :", (symPrim as any) in obj2); + +// OrdinaryToPrimitive(string) tries toString first; a non-primitive toString +// result falls through to valueOf +const valOf: any = { + toString(): any { + return {}; + }, + valueOf(): number { + return 42; + }, +}; +const obj3: any = { 42: "answer" }; +console.log("valueOf fallback :", (valOf as any) in obj3); + +// toString wins over valueOf (string hint order) +const both: any = { + toString(): string { + return "str"; + }, + valueOf(): number { + return 99; + }, +}; +const obj4: any = { str: 1, 99: 2 }; +console.log("toString > valueOf :", (both as any) in obj4); + +// an object key coercing to a Symbol matches the symbol-keyed property +const sym = Symbol("s"); +const symKey: any = { + toString(): any { + return sym; + }, +}; +const withSym: any = { [sym]: 1 }; +console.log("object -> symbol key:", (symKey as any) in withSym); + +// non-string primitives are stringified too +const primitives: any = { true: 1, null: 2, undefined: 3, "1": 4 }; +console.log("boolean key :", (true as any) in primitives); +console.log("null key :", (null as any) in primitives); +console.log("undefined key :", (undefined as any) in primitives); +console.log("bigint key :", (1n as any) in primitives); + +// int32-boxed and f64 numeric keys agree on a plain object +const small = 1; +console.log("int32 numeric key :", (small as any) in primitives); +console.log("f64 numeric key :", 1.0 in primitives); + +// a proxy receiver observes the COERCED key in its `has` trap +const seen: string[] = []; +const proxy = new Proxy({ here: 1 }, { + has(target, prop) { + seen.push(String(prop)); + return prop in target; + }, +}); +console.log("proxy has trap :", (k as any) in proxy); +console.log("proxy saw key :", seen.join(",")); From 1c8594bfdcc64ce7122ee4d1dea0a9274fa2423d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 31 Jul 2026 09:49:26 +0200 Subject: [PATCH 2/3] docs(changelog): fragment for #7127 --- changelog.d/7127-in-operator-to-property-key.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog.d/7127-in-operator-to-property-key.md diff --git a/changelog.d/7127-in-operator-to-property-key.md b/changelog.d/7127-in-operator-to-property-key.md new file mode 100644 index 0000000000..293eec64fd --- /dev/null +++ b/changelog.d/7127-in-operator-to-property-key.md @@ -0,0 +1,7 @@ +fix(runtime): `in` operator runs `ToPropertyKey` for every key type (#6944) + +`key in obj` only coerced NUMBER keys through `ToPropertyKey` (the arm added for Next.js's `Number(digest.at(-2)) in RedirectStatusCode`). Per spec, `RelationalExpression in ShiftExpression` is `ToPropertyKey(lval)` for **every** key type: an object key must have its `Symbol.toPrimitive` / `toString` / `valueOf` invoked — exactly once, and even when the property is absent, because the coercion is observable. Pre-fix the object key was compared as a raw pointer and never matched, and the user coercion never ran. + +`js_object_has_property` now coerces every non-string, non-symbol key via `js_to_property_key` before the lookup. Strings (heap or SSO) and symbols are already property keys — the coercion is identity and allocates nothing — so they keep the pre-fix fast path verbatim. The coercion allocates and (for object keys) runs user JS, so it can GC and evacuate the receiver; the receiver is rooted across it via `RuntimeHandleScope::root_heap_word_u64` and read back through its handle, the same idiom as `js_object_get_property_key` / `js_object_set_property_key` (#6935) and the number arm's #6941 rooting. + +New gap test `test-files/test_gap_in_operator_to_property_key.ts`, byte-identical to node 26.5.1: object keys via `toString` / `valueOf` fallback / `Symbol.toPrimitive`, exactly-once coercion on an absent property, object→Symbol coercion matching a symbol-keyed property, primitive keys (`true` / `null` / `undefined` / `1n`), int32-vs-f64 numeric-key agreement on a plain object, and a proxy `has` trap receiving the coerced key. From 9ddd6e649959eaf95c5da19096136e22c6521dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 31 Jul 2026 09:50:25 +0200 Subject: [PATCH 3/3] chore: revert version bump (maintainer bumps at merge time) --- CLAUDE.md | 2 +- Cargo.lock | 152 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a642c4bad7..355e1e5b8d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1266 +**Current Version:** 0.5.1265 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 8f7e7875d7..09786f5717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5503,7 +5503,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "base64", @@ -5563,14 +5563,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "cc", "libc", @@ -5578,7 +5578,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "log", @@ -5592,7 +5592,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-hir", @@ -5600,7 +5600,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-hir", @@ -5608,7 +5608,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-dispatch", @@ -5617,7 +5617,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-hir", @@ -5625,7 +5625,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "base64", @@ -5637,7 +5637,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-hir", @@ -5645,7 +5645,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "async-trait", @@ -5674,14 +5674,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "serde", "serde_json", @@ -5689,7 +5689,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1266" +version = "0.5.1265" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5700,7 +5700,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "clap", @@ -5715,7 +5715,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "block2", "objc2", @@ -5725,7 +5725,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "argon2", "perry-ffi", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "reqwest", @@ -5742,7 +5742,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bcrypt", "perry-ffi", @@ -5750,7 +5750,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "rusqlite", @@ -5758,7 +5758,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "scraper", @@ -5766,7 +5766,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "perry-runtime", @@ -5774,7 +5774,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "chrono", "cron", @@ -5784,7 +5784,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "chrono", "perry-ffi", @@ -5792,7 +5792,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "rust_decimal", @@ -5800,7 +5800,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "serde_json", @@ -5808,7 +5808,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5816,7 +5816,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "perry-runtime", @@ -5824,14 +5824,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bytes", "http-body-util", @@ -5849,7 +5849,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bytes", "lazy_static", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bytes", "h2", @@ -5886,7 +5886,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "lazy_static", "perry-ffi", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "jsonwebtoken", @@ -5907,7 +5907,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "lru", "perry-ffi", @@ -5915,7 +5915,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "chrono", "perry-ffi", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bson", "futures-util", @@ -5935,7 +5935,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "chrono", "perry-ffi", @@ -5945,7 +5945,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "nanoid", "perry-ffi", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "bytes", "perry-ffi", @@ -5967,7 +5967,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -5986,7 +5986,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "lettre", "perry-ffi", @@ -5996,7 +5996,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "printpdf", @@ -6004,7 +6004,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "sqlx", @@ -6013,7 +6013,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "governor", "perry-ffi", @@ -6021,7 +6021,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "fast_image_resize", "image", @@ -6031,14 +6031,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "lazy_static", "perry-ffi", @@ -6047,7 +6047,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "perry-runtime", @@ -6056,7 +6056,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "uuid", @@ -6064,7 +6064,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ffi", "regex", @@ -6074,7 +6074,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "futures-util", "lazy_static", @@ -6087,7 +6087,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "brotli", "flate2", @@ -6097,7 +6097,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "dashmap", "once_cell", @@ -6106,7 +6106,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-api-manifest", @@ -6124,7 +6124,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-diagnostics", @@ -6136,7 +6136,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "base64", @@ -6177,14 +6177,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6279,14 +6279,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "perry-hir", @@ -6295,14 +6295,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "itoa", @@ -6319,7 +6319,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "rand 0.10.1", "serde", @@ -6329,7 +6329,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6352,7 +6352,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "block2", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "block2", @@ -6383,7 +6383,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1266" +version = "0.5.1265" [[package]] name = "perry-ui-test" @@ -6394,11 +6394,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1266" +version = "0.5.1265" [[package]] name = "perry-ui-tvos" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "block2", @@ -6414,7 +6414,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "block2", @@ -6430,7 +6430,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "block2", "libc", @@ -6443,7 +6443,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "base64", "libc", @@ -6460,14 +6460,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "anyhow", "base64", @@ -6483,7 +6483,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1266" +version = "0.5.1265" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 19b4d94ebb..7358b3c628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -292,7 +292,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1266" +version = "0.5.1265" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry"