Skip to content

Commit 5dfd7c0

Browse files
Dominator008blickly
authored andcommitted
Transpile ES6 rest parameters using a vanilla for loop.
Fixes #1015 on github Also, preserve type information on rest parameters. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=96843298
1 parent 7dde811 commit 5dfd7c0

File tree

3 files changed

+166
-16
lines changed

3 files changed

+166
-16
lines changed

src/com/google/javascript/jscomp/Es6ToEs3Converter.java

+67-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ public final class Es6ToEs3Converter implements NodeTraversal.Callback, HotSwapC
6868
"CONFLICTING_GETTER_SETTER_TYPE",
6969
"The types of the getter and setter for property ''{0}'' do not match.");
7070

71+
static final DiagnosticType BAD_REST_PARAMETER_ANNOTATION = DiagnosticType.warning(
72+
"BAD_REST_PARAMETER_ANNOTATION",
73+
"Missing \"...\" in type annotation for rest parameter.");
74+
75+
// The name of the index variable for populating the rest parameter array.
76+
private static final String REST_INDEX = "$jscomp$restIndex";
77+
78+
// The name of the placeholder for the rest parameters.
79+
private static final String REST_PARAMS = "$jscomp$restParams";
80+
7181
// The name of the vars that capture 'this' and 'arguments'
7282
// for converting arrow functions.
7383
private static final String THIS_VAR = "$jscomp$this";
@@ -542,11 +552,64 @@ private void visitParamList(Node paramList, Node function) {
542552
} else if (param.isRest()) { // rest parameter
543553
param.setType(Token.NAME);
544554
param.setVarArgs(true);
545-
// Transpile to: param = [].slice.call(arguments, i);
546-
Node newArr = IR.exprResult(IR.assign(IR.name(param.getString()),
547-
IR.call(IR.getprop(IR.getprop(IR.arraylit(), IR.string("slice")),
548-
IR.string("call")), IR.name("arguments"), IR.number(i))));
555+
String paramName = param.getString();
556+
557+
Node newBlock = IR.block().useSourceInfoFrom(block);
558+
Node name = IR.name(paramName);
559+
Node let = IR.let(name, IR.name(REST_PARAMS))
560+
.useSourceInfoIfMissingFromForTree(block);
561+
newBlock.addChildToFront(let);
562+
if (insertSpot != null) {
563+
for (Node statement = insertSpot.getNext(); statement != null;
564+
statement = statement.getNext()) {
565+
newBlock.addChildToBack(statement.detachFromParent());
566+
}
567+
} else {
568+
for (Node child : block.children()) {
569+
newBlock.addChildToBack(child.detachFromParent());
570+
}
571+
}
572+
573+
// Make sure rest parameters are typechecked
574+
JSTypeExpression type = null;
575+
JSDocInfo info = param.getJSDocInfo();
576+
if (info != null) {
577+
type = info.getType();
578+
} else {
579+
JSDocInfo functionInfo = function.getJSDocInfo();
580+
if (functionInfo != null) {
581+
type = functionInfo.getParameterType(paramName);
582+
}
583+
}
584+
if (type != null) {
585+
Node arrayType = IR.string("Array");
586+
Node typeNode = type.getRoot();
587+
if (typeNode.getType() != Token.ELLIPSIS) {
588+
compiler.report(JSError.make(typeNode, BAD_REST_PARAMETER_ANNOTATION));
589+
}
590+
Node memberType = typeNode.getType() == Token.ELLIPSIS
591+
? typeNode.getFirstChild().cloneNode()
592+
: typeNode.cloneNode();
593+
arrayType.addChildToFront(
594+
new Node(Token.BLOCK, memberType).copyInformationFrom(typeNode));
595+
JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
596+
builder.recordType(new JSTypeExpression(new Node(Token.BANG, arrayType),
597+
param.getSourceFileName()));
598+
name.setJSDocInfo(builder.build());
599+
}
600+
601+
Node newArr = IR.var(IR.name(REST_PARAMS), IR.arraylit());
549602
block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot);
603+
Node init = IR.var(IR.name(REST_INDEX), IR.number(i));
604+
Node cond = IR.lt(IR.name(REST_INDEX),
605+
IR.getprop(IR.name("arguments"), IR.string("length")));
606+
Node incr = IR.inc(IR.name(REST_INDEX), false);
607+
Node body = IR.block(IR.exprResult(IR.assign(
608+
IR.getelem(IR.name(REST_PARAMS), IR.sub(IR.name(REST_INDEX), IR.number(i))),
609+
IR.getelem(IR.name("arguments"), IR.name(REST_INDEX)))));
610+
block.addChildAfter(IR.forNode(init, cond, incr, body)
611+
.useSourceInfoIfMissingFromForTree(param), newArr);
612+
block.addChildToBack(newBlock);
550613
compiler.reportCodeChange();
551614
} else if (param.isDestructuringPattern()) {
552615
String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);

test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java

+82-12
Original file line numberDiff line numberDiff line change
@@ -1319,24 +1319,94 @@ public void testDefaultUndefinedParameters() {
13191319
}
13201320

13211321
public void testRestParameter() {
1322-
test(
1323-
"function f(...zero) {}",
1324-
"function f(zero) { zero = [].slice.call(arguments, 0); }");
1325-
test(
1326-
"function f(zero, ...one) {}",
1327-
"function f(zero, one) { one = [].slice.call(arguments, 1); }");
1328-
test(
1329-
"function f(zero, one, ...two) {}",
1330-
"function f(zero, one, two) { two = [].slice.call(arguments, 2); }");
1322+
test("function f(...zero) { return zero; }",
1323+
LINE_JOINER.join(
1324+
"function f(zero) {",
1325+
" var $jscomp$restParams = [];",
1326+
" for (var $jscomp$restIndex = 0; $jscomp$restIndex < arguments.length;",
1327+
" ++$jscomp$restIndex) {",
1328+
" $jscomp$restParams[$jscomp$restIndex - 0] = arguments[$jscomp$restIndex];",
1329+
" }",
1330+
" {",
1331+
" var zero$0 = $jscomp$restParams;",
1332+
" return zero$0;",
1333+
" }",
1334+
"}"));
1335+
1336+
test("function f(zero, ...one) {}",
1337+
LINE_JOINER.join(
1338+
"function f(zero, one) {",
1339+
" var $jscomp$restParams = [];",
1340+
" for (var $jscomp$restIndex = 1; $jscomp$restIndex < arguments.length;",
1341+
" ++$jscomp$restIndex) {",
1342+
" $jscomp$restParams[$jscomp$restIndex - 1] = arguments[$jscomp$restIndex];",
1343+
" }",
1344+
" {",
1345+
" var one$0 = $jscomp$restParams;",
1346+
" }",
1347+
"}"));
1348+
1349+
test("function f(zero, one, ...two) {}",
1350+
LINE_JOINER.join(
1351+
"function f(zero, one, two) {",
1352+
" var $jscomp$restParams = [];",
1353+
" for (var $jscomp$restIndex = 2; $jscomp$restIndex < arguments.length;",
1354+
" ++$jscomp$restIndex) {",
1355+
" $jscomp$restParams[$jscomp$restIndex - 2] = arguments[$jscomp$restIndex];",
1356+
" }",
1357+
" {",
1358+
" var two$0 = $jscomp$restParams;",
1359+
" }",
1360+
"}"));
1361+
1362+
// Make sure we get type checking on the rest parameters
1363+
test("/** @param {...number} zero */ function f(...zero) {}",
1364+
LINE_JOINER.join(
1365+
"/** @param {...number} zero */ function f(zero) {",
1366+
" var $jscomp$restParams = [];",
1367+
" for (var $jscomp$restIndex = 0; $jscomp$restIndex < arguments.length;",
1368+
" ++$jscomp$restIndex) {",
1369+
" $jscomp$restParams[$jscomp$restIndex - 0] = arguments[$jscomp$restIndex];",
1370+
" }",
1371+
" {",
1372+
" var /** !Array<number> */ zero$0 = $jscomp$restParams;",
1373+
" }",
1374+
"}"));
1375+
1376+
// Inline type
1377+
test("function f(/** ...number */ ...zero) {}",
1378+
LINE_JOINER.join(
1379+
"function f(/** ...number */ zero) {",
1380+
" var $jscomp$restParams = [];",
1381+
" for (var $jscomp$restIndex = 0; $jscomp$restIndex < arguments.length;",
1382+
" ++$jscomp$restIndex) {",
1383+
" $jscomp$restParams[$jscomp$restIndex - 0] = arguments[$jscomp$restIndex];",
1384+
" }",
1385+
" {",
1386+
" var /** !Array<number> */ zero$0 = $jscomp$restParams;",
1387+
" }",
1388+
"}"));
1389+
1390+
// Warn on /** number */
1391+
testWarning("function f(/** number */ ...zero) {}",
1392+
Es6ToEs3Converter.BAD_REST_PARAMETER_ANNOTATION);
1393+
testWarning("/** @param {number} zero */ function f(...zero) {}",
1394+
Es6ToEs3Converter.BAD_REST_PARAMETER_ANNOTATION);
13311395
}
13321396

13331397
public void testDefaultAndRestParameters() {
1334-
test(
1335-
"function f(zero, one = 1, ...two) {}",
1398+
test("function f(zero, one = 1, ...two) {}",
13361399
LINE_JOINER.join(
13371400
"function f(zero, one, two) {",
13381401
" one = (one === undefined) ? 1 : one;",
1339-
" two = [].slice.call(arguments, 2);",
1402+
" var $jscomp$restParams = [];",
1403+
" for (var $jscomp$restIndex = 2; $jscomp$restIndex < arguments.length;",
1404+
" ++$jscomp$restIndex) {",
1405+
" $jscomp$restParams[$jscomp$restIndex - 2] = arguments[$jscomp$restIndex];",
1406+
" }",
1407+
" {",
1408+
" var two$0 = $jscomp$restParams;",
1409+
" }",
13401410
"}"));
13411411
}
13421412

test/com/google/javascript/jscomp/NewTypeInferenceES6TypedTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,23 @@ public void testOptionalParameter() {
133133
NewTypeInference.INVALID_ARGUMENT_TYPE);
134134
}
135135

136+
public void testRestParameter() {
137+
typeCheck(LINE_JOINER.join(
138+
"function foo(...p1: number[]) {}",
139+
"foo(); foo(3); foo(3, 4);"));
140+
141+
typeCheck(LINE_JOINER.join(
142+
"function foo(...p1: number[]) {}",
143+
"foo('3')"),
144+
NewTypeInference.INVALID_ARGUMENT_TYPE);
145+
146+
typeCheck("function foo(...p1: number[]) { var s:string = p1[0]; }",
147+
NewTypeInference.MISTYPED_ASSIGN_RHS);
148+
149+
typeCheck("function foo(...p1: number[]) { p1 = ['3']; }",
150+
NewTypeInference.MISTYPED_ASSIGN_RHS);
151+
}
152+
136153
public void testClass() {
137154
typeCheck(LINE_JOINER.join(
138155
"class Foo {",

0 commit comments

Comments
 (0)