| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index bdd9bf2209b5a47371d2cbbaed32cc32eff86bf1..e365f91060c3acf128389091de8651711455e9ed 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -430,7 +430,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
|
|
| var classExpr = new JS.ClassExpression(new JS.Identifier(type.name),
|
| _classHeritage(classElem), _emitClassMethods(node, ctors, fields),
|
| - typeParams: _emitTypeParams(classElem).toList(),
|
| + typeParams: _emitTypeFormals(classElem.typeParameters),
|
| fields:
|
| _emitFieldDeclarations(classElem, fields, staticFields).toList());
|
|
|
| @@ -473,11 +473,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| return result;
|
| }
|
|
|
| - Iterable<JS.Identifier> _emitTypeParams(TypeParameterizedElement e) sync* {
|
| - if (!options.closure) return;
|
| - for (var typeParam in e.typeParameters) {
|
| - yield new JS.Identifier(typeParam.name);
|
| - }
|
| + List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) {
|
| + if (!options.closure) return null;
|
| + return typeFormals
|
| + .map((t) => new JS.Identifier(t.name))
|
| + .toList(growable: false);
|
| }
|
|
|
| /// Emit field declarations for TypeScript & Closure's ES6_TYPED
|
| @@ -1057,7 +1057,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| // TODO(jmesserly): we'll need something different once we have
|
| // rest/spread support, but this should work for now.
|
| var params =
|
| - _emitFormalParameterList(node.parameters, allowDestructuring: false);
|
| + visitFormalParameterList(node.parameters, destructure: false);
|
|
|
| var fun = new JS.Fun(
|
| params,
|
| @@ -1072,7 +1072,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| // available for use by top-level constant initializers.
|
| ClassDeclaration cls = node.parent;
|
| if (node.constKeyword != null) _loader.startTopLevel(cls.element);
|
| - var params = _emitFormalParameterList(node.parameters);
|
| + var params = visitFormalParameterList(node.parameters);
|
| if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
|
|
|
| // Factory constructors are essentially static methods.
|
| @@ -1378,13 +1378,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| }
|
| }
|
|
|
| - JS.Fun _emitNativeFunctionBody(List<JS.Parameter> params,
|
| - List<JS.Expression> paramRefs, MethodDeclaration node) {
|
| + JS.Fun _emitNativeFunctionBody(MethodDeclaration node) {
|
| if (node.isStatic) {
|
| // TODO(vsm): Do we need to handle this case?
|
| return null;
|
| }
|
|
|
| + var params = visitFormalParameterList(node.parameters, destructure: false);
|
| String name = node.name.name;
|
| var annotation = findAnnotation(node.element, isJsName);
|
| if (annotation != null) {
|
| @@ -1395,10 +1395,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| return new JS.Fun(params, js.statement('{ return this.#; }', [name]));
|
| } else if (node.isSetter) {
|
| return new JS.Fun(
|
| - params, js.statement('{ this.# = #; }', [name, paramRefs.last]));
|
| + params, js.statement('{ this.# = #; }', [name, params.last]));
|
| } else {
|
| return new JS.Fun(
|
| - params, js.statement('{ return this.#(#); }', [name, paramRefs]));
|
| + params, js.statement('{ return this.#(#); }', [name, params]));
|
| }
|
| }
|
|
|
| @@ -1407,40 +1407,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| return null;
|
| }
|
|
|
| - 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, paramRefs, node);
|
| + fn = _emitNativeFunctionBody(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, paramRefs, node.body, typeParams, returnType);
|
| - }
|
| -
|
| - if (node.operatorKeyword != null &&
|
| - node.name.name == '[]=' &&
|
| - 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);
|
| - var body = fn.body;
|
| - if (JS.Return.foundIn(fn)) {
|
| - // If a return is inside body, transform `(params) { body }` to
|
| - // `(params) { (() => { body })(); return value; }`.
|
| - // TODO(jmesserly): we could instead generate the return differently,
|
| - // and avoid the immediately invoked function.
|
| - body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement();
|
| - }
|
| - // Rewrite the function to include the return.
|
| - fn = new JS.Fun(fn.params, new JS.Block([body, returnValue]),
|
| - typeParams: fn.typeParams,
|
| - returnType: fn.returnType)..sourceInformation = fn.sourceInformation;
|
| + fn = _emitFunctionBody(node.element, node.parameters, node.body);
|
| +
|
| + if (node.operatorKeyword != null &&
|
| + node.name.name == '[]=' &&
|
| + fn.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.
|
| + fn = _alwaysReturnLastParameter(fn);
|
| + }
|
| }
|
|
|
| return annotate(
|
| @@ -1452,6 +1433,26 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| node.element);
|
| }
|
|
|
| + /// Transform the function so the last parameter is always returned.
|
| + ///
|
| + /// This is useful for indexed set methods, which otherwise would not have
|
| + /// the right return value in JS.
|
| + JS.Fun _alwaysReturnLastParameter(JS.Fun fn) {
|
| + var body = fn.body;
|
| + if (JS.Return.foundIn(fn)) {
|
| + // If a return is inside body, transform `(params) { body }` to
|
| + // `(params) { (() => { body })(); return value; }`.
|
| + // TODO(jmesserly): we could instead generate the return differently,
|
| + // and avoid the immediately invoked function.
|
| + body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement();
|
| + }
|
| + // Rewrite the function to include the return.
|
| + return new JS.Fun(
|
| + fn.params, new JS.Block([body, new JS.Return(fn.params.last)]),
|
| + typeParams: fn.typeParams,
|
| + returnType: fn.returnType)..sourceInformation = fn.sourceInformation;
|
| + }
|
| +
|
| @override
|
| JS.Statement visitFunctionDeclaration(FunctionDeclaration node) {
|
| assert(node.parent is CompilationUnit);
|
| @@ -1580,80 +1581,76 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
|
|
| @override
|
| JS.Expression visitFunctionExpression(FunctionExpression node) {
|
| - var params = _visit(node.parameters) as List<JS.Parameter>;
|
| - if (params == null) params = <JS.Parameter>[];
|
| + var params = visitFormalParameterList(node.parameters);
|
|
|
| + var body = node.body;
|
| var parent = node.parent;
|
| var inStmt = parent.parent is FunctionDeclarationStatement;
|
| - var typeParams = _emitTypeParams(node.element).toList();
|
| - var returnType = emitTypeRef(node.element.returnType);
|
| if (parent is FunctionDeclaration) {
|
| - var paramRefs = _emitParameterReferences(node.parameters);
|
| - return _emitFunctionBody(
|
| - params, paramRefs, node.body, typeParams, returnType);
|
| + return _emitFunctionBody(node.element, node.parameters, body);
|
| + }
|
| +
|
| + JS.Node jsBody;
|
| + if (body.isGenerator || body.isAsynchronous) {
|
| + jsBody = _emitGeneratorFunctionBody(node.element, node.parameters, body);
|
| + } else if (body is ExpressionFunctionBody) {
|
| + jsBody = _visit(body.expression);
|
| + } else {
|
| + jsBody = _visit(body);
|
| + }
|
| +
|
| + var type = node.element.type;
|
| + var typeFormals = _emitTypeFormals(type.typeFormals);
|
| + var returnType = emitTypeRef(type.returnType);
|
| +
|
| + JS.FunctionExpression fn;
|
| + if (node.parameters.parameters
|
| + .every((p) => p.kind != ParameterKind.NAMED)) {
|
| + fn = new JS.ArrowFun(params, jsBody,
|
| + typeParams: typeFormals, returnType: returnType);
|
| } else {
|
| // Chrome Canary does not accept default values with destructuring in
|
| // arrow functions yet (e.g. `({a} = {}) => 1`) but happily accepts them
|
| // with regular functions (e.g. `function({a} = {}) { return 1 }`).
|
| // Note that Firefox accepts both syntaxes just fine.
|
| // TODO(ochafik): Simplify this code when Chrome Canary catches up.
|
| - var canUseArrowFun = !node.parameters.parameters.any(_isNamedParam);
|
| -
|
| - JS.Node jsBody;
|
| - var body = node.body;
|
| - if (body.isGenerator || body.isAsynchronous) {
|
| - var paramRefs = _emitParameterReferences(node.parameters);
|
| - jsBody =
|
| - _emitGeneratorFunctionBody(params, paramRefs, body, returnType);
|
| - } else if (body is ExpressionFunctionBody) {
|
| - jsBody = _visit(body.expression);
|
| - } else {
|
| - jsBody = _visit(body);
|
| - }
|
| - if (jsBody is JS.Expression && !canUseArrowFun) {
|
| +
|
| + if (jsBody is JS.Expression) {
|
| jsBody = js.statement("{ return #; }", [jsBody]);
|
| }
|
| - var clos = canUseArrowFun
|
| - ? new JS.ArrowFun(params, jsBody,
|
| - typeParams: typeParams, returnType: returnType)
|
| - : new JS.Fun(params, jsBody,
|
| - typeParams: typeParams, returnType: returnType);
|
| - if (!inStmt) {
|
| - var type = getStaticType(node);
|
| - return _emitFunctionTagged(clos, type,
|
| - topLevel: _executesAtTopLevel(node));
|
| - }
|
| - return clos;
|
| + fn = new JS.Fun(params, jsBody,
|
| + typeParams: typeFormals, returnType: returnType);
|
| + }
|
| +
|
| + if (!inStmt) {
|
| + var type = getStaticType(node);
|
| + return _emitFunctionTagged(fn, type, topLevel: _executesAtTopLevel(node));
|
| }
|
| + return fn;
|
| }
|
|
|
| - JS.Fun _emitFunctionBody(
|
| - List<JS.Parameter> params,
|
| - List<JS.Expression> paramRefs,
|
| - FunctionBody body,
|
| - List<JS.Identifier> typeParams,
|
| - JS.TypeRef returnType) {
|
| + JS.Fun _emitFunctionBody(ExecutableElement element,
|
| + FormalParameterList parameters, FunctionBody body) {
|
| + var returnType = emitTypeRef(element.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.
|
| + if (element.isAsynchronous || element.isGenerator) {
|
| return new JS.Fun(
|
| - params,
|
| - js.statement('{ return #; }', [
|
| - _emitGeneratorFunctionBody(params, paramRefs, body, returnType)
|
| - ]),
|
| + visitFormalParameterList(parameters, destructure: false),
|
| + js.statement('{ return #; }',
|
| + [_emitGeneratorFunctionBody(element, parameters, body)]),
|
| returnType: returnType);
|
| }
|
| // normal function (sync)
|
| - return new JS.Fun(params, _visit(body),
|
| - typeParams: typeParams, returnType: returnType);
|
| + return new JS.Fun(visitFormalParameterList(parameters), _visit(body),
|
| + typeParams: _emitTypeFormals(element.typeParameters),
|
| + returnType: returnType);
|
| }
|
|
|
| - JS.Expression _emitGeneratorFunctionBody(List<JS.Parameter> params,
|
| - List<JS.Expression> paramRefs, FunctionBody body, JS.TypeRef returnType) {
|
| - var kind = body.isSynchronous ? 'sync' : 'async';
|
| - if (body.isGenerator) kind += 'Star';
|
| + JS.Expression _emitGeneratorFunctionBody(ExecutableElement element,
|
| + FormalParameterList parameters, FunctionBody body) {
|
| + var kind = element.isSynchronous ? 'sync' : 'async';
|
| + if (element.isGenerator) kind += 'Star';
|
|
|
| // Transforms `sync*` `async` and `async*` function bodies
|
| // using ES6 generators.
|
| @@ -1687,25 +1684,28 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| // `await` is generated as `yield`.
|
| // runtime/_generators.js has an example of what the code is generated as.
|
| var savedController = _asyncStarController;
|
| - List jsParams;
|
| + List jsParams = visitFormalParameterList(parameters);
|
| if (kind == 'asyncStar') {
|
| _asyncStarController = new JS.TemporaryId('stream');
|
| - jsParams = [_asyncStarController]..addAll(params);
|
| + jsParams.insert(0, _asyncStarController);
|
| } else {
|
| _asyncStarController = null;
|
| - jsParams = params;
|
| }
|
| - JS.Expression gen = new JS.Fun(jsParams, _visit(body),
|
| - isGenerator: true, returnType: returnType);
|
| + // Visit the body with our async* controller set.
|
| + var jsBody = _visit(body);
|
| + _asyncStarController = savedController;
|
| +
|
| + DartType returnType = _getExpectedReturnType(element);
|
| + JS.Expression gen = new JS.Fun(jsParams, jsBody,
|
| + isGenerator: true, returnType: emitTypeRef(returnType));
|
| if (JS.This.foundIn(gen)) {
|
| gen = js.call('#.bind(this)', gen);
|
| }
|
| - _asyncStarController = savedController;
|
|
|
| - var T = _emitTypeName(_getExpectedReturnType(body));
|
| + var T = _emitTypeName(returnType);
|
| return js.call('dart.#(#)', [
|
| kind,
|
| - [gen, T]..addAll(paramRefs)
|
| + [gen, T]..addAll(visitFormalParameterList(parameters, destructure: false))
|
| ]);
|
| }
|
|
|
| @@ -2210,29 +2210,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| _propertyName(node.name.label.name), _visit(node.expression));
|
| }
|
|
|
| - bool _isNamedParam(FormalParameter param) =>
|
| - param.kind == ParameterKind.NAMED;
|
| -
|
| @override
|
| - List<JS.Parameter> visitFormalParameterList(FormalParameterList node) =>
|
| - _emitFormalParameterList(node);
|
| + List<JS.Parameter> visitFormalParameterList(FormalParameterList node,
|
| + {bool destructure: true}) {
|
| + if (node == null) return [];
|
|
|
| - // 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();
|
| + destructure = destructure && options.destructureNamedParams;
|
|
|
| - List<JS.Parameter> _emitFormalParameterList(FormalParameterList node,
|
| - {bool allowDestructuring: true}) {
|
| var result = <JS.Parameter>[];
|
| -
|
| var namedVars = <JS.DestructuredVariable>[];
|
| - var destructure = allowDestructuring && options.destructureNamedParams;
|
| var hasNamedArgsConflictingWithObjectProperties = false;
|
| var needsOpts = false;
|
|
|
| @@ -3683,23 +3669,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
|
|
| // TODO(leafp): Various analyzer pieces computed similar things.
|
| // Share this logic somewhere?
|
| - DartType _getExpectedReturnType(FunctionBody body) {
|
| - FunctionType functionType;
|
| - var parent = body.parent;
|
| - if (parent is Declaration) {
|
| - functionType = (parent.element as dynamic)?.type;
|
| - } else {
|
| - assert(parent is FunctionExpression);
|
| - functionType = parent.staticType;
|
| - }
|
| + DartType _getExpectedReturnType(ExecutableElement element) {
|
| + FunctionType functionType = element.type;
|
| if (functionType == null) {
|
| return DynamicTypeImpl.instance;
|
| }
|
| var type = functionType.returnType;
|
|
|
| InterfaceType expectedType = null;
|
| - if (body.isAsynchronous) {
|
| - if (body.isGenerator) {
|
| + if (element.isAsynchronous) {
|
| + if (element.isGenerator) {
|
| // Stream<T> -> T
|
| expectedType = _types.streamType;
|
| } else {
|
| @@ -3708,7 +3687,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor
|
| expectedType = _types.futureType;
|
| }
|
| } else {
|
| - if (body.isGenerator) {
|
| + if (element.isGenerator) {
|
| // Iterable<T> -> T
|
| expectedType = _types.iterableType;
|
| } else {
|
|
|