Skip to content

Commit 63d9d4c

Browse files
authored
Transform param patterns/initializers after object rest (#47095)
1 parent 1ebdcc6 commit 63d9d4c

File tree

3 files changed

+179
-19
lines changed

3 files changed

+179
-19
lines changed

src/compiler/transformers/es2018.ts

Lines changed: 133 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ namespace ts {
5959
let exportedVariableStatement = false;
6060
let enabledSubstitutions: ESNextSubstitutionFlags;
6161
let enclosingFunctionFlags: FunctionFlags;
62+
let parametersWithPrecedingObjectRestOrSpread: Set<ParameterDeclaration> | undefined;
6263
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
6364
let hierarchyFacts: HierarchyFacts = 0;
6465

@@ -785,7 +786,24 @@ namespace ts {
785786
);
786787
}
787788

789+
function parameterVisitor(node: Node) {
790+
Debug.assertNode(node, isParameter);
791+
return visitParameter(node);
792+
}
793+
788794
function visitParameter(node: ParameterDeclaration): ParameterDeclaration {
795+
if (parametersWithPrecedingObjectRestOrSpread?.has(node)) {
796+
return factory.updateParameterDeclaration(
797+
node,
798+
/*decorators*/ undefined,
799+
/*modifiers*/ undefined,
800+
node.dotDotDotToken,
801+
isBindingPattern(node.name) ? factory.getGeneratedNameForNode(node) : node.name,
802+
/*questionToken*/ undefined,
803+
/*type*/ undefined,
804+
/*initializer*/ undefined
805+
);
806+
}
789807
if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
790808
// Binding patterns are converted into a generated name and are
791809
// evaluated inside the function body.
@@ -803,54 +821,78 @@ namespace ts {
803821
return visitEachChild(node, visitor, context);
804822
}
805823

824+
function collectParametersWithPrecedingObjectRestOrSpread(node: SignatureDeclaration) {
825+
let parameters: Set<ParameterDeclaration> | undefined;
826+
for (const parameter of node.parameters) {
827+
if (parameters) {
828+
parameters.add(parameter);
829+
}
830+
else if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
831+
parameters = new Set();
832+
}
833+
}
834+
return parameters;
835+
}
836+
806837
function visitConstructorDeclaration(node: ConstructorDeclaration) {
807838
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
808-
enclosingFunctionFlags = FunctionFlags.Normal;
839+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
840+
enclosingFunctionFlags = getFunctionFlags(node);
841+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
809842
const updated = factory.updateConstructorDeclaration(
810843
node,
811844
/*decorators*/ undefined,
812845
node.modifiers,
813-
visitParameterList(node.parameters, visitor, context),
846+
visitParameterList(node.parameters, parameterVisitor, context),
814847
transformFunctionBody(node)
815848
);
816849
enclosingFunctionFlags = savedEnclosingFunctionFlags;
850+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
817851
return updated;
818852
}
819853

820854
function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
821855
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
822-
enclosingFunctionFlags = FunctionFlags.Normal;
856+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
857+
enclosingFunctionFlags = getFunctionFlags(node);
858+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
823859
const updated = factory.updateGetAccessorDeclaration(
824860
node,
825861
/*decorators*/ undefined,
826862
node.modifiers,
827863
visitNode(node.name, visitor, isPropertyName),
828-
visitParameterList(node.parameters, visitor, context),
864+
visitParameterList(node.parameters, parameterVisitor, context),
829865
/*type*/ undefined,
830866
transformFunctionBody(node)
831867
);
832868
enclosingFunctionFlags = savedEnclosingFunctionFlags;
869+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
833870
return updated;
834871
}
835872

836873
function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
837874
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
838-
enclosingFunctionFlags = FunctionFlags.Normal;
875+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
876+
enclosingFunctionFlags = getFunctionFlags(node);
877+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
839878
const updated = factory.updateSetAccessorDeclaration(
840879
node,
841880
/*decorators*/ undefined,
842881
node.modifiers,
843882
visitNode(node.name, visitor, isPropertyName),
844-
visitParameterList(node.parameters, visitor, context),
883+
visitParameterList(node.parameters, parameterVisitor, context),
845884
transformFunctionBody(node)
846885
);
847886
enclosingFunctionFlags = savedEnclosingFunctionFlags;
887+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
848888
return updated;
849889
}
850890

851891
function visitMethodDeclaration(node: MethodDeclaration) {
852892
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
893+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
853894
enclosingFunctionFlags = getFunctionFlags(node);
895+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
854896
const updated = factory.updateMethodDeclaration(
855897
node,
856898
/*decorators*/ undefined,
@@ -863,19 +905,22 @@ namespace ts {
863905
visitNode(node.name, visitor, isPropertyName),
864906
visitNode<Token<SyntaxKind.QuestionToken>>(/*questionToken*/ undefined, visitor, isToken),
865907
/*typeParameters*/ undefined,
866-
visitParameterList(node.parameters, visitor, context),
908+
visitParameterList(node.parameters, parameterVisitor, context),
867909
/*type*/ undefined,
868910
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
869911
? transformAsyncGeneratorFunctionBody(node)
870912
: transformFunctionBody(node)
871913
);
872914
enclosingFunctionFlags = savedEnclosingFunctionFlags;
915+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
873916
return updated;
874917
}
875918

876919
function visitFunctionDeclaration(node: FunctionDeclaration) {
877920
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
921+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
878922
enclosingFunctionFlags = getFunctionFlags(node);
923+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
879924
const updated = factory.updateFunctionDeclaration(
880925
node,
881926
/*decorators*/ undefined,
@@ -887,35 +932,41 @@ namespace ts {
887932
: node.asteriskToken,
888933
node.name,
889934
/*typeParameters*/ undefined,
890-
visitParameterList(node.parameters, visitor, context),
935+
visitParameterList(node.parameters, parameterVisitor, context),
891936
/*type*/ undefined,
892937
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
893938
? transformAsyncGeneratorFunctionBody(node)
894939
: transformFunctionBody(node)
895940
);
896941
enclosingFunctionFlags = savedEnclosingFunctionFlags;
942+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
897943
return updated;
898944
}
899945

900946
function visitArrowFunction(node: ArrowFunction) {
901947
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
948+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
902949
enclosingFunctionFlags = getFunctionFlags(node);
950+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
903951
const updated = factory.updateArrowFunction(
904952
node,
905953
node.modifiers,
906954
/*typeParameters*/ undefined,
907-
visitParameterList(node.parameters, visitor, context),
955+
visitParameterList(node.parameters, parameterVisitor, context),
908956
/*type*/ undefined,
909957
node.equalsGreaterThanToken,
910958
transformFunctionBody(node),
911959
);
912960
enclosingFunctionFlags = savedEnclosingFunctionFlags;
961+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
913962
return updated;
914963
}
915964

916965
function visitFunctionExpression(node: FunctionExpression) {
917966
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
967+
const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread;
918968
enclosingFunctionFlags = getFunctionFlags(node);
969+
parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node);
919970
const updated = factory.updateFunctionExpression(
920971
node,
921972
enclosingFunctionFlags & FunctionFlags.Generator
@@ -926,13 +977,14 @@ namespace ts {
926977
: node.asteriskToken,
927978
node.name,
928979
/*typeParameters*/ undefined,
929-
visitParameterList(node.parameters, visitor, context),
980+
visitParameterList(node.parameters, parameterVisitor, context),
930981
/*type*/ undefined,
931982
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
932983
? transformAsyncGeneratorFunctionBody(node)
933984
: transformFunctionBody(node)
934985
);
935986
enclosingFunctionFlags = savedEnclosingFunctionFlags;
987+
parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread;
936988
return updated;
937989
}
938990

@@ -1007,6 +1059,7 @@ namespace ts {
10071059
statementOffset = factory.copyPrologue(body.statements, statements, /*ensureUseStrict*/ false, visitor);
10081060
}
10091061
addRange(statements, appendObjectRestAssignmentsIfNeeded(/*statements*/ undefined, node));
1062+
10101063
const leadingStatements = endLexicalEnvironment();
10111064
if (statementOffset > 0 || some(statements) || some(leadingStatements)) {
10121065
const block = factory.converters.convertToFunctionBlock(body, /*multiLine*/ true);
@@ -1018,25 +1071,86 @@ namespace ts {
10181071
}
10191072

10201073
function appendObjectRestAssignmentsIfNeeded(statements: Statement[] | undefined, node: FunctionLikeDeclaration): Statement[] | undefined {
1074+
let containsPrecedingObjectRestOrSpread = false;
10211075
for (const parameter of node.parameters) {
1022-
if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
1023-
const temp = factory.getGeneratedNameForNode(parameter);
1076+
if (containsPrecedingObjectRestOrSpread) {
1077+
if (isBindingPattern(parameter.name)) {
1078+
// In cases where a binding pattern is simply '[]' or '{}',
1079+
// we usually don't want to emit a var declaration; however, in the presence
1080+
// of an initializer, we must emit that expression to preserve side effects.
1081+
//
1082+
// NOTE: see `insertDefaultValueAssignmentForBindingPattern` in es2015.ts
1083+
if (parameter.name.elements.length > 0) {
1084+
const declarations = flattenDestructuringBinding(
1085+
parameter,
1086+
visitor,
1087+
context,
1088+
FlattenLevel.All,
1089+
factory.getGeneratedNameForNode(parameter));
1090+
if (some(declarations)) {
1091+
const declarationList = factory.createVariableDeclarationList(declarations);
1092+
const statement = factory.createVariableStatement(/*modifiers*/ undefined, declarationList);
1093+
setEmitFlags(statement, EmitFlags.CustomPrologue);
1094+
statements = append(statements, statement);
1095+
}
1096+
}
1097+
else if (parameter.initializer) {
1098+
const name = factory.getGeneratedNameForNode(parameter);
1099+
const initializer = visitNode(parameter.initializer, visitor, isExpression);
1100+
const assignment = factory.createAssignment(name, initializer);
1101+
const statement = factory.createExpressionStatement(assignment);
1102+
setEmitFlags(statement, EmitFlags.CustomPrologue);
1103+
statements = append(statements, statement);
1104+
}
1105+
}
1106+
else if (parameter.initializer) {
1107+
// Converts a parameter initializer into a function body statement, i.e.:
1108+
//
1109+
// function f(x = 1) { }
1110+
//
1111+
// becomes
1112+
//
1113+
// function f(x) {
1114+
// if (typeof x === "undefined") { x = 1; }
1115+
// }
1116+
1117+
const name = factory.cloneNode(parameter.name);
1118+
setTextRange(name, parameter.name);
1119+
setEmitFlags(name, EmitFlags.NoSourceMap);
1120+
1121+
const initializer = visitNode(parameter.initializer, visitor, isExpression);
1122+
addEmitFlags(initializer, EmitFlags.NoSourceMap | EmitFlags.NoComments);
1123+
1124+
const assignment = factory.createAssignment(name, initializer);
1125+
setTextRange(assignment, parameter);
1126+
setEmitFlags(assignment, EmitFlags.NoComments);
1127+
1128+
const block = factory.createBlock([factory.createExpressionStatement(assignment)]);
1129+
setTextRange(block, parameter);
1130+
setEmitFlags(block, EmitFlags.SingleLine | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments);
1131+
1132+
const typeCheck = factory.createTypeCheck(factory.cloneNode(parameter.name), "undefined");
1133+
const statement = factory.createIfStatement(typeCheck, block);
1134+
startOnNewLine(statement);
1135+
setTextRange(statement, parameter);
1136+
setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue | EmitFlags.NoComments);
1137+
statements = append(statements, statement);
1138+
}
1139+
}
1140+
else if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
1141+
containsPrecedingObjectRestOrSpread = true;
10241142
const declarations = flattenDestructuringBinding(
10251143
parameter,
10261144
visitor,
10271145
context,
10281146
FlattenLevel.ObjectRest,
1029-
temp,
1147+
factory.getGeneratedNameForNode(parameter),
10301148
/*doNotRecordTempVariablesInLine*/ false,
10311149
/*skipInitializer*/ true,
10321150
);
10331151
if (some(declarations)) {
1034-
const statement = factory.createVariableStatement(
1035-
/*modifiers*/ undefined,
1036-
factory.createVariableDeclarationList(
1037-
declarations
1038-
)
1039-
);
1152+
const declarationList = factory.createVariableDeclarationList(declarations);
1153+
const statement = factory.createVariableStatement(/*modifiers*/ undefined, declarationList);
10401154
setEmitFlags(statement, EmitFlags.CustomPrologue);
10411155
statements = append(statements, statement);
10421156
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [functionParameterObjectRestAndInitializers.ts]
2+
// https://github.com/microsoft/TypeScript/issues/47079
3+
4+
function f({a, ...x}, b = a) {
5+
return b;
6+
}
7+
8+
function g({a, ...x}, b = ({a}, b = a) => {}) {
9+
return b;
10+
}
11+
12+
13+
//// [functionParameterObjectRestAndInitializers.js]
14+
// https://github.com/microsoft/TypeScript/issues/47079
15+
var __rest = (this && this.__rest) || function (s, e) {
16+
var t = {};
17+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
18+
t[p] = s[p];
19+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
20+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
21+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
22+
t[p[i]] = s[p[i]];
23+
}
24+
return t;
25+
};
26+
function f(_a, b) {
27+
var { a } = _a, x = __rest(_a, ["a"]);
28+
if (b === void 0) { b = a; }
29+
return b;
30+
}
31+
function g(_a, b) {
32+
var { a } = _a, x = __rest(_a, ["a"]);
33+
if (b === void 0) { b = ({ a }, b = a) => { }; }
34+
return b;
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @target: es2015
2+
// @noTypesAndSymbols: true
3+
// https://github.com/microsoft/TypeScript/issues/47079
4+
5+
function f({a, ...x}, b = a) {
6+
return b;
7+
}
8+
9+
function g({a, ...x}, b = ({a}, b = a) => {}) {
10+
return b;
11+
}

0 commit comments

Comments
 (0)