Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
index 9650a36306b195dc94a98398d8b63c6e0951ea0b..005808114b65eefe3e35961473b15a75dcb16424 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/codegen/js_codegen.dart |
@@ -2,6 +2,8 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
+library js_codegen; |
+ |
import 'dart:collection' show HashSet, HashMap, SplayTreeSet; |
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
@@ -41,6 +43,8 @@ import 'module_builder.dart'; |
import 'nullability_inferrer.dart'; |
import 'side_effect_analysis.dart'; |
+part 'js_typeref_codegen.dart'; |
+ |
// Various dynamic helpers we call. |
// If renaming these, make sure to check other places like the |
// _runtime.js file and comments. |
@@ -53,7 +57,8 @@ const DSETINDEX = 'dsetindex'; |
const DCALL = 'dcall'; |
const DSEND = 'dsend'; |
-class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
+class JSCodegenVisitor extends GeneralizingAstVisitor |
+ with ClosureAnnotator, JsTypeRefCodegen { |
final AbstractCompiler compiler; |
final CodegenOptions options; |
final LibraryElement currentLibrary; |
@@ -348,12 +353,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var type = element.type; |
var name = element.name; |
- var fnType = annotateTypeDef( |
+ var fnType = annotate( |
js.statement('const # = dart.typedef(#, () => #);', [ |
name, |
js.string(name, "'"), |
_emitTypeName(type, lowerTypedef: true) |
]), |
+ node, |
node.element); |
return _finishClassDef(type, fnType); |
@@ -424,7 +430,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
} |
var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
- _classHeritage(classElem), _emitClassMethods(node, ctors, fields)); |
+ _classHeritage(classElem), _emitClassMethods(node, ctors, fields), |
+ typeParams: _emitTypeParams(classElem).toList(), |
+ fields: |
+ _emitFieldDeclarations(classElem, fields, staticFields).toList()); |
String jsPeerName; |
var jsPeer = findAnnotation(classElem, isJsPeerInterface); |
@@ -457,6 +466,39 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
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); |
+ } |
+ } |
+ |
+ /// Emit field declarations for TypeScript & Closure's ES6_TYPED |
+ /// (e.g. `class Foo { i: string; }`) |
+ Iterable<JS.VariableDeclarationList> _emitFieldDeclarations( |
+ ClassElement classElem, |
+ List<FieldDeclaration> fields, |
+ List<FieldDeclaration> staticFields) sync* { |
+ if (!options.closure) return; |
+ |
+ 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); |
+ |
+ for (var field in fields) { |
+ yield new JS.VariableDeclarationList( |
+ null, field.fields.variables.map(makeInitialization).toList()); |
+ } |
+ for (var field in staticFields) { |
+ yield new JS.VariableDeclarationList( |
+ 'static', field.fields.variables.map(makeInitialization).toList()); |
+ } |
+ } |
+ |
@override |
JS.Statement visitEnumDeclaration(EnumDeclaration node) { |
var element = node.element; |
@@ -689,8 +731,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var superVar = new JS.TemporaryId(cls.name.name + r'$super'); |
return _statement([ |
js.statement('const # = #;', [superVar, cls.heritage]), |
- new JS.ClassDeclaration( |
- new JS.ClassExpression(cls.name, superVar, cls.methods)) |
+ new JS.ClassDeclaration(new JS.ClassExpression( |
+ cls.name, superVar, cls.methods, |
+ typeParams: cls.typeParams, fields: cls.fields)) |
]); |
} |
return new JS.ClassDeclaration(cls); |
@@ -935,8 +978,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
]; |
} |
var name = _constructorName(node.element.unnamedConstructor); |
- return annotateDefaultConstructor( |
+ return annotate( |
new JS.Method(name, js.call('function() { #; }', body) as JS.Fun), |
+ node, |
node.element); |
} |
@@ -945,6 +989,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
if (_externalOrNative(node)) return null; |
var name = _constructorName(node.element); |
+ var returnType = emitTypeRef(node.element.enclosingElement.type); |
// Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
var redirect = node.redirectedConstructor; |
@@ -956,11 +1001,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var params = |
_emitFormalParameterList(node.parameters, allowDestructuring: false); |
- var fun = js.call('function(#) { return $newKeyword #(#); }', |
- [params, _visit(redirect), params]) as JS.Fun; |
+ var fun = new JS.Fun( |
+ params, |
+ js.statement( |
+ '{ return $newKeyword #(#); }', [_visit(redirect), params]), |
+ returnType: returnType); |
return annotate( |
- new JS.Method(name, fun, isStatic: true)..sourceInformation = node, |
- node.element); |
+ new JS.Method(name, fun, isStatic: true), node, node.element); |
} |
// For const constructors we need to ensure default values are |
@@ -976,10 +1023,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var init = _emitArgumentInitializers(node, constructor: true); |
if (init != null) body.add(init); |
body.add(_visit(node.body)); |
- var fun = new JS.Fun(params, new JS.Block(body)); |
+ var fun = new JS.Fun(params, new JS.Block(body), returnType: returnType); |
return annotate( |
- new JS.Method(name, fun, isStatic: true)..sourceInformation = node, |
- node.element); |
+ new JS.Method(name, fun, isStatic: true), node, node.element); |
} |
// Code generation for Object's constructor. |
@@ -1015,7 +1061,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
// this allows use of `super` for instance methods/properties. |
// It also avoids V8 restrictions on `super` in default constructors. |
return annotate( |
- new JS.Method(name, new JS.Fun(params, body))..sourceInformation = node, |
+ new JS.Method(name, new JS.Fun(params, body, returnType: returnType)), |
+ node, |
node.element); |
} |
@@ -1112,7 +1159,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var name = _constructorName(superCtor); |
var args = node != null ? _visit(node.argumentList) : []; |
- return js.statement('super.#(#);', [name, args])..sourceInformation = node; |
+ return annotate(js.statement('super.#(#);', [name, args]), node); |
} |
bool _shouldCallUnnamedSuperCtor(ClassElement e) { |
@@ -1164,7 +1211,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
for (var p in ctor.parameters.parameters) { |
var element = p.element; |
if (element is FieldFormalParameterElement) { |
- fields[element.field] = _visit(p); |
+ fields[element.field] = _emitFormalParameter(p, allowType: false); |
} |
} |
@@ -1222,7 +1269,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var body = <JS.Statement>[]; |
for (var param in parameters.parameters) { |
- var jsParam = _visit(param.identifier); |
+ var jsParam = _emitSimpleIdentifier(param.identifier, allowType: false); |
if (param.kind == ParameterKind.NAMED) { |
if (!options.destructureNamedParams) { |
@@ -1281,7 +1328,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var params = _visit(node.parameters) as List<JS.Parameter>; |
if (params == null) params = <JS.Parameter>[]; |
- JS.Fun fn = _emitFunctionBody(params, node.body); |
+ var typeParams = _emitTypeParams(node.element).toList(); |
+ var returnType = emitTypeRef(node.element.returnType); |
+ JS.Fun fn = _emitFunctionBody(params, node.body, typeParams, returnType); |
if (node.operatorKeyword != null && |
node.name.name == '[]=' && |
params.isNotEmpty) { |
@@ -1297,8 +1346,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
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])) |
- ..sourceInformation = fn.sourceInformation; |
+ fn = new JS.Fun(fn.params, new JS.Block([body, returnValue]), |
+ typeParams: fn.typeParams, |
+ returnType: fn.returnType)..sourceInformation = fn.sourceInformation; |
} |
return annotate( |
@@ -1306,6 +1356,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
isGetter: node.isGetter, |
isSetter: node.isSetter, |
isStatic: node.isStatic), |
+ node, |
node.element); |
} |
@@ -1313,7 +1364,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
/// the SDK), or `null` if there's none. This is used to control the name |
/// under which functions are compiled and exported. |
String _getJSExportName(Element e) { |
- if (!currentLibrary.source.isInSystemLibrary) { |
+ if (!e.source.isInSystemLibrary) { |
return null; |
} |
var jsName = findAnnotation(e, isJSExportNameAnnotation); |
@@ -1345,7 +1396,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
} |
var id = new JS.Identifier(name); |
- body.add(annotate(new JS.FunctionDeclaration(id, fn), node.element)); |
+ body.add(annotate(new JS.FunctionDeclaration(id, fn), node, node.element)); |
if (!_isDartRuntime) { |
body.add(_emitFunctionTagged(id, node.element.type, topLevel: true) |
.toStatement()); |
@@ -1386,7 +1437,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
if (call.target is JS.ArrowFun && call.arguments.isEmpty) { |
JS.ArrowFun innerFun = call.target; |
if (innerFun.params.isEmpty) { |
- return new JS.Fun(fn.params, innerFun.body); |
+ return new JS.Fun(fn.params, innerFun.body, |
+ typeParams: fn.typeParams, returnType: fn.returnType); |
} |
} |
} |
@@ -1399,6 +1451,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
return annotate( |
new JS.Method(_propertyName(name), _visit(node.functionExpression), |
isGetter: node.isGetter, isSetter: node.isSetter), |
+ node, |
node.element); |
} |
@@ -1451,8 +1504,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
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) { |
- return _emitFunctionBody(params, node.body); |
+ return _emitFunctionBody(params, 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 |
@@ -1461,18 +1516,23 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
// TODO(ochafik): Simplify this code when Chrome Canary catches up. |
var canUseArrowFun = !node.parameters.parameters.any(_isNamedParam); |
- String code = canUseArrowFun ? '(#) => #' : 'function(#) { return # }'; |
JS.Node jsBody; |
var body = node.body; |
if (body.isGenerator || body.isAsynchronous) { |
- jsBody = _emitGeneratorFunctionBody(params, body); |
+ jsBody = _emitGeneratorFunctionBody(params, body, returnType); |
} else if (body is ExpressionFunctionBody) { |
jsBody = _visit(body.expression); |
} else { |
- code = canUseArrowFun ? '(#) => { #; }' : 'function(#) { #; }'; |
jsBody = _visit(body); |
} |
- var clos = js.call(code, [params, jsBody]); |
+ if (jsBody is JS.Expression && !canUseArrowFun) { |
+ 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, |
@@ -1482,20 +1542,23 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
} |
} |
- JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body) { |
+ JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body, |
+ List<JS.Identifier> typeParams, JS.TypeRef returnType) { |
// sync*, async, async* |
if (body.isAsynchronous || body.isGenerator) { |
return new JS.Fun( |
params, |
- js.statement( |
- '{ return #; }', [_emitGeneratorFunctionBody(params, body)])); |
+ js.statement('{ return #; }', |
+ [_emitGeneratorFunctionBody(params, body, returnType)]), |
+ returnType: returnType); |
} |
// normal function (sync) |
- return new JS.Fun(params, _visit(body)); |
+ return new JS.Fun(params, _visit(body), |
+ typeParams: typeParams, returnType: returnType); |
} |
JS.Expression _emitGeneratorFunctionBody( |
- List<JS.Parameter> params, FunctionBody body) { |
+ List<JS.Parameter> params, FunctionBody body, JS.TypeRef returnType) { |
var kind = body.isSynchronous ? 'sync' : 'async'; |
if (body.isGenerator) kind += 'Star'; |
@@ -1539,7 +1602,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
_asyncStarController = null; |
jsParams = params; |
} |
- JS.Expression gen = new JS.Fun(jsParams, _visit(body), isGenerator: true); |
+ JS.Expression gen = new JS.Fun(jsParams, _visit(body), |
+ isGenerator: true, returnType: returnType); |
if (JS.This.foundIn(gen)) { |
gen = js.call('#.bind(this)', gen); |
} |
@@ -1569,7 +1633,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
} else { |
declareFn = new JS.FunctionDeclaration(name, fn); |
} |
- declareFn = annotate(declareFn, node.functionDeclaration.element); |
+ declareFn = annotate(declareFn, node, node.functionDeclaration.element); |
return new JS.Block([ |
declareFn, |
@@ -1577,10 +1641,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
]); |
} |
+ @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. |
- @override |
- JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { |
+ JS.Expression _emitSimpleIdentifier(SimpleIdentifier node, |
+ {bool allowType: false}) { |
var accessor = node.staticElement; |
if (accessor == null) { |
return js.commentExpression( |
@@ -1649,7 +1717,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
} |
} |
- return new JS.Identifier(name); |
+ return annotate( |
+ new JS.Identifier(name, |
+ type: allowType ? emitTypeRef(node.bestType) : null), |
+ node); |
} |
JS.TemporaryId _getTemp(Element key, String name) => |
@@ -2105,6 +2176,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
: js.call('{}'); |
result.add(new JS.DestructuredVariable( |
structure: new JS.ObjectBindingPattern(namedVars), |
+ type: emitNamedParamsArgType(node.parameterElements), |
defaultValue: defaultOpts)); |
} |
return result; |
@@ -2236,7 +2308,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
return _emitTopLevelField(node); |
} |
- var name = new JS.Identifier(node.name.name); |
+ var name = |
+ new JS.Identifier(node.name.name, type: emitTypeRef(node.element.type)); |
return new JS.VariableInitialization(name, _visitInitializer(node)); |
} |
@@ -2272,12 +2345,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
var fieldName = field.name.name; |
if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
- return annotateVariable( |
+ return annotate( |
js.statement('#.# = #;', [ |
classElem.name, |
_emitMemberName(fieldName, isStatic: true), |
jsInit |
]), |
+ field, |
field.element); |
} |
@@ -2325,15 +2399,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
if (isPublic(fieldName)) _addExport(fieldName, exportName); |
var declKeyword = field.isConst || field.isFinal ? 'const' : 'let'; |
- return annotateVariable( |
- js.statement( |
- '$declKeyword # = #;', [new JS.Identifier(fieldName), jsInit]), |
- field.element); |
+ return js.statement('#;', [ |
+ annotate( |
+ new JS.VariableDeclarationList(declKeyword, [ |
+ new JS.VariableInitialization( |
+ new JS.Identifier(fieldName, |
+ type: emitTypeRef(field.element.type)), |
+ jsInit) |
+ ]), |
+ field, |
+ field.element) |
+ ]); |
} |
if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
- return annotateVariable( |
- js.statement('# = #;', [_visit(field.name), jsInit]), field.element); |
+ return annotate(js.statement('# = #;', [_visit(field.name), jsInit]), |
+ field, field.element); |
} |
return _emitLazyFields(currentLibrary, [field]); |
@@ -2359,6 +2440,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
js.call('function() { return #; }', _visit(node.initializer)) |
as JS.Fun, |
isGetter: true), |
+ node, |
_findAccessor(element, getter: true))); |
// TODO(jmesserly): currently uses a dummy setter to indicate writable. |
@@ -2366,6 +2448,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
methods.add(annotate( |
new JS.Method(access, js.call('function(_) {}') as JS.Fun, |
isSetter: true), |
+ node, |
_findAccessor(element, getter: false))); |
} |
} |
@@ -2754,8 +2837,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
_visit(node.expression); |
@override |
- visitFormalParameter(FormalParameter node) { |
- var id = visitSimpleIdentifier(node.identifier); |
+ 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; |
@@ -3315,7 +3400,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
_visit(AstNode node) { |
if (node == null) return null; |
var result = node.accept(this); |
- if (result is JS.Node) result.sourceInformation = node; |
+ if (result is JS.Node) result = annotate(result, node); |
return result; |
} |
@@ -3458,34 +3543,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
DartType getStaticType(Expression e) => |
e.staticType ?? DynamicTypeImpl.instance; |
- @override |
- String getQualifiedName(TypeDefiningElement type) { |
- JS.TemporaryId id = _imports[type.library]; |
- return id == null ? type.name : '${id.name}.${type.name}'; |
+ JS.Node annotate(JS.Node node, AstNode original, [Element element]) { |
+ if (options.closure && element != null) { |
+ node = node.withClosureAnnotation( |
+ closureAnnotationFor(node, original, element, _namedArgTemp.name)); |
+ } |
+ return node..sourceInformation = original; |
} |
- JS.Node annotate(JS.Node method, ExecutableElement e) => |
- options.closure && e != null |
- ? method.withClosureAnnotation( |
- closureAnnotationFor(e, _namedArgTemp.name)) |
- : method; |
- |
- JS.Node annotateDefaultConstructor(JS.Node method, ClassElement e) => |
- options.closure && e != null |
- ? method |
- .withClosureAnnotation(closureAnnotationForDefaultConstructor(e)) |
- : method; |
- |
- JS.Node annotateVariable(JS.Node node, VariableElement e) => |
- options.closure && e != null |
- ? node.withClosureAnnotation(closureAnnotationForVariable(e)) |
- : node; |
- |
- JS.Node annotateTypeDef(JS.Node node, FunctionTypeAliasElement e) => |
- options.closure && e != null |
- ? node.withClosureAnnotation(closureAnnotationForTypeDef(e)) |
- : node; |
- |
/// Returns true if this is any kind of object represented by `Number` in JS. |
/// |
/// In practice, this is 4 types: num, int, double, and JSNumber. |