From 8becdf2b40eff9c80b3c399c46cd49a388d9433e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 22 Feb 2023 20:41:09 -0500 Subject: [PATCH] Fix emit for nested object rest in assignment pattern (#52922) --- src/compiler/factory/nodeFactory.ts | 23 ++------------ src/compiler/factory/utilities.ts | 31 +++++++++++++++++++ src/compiler/transformers/es2018.ts | 6 ++-- tests/baselines/reference/nestedObjectRest.js | 27 ++++++++++++++++ .../reference/nestedObjectRest.symbols | 14 +++++++++ .../reference/nestedObjectRest.types | 26 ++++++++++++++++ .../reference/privateWriteOnlyAccessorRead.js | 4 +-- tests/cases/compiler/nestedObjectRest.ts | 7 +++++ 8 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 tests/baselines/reference/nestedObjectRest.js create mode 100644 tests/baselines/reference/nestedObjectRest.symbols create mode 100644 tests/baselines/reference/nestedObjectRest.types create mode 100644 tests/cases/compiler/nestedObjectRest.ts diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 571ea07d47c..f7675141469 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -58,6 +58,7 @@ import { ConstructorDeclaration, ConstructorTypeNode, ConstructSignatureDeclaration, + containsObjectRestOrSpread, ContinueStatement, createBaseNodeFactory, createNodeConverters, @@ -114,7 +115,6 @@ import { getAllUnscopedEmitHelpers, getBuildInfo, getCommentRange, - getElementsOfBindingOrAssignmentPattern, getEmitFlags, getIdentifierTypeArguments, getJSDocTypeAliasName, @@ -124,7 +124,6 @@ import { getSourceMapRange, getSyntheticLeadingComments, getSyntheticTrailingComments, - getTargetOfBindingOrAssignmentElement, getTextOfIdentifierOrLiteral, hasInvalidEscape, HasModifiers, @@ -150,7 +149,6 @@ import { isArray, isArrayLiteralExpression, isArrowFunction, - isAssignmentPattern, isBinaryExpression, isCallChain, isClassDeclaration, @@ -3326,24 +3324,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags { - if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread; - if (node.transformFlags & TransformFlags.ContainsES2018) { - // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' - // will not be correctly interpreted by the ES2018 transformer - for (const element of getElementsOfBindingOrAssignmentPattern(node)) { - const target = getTargetOfBindingOrAssignmentElement(element); - if (target && isAssignmentPattern(target)) { - if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { - return TransformFlags.ContainsObjectRestOrSpread; - } - if (target.transformFlags & TransformFlags.ContainsES2018) { - const flags = propagateAssignmentPatternFlags(target); - if (flags) return flags; - } - } - } - } - return TransformFlags.None; + return containsObjectRestOrSpread(node) ? TransformFlags.ContainsObjectRestOrSpread : TransformFlags.None; } // @api diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 83a40f3b3ff..dcc3de87f81 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -7,6 +7,7 @@ import { AssertionLevel, AssignmentExpression, AssignmentOperatorOrHigher, + AssignmentPattern, BinaryExpression, BinaryOperator, BinaryOperatorToken, @@ -76,6 +77,7 @@ import { InternalEmitFlags, isAssignmentExpression, isAssignmentOperator, + isAssignmentPattern, isBlock, isCommaListExpression, isComputedPropertyName, @@ -174,6 +176,7 @@ import { TextRange, ThisTypeNode, Token, + TransformFlags, TypeNode, } from "../_namespaces/ts"; @@ -1742,3 +1745,31 @@ export function flattenCommaList(node: Expression) { flattenCommaListWorker(node, expressions); return expressions; } + +/** + * Walk an AssignmentPattern to determine if it contains object rest (`...`) syntax. We cannot rely on + * propagation of `TransformFlags.ContainsObjectRestOrSpread` since it isn't propagated by default in + * ObjectLiteralExpression and ArrayLiteralExpression since we do not know whether they belong to an + * AssignmentPattern at the time the nodes are parsed. + * + * @internal + */ +export function containsObjectRestOrSpread(node: AssignmentPattern): boolean { + if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return true; + if (node.transformFlags & TransformFlags.ContainsES2018) { + // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' + // will not be correctly interpreted by the ES2018 transformer + for (const element of getElementsOfBindingOrAssignmentPattern(node)) { + const target = getTargetOfBindingOrAssignmentElement(element); + if (target && isAssignmentPattern(target)) { + if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + return true; + } + if (target.transformFlags & TransformFlags.ContainsES2018) { + if (containsObjectRestOrSpread(target)) return true; + } + } + } + } + return false; +} diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 35f28a2ee8d..e3188ade77b 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -19,6 +19,7 @@ import { concatenate, ConciseBody, ConstructorDeclaration, + containsObjectRestOrSpread, createForOfBindingStatement, createSuperAccessVariableStatement, Debug, @@ -577,7 +578,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile * expression of an `ExpressionStatement`). */ function visitBinaryExpression(node: BinaryExpression, expressionResultIsUnused: boolean): Expression { - if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + if (isDestructuringAssignment(node) && containsObjectRestOrSpread(node.left)) { return flattenDestructuringAssignment( node, visitor, @@ -703,7 +704,8 @@ export function transformES2018(context: TransformationContext): (x: SourceFile */ function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement | undefined): VisitResult { const ancestorFacts = enterSubtree(HierarchyFacts.IterationStatementExcludes, HierarchyFacts.IterationStatementIncludes); - if (node.initializer.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + if (node.initializer.transformFlags & TransformFlags.ContainsObjectRestOrSpread || + isAssignmentPattern(node.initializer) && containsObjectRestOrSpread(node.initializer)) { node = transformForOfStatementWithObjectRest(node); } const result = node.awaitModifier ? diff --git a/tests/baselines/reference/nestedObjectRest.js b/tests/baselines/reference/nestedObjectRest.js new file mode 100644 index 00000000000..708905b3e15 --- /dev/null +++ b/tests/baselines/reference/nestedObjectRest.js @@ -0,0 +1,27 @@ +//// [nestedObjectRest.ts] +// https://github.com/microsoft/TypeScript/issues/43400 +var x, y; + +[{ ...x }] = [{ abc: 1 }]; +for ([{ ...y }] of [[{ abc: 1 }]]) ; + +//// [nestedObjectRest.js] +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var _a, _b; +// https://github.com/microsoft/TypeScript/issues/43400 +var x, y; +[_a] = [{ abc: 1 }], x = __rest(_a, []); +for (let _c of [[{ abc: 1 }]]) { + [_b] = _c, y = __rest(_b, []); + ; +} diff --git a/tests/baselines/reference/nestedObjectRest.symbols b/tests/baselines/reference/nestedObjectRest.symbols new file mode 100644 index 00000000000..47c3fce0b45 --- /dev/null +++ b/tests/baselines/reference/nestedObjectRest.symbols @@ -0,0 +1,14 @@ +=== tests/cases/compiler/nestedObjectRest.ts === +// https://github.com/microsoft/TypeScript/issues/43400 +var x, y; +>x : Symbol(x, Decl(nestedObjectRest.ts, 1, 3)) +>y : Symbol(y, Decl(nestedObjectRest.ts, 1, 6)) + +[{ ...x }] = [{ abc: 1 }]; +>x : Symbol(x, Decl(nestedObjectRest.ts, 1, 3)) +>abc : Symbol(abc, Decl(nestedObjectRest.ts, 3, 15)) + +for ([{ ...y }] of [[{ abc: 1 }]]) ; +>y : Symbol(y, Decl(nestedObjectRest.ts, 1, 6)) +>abc : Symbol(abc, Decl(nestedObjectRest.ts, 4, 22)) + diff --git a/tests/baselines/reference/nestedObjectRest.types b/tests/baselines/reference/nestedObjectRest.types new file mode 100644 index 00000000000..c69a1a636f1 --- /dev/null +++ b/tests/baselines/reference/nestedObjectRest.types @@ -0,0 +1,26 @@ +=== tests/cases/compiler/nestedObjectRest.ts === +// https://github.com/microsoft/TypeScript/issues/43400 +var x, y; +>x : any +>y : any + +[{ ...x }] = [{ abc: 1 }]; +>[{ ...x }] = [{ abc: 1 }] : [{ abc: number; }] +>[{ ...x }] : [any] +>{ ...x } : any +>x : any +>[{ abc: 1 }] : [{ abc: number; }] +>{ abc: 1 } : { abc: number; } +>abc : number +>1 : 1 + +for ([{ ...y }] of [[{ abc: 1 }]]) ; +>[{ ...y }] : [any] +>{ ...y } : any +>y : any +>[[{ abc: 1 }]] : { abc: number; }[][] +>[{ abc: 1 }] : { abc: number; }[] +>{ abc: 1 } : { abc: number; } +>abc : number +>1 : 1 + diff --git a/tests/baselines/reference/privateWriteOnlyAccessorRead.js b/tests/baselines/reference/privateWriteOnlyAccessorRead.js index 1b822605639..150554daf9b 100644 --- a/tests/baselines/reference/privateWriteOnlyAccessorRead.js +++ b/tests/baselines/reference/privateWriteOnlyAccessorRead.js @@ -72,9 +72,7 @@ class Test { (_a = this, { o: ({ set value(_e) { __classPrivateFieldSet(_a, _Test_instances, _e, "a", _Test_value_set); } }).value } = { o: { foo } }); //ok (_b = this, ({ set value(_e) { __classPrivateFieldSet(_b, _Test_instances, _e, "a", _Test_value_set); } }).value = __rest({ foo }, [])); //ok ({ foo: __classPrivateFieldGet(this, _Test_instances, "a").foo } = { foo }); //error - ({ - foo: Object.assign({}, __classPrivateFieldGet(this, _Test_instances, "a").foo), - } = { foo }); //error + (__classPrivateFieldGet(this, _Test_instances, "a").foo = __rest({ foo }.foo, [])); //error let r = { o: __classPrivateFieldGet(this, _Test_instances, "a") }; //error _c = this, _d = this, [({ set value(_e) { __classPrivateFieldSet(_c, _Test_instances, _e, "a", _Test_valueOne_set); } }).value, ...({ set value(_e) { __classPrivateFieldSet(_d, _Test_instances, _e, "a", _Test_valueRest_set); } }).value] = [1, 2, 3]; let arr = [ diff --git a/tests/cases/compiler/nestedObjectRest.ts b/tests/cases/compiler/nestedObjectRest.ts new file mode 100644 index 00000000000..033cc3e0764 --- /dev/null +++ b/tests/cases/compiler/nestedObjectRest.ts @@ -0,0 +1,7 @@ +// @target: es2017 + +// https://github.com/microsoft/TypeScript/issues/43400 +var x, y; + +[{ ...x }] = [{ abc: 1 }]; +for ([{ ...y }] of [[{ abc: 1 }]]) ; \ No newline at end of file