From 8596c6590e89d5130fad8749098e048a00793352 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 27 Sep 2026 13:53:11 -0500 Subject: [PATCH 1/2] Admit receiver-bound this in static Test262 scripts --- tests/harness/test262-runner.test.ts | 21 ++++- tests/test262/support.mjs | 18 ++-- tests/test262/upstream.json | 6 ++ .../inst-private-name-ZWNJ.js | 78 +++++++++++++++++ .../private-field-as-arrow-function.js | 33 +++++++ ...s-rs-privatename-identifier-initializer.js | 87 +++++++++++++++++++ 6 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 tests/test262/vendor/test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js create mode 100644 tests/test262/vendor/test/language/statements/class/elements/private-field-as-arrow-function.js create mode 100644 tests/test262/vendor/test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js diff --git a/tests/harness/test262-runner.test.ts b/tests/harness/test262-runner.test.ts index be9a5f5aa..fd855300e 100644 --- a/tests/harness/test262-runner.test.ts +++ b/tests/harness/test262-runner.test.ts @@ -47,13 +47,32 @@ test("unsupported execution requirements and assertion reflection remain exclusi expect(exclusion(text, metadata(text), "strict")).toBeUndefined(); expect(exclusion(text, metadata(text), "sloppy")).toBeUndefined(); const scriptGlobal = source("description: script global", "assert.sameValue((function () { return this; })(), globalThis);"); - expect(exclusion(scriptGlobal, metadata(scriptGlobal), "sloppy")).toBe("host:script-environment"); + expect(exclusion(scriptGlobal, metadata(scriptGlobal), "sloppy")).toBe("host:globalThis"); const commonJsGlobal = source("description: CommonJS global", "assert.sameValue(module.exports, {});"); expect(exclusion(commonJsGlobal, metadata(commonJsGlobal), "sloppy")).toBe("host:module"); const asyncText = source("flags: [async]", "Promise.resolve().then(() => $DONE());"); expect(exclusion(asyncText, metadata(asyncText), "strict")).toBeUndefined(); }); +test("receiver-bound this is admitted without adapting script-level this", () => { + for (const [body, expected] of [ + ["class C { read() { return this.value; } }", undefined], + ["class C { read() { return () => this.value; } }", undefined], + ["class C { value = this; }", undefined], + ["class C { static { this.value = 1; } }", undefined], + ["function read() { return this; }", undefined], + ["const read = () => this;", "host:script-environment"], + ["assert.sameValue(this, undefined);", "host:script-environment"], + ["class C { [this.key]() {} }", "host:script-environment"], + ["class C extends this.Base {}", "host:script-environment"], + ] as const) { + const text = source("description: this binding", body); + for (const variant of ["strict", "sloppy"] as const) { + expect(exclusion(text, metadata(text), variant), `${variant}: ${body}`).toBe(expected); + } + } +}); + test("negative parse cases require the compiler's matching source diagnostic", () => { const text = source("negative: {phase: parse, type: SyntaxError}", "$DONOTEVALUATE();\nconst = ;"); expect(exclusion(text, metadata(text), "strict")).toBeUndefined(); diff --git a/tests/test262/support.mjs b/tests/test262/support.mjs index dcd1dd01e..72217de76 100644 --- a/tests/test262/support.mjs +++ b/tests/test262/support.mjs @@ -84,6 +84,17 @@ export function variants(meta) { return ["sloppy", "strict"]; } +function hasOwnThisBinding(node) { + let child = node; + for (let parent = node.parent; parent; child = parent, parent = parent.parent) { + if (ts.isArrowFunction(parent)) continue; + if (ts.isFunctionLike(parent) && child === parent.body) return true; + if (ts.isPropertyDeclaration(parent) && child === parent.initializer) return true; + if (ts.isClassStaticBlockDeclaration(parent) && child === parent.body) return true; + } + return false; +} + // The first static profile adapts scripts to standalone strict modules. Be // conservative about observable global-script semantics and helper reflection. // Exclusions are runner limitations, never implementation support claims. @@ -105,11 +116,8 @@ export function exclusion(source, meta, variant) { if (meta.flags.includes("async")) forbidden.delete("$DONE"); const visit = (node) => { if (reason) return; - if (variant === "sloppy" && node.kind === ts.SyntaxKind.ThisKeyword) { - reason = "host:script-environment"; - return; - } - if (node.kind === ts.SyntaxKind.ThisKeyword || node.kind === ts.SyntaxKind.ImportKeyword || ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { + if ((node.kind === ts.SyntaxKind.ThisKeyword && !hasOwnThisBinding(node)) || + node.kind === ts.SyntaxKind.ImportKeyword || ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { reason = "host:script-environment"; } else if (ts.isIdentifier(node) && forbidden.has(node.text)) { reason = `host:${node.text}`; diff --git a/tests/test262/upstream.json b/tests/test262/upstream.json index 9709ca2fd..499ae2c78 100644 --- a/tests/test262/upstream.json +++ b/tests/test262/upstream.json @@ -176,6 +176,9 @@ "test/language/expressions/class/elements/fields-string-name-propname-constructor.js", "test/language/expressions/function/dflt-params-duplicates.js", "test/language/statements/class/class-name-ident-static.js", + "test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js", + "test/language/statements/class/elements/private-field-as-arrow-function.js", + "test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js", "test/language/statements/const/dstr/ary-ptrn-elem-ary-elem-init.js", "test/language/statements/const/dstr/ary-ptrn-elem-id-init-exhausted.js", "test/language/statements/const/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js", @@ -370,6 +373,9 @@ "test/language/expressions/class/elements/fields-string-name-propname-constructor.js": "70bce532b9f18d8795e9ea5d640bdd063004ce873d614cca05d8071aa615a605", "test/language/expressions/function/dflt-params-duplicates.js": "243c05deb19b7d54a13d4f69059a8fb8d55911f066b5cc0ae14cfc41503c28ae", "test/language/statements/class/class-name-ident-static.js": "04d24cb0b646cf244fdde005cf3c6ecf6ae82897265ab3e1fe5cab7a4d6f6a64", + "test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js": "32add15642ac1c1f8867fd66e72f2643d78aa0abcecc1a92d1467c710ee3f425", + "test/language/statements/class/elements/private-field-as-arrow-function.js": "1f1816c812604cc4aa23446b063bae8858287646ec43df4cfd076f11510ec6a1", + "test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js": "a9920a5506cadfef3ad399bb07826345913df62e7f73616f4024df82bbeffbf1", "test/language/statements/const/dstr/ary-ptrn-elem-ary-elem-init.js": "00f23ce81293acc6bd718a5a0de431928ed18db45c7ce849a0f76c365bc1ec9a", "test/language/statements/const/dstr/ary-ptrn-elem-id-init-exhausted.js": "73e765928b855ea0f89bd159a317ec212179260523d444e38b3518c8ac6c1681", "test/language/statements/const/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": "086210fc19498dad9b760b90398a3b46b366f3c4d4ee184200136641752ba832", diff --git a/tests/test262/vendor/test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js b/tests/test262/vendor/test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js new file mode 100644 index 000000000..f1adbc449 --- /dev/null +++ b/tests/test262/vendor/test/language/statements/class/elements/private-accessor-name/inst-private-name-ZWNJ.js @@ -0,0 +1,78 @@ +// This file was procedurally generated from the following sources: +// - src/accessor-names/private-name-ZWNJ.case +// - src/accessor-names/private/cls-private-decl-inst.template +/*--- +description: Private IdentifierName - ZWNJ (Class declaration, private instance method) +features: [class, class-methods-private] +flags: [generated] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + ; + + FieldDefinition : + ClassElementName Initializer _opt + + ClassElementName : + PropertyName + PrivateName + + PrivateName :: + # IdentifierName + + Initializer : + = AssignmentExpression + + IdentifierName :: + IdentifierStart + IdentifierName IdentifierPart + + IdentifierStart :: + UnicodeIDStart + $ + _ + \ UnicodeEscapeSequence + + IdentifierPart:: + UnicodeIDContinue + $ + \ UnicodeEscapeSequence + + + UnicodeIDStart:: + any Unicode code point with the Unicode property "ID_Start" + + UnicodeIDContinue:: + any Unicode code point with the Unicode property "ID_Continue" + + + NOTE 3 + The sets of code points with Unicode properties "ID_Start" and + "ID_Continue" include, respectively, the code points with Unicode + properties "Other_ID_Start" and "Other_ID_Continue". + +---*/ + +var stringSet; + +class C { + get #ZW_‌_NJ() { return 'get string'; } + set #ZW_‌_NJ(param) { stringSet = param; } + + getPrivateReference() { + return this.#ZW_‌_NJ; + } + + setPrivateReference(value) { + this.#ZW_‌_NJ = value; + } +}; + +var inst = new C(); + +assert.sameValue(inst.getPrivateReference(), 'get string'); + +inst.setPrivateReference('set string'); +assert.sameValue(stringSet, 'set string'); diff --git a/tests/test262/vendor/test/language/statements/class/elements/private-field-as-arrow-function.js b/tests/test262/vendor/test/language/statements/class/elements/private-field-as-arrow-function.js new file mode 100644 index 000000000..c78e102bd --- /dev/null +++ b/tests/test262/vendor/test/language/statements/class/elements/private-field-as-arrow-function.js @@ -0,0 +1,33 @@ +// This file was procedurally generated from the following sources: +// - src/class-elements/private-field-as-arrow-function.case +// - src/class-elements/default/cls-decl.template +/*--- +description: Calling arrow function returned from private field access (field definitions in a class declaration) +esid: prod-FieldDefinition +features: [class-fields-private, arrow-function, class] +flags: [generated] +info: | + Updated Productions + + CallExpression[Yield, Await]: + CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] + SuperCall[?Yield, ?Await] + CallExpression[?Yield, ?Await]Arguments[?Yield, ?Await] + CallExpression[?Yield, ?Await][Expression[+In, ?Yield, ?Await]] + CallExpression[?Yield, ?Await].IdentifierName + CallExpression[?Yield, ?Await]TemplateLiteral[?Yield, ?Await] + CallExpression[?Yield, ?Await].PrivateName + +---*/ + + +class C { + #m = () => 'test262'; + + method() { + return this.#m(); + } +} + +let c = new C(); +assert.sameValue(c.method(), 'test262'); diff --git a/tests/test262/vendor/test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js b/tests/test262/vendor/test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js new file mode 100644 index 000000000..103e74018 --- /dev/null +++ b/tests/test262/vendor/test/language/statements/class/elements/regular-definitions-rs-privatename-identifier-initializer.js @@ -0,0 +1,87 @@ +// This file was procedurally generated from the following sources: +// - src/class-elements/rs-privatename-identifier-initializer.case +// - src/class-elements/productions/cls-decl-regular-definitions.template +/*--- +description: Valid PrivateName (regular fields defintion) +esid: prod-FieldDefinition +features: [class-fields-private, class, class-fields-public] +flags: [generated] +info: | + ClassElement : + MethodDefinition + static MethodDefinition + FieldDefinition ; + ; + + FieldDefinition : + ClassElementName Initializer _opt + + ClassElementName : + PropertyName + PrivateName + + PrivateName :: + # IdentifierName + + IdentifierName :: + IdentifierStart + IdentifierName IdentifierPart + + IdentifierStart :: + UnicodeIDStart + $ + _ + \ UnicodeEscapeSequence + + IdentifierPart:: + UnicodeIDContinue + $ + \ UnicodeEscapeSequence + + + UnicodeIDStart:: + any Unicode code point with the Unicode property "ID_Start" + + UnicodeIDContinue:: + any Unicode code point with the Unicode property "ID_Continue" + + + NOTE 3 + The sets of code points with Unicode properties "ID_Start" and + "ID_Continue" include, respectively, the code points with Unicode + properties "Other_ID_Start" and "Other_ID_Continue". + +---*/ + + +class C { + #$ = 1; #_ = 1; #\u{6F} = 1; #\u2118 = 1; #ZW_\u200C_NJ = 1; #ZW_\u200D_J = 1 + $() { + return this.#$; + } + _() { + return this.#_; + } + \u{6F}() { + return this.#\u{6F}; + } + \u2118() { + return this.#\u2118; + } + ZW_\u200C_NJ() { + return this.#ZW_\u200C_NJ; + } + ZW_\u200D_J() { + return this.#ZW_\u200D_J; + } +} + +var c = new C(); + +assert.sameValue(c.$(), 1); +assert.sameValue(c._(), 1); +assert.sameValue(c.\u{6F}(), 1); +assert.sameValue(c.\u2118(), 1); +assert.sameValue(c.ZW_\u200C_NJ(), 1); +assert.sameValue(c.ZW_\u200D_J(), 1); + From 5e5a55f370fc4e891745298b6a559a3f61f1b7f2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 27 Sep 2026 15:28:53 -0500 Subject: [PATCH 2/2] Refresh Set seed diagnostic snapshot --- tests/diagnostics/stdlib-fence.ts | 2 +- tests/harness/__snapshots__/stdlib-fence.ts.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/diagnostics/stdlib-fence.ts b/tests/diagnostics/stdlib-fence.ts index 6a8be8da2..8d01febad 100644 --- a/tests/diagnostics/stdlib-fence.ts +++ b/tests/diagnostics/stdlib-fence.ts @@ -27,7 +27,7 @@ const sameDate = mutableDate === mutableDate; // scalar storage must not fake ob const wm = new WeakMap(); const px = new Proxy({ a: 1 }, {}); const buf = new ArrayBuffer(8); -const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced +const st = new Set(new Set([1, 2])); // string and array seeds lower; Set-valued seeds stay fenced // (new Error / TypeError / RangeError / SyntaxError now LOWER — the error // battery lives in the corpus; what stays fenced is the rest of the family // and the unlowered members of lowered error objects.) diff --git a/tests/harness/__snapshots__/stdlib-fence.ts.txt b/tests/harness/__snapshots__/stdlib-fence.ts.txt index 6725ddeb8..526abcb48 100644 --- a/tests/harness/__snapshots__/stdlib-fence.ts.txt +++ b/tests/harness/__snapshots__/stdlib-fence.ts.txt @@ -114,7 +114,7 @@ stdlib-fence.ts:29:7 - error SC2020: 'ArrayBuffer' is part of the standard libra 28 | const px = new Proxy({ a: 1 }, {}); 29 | const buf = new ArrayBuffer(8); | ^~~ - 30 | const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced + 30 | const st = new Set(new Set([1, 2])); // string and array seeds lower; Set-valued seeds stay fenced hint: no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view) @@ -123,18 +123,18 @@ stdlib-fence.ts:29:13 - error SC2020: 'new ArrayBuffer' is part of the standard 28 | const px = new Proxy({ a: 1 }, {}); 29 | const buf = new ArrayBuffer(8); | ^~~~~~~~~~~~~~~~~~ - 30 | const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced + 30 | const st = new Set(new Set([1, 2])); // string and array seeds lower; Set-valued seeds stay fenced hint: no free-standing ArrayBuffer value exists — typed arrays own their storage: allocate the view directly (new Uint8Array(n)), or erase a fresh buffer into one (new Uint8Array(new ArrayBuffer(n)), new DataView(new ArrayBuffer(n), ...)) stdlib-fence.ts:30:12 - error SC2020: 'new Set(values)' is part of the standard library types but has no scriptc lowering yet 29 | const buf = new ArrayBuffer(8); - 30 | const st = new Set(new Set([1, 2])); // array seeds lower; Set/iterable seeds stay fenced + 30 | const st = new Set(new Set([1, 2])); // string and array seeds lower; Set-valued seeds stay fenced | ^~~~~~~~~~~~~~~~~~~~~~~~ 31 | // (new Error / TypeError / RangeError / SyntaxError now LOWER — the error - hint: construct the Set empty and add() each value — only an array or fixed tuple of already-legal elements (string or number) seeds a Set + hint: construct the Set empty and add() each value — only a string, array, or fixed tuple of already-legal elements (string or number) seeds a Set stdlib-fence.ts:34:7 - error SC2020: 'AggregateError' is part of the standard library types but has no scriptc lowering yet