Split Parameter and TypeParameter out of Identifier

(and drop RestParameter)
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index 0058081..82fc40f 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -466,10 +466,12 @@
     return result;
   }
 
-  Iterable<JS.Identifier> _emitTypeParams(TypeParameterizedElement e) sync* {
+  Iterable<JS.TypeParameter> _emitTypeParams(TypeParameterizedElement e) sync* {
     if (!options.closure) return;
     for (var typeParam in e.typeParameters) {
-      yield new JS.Identifier(typeParam.name);
+      yield new JS.TypeParameter(
+          new JS.Identifier(typeParam.name),
+          bound: emitTypeRef(typeParam.bound));
     }
   }
 
@@ -483,11 +485,10 @@
 
     makeInitialization(VariableDeclaration decl) =>
         new JS.VariableInitialization(
-            new JS.Identifier(
-                // TODO(ochafik): use a refactored _emitMemberName instead.
-                decl.name.name,
-                type: emitTypeRef(decl.element.type)),
-            null);
+            // TODO(ochafik): use a refactored _emitMemberName instead.
+            new JS.Identifier(decl.name.name),
+            null,
+            type: emitTypeRef(decl.element.type));
 
     for (var field in fields) {
       yield new JS.VariableDeclarationList(
@@ -1269,7 +1270,7 @@
 
     var body = <JS.Statement>[];
     for (var param in parameters.parameters) {
-      var jsParam = _emitSimpleIdentifier(param.identifier, allowType: false);
+      var jsParam = _visit(param.identifier);
 
       if (param.kind == ParameterKind.NAMED) {
         if (!options.destructureNamedParams) {
@@ -1336,7 +1337,8 @@
         params.isNotEmpty) {
       // []= methods need to return the value. We could also address this at
       // call sites, but it's cleaner to instead transform the operator method.
-      var returnValue = new JS.Return(params.last);
+      // TODO(ochafik): How do we ensure this is an expression and not a destructing pattern?
+      var returnValue = new JS.Return(params.last.binding as JS.Expression);
       var body = fn.body;
       if (JS.Return.foundIn(fn)) {
         // If a return is inside body, transform `(params) { body }` to
@@ -1543,7 +1545,7 @@
   }
 
   JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body,
-      List<JS.Identifier> typeParams, JS.TypeRef returnType) {
+      List<JS.TypeParameter> typeParams, JS.TypeRef returnType) {
     // sync*, async, async*
     if (body.isAsynchronous || body.isGenerator) {
       return new JS.Fun(
@@ -1641,14 +1643,10 @@
     ]);
   }
 
-  @override
-  JS.Expression visitSimpleIdentifier(SimpleIdentifier node) =>
-      _emitSimpleIdentifier(node);
-
   /// Writes a simple identifier. This can handle implicit `this` as well as
   /// going through the qualified library name if necessary.
-  JS.Expression _emitSimpleIdentifier(SimpleIdentifier node,
-      {bool allowType: false}) {
+  @override
+  JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
     var accessor = node.staticElement;
     if (accessor == null) {
       return js.commentExpression(
@@ -1717,10 +1715,7 @@
       }
     }
 
-    return annotate(
-        new JS.Identifier(name,
-            type: allowType ? emitTypeRef(node.bestType) : null),
-        node);
+    return annotate(new JS.Identifier(name), node);
   }
 
   JS.TemporaryId _getTemp(Element key, String name) =>
@@ -2095,13 +2090,13 @@
     var args = <JS.Expression>[];
     var named = <JS.Property>[];
     for (var arg in node.arguments) {
+      var jsArg = _visit(arg);
       if (arg is NamedExpression) {
-        named.add(_visit(arg));
+        named.add(jsArg);
       } else if (arg is MethodInvocation && isJsSpreadInvocation(arg)) {
-        args.add(
-            new JS.RestParameter(_visit(arg.argumentList.arguments.single)));
+        args.add(new JS.Spread(jsArg));
       } else {
-        args.add(_visit(arg));
+        args.add(jsArg);
       }
     }
     if (named.isNotEmpty) {
@@ -2156,17 +2151,18 @@
           needsOpts = true;
         }
       } else {
-        var jsParam = _visit(param);
-        result.add(
-            param is DefaultFormalParameter && options.destructureNamedParams
-                ? new JS.DestructuredVariable(
-                    name: jsParam, defaultValue: _defaultParamValue(param))
-                : jsParam);
+        var name = new JS.Identifier(param.element.name);
+        var type = emitTypeRef(param.element.type);
+        result.add(new JS.Parameter(
+            name,
+            type: type,
+            defaultValue: param is DefaultFormalParameter && options.destructureNamedParams
+                ? _defaultParamValue(param) : null));
       }
     }
 
     if (needsOpts) {
-      result.add(_namedArgTemp);
+      result.add(new JS.Parameter(_namedArgTemp));
     } else if (namedVars.isNotEmpty) {
       // Note: `var {valueOf} = {}` extracts `Object.prototype.valueOf`, so
       // in case there are conflicting names we create an object without
@@ -2174,8 +2170,8 @@
       var defaultOpts = hasNamedArgsConflictingWithObjectProperties
           ? js.call('Object.create(null)')
           : js.call('{}');
-      result.add(new JS.DestructuredVariable(
-          structure: new JS.ObjectBindingPattern(namedVars),
+      result.add(new JS.Parameter(
+          new JS.ObjectBindingPattern(namedVars),
           type: emitNamedParamsArgType(node.parameterElements),
           defaultValue: defaultOpts));
     }
@@ -2308,9 +2304,9 @@
       return _emitTopLevelField(node);
     }
 
-    var name =
-        new JS.Identifier(node.name.name, type: emitTypeRef(node.element.type));
-    return new JS.VariableInitialization(name, _visitInitializer(node));
+    var name = new JS.Identifier(node.name.name);
+    return new JS.VariableInitialization(name, _visitInitializer(node),
+        type: emitTypeRef(node.element.type));
   }
 
   bool _isFinalJSDecl(AstNode field) =>
@@ -2403,9 +2399,9 @@
         annotate(
             new JS.VariableDeclarationList(declKeyword, [
               new JS.VariableInitialization(
-                  new JS.Identifier(fieldName,
-                      type: emitTypeRef(field.element.type)),
-                  jsInit)
+                  new JS.Identifier(fieldName),
+                  jsInit,
+                  type: emitTypeRef(field.element.type))
             ]),
             field,
             field.element)
@@ -2839,12 +2835,13 @@
   @override
   visitFormalParameter(FormalParameter node) => _emitFormalParameter(node);
 
-  _emitFormalParameter(FormalParameter node, {bool allowType: true}) {
-    var id = _emitSimpleIdentifier(node.identifier, allowType: allowType);
-
-    var isRestArg = findAnnotation(node.element, isJsRestAnnotation) != null;
-    return isRestArg ? new JS.RestParameter(id) : id;
-  }
+  _emitFormalParameter(FormalParameter node,
+      {bool allowType: true, bool isRest}) =>
+          new JS.Parameter(
+              _visit(node.identifier),
+              type: allowType ? emitTypeRef(node.element.type) : null,
+              isRest: isRest ??
+                  findAnnotation(node.element, isJsRestAnnotation) != null);
 
   @override
   JS.This visitThisExpression(ThisExpression node) => new JS.This();
diff --git a/lib/src/js/builder.dart b/lib/src/js/builder.dart
index 5037a3f..73caa96 100644
--- a/lib/src/js/builder.dart
+++ b/lib/src/js/builder.dart
@@ -840,7 +840,7 @@
       return parseArrowFunctionBody(<Parameter>[]);
     }
     if (acceptCategory(ELLIPSIS)) {
-      var params = <Parameter>[new RestParameter(parseParameter())];
+      var params = <Parameter>[parseParameter(isRest: true)];
       expectCategory(RPAREN);
       expectCategory(ARROW);
       return parseArrowFunctionBody(params);
@@ -850,7 +850,7 @@
       if (acceptCategory(ELLIPSIS)) {
         var params = <Parameter>[];
         _expressionToParameterList(expression, params);
-        params.add(new RestParameter(parseParameter()));
+        params.add(parseParameter(isRest: true));
         expectCategory(RPAREN);
         expectCategory(ARROW);
         return parseArrowFunctionBody(params);
@@ -873,7 +873,7 @@
    */
   void _expressionToParameterList(Expression node, List<Parameter> params) {
     if (node is Identifier) {
-      params.add(node);
+      params.add(new Parameter(node));
     } else if (node is Binary && node.op == ',') {
       // TODO(jmesserly): this will allow illegal parens, such as
       // `((a, b), (c, d))`. Fixing it on the left side needs an explicit
@@ -915,7 +915,7 @@
     if (!acceptCategory(RPAREN)) {
       for (;;) {
         if (acceptCategory(ELLIPSIS)) {
-          params.add(new RestParameter(parseParameter()));
+          params.add(parseParameter(isRest: true));
           expectCategory(RPAREN);
           break;
         }
@@ -946,7 +946,7 @@
   }
 
   /** Parse parameter name or interpolated parameter. */
-  Identifier parseParameter() {
+  Parameter parseParameter({bool isRest: false}) {
     if (acceptCategory(HASH)) {
       var nameOrPosition = parseHash();
       var parameter = new InterpolatedParameter(nameOrPosition);
@@ -956,7 +956,7 @@
       // TODO(jmesserly): validate this is not a keyword
       String argumentName = lastToken;
       expectCategory(ALPHA);
-      return new Identifier(argumentName);
+      return new Parameter(new Identifier(argumentName), isRest: isRest);
     }
   }
 
diff --git a/lib/src/js/nodes.dart b/lib/src/js/nodes.dart
index cd0e807..52181fb 100644
--- a/lib/src/js/nodes.dart
+++ b/lib/src/js/nodes.dart
@@ -47,7 +47,6 @@
   T visitThis(This node);
   T visitSuper(Super node);
   T visitAccess(PropertyAccess node);
-  T visitRestParameter(RestParameter node);
 
   T visitNamedFunction(NamedFunction node);
   T visitFun(Fun node);
@@ -89,6 +88,9 @@
   T visitInterpolatedMethod(InterpolatedMethod node);
   T visitInterpolatedIdentifier(InterpolatedIdentifier node);
 
+  T visitParameter(Parameter node);
+  T visitTypeParameter(TypeParameter node);
+
   T visitArrayBindingPattern(ArrayBindingPattern node);
   T visitObjectBindingPattern(ObjectBindingPattern node);
   T visitDestructuredVariable(DestructuredVariable node);
@@ -171,8 +173,6 @@
   T visitThis(This node) => visitExpression(node);
   T visitSuper(Super node) => visitExpression(node);
 
-  T visitRestParameter(RestParameter node) => visitNode(node);
-
   T visitNamedFunction(NamedFunction node) => visitExpression(node);
   T visitFunctionExpression(FunctionExpression node) => visitExpression(node);
   T visitFun(Fun node) => visitFunctionExpression(node);
@@ -228,6 +228,9 @@
   T visitAwait(Await node) => visitExpression(node);
   T visitDartYield(DartYield node) => visitStatement(node);
 
+  T visitParameter(Parameter node) => visitNode(node);
+  T visitTypeParameter(TypeParameter node) => visitNode(node);
+
   T visitBindingPattern(BindingPattern node) => visitNode(node);
   T visitArrayBindingPattern(ArrayBindingPattern node)
       => visitBindingPattern(node);
@@ -688,7 +691,7 @@
   DartYield _clone() => new DartYield(expression, hasStar);
 }
 
-abstract class Expression extends Node {
+abstract class Expression extends Node implements LValue {
   Expression();
 
   factory Expression.binary(List<Expression> exprs, String op) {
@@ -761,8 +764,10 @@
   int get precedenceLevel => EXPRESSION;
 }
 
+abstract class LValue implements Node {}
+
 class Assignment extends Expression {
-  final Expression leftHandSide;
+  final LValue leftHandSide;
   final String op;         // Null, if the assignment is not compound.
   final Expression value;  // May be null, for [VariableInitialization]s.
 
@@ -786,8 +791,9 @@
 }
 
 class VariableInitialization extends Assignment {
+  final TypeRef type;
   /** [value] may be null. */
-  VariableInitialization(VariableBinding declaration, Expression value)
+  VariableInitialization(VariableBinding declaration, Expression value, {this.type})
       : super(declaration, value);
 
   VariableBinding get declaration => leftHandSide;
@@ -798,16 +804,15 @@
       new VariableInitialization(declaration, value);
 }
 
-abstract class VariableBinding extends Expression {
-}
+abstract class VariableBinding implements LValue {}
 
-class DestructuredVariable extends Expression implements Parameter {
+class DestructuredVariable extends Node implements VariableBinding {
   /// [LiteralString] or [Identifier].
   final Expression name;
   final BindingPattern structure;
   final Expression defaultValue;
   final TypeRef type;
-  DestructuredVariable({this.name, this.structure, this.defaultValue, this.type}) {
+  DestructuredVariable({this.name, this.structure, this.type, this.defaultValue}) {
     assert(name != null || structure != null);
   }
 
@@ -815,11 +820,10 @@
   void visitChildren(NodeVisitor visitor) {
     name?.accept(visitor);
     structure?.accept(visitor);
+    type?.accept(visitor);
     defaultValue?.accept(visitor);
   }
 
-  /// Avoid parenthesis when pretty-printing.
-  @override int get precedenceLevel => PRIMARY;
   @override Node _clone() =>
       new DestructuredVariable(
           name: name, structure: structure, defaultValue: defaultValue);
@@ -1060,16 +1064,40 @@
   int get precedenceLevel => UNARY;
 }
 
-abstract class Parameter implements Expression, VariableBinding {
-  TypeRef get type;
+class Parameter extends Node implements VariableBinding {
+  final VariableBinding binding;
+  final TypeRef type;
+  final Expression defaultValue;
+  final bool isRest;
+  Parameter(this.binding, {this.type, this.defaultValue, this.isRest: false});
+
+  accept(NodeVisitor visitor) => visitor.visitParameter(this);
+  void visitChildren(NodeVisitor visitor) {
+    binding?.accept(visitor);
+    defaultValue?.accept(visitor);
+    type?.accept(visitor);
+  }
+  Parameter _clone() => new Parameter(binding, type: type, isRest: isRest);
 }
 
-class Identifier extends Expression implements Parameter, VariableBinding {
+class TypeParameter extends Node {
+  final Identifier name;
+  final TypeRef bound;
+  TypeParameter(this.name, {this.bound});
+
+  accept(NodeVisitor visitor) => visitor.visitTypeParameter(this);
+  void visitChildren(NodeVisitor visitor) {
+    name?.accept(visitor);
+    bound?.accept(visitor);
+  }
+  TypeParameter _clone() => new TypeParameter(name, bound: bound);
+}
+
+class Identifier extends Expression implements VariableBinding {
   final String name;
   final bool allowRename;
-  final TypeRef type;
 
-  Identifier(this.name, {this.allowRename: true, this.type}) {
+  Identifier(this.name, {this.allowRename: true}) {
     if (!_identifierRE.hasMatch(name)) {
       throw new ArgumentError.value(name, "name", "not a valid identifier");
     }
@@ -1083,21 +1111,6 @@
   void visitChildren(NodeVisitor visitor) {}
 }
 
-// This is an expression for convenience in the AST.
-class RestParameter extends Expression implements Parameter {
-  final Identifier parameter;
-  TypeRef get type => null;
-
-  RestParameter(this.parameter);
-
-  RestParameter _clone() => new RestParameter(parameter);
-  accept(NodeVisitor visitor) => visitor.visitRestParameter(this);
-  void visitChildren(NodeVisitor visitor) {
-    parameter.accept(visitor);
-  }
-  int get precedenceLevel => PRIMARY;
-}
-
 class This extends Expression {
   accept(NodeVisitor visitor) => visitor.visitThis(this);
   This _clone() => new This();
@@ -1155,7 +1168,7 @@
   get body; // Expression or block
   /// Type parameters passed to this generic function, if any. `null` otherwise.
   // TODO(ochafik): Support type bounds.
-  List<Identifier> get typeParams;
+  List<TypeParameter> get typeParams;
   /// Return type of this function, if any. `null` otherwise.
   TypeRef get returnType;
 }
@@ -1163,7 +1176,7 @@
 class Fun extends FunctionExpression {
   final List<Parameter> params;
   final Block body;
-  @override final List<Identifier> typeParams;
+  @override final List<TypeParameter> typeParams;
   @override final TypeRef returnType;
   /** Whether this is a JS generator (`function*`) that may contain `yield`. */
   final bool isGenerator;
@@ -1190,7 +1203,7 @@
 class ArrowFun extends FunctionExpression {
   final List<Parameter> params;
   final body; // Expression or Block
-  @override final List<Identifier> typeParams;
+  @override final List<TypeParameter> typeParams;
   @override final TypeRef returnType;
 
   ArrowFun(this.params, this.body, {this.typeParams, this.returnType});
@@ -1482,7 +1495,7 @@
   final List<Method> methods;
   /// Type parameters of this class, if any. `null` otherwise.
   // TODO(ochafik): Support type bounds.
-  final List<Identifier> typeParams;
+  final List<TypeParameter> typeParams;
   /// Field declarations of this class (TypeScript / ES6_TYPED).
   final List<VariableDeclarationList> fields;
 
@@ -1571,18 +1584,21 @@
 }
 
 class InterpolatedParameter extends Expression with InterpolatedNode
-    implements Identifier {
+    implements Parameter {
   final nameOrPosition;
+  final bool isRest;
   TypeRef get type => null;
 
-  String get name { throw "InterpolatedParameter.name must not be invoked"; }
+  Identifier get binding { throw "InterpolatedParameter.binding must not be invoked"; }
+  Expression get defaultValue { throw "InterpolatedParameter.defaultValue must not be invoked"; }
   bool get allowRename => false;
 
-  InterpolatedParameter(this.nameOrPosition);
+  InterpolatedParameter(this.nameOrPosition, {this.isRest: false});
 
   accept(NodeVisitor visitor) => visitor.visitInterpolatedParameter(this);
   void visitChildren(NodeVisitor visitor) {}
-  InterpolatedParameter _clone() => new InterpolatedParameter(nameOrPosition);
+  InterpolatedParameter _clone() =>
+      new InterpolatedParameter(nameOrPosition, isRest: isRest);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1633,7 +1649,6 @@
 class InterpolatedIdentifier extends Expression with InterpolatedNode
     implements Identifier {
   final nameOrPosition;
-  TypeRef get type => null;
 
   InterpolatedIdentifier(this.nameOrPosition);
 
diff --git a/lib/src/js/printer.dart b/lib/src/js/printer.dart
index cc34725..af3d0e2 100644
--- a/lib/src/js/printer.dart
+++ b/lib/src/js/printer.dart
@@ -878,12 +878,20 @@
 
   visitIdentifier(Identifier node) {
     out(localNamer.getName(node));
+  }
+
+  visitParameter(Parameter node) {
+    if (node.isRest) out('...');
+    visit(node.binding);
     outTypeAnnotation(node.type);
   }
 
-  visitRestParameter(RestParameter node) {
-    out('...');
-    visitIdentifier(node.parameter);
+  visitTypeParameter(TypeParameter node) {
+    visit(node.name);
+    if (node.bound != null && options.shouldEmitTypes) {
+      out(" extends ");
+      visit(node.bound);
+    }
   }
 
   bool isDigit(int charCode) {
@@ -944,10 +952,11 @@
 
   visitArrowFun(ArrowFun fun) {
     localNamer.enterScope(fun);
-    if (fun.params.length == 1 &&
+    if (fun.params.length == 1 && fun.params.single.binding is Expression &&
         (fun.params.single.type == null || !options.shouldEmitTypes)) {
-      visitNestedExpression(fun.params.single, SPREAD,
-          newInForInit: false, newAtStatementBegin: false);
+      visit(fun.params.single.binding);
+      // visitNestedExpression(fun.params.single, SPREAD,
+      //     newInForInit: false, newAtStatementBegin: false);
     } else {
       out("(");
       visitCommaSeparated(fun.params, SPREAD,
@@ -1093,7 +1102,7 @@
     lineOut();
   }
 
-  void outTypeParams(Iterable<Identifier> typeParams) {
+  void outTypeParams(Iterable<TypeParameter> typeParams) {
     if (typeParams != null && options.shouldEmitTypes && typeParams.isNotEmpty) {
       out("<");
       var first = true;
@@ -1391,7 +1400,18 @@
       nested = true;
       if (fun.params != null) {
         for (var param in fun.params) {
-          params.add(param.name);
+          collectBinding(Node node) {
+            if (node is Identifier) {
+              // TODO(ochafik): collect destructured
+              params.add(node.name);
+            } else if (node is DestructuredVariable) {
+              collectBinding(node.name);
+              collectBinding(node.structure);
+            } else if (node is BindingPattern) {
+              node.variables.forEach(collectBinding);
+            }
+          }
+          collectBinding(param.binding);
         }
       }
       fun.body.accept(this);
@@ -1647,9 +1667,9 @@
     else d.accept(this);
   }
 
-  visitRestParameter(RestParameter node) {
-    _scanVariableBinding(node.parameter);
-    super.visitRestParameter(node);
+  visitParameter(Parameter node) {
+    _scanVariableBinding(node);
+    super.visitParameter(node);
   }
 
   visitDestructuredVariable(DestructuredVariable node) {
diff --git a/lib/src/js/template.dart b/lib/src/js/template.dart
index eb55252..bd9ee43 100644
--- a/lib/src/js/template.dart
+++ b/lib/src/js/template.dart
@@ -232,7 +232,7 @@
 
       Parameter toIdentifier(item) {
         if (item is Parameter) return item;
-        if (item is String) return new Identifier(item);
+        if (item is String) return new Parameter(new Identifier(item));
         return error('Interpolated value #$nameOrPosition is not an Identifier'
             ' or List of Identifiers: $value');
       }
@@ -627,6 +627,21 @@
   Instantiator visitThis(This node) => (arguments) => new This();
   Instantiator visitSuper(Super node) => (arguments) => new Super();
 
+  Instantiator visitParameter(Parameter node) {
+    Instantiator makeBinding = visit(node.binding);
+    Instantiator makeType = visit(node.type);
+    var isRest = node.isRest;
+    return (arguments) => new Parameter(
+        makeBinding(arguments), type: makeType(arguments), isRest: isRest);
+  }
+
+  Instantiator visitTypeParameter(TypeParameter node) {
+    Instantiator makeName = visit(node.name);
+    Instantiator makeBound = visit(node.bound);
+    return (arguments) =>
+        new TypeParameter(makeName(arguments), bound: makeBound(arguments));
+  }
+
   Instantiator visitIdentifier(Identifier node) =>
       (arguments) => new Identifier(node.name);
 
@@ -637,9 +652,6 @@
       (args) => new Yield(node.value != null ? visit(node.value)(args) : null,
           star: node.star);
 
-  Instantiator visitRestParameter(RestParameter node) =>
-      (args) => new RestParameter(visit(node.parameter)(args));
-
   Instantiator visitAccess(PropertyAccess node) {
     Instantiator makeReceiver = visit(node.receiver);
     Instantiator makeSelector = visit(node.selector);