From 2f00b58a9ab9287980747f04207388d2ec5e635e Mon Sep 17 00:00:00 2001 From: JemmyIndex Date: Thu, 5 Nov 2026 14:52:51 +0800 Subject: [PATCH] Allow `in` in array literals in a for statement initializer The [~In] of a for statement head does not reach into array literals (ElementList : AssignmentExpression[+In]), but parseArrayLiteralExpression parsed its elements in the caller's context, so the DisallowInContext flag leaked into the element list. Clear it the same way parseArgumentExpression and object literal property values already do. --- tsc/internal/parser/parser.go | 7 +- .../arrayLiteralInOperatorInForInitializer.js | 52 +++++++++ ...yLiteralInOperatorInForInitializer.symbols | 73 ++++++++++++ ...rayLiteralInOperatorInForInitializer.types | 104 ++++++++++++++++++ .../arrayLiteralInOperatorInForInitializer.ts | 28 +++++ 5 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.js create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.types create mode 100644 tsc/testdata/tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index db1bb03defb8c..05dc9a7584a0c 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -5781,7 +5781,12 @@ func (p *Parser) parseArrayLiteralExpression() *ast.Expression { openBracketPosition := p.scanner.TokenStart() openBracketParsed := p.parseExpected(ast.KindOpenBracketToken) multiLine := p.hasPrecedingLineBreak() - elements := p.parseDelimitedList(PCArrayLiteralMembers, (*Parser).parseArgumentOrArrayLiteralElement) + // The [~In] of a `for` statement head does not reach into array literals: + // ElementList elements are AssignmentExpression[+In]. Parse them with `in` allowed, + // as is already done for object literal property values and call arguments. + elements := p.doInContext(ast.NodeFlagsDisallowInContext, false, func(p *Parser) *ast.NodeList { + return p.parseDelimitedList(PCArrayLiteralMembers, (*Parser).parseArgumentOrArrayLiteralElement) + }) p.parseExpectedMatchingBrackets(ast.KindOpenBracketToken, ast.KindCloseBracketToken, openBracketParsed, openBracketPosition) return p.finishNode(p.factory.NewArrayLiteralExpression(elements, multiLine), pos) } diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.js b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.js new file mode 100644 index 0000000000000..b277eb015983e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts] //// + +//// [arrayLiteralInOperatorInForInitializer.ts] +// The `in` operator is allowed in an array literal that appears in the head of +// a `for` statement: the for head's [~In] parameter does not reach into +// array literals (ElementList : AssignmentExpression[+In]). + +const o = { a: 1 }; +const k = "a"; + +for (let i = 0, has = [k in o]; i < 1; i++) { + console.log(has); +} + +for (let i = 0, has = [k in o, "b" in o, !(k in o)]; i < 1; i++) { + console.log(has); +} + +let x: any; +for ([x = "x" in {}] of [[]]) { + console.log(x); +} + +// The initializer of a `for` statement still ends at `in`, so that `for-in` +// statements keep parsing as before. +for (const key in o) { + console.log(key); +} + + +//// [arrayLiteralInOperatorInForInitializer.js] +"use strict"; +// The `in` operator is allowed in an array literal that appears in the head of +// a `for` statement: the for head's [~In] parameter does not reach into +// array literals (ElementList : AssignmentExpression[+In]). +const o = { a: 1 }; +const k = "a"; +for (let i = 0, has = [k in o]; i < 1; i++) { + console.log(has); +} +for (let i = 0, has = [k in o, "b" in o, !(k in o)]; i < 1; i++) { + console.log(has); +} +let x; +for ([x = "x" in {}] of [[]]) { + console.log(x); +} +// The initializer of a `for` statement still ends at `in`, so that `for-in` +// statements keep parsing as before. +for (const key in o) { + console.log(key); +} diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.symbols b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.symbols new file mode 100644 index 0000000000000..b6109b0b9df67 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.symbols @@ -0,0 +1,73 @@ +//// [tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts] //// + +=== arrayLiteralInOperatorInForInitializer.ts === +// The `in` operator is allowed in an array literal that appears in the head of +// a `for` statement: the for head's [~In] parameter does not reach into +// array literals (ElementList : AssignmentExpression[+In]). + +const o = { a: 1 }; +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) +>a : Symbol(a, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 11)) + +const k = "a"; +>k : Symbol(k, Decl(arrayLiteralInOperatorInForInitializer.ts, 5, 5)) + +for (let i = 0, has = [k in o]; i < 1; i++) { +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 7, 8)) +>has : Symbol(has, Decl(arrayLiteralInOperatorInForInitializer.ts, 7, 15)) +>k : Symbol(k, Decl(arrayLiteralInOperatorInForInitializer.ts, 5, 5)) +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 7, 8)) +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 7, 8)) + + console.log(has); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>has : Symbol(has, Decl(arrayLiteralInOperatorInForInitializer.ts, 7, 15)) +} + +for (let i = 0, has = [k in o, "b" in o, !(k in o)]; i < 1; i++) { +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 11, 8)) +>has : Symbol(has, Decl(arrayLiteralInOperatorInForInitializer.ts, 11, 15)) +>k : Symbol(k, Decl(arrayLiteralInOperatorInForInitializer.ts, 5, 5)) +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) +>k : Symbol(k, Decl(arrayLiteralInOperatorInForInitializer.ts, 5, 5)) +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 11, 8)) +>i : Symbol(i, Decl(arrayLiteralInOperatorInForInitializer.ts, 11, 8)) + + console.log(has); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>has : Symbol(has, Decl(arrayLiteralInOperatorInForInitializer.ts, 11, 15)) +} + +let x: any; +>x : Symbol(x, Decl(arrayLiteralInOperatorInForInitializer.ts, 15, 3)) + +for ([x = "x" in {}] of [[]]) { +>x : Symbol(x, Decl(arrayLiteralInOperatorInForInitializer.ts, 15, 3)) + + console.log(x); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>x : Symbol(x, Decl(arrayLiteralInOperatorInForInitializer.ts, 15, 3)) +} + +// The initializer of a `for` statement still ends at `in`, so that `for-in` +// statements keep parsing as before. +for (const key in o) { +>key : Symbol(key, Decl(arrayLiteralInOperatorInForInitializer.ts, 22, 10)) +>o : Symbol(o, Decl(arrayLiteralInOperatorInForInitializer.ts, 4, 5)) + + console.log(key); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>key : Symbol(key, Decl(arrayLiteralInOperatorInForInitializer.ts, 22, 10)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.types b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.types new file mode 100644 index 0000000000000..378479194e439 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralInOperatorInForInitializer.types @@ -0,0 +1,104 @@ +//// [tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts] //// + +=== arrayLiteralInOperatorInForInitializer.ts === +// The `in` operator is allowed in an array literal that appears in the head of +// a `for` statement: the for head's [~In] parameter does not reach into +// array literals (ElementList : AssignmentExpression[+In]). + +const o = { a: 1 }; +>o : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +const k = "a"; +>k : "a" +>"a" : "a" + +for (let i = 0, has = [k in o]; i < 1; i++) { +>i : number +>0 : 0 +>has : boolean[] +>[k in o] : boolean[] +>k in o : boolean +>k : "a" +>o : { a: number; } +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + + console.log(has); +>console.log(has) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>has : boolean[] +} + +for (let i = 0, has = [k in o, "b" in o, !(k in o)]; i < 1; i++) { +>i : number +>0 : 0 +>has : boolean[] +>[k in o, "b" in o, !(k in o)] : boolean[] +>k in o : boolean +>k : "a" +>o : { a: number; } +>"b" in o : boolean +>"b" : "b" +>o : { a: number; } +>!(k in o) : boolean +>(k in o) : boolean +>k in o : boolean +>k : "a" +>o : { a: number; } +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + + console.log(has); +>console.log(has) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>has : boolean[] +} + +let x: any; +>x : any + +for ([x = "x" in {}] of [[]]) { +>[x = "x" in {}] : [boolean] +>x = "x" in {} : boolean +>x : any +>"x" in {} : boolean +>"x" : "x" +>{} : {} +>[[]] : undefined[][] +>[] : undefined[] + + console.log(x); +>console.log(x) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>x : any +} + +// The initializer of a `for` statement still ends at `in`, so that `for-in` +// statements keep parsing as before. +for (const key in o) { +>key : string +>o : { a: number; } + + console.log(key); +>console.log(key) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>key : string +} + diff --git a/tsc/testdata/tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts b/tsc/testdata/tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts new file mode 100644 index 0000000000000..51dfa5e6971e9 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/arrayLiteralInOperatorInForInitializer.ts @@ -0,0 +1,28 @@ +// @target: esnext +// @strict: false + +// The `in` operator is allowed in an array literal that appears in the head of +// a `for` statement: the for head's [~In] parameter does not reach into +// array literals (ElementList : AssignmentExpression[+In]). + +const o = { a: 1 }; +const k = "a"; + +for (let i = 0, has = [k in o]; i < 1; i++) { + console.log(has); +} + +for (let i = 0, has = [k in o, "b" in o, !(k in o)]; i < 1; i++) { + console.log(has); +} + +let x: any; +for ([x = "x" in {}] of [[]]) { + console.log(x); +} + +// The initializer of a `for` statement still ends at `in`, so that `for-in` +// statements keep parsing as before. +for (const key in o) { + console.log(key); +}