Introduce _emitParameterReferences to avoid mixing params with param references.

BUG=
[email protected]

Review URL: https://codereview.chromium.org/1701583003 .
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index b41607f..663786a 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -1329,7 +1329,8 @@
   }
 
   JS.Fun _emitNativeFunctionBody(
-      List<JS.Parameter> params, MethodDeclaration node) {
+      List<JS.Parameter> params, List<JS.Expression> paramRefs,
+      MethodDeclaration node) {
     if (node.isStatic) {
       // TODO(vsm): Do we need to handle this case?
       return null;
@@ -1345,10 +1346,10 @@
       return new JS.Fun(params, js.statement('{ return this.#; }', [name]));
     } else if (node.isSetter) {
       return new JS.Fun(
-          params, js.statement('{ this.# = #; }', [name, params.last]));
+          params, js.statement('{ this.# = #; }', [name, paramRefs.last]));
     } else {
       return new JS.Fun(
-          params, js.statement('{ return this.#(#); }', [name, params]));
+          params, js.statement('{ return this.#(#); }', [name, paramRefs]));
     }
   }
 
@@ -1359,16 +1360,18 @@
 
     var params = _visit(node.parameters) as List<JS.Parameter>;
     if (params == null) params = <JS.Parameter>[];
+    var paramRefs = _emitParameterReferences(node.parameters);
 
     JS.Fun fn;
     if (_externalOrNative(node)) {
-      fn = _emitNativeFunctionBody(params, node);
+      fn = _emitNativeFunctionBody(params, paramRefs, node);
       // TODO(vsm): Remove if / when we handle the static case above.
       if (fn == null) return null;
     } else {
       var typeParams = _emitTypeParams(node.element).toList();
       var returnType = emitTypeRef(node.element.returnType);
-      fn = _emitFunctionBody(params, node.body, typeParams, returnType);
+      fn = _emitFunctionBody(
+          params, paramRefs, node.body, typeParams, returnType);
     }
 
     if (node.operatorKeyword != null &&
@@ -1547,7 +1550,9 @@
     var typeParams = _emitTypeParams(node.element).toList();
     var returnType = emitTypeRef(node.element.returnType);
     if (parent is FunctionDeclaration) {
-      return _emitFunctionBody(params, node.body, typeParams, returnType);
+      var paramRefs = _emitParameterReferences(node.parameters);
+      return _emitFunctionBody(
+          params, paramRefs, node.body, typeParams, returnType);
     } else {
       // Chrome Canary does not accept default values with destructuring in
       // arrow functions yet (e.g. `({a} = {}) => 1`) but happily accepts them
@@ -1559,7 +1564,9 @@
       JS.Node jsBody;
       var body = node.body;
       if (body.isGenerator || body.isAsynchronous) {
-        jsBody = _emitGeneratorFunctionBody(params, body, returnType);
+        var paramRefs = _emitParameterReferences(node.parameters);
+        jsBody = _emitGeneratorFunctionBody(
+            params, paramRefs, body, returnType);
       } else if (body is ExpressionFunctionBody) {
         jsBody = _visit(body.expression);
       } else {
@@ -1582,14 +1589,19 @@
     }
   }
 
-  JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body,
+  JS.Fun _emitFunctionBody(List<JS.Parameter> params,
+      List<JS.Expression> paramRefs, FunctionBody body,
       List<JS.Identifier> typeParams, JS.TypeRef returnType) {
     // sync*, async, async*
     if (body.isAsynchronous || body.isGenerator) {
+      // TODO(ochafik): Refine params: we don't need default values in the
+      // nested function, so we'd need to generate a custom, simpler params
+      // list here.
       return new JS.Fun(
           params,
-          js.statement('{ return #; }',
-              [_emitGeneratorFunctionBody(params, body, returnType)]),
+          js.statement('{ return #; }', [
+            _emitGeneratorFunctionBody(params, paramRefs, body, returnType)
+          ]),
           returnType: returnType);
     }
     // normal function (sync)
@@ -1598,7 +1610,8 @@
   }
 
   JS.Expression _emitGeneratorFunctionBody(
-      List<JS.Parameter> params, FunctionBody body, JS.TypeRef returnType) {
+      List<JS.Parameter> params, List<JS.Expression> paramRefs,
+      FunctionBody body, JS.TypeRef returnType) {
     var kind = body.isSynchronous ? 'sync' : 'async';
     if (body.isGenerator) kind += 'Star';
 
@@ -1652,7 +1665,7 @@
     var T = _emitTypeName(_getExpectedReturnType(body));
     return js.call('dart.#(#)', [
       kind,
-      [gen, T]..addAll(params)
+      [gen, T]..addAll(paramRefs)
     ]);
   }
 
@@ -2164,6 +2177,17 @@
   List<JS.Parameter> visitFormalParameterList(FormalParameterList node) =>
       _emitFormalParameterList(node);
 
+  // TODO(ochafik): Decouple Parameter from Identifier.
+  List<JS.Expression> _emitParameterReferences(FormalParameterList node) =>
+      node == null
+      ? <JS.Expression>[]
+      : _emitFormalParameterList(node, allowDestructuring: false)
+          .map((JS.Parameter p) {
+            if (p is JS.RestParameter) return new JS.Spread(p.parameter);
+            return p as JS.Identifier;
+          })
+          .toList();
+
   List<JS.Parameter> _emitFormalParameterList(FormalParameterList node,
       {bool allowDestructuring: true}) {
     var result = <JS.Parameter>[];
@@ -2198,7 +2222,7 @@
       } else {
         var jsParam = _visit(param);
         result.add(
-            param is DefaultFormalParameter && options.destructureNamedParams
+            param is DefaultFormalParameter && destructure
                 ? new JS.DestructuredVariable(
                     name: jsParam, defaultValue: _defaultParamValue(param))
                 : jsParam);
diff --git a/test/codegen/destructuring.dart b/test/codegen/destructuring.dart
index a091243..51bf057 100644
--- a/test/codegen/destructuring.dart
+++ b/test/codegen/destructuring.dart
@@ -1,9 +1,29 @@
+import 'dart-ext:foo';
+import 'package:js/src/varargs.dart';
+
 f(int a, b, [c = 1]) {
   f(a, b, c);
 }
+external f_ext(int a, b, [c = 1]);
+f_nat(int a, b, [c = 1]) native "f_nat";
+f_sync(int a, b, [c = 1]) sync* {}
+f_async(int a, b, [c = 1]) async* {}
+
 g(int a, b, {c : 1}) {
   f(a, b, c);
 }
+external g_ext(int a, b, {c : 1});
+g_nat(int a, b, {c : 1}) native "g_nat";
+g_sync(int a, b, {c : 1}) sync* {}
+g_async(int a, b, {c : 1}) async* {}
+
+r(int a, @rest others) {
+  r(a, spread(others));
+}
+external r_ext(int a, @rest others);
+r_nat(int a, @rest others) native "r_nat";
+r_sync(int a, @rest others) sync* {}
+r_async(int a, @rest others) async* {}
 
 invalid_names1(int let, function, arguments) {
   f(let, function, arguments);
diff --git a/test/codegen/expect/destructuring.js b/test/codegen/expect/destructuring.js
index 7049614..4558340 100644
--- a/test/codegen/expect/destructuring.js
+++ b/test/codegen/expect/destructuring.js
@@ -9,10 +9,44 @@
     f(a, b, c);
   }
   dart.fn(f, dart.dynamic, [core.int, dart.dynamic], [dart.dynamic]);
+  function f_sync(a, b, c = 1) {
+    return dart.syncStar(function*(a, b, c = 1) {
+    }, dart.dynamic, a, b, c);
+  }
+  dart.fn(f_sync, dart.dynamic, [core.int, dart.dynamic], [dart.dynamic]);
+  function f_async(a, b, c = 1) {
+    return dart.asyncStar(function*(stream, a, b, c = 1) {
+    }, dart.dynamic, a, b, c);
+  }
+  dart.fn(f_async, dart.dynamic, [core.int, dart.dynamic], [dart.dynamic]);
   function g(a, b, {c = 1} = {}) {
     f(a, b, c);
   }
   dart.fn(g, dart.dynamic, [core.int, dart.dynamic], {c: dart.dynamic});
+  function g_sync(a, b, {c = 1} = {}) {
+    return dart.syncStar(function*(a, b, {c = 1} = {}) {
+    }, dart.dynamic, a, b, opts);
+  }
+  dart.fn(g_sync, dart.dynamic, [core.int, dart.dynamic], {c: dart.dynamic});
+  function g_async(a, b, {c = 1} = {}) {
+    return dart.asyncStar(function*(stream, a, b, {c = 1} = {}) {
+    }, dart.dynamic, a, b, opts);
+  }
+  dart.fn(g_async, dart.dynamic, [core.int, dart.dynamic], {c: dart.dynamic});
+  function r(a, ...others) {
+    r(a, ...others);
+  }
+  dart.fn(r, dart.dynamic, [core.int, dart.dynamic]);
+  function r_sync(a, ...others) {
+    return dart.syncStar(function*(a, ...others) {
+    }, dart.dynamic, a, ...others);
+  }
+  dart.fn(r_sync, dart.dynamic, [core.int, dart.dynamic]);
+  function r_async(a, ...others) {
+    return dart.asyncStar(function*(stream, a, ...others) {
+    }, dart.dynamic, a, ...others);
+  }
+  dart.fn(r_async, dart.dynamic, [core.int, dart.dynamic]);
   function invalid_names1(let$, func, arguments$) {
     f(let$, func, arguments$);
   }
@@ -31,7 +65,14 @@
   dart.fn(names_clashing_with_object_props, dart.dynamic, [], {constructor: core.int, valueOf: dart.dynamic, hasOwnProperty: dart.dynamic});
   // Exports:
   exports.f = f;
+  exports.f_sync = f_sync;
+  exports.f_async = f_async;
   exports.g = g;
+  exports.g_sync = g_sync;
+  exports.g_async = g_async;
+  exports.r = r;
+  exports.r_sync = r_sync;
+  exports.r_async = r_async;
   exports.invalid_names1 = invalid_names1;
   exports.invalid_names2 = invalid_names2;
   exports.invalid_names3 = invalid_names3;