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);