Simplistic JsBackend
diff --git a/lib/src/codegen/backend.dart b/lib/src/codegen/backend.dart
new file mode 100644
index 0000000..ae421a1
--- /dev/null
+++ b/lib/src/codegen/backend.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// 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 dev_compiler.src.codegen.backend;
+
+// TODO(jmesserly): import from its own package
+import '../js/dart_nodes.dart';
+import '../js/js_ast.dart' as JS;
+import 'js_names.dart' as JS;
+import 'js_metalet.dart' as JS;
+
+abstract class JsBackend extends DartVisitor<JS.Node> {}
diff --git a/lib/src/codegen/default_backend.dart b/lib/src/codegen/default_backend.dart
new file mode 100644
index 0000000..13db110
--- /dev/null
+++ b/lib/src/codegen/default_backend.dart
@@ -0,0 +1,188 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// 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 dev_compiler.src.codegen.default_backend;
+
+// TODO(jmesserly): import from its own package
+import '../js/dart_nodes.dart';
+import '../js/js_ast.dart' as JS;
+import '../js/js_ast.dart' show js;
+
+import 'backend.dart';
+import 'js_names.dart' as JS;
+import 'js_metalet.dart' as JS;
+import 'package:analyzer/src/generated/element.dart' as analyzer;
+
+const DPUT = 'dput';
+const DLOAD = 'dload';
+const DINDEX = 'dindex';
+const DSETINDEX = 'dsetindex';
+const DCALL = 'dcall';
+const DSEND = 'dsend';
+
+class DefaultJsBackend extends JsBackend {
+ Function _statement;
+
+ DefaultJsBackend(JS.Statement this._statement(List<JS.Statement> statements));
+
+ @override
+ JS.Expression visitDartMethodCall(DartMethodCall node) {
+ var target = node.target;
+ var memberName = node.memberName;
+ var arguments = node.arguments;
+
+ switch (node.callType) {
+ case DartMethodCallType.dsend:
+ return js.call('dart.$DSEND(#, #, #)',
+ [target, memberName, arguments]);
+
+ case DartMethodCallType.dcall:
+ if (target == null) {
+ return js.call('dart.$DCALL(#, #)', [memberName, arguments]);
+ } else {
+ return js.call('dart.$DCALL(#.#, #)',
+ [target, memberName, arguments]);
+ }
+
+ case DartMethodCallType.staticDispatch:
+ return js.call('dart.#(#, #)',
+ [memberName, target, arguments]);
+
+ case DartMethodCallType.directDispatch:
+ if (target == null) {
+ return js.call('#(#)', [memberName, arguments]);
+ } else {
+ return js.call('#.#(#)', [target, memberName, arguments]);
+ }
+
+ default:
+ throw new StateError('Invalid call type: ${node.callType}');
+ }
+ }
+
+ @override
+ JS.Node visitDartCallableDeclaration(DartCallableDeclaration node) {
+ JS.Method method;
+ var element = node.element;
+ // if (element is analyzer.MethodElement) {
+ //
+ // } else
+ if (element is analyzer.PropertyAccessorElement) {
+ method = new JS.Method(node.name, node.body,
+ isGetter: element.isGetter,
+ isSetter: element.isSetter,
+ isStatic: element.isStatic);
+ } else if (element is analyzer.ConstructorElement) {
+ method = new JS.Method(node.name, node.body, isStatic: element.isFactory);
+ } else if (element is analyzer.ClassElement) {
+ method = new JS.Method(node.name, node.body);
+ } else {
+ // TODO('visitDartCallableDeclaration: ${element.runtimeType}');
+ method = new JS.Method(node.name, node.body,
+ isStatic: (element?.isStatic ?? false));
+ }
+ return //annotate(
+ method;//..sourceInformation = node;
+ //node.element);
+ }
+
+ @override
+ JS.Node visitDartClassDeclaration(DartClassDeclaration node) {
+ var classElem = node.element;
+ var name = classElem.name;
+ var body = <JS.Statement>[];
+
+ var cls = new JS.ClassExpression(
+ new JS.Identifier(classElem.type.name),
+ node.parentRef,
+ node.members.map(visit).toList());
+
+ if (node.extensionNames.isNotEmpty) {
+ body.add(js.statement('dart.defineExtensionNames(#)',
+ [new JS.ArrayInitializer(node.extensionNames, multiline: true)]));
+ }
+
+ body.add(new JS.ClassDeclaration(cls));
+
+ // TODO(jmesserly): we should really just extend native Array.
+ if (node.jsPeerName != null && classElem.typeParameters.isNotEmpty) {
+ body.add(js.statement('dart.setBaseClass(#, dart.global.#);',
+ [classElem.name, node.jsPeerName]));
+ }
+
+ // Deferred Superclass
+ if (node.deferredParentRef != null) {
+ body.add(js.statement('#.prototype.__proto__ = #.prototype;',
+ [name, node.deferredParentRef]));
+ }
+
+ // Interfaces
+ if (node.implementedRefs.isNotEmpty) {
+ body.add(js.statement('#[dart.implements] = () => #;', [
+ name,
+ new JS.ArrayInitializer(node.implementedRefs)
+ ]));
+ }
+
+ // Named constructors
+ for (var ctorName in node.constructorNames) {
+ body.add(js.statement('dart.defineNamedConstructor(#, #);',
+ [name, ctorName]));
+ }
+
+ // Instance fields, if they override getter/setter pairs
+ for (var overrideField in node.overrideFields) {
+ return js.statement('dart.virtualField(#, #)', [cls.name, overrideField]);
+ }
+
+ // Emit the signature on the class recording the runtime type information
+
+ if (node.signature != null) {
+ var classExpr = new JS.Identifier(name);
+ body.add(js.statement('dart.setSignature(#, #);', [classExpr, node.signature]));
+ }
+
+ // If a concrete class implements one of our extensions, we might need to
+ // add forwarders.
+ if (node.extensionNames.isNotEmpty) {
+ body.add(js.statement('dart.defineExtensionMembers(#, #);', [
+ name,
+ new JS.ArrayInitializer(node.extensionNames,
+ multiline: node.extensionNames.length > 4)
+ ]));
+ }
+
+ // TODO(vsm): Make this optional per #268.
+ if (node.metadataExpressions.isNotEmpty) {
+ body.add(js.statement('#[dart.metadata] = () => #;', [
+ name,
+ new JS.ArrayInitializer(node.metadataExpressions)
+ ]));
+ }
+
+ return _statement(body);
+ }
+
+ @override
+ JS.Node visitDartDeclaration(DartDeclaration node) =>
+ TODO('visitDartDeclaration');
+
+ @override
+ JS.Node visitDartLibrary(DartLibrary node) =>
+ TODO('visitDartLibrary');
+
+ @override
+ JS.Node visitDartLibraryPart(DartLibraryPart node) =>
+ TODO('visitDartLibraryPart');
+
+ @override
+ JS.Node visitDartTypedef(DartTypedef node) =>
+ TODO('visitDartTypedef');
+
+ @override
+ JS.Node visitOpaqueDartDeclaration(OpaqueDartDeclaration node) =>
+ node.statement;
+
+ TODO(String s) => throw new StateError('TODO: $s');
+}
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index 693afed..23d59fc 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -30,7 +30,9 @@
import '../options.dart' show CodegenOptions;
import '../utils.dart';
+import 'backend.dart';
import 'code_generator.dart';
+import 'default_backend.dart';
import 'js_field_storage.dart';
import 'js_interop.dart';
import 'js_names.dart' as JS;
@@ -39,6 +41,8 @@
import 'js_names.dart';
import 'js_printer.dart' show writeJsLibrary;
import 'side_effect_analysis.dart';
+import 'package:dev_compiler/src/js/dart_nodes.dart';
+import 'package:dev_compiler/src/js/js_types.dart';
// Various dynamic helpers we call.
// If renaming these, make sure to check other places like the
@@ -95,6 +99,7 @@
final _namedArgTemp = new JS.TemporaryId('opts');
final TypeProvider _types;
+ final JsBackend _backend;
ConstFieldVisitor _constField;
@@ -109,7 +114,7 @@
bool _isDartRuntime;
JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary,
- this._extensionTypes, this._fieldsNeedingStorage)
+ this._extensionTypes, this._fieldsNeedingStorage, this._backend)
: compiler = compiler,
options = compiler.options.codegenOptions,
_types = compiler.context.typeProvider {
@@ -124,6 +129,19 @@
TypeProvider get types => _types;
+ JS.Node _emitDart(DartNode node) {
+ return _backend.visit(node);
+ // Convert things we can convert immediately, and register the rest for
+ // future conversion.
+ // if (node is DartMethodCall) {
+ // return _backend.visit(node);
+ // } else {
+ // var placeholder = new JS.PlaceholderExpression(node);
+ // _placeholders.add(placeholder);
+ // return placeholder;
+ // }
+ }
+
JS.Program emitLibrary(LibraryUnit library) {
// Modify the AST to make coercions explicit.
new CoercionReifier(library, rules).reify();
@@ -445,9 +463,6 @@
}
}
- var classExpr = new JS.ClassExpression(new JS.Identifier(type.name),
- _classHeritage(classElem), _emitClassMethods(node, ctors, fields));
-
String jsPeerName;
var jsPeer = findAnnotation(classElem, isJsPeerInterface);
if (jsPeer != null) {
@@ -455,7 +470,8 @@
getConstantField(jsPeer, 'name', types.stringType)?.toStringValue();
}
- var body = _finishClassMembers(classElem, classExpr, ctors, fields, methods,
+ var members = _emitClassMethods(node, ctors, fields);
+ var body = _finishClassMembers(node, classElem, members, ctors, fields, methods,
node.metadata, jsPeerName);
var result = _finishClassDef(type, body);
@@ -588,7 +604,7 @@
return false;
}
- JS.Expression _classHeritage(ClassElement element) {
+ TypeRef _classHeritage(ClassElement element) {
var type = element.type;
if (type.isObject) return null;
@@ -612,10 +628,11 @@
}
_loader.finishTopLevel(element);
- return heritage;
+ // TODO(ochafik): Refine this.
+ return new OpaqueTypeRef(heritage);
}
- List<JS.Method> _emitClassMethods(ClassDeclaration node,
+ List<DartCallableDeclaration> _emitClassMethods(ClassDeclaration node,
List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
var element = node.element;
var type = element.type;
@@ -623,7 +640,7 @@
// Iff no constructor is specified for a class C, it implicitly has a
// default constructor `C() : super() {}`, unless C is class Object.
- var jsMethods = <JS.Method>[];
+ var jsMethods = <DartCallableDeclaration>[];
if (ctors.isEmpty && !isObject) {
jsMethods.add(_emitImplicitConstructor(node, fields));
}
@@ -670,7 +687,7 @@
/// This will return `null` if the adapter was already added on a super type,
/// otherwise it returns the adapter code.
// TODO(jmesserly): should we adapt `Iterator` too?
- JS.Method _emitIterable(InterfaceType t) {
+ DartCallableDeclaration _emitIterable(InterfaceType t) {
// If a parent had an `iterator` (concrete or abstract) or implements
// Iterable, we know the adapter is already there, so we can skip it as a
// simple code size optimization.
@@ -681,7 +698,7 @@
// Otherwise, emit the adapter method, which wraps the Dart iterator in
// an ES6 iterator.
- return new JS.Method(
+ return newDartMethod(null, //new JS.Method(
js.call('$_SYMBOL.iterator'),
js.call('function() { return new dart.JsIterator(this.#); }',
[_emitMemberName('iterator', type: t)]) as JS.Fun);
@@ -700,58 +717,46 @@
/// Emit class members that need to come after the class declaration, such
/// as static fields. See [_emitClassMethods] for things that are emitted
/// inside the ES6 `class { ... }` node.
- JS.Statement _finishClassMembers(
+ JS.Node _finishClassMembers(
+ ClassDeclaration node,
ClassElement classElem,
- JS.ClassExpression cls,
+ // JS.ClassExpression cls,
+ List<DartCallableDeclaration> members,
List<ConstructorDeclaration> ctors,
List<FieldDeclaration> fields,
List<MethodDeclaration> methods,
List<Annotation> metadata,
String jsPeerName) {
- var name = classElem.name;
- var body = <JS.Statement>[];
+
+ // var members = _emitClassMethods(node, ctors, fields);
+
+ TypeRef parentRef = _classHeritage(classElem);
+ final mixinRefs = <TypeRef>[];
+ final genericTypeNames = <String>[];
+ final extensionNames = <JS.Expression>[];
+ final overrideFields = <JS.Expression>[];
+ final constructorNames = <JS.Expression>[];
if (_extensionTypes.contains(classElem)) {
- var dartxNames = <JS.Expression>[];
for (var m in methods) {
if (!m.isAbstract && !m.isStatic && m.element.isPublic) {
- dartxNames.add(_elementMemberName(m.element, allowExtensions: false));
+ extensionNames.add(_elementMemberName(m.element, allowExtensions: false));
}
}
- if (dartxNames.isNotEmpty) {
- body.add(js.statement('dart.defineExtensionNames(#)',
- [new JS.ArrayInitializer(dartxNames, multiline: true)]));
- }
- }
-
- body.add(new JS.ClassDeclaration(cls));
-
- // TODO(jmesserly): we should really just extend native Array.
- if (jsPeerName != null && classElem.typeParameters.isNotEmpty) {
- body.add(js.statement('dart.setBaseClass(#, dart.global.#);',
- [classElem.name, _propertyName(jsPeerName)]));
}
// Deferred Superclass
- if (_hasDeferredSupertype.contains(classElem)) {
- body.add(js.statement('#.prototype.__proto__ = #.prototype;',
- [name, _emitTypeName(classElem.type.superclass)]));
- }
+ JS.Expression deferredSuperType = _hasDeferredSupertype.contains(classElem)
+ ? _emitTypeName(classElem.type.superclass) : null;
// Interfaces
- if (classElem.interfaces.isNotEmpty) {
- body.add(js.statement('#[dart.implements] = () => #;', [
- name,
- new JS.ArrayInitializer(new List<JS.Expression>.from(
- classElem.interfaces.map(_emitTypeName)))
- ]));
- }
+ final implementedRefs = classElem.interfaces
+ .map((i) => new OpaqueTypeRef(_emitTypeName(i))).toList();
// Named constructors
for (ConstructorDeclaration member in ctors) {
if (member.name != null && member.factoryKeyword == null) {
- body.add(js.statement('dart.defineNamedConstructor(#, #);',
- [name, _emitMemberName(member.name.name, isStatic: true)]));
+ constructorNames.add(_emitMemberName(member.name.name, isStatic: true));
}
}
@@ -760,97 +765,97 @@
for (VariableDeclaration fieldDecl in member.fields.variables) {
var field = fieldDecl.element as FieldElement;
if (_fieldsNeedingStorage.contains(field)) {
- body.add(_overrideField(field));
+ overrideFields.add(_emitMemberName(field.name, type: classElem.type));
}
}
}
// Emit the signature on the class recording the runtime type information
var extensions = _extensionsToImplement(classElem);
- {
- var tStatics = <JS.Property>[];
- var tMethods = <JS.Property>[];
- var sNames = <JS.Expression>[];
- for (MethodDeclaration node in methods) {
- if (!(node.isSetter || node.isGetter || node.isAbstract)) {
- var name = node.name.name;
- var element = node.element;
- var inheritedElement =
- classElem.lookUpInheritedConcreteMethod(name, currentLibrary);
- if (inheritedElement != null &&
- inheritedElement.type == element.type) {
- continue;
- }
- var memberName = _elementMemberName(element);
- var parts = _emitFunctionTypeParts(element.type);
- var property =
- new JS.Property(memberName, new JS.ArrayInitializer(parts));
- if (node.isStatic) {
- tStatics.add(property);
- sNames.add(memberName);
- } else {
- tMethods.add(property);
- }
- }
- }
-
- var tCtors = <JS.Property>[];
- for (ConstructorDeclaration node in ctors) {
- var memberName = _constructorName(node.element);
- var element = node.element;
- var parts = _emitFunctionTypeParts(element.type, node.parameters);
- var property =
- new JS.Property(memberName, new JS.ArrayInitializer(parts));
- tCtors.add(property);
- }
-
- JS.Property build(String name, List<JS.Property> elements) {
- var o =
- new JS.ObjectInitializer(elements, multiline: elements.length > 1);
- var e = js.call('() => #', o);
- return new JS.Property(_propertyName(name), e);
- }
- var sigFields = <JS.Property>[];
- if (!tCtors.isEmpty) sigFields.add(build('constructors', tCtors));
- if (!tMethods.isEmpty) sigFields.add(build('methods', tMethods));
- if (!tStatics.isEmpty) {
- assert(!sNames.isEmpty);
- var aNames = new JS.Property(
- _propertyName('names'), new JS.ArrayInitializer(sNames));
- sigFields.add(build('statics', tStatics));
- sigFields.add(aNames);
- }
- if (!sigFields.isEmpty || extensions.isNotEmpty) {
- var sig = new JS.ObjectInitializer(sigFields);
- var classExpr = new JS.Identifier(name);
- body.add(js.statement('dart.setSignature(#, #);', [classExpr, sig]));
- }
- }
// If a concrete class implements one of our extensions, we might need to
// add forwarders.
- if (extensions.isNotEmpty) {
- var methodNames = <JS.Expression>[];
- for (var e in extensions) {
- methodNames.add(_elementMemberName(e));
+ extensionNames.addAll(extensions.map(_elementMemberName).toList());
+
+ var decl = new DartClassDeclaration(
+ element: classElem,
+ jsPeerName: _propertyName(jsPeerName),
+ parentRef: parentRef,
+ mixinRefs: mixinRefs,
+ implementedRefs: implementedRefs,
+ genericTypeNames: genericTypeNames,
+ members: members,
+ extensionNames: extensionNames,
+ // TODO(vsm): Make this optional per #268.
+ metadataExpressions: metadata.map(_instantiateAnnotation).toList(),
+ constructorNames: constructorNames,
+ overrideFields: overrideFields,
+ // TODO(ochafik): Move to backend
+ signature: makeSignature(classElem, methods, ctors, extensions),
+ deferredSuperType: deferredSuperType);
+
+ return _emitDart(decl);
+ }
+
+ JS.Expression makeSignature(
+ ClassElement classElem,
+ List<MethodDeclaration> methods,
+ List<ConstructorDeclaration> ctors,
+ List<ExecutableElement> extensions) {
+ var tStatics = <JS.Property>[];
+ var tMethods = <JS.Property>[];
+ var sNames = <JS.Expression>[];
+ for (MethodDeclaration node in methods) {
+ if (!(node.isSetter || node.isGetter || node.isAbstract)) {
+ var name = node.name.name;
+ var element = node.element;
+ var inheritedElement =
+ classElem.lookUpInheritedConcreteMethod(name, currentLibrary);
+ if (inheritedElement != null &&
+ inheritedElement.type == element.type) continue;
+ var memberName = _elementMemberName(element);
+ var parts = _emitFunctionTypeParts(element.type);
+ var property =
+ new JS.Property(memberName, new JS.ArrayInitializer(parts));
+ if (node.isStatic) {
+ tStatics.add(property);
+ sNames.add(memberName);
+ } else {
+ tMethods.add(property);
+ }
}
- body.add(js.statement('dart.defineExtensionMembers(#, #);', [
- name,
- new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4)
- ]));
}
- // TODO(vsm): Make this optional per #268.
- // Metadata
- if (metadata.isNotEmpty) {
- body.add(js.statement('#[dart.metadata] = () => #;', [
- name,
- new JS.ArrayInitializer(
- new List<JS.Expression>.from(metadata.map(_instantiateAnnotation)))
- ]));
+ var tCtors = <JS.Property>[];
+ for (ConstructorDeclaration node in ctors) {
+ var memberName = _constructorName(node.element);
+ var element = node.element;
+ var parts = _emitFunctionTypeParts(element.type, node.parameters);
+ var property =
+ new JS.Property(memberName, new JS.ArrayInitializer(parts));
+ tCtors.add(property);
}
- return _statement(body);
+ JS.Property build(String name, List<JS.Property> elements) {
+ var o =
+ new JS.ObjectInitializer(elements, multiline: elements.length > 1);
+ var e = js.call('() => #', o);
+ return new JS.Property(_propertyName(name), e);
+ }
+ var sigFields = <JS.Property>[];
+ if (!tCtors.isEmpty) sigFields.add(build('constructors', tCtors));
+ if (!tMethods.isEmpty) sigFields.add(build('methods', tMethods));
+ if (!tStatics.isEmpty) {
+ assert(!sNames.isEmpty);
+ var aNames = new JS.Property(
+ _propertyName('names'), new JS.ArrayInitializer(sNames));
+ sigFields.add(build('statics', tStatics));
+ sigFields.add(aNames);
+ }
+ if (!sigFields.isEmpty || extensions.isNotEmpty) {
+ return new JS.ObjectInitializer(sigFields);
+ }
+ return null;
}
List<ExecutableElement> _extensionsToImplement(ClassElement element) {
@@ -895,15 +900,10 @@
_collectExtensions(type.superclass, types);
}
- JS.Statement _overrideField(FieldElement e) {
- var cls = e.enclosingElement;
- return js.statement('dart.virtualField(#, #)',
- [cls.name, _emitMemberName(e.name, type: cls.type)]);
- }
/// Generates the implicit default constructor for class C of the form
/// `C() : super() {}`.
- JS.Method _emitImplicitConstructor(
+ DartCallableDeclaration _emitImplicitConstructor(
ClassDeclaration node, List<FieldDeclaration> fields) {
assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty);
@@ -918,12 +918,14 @@
];
}
var name = _constructorName(node.element.unnamedConstructor);
- return annotateDefaultConstructor(
- new JS.Method(name, js.call('function() { #; }', body) as JS.Fun),
- node.element);
+ return newDartConstructor(node.element, name,
+ js.call('function() { #; }', body));
+ // return annotateDefaultConstructor(
+ // new JS.Method(name, js.call('function() { #; }', body) as JS.Fun),
+ // node.element);
}
- JS.Method _emitConstructor(ConstructorDeclaration node, InterfaceType type,
+ DartCallableDeclaration _emitConstructor(ConstructorDeclaration node, InterfaceType type,
List<FieldDeclaration> fields, bool isObject) {
if (_externalOrNative(node)) return null;
@@ -941,9 +943,10 @@
var fun = js.call('function(#) { return $newKeyword #(#); }',
[params, _visit(redirect), params]) as JS.Fun;
- return annotate(
- new JS.Method(name, fun, isStatic: true)..sourceInformation = node,
- node.element);
+ return newDartConstructor(node.element, name, fun);
+ // return annotate(
+ // new JS.Method(name, fun, isStatic: true)..sourceInformation = node,
+ // node.element);
}
// Factory constructors are essentially static methods.
@@ -954,9 +957,10 @@
body.add(_visit(node.body));
var fun = new JS.Fun(
_visit(node.parameters) as List<JS.Parameter>, new JS.Block(body));
- return annotate(
- new JS.Method(name, fun, isStatic: true)..sourceInformation = node,
- node.element);
+ return newDartConstructor(node.element, name, fun);
+ // return annotate(
+ // new JS.Method(name, fun, isStatic: true)..sourceInformation = node,
+ // node.element);
}
// Code generation for Object's constructor.
@@ -991,11 +995,13 @@
// We generate constructors as initializer methods in the class;
// 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(_visit(node.parameters) as List<JS.Parameter>, body))
- ..sourceInformation = node,
- node.element);
+ return newDartConstructor(node.element, name,
+ new JS.Fun(_visit(node.parameters) as List<JS.Parameter>, body));
+ // return annotate(
+ // new JS.Method(name,
+ // new JS.Fun(_visit(node.parameters) as List<JS.Parameter>, body))
+ // ..sourceInformation = node,
+ // node.element);
}
JS.Expression _constructorName(ConstructorElement ctor) {
@@ -1251,7 +1257,7 @@
}
}
- JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
+ DartCallableDeclaration _emitMethodDeclaration(DartType type, MethodDeclaration node) {
if (node.isAbstract || _externalOrNative(node)) {
return null;
}
@@ -1279,12 +1285,13 @@
..sourceInformation = fn.sourceInformation;
}
- return annotate(
- new JS.Method(_elementMemberName(node.element), fn,
- isGetter: node.isGetter,
- isSetter: node.isSetter,
- isStatic: node.isStatic),
- node.element);
+ return newDartMethod(node.element, _elementMemberName(node.element), fn);
+ // return annotate(
+ // new JS.Method(_elementMemberName(node.element), fn,
+ // isGetter: node.isGetter,
+ // isSetter: node.isSetter,
+ // isStatic: node.isStatic),
+ // node.element);
}
/// Returns the name value of the `JSExportName` annotation (when compiling
@@ -1370,12 +1377,13 @@
return fn;
}
- JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
+ DartCallableDeclaration _emitTopLevelProperty(FunctionDeclaration node) {
var name = node.name.name;
- return annotate(
- new JS.Method(_propertyName(name), _visit(node.functionExpression),
- isGetter: node.isGetter, isSetter: node.isSetter),
- node.element);
+ return newDartMethod(node.element, _propertyName(name), _visit(node.functionExpression));
+ // return annotate(
+ // new JS.Method(_propertyName(name), _visit(node.functionExpression),
+ // isGetter: node.isGetter, isSetter: node.isSetter),
+ // node.element);
}
bool _executesAtTopLevel(AstNode node) {
@@ -1906,40 +1914,44 @@
var result = _emitForeignJS(node);
if (result != null) return result;
- String code;
+ JS.Expression callTarget;
+ JS.Expression memberName;
+ DartMethodCallType callType;
+
if (target == null || isLibraryPrefix(target)) {
+ callTarget = null;
+ memberName = _visit(node.methodName);
if (DynamicInvoke.get(node.methodName)) {
- code = 'dart.$DCALL(#, #)';
+ callType = DartMethodCallType.dcall;
} else {
- code = '#(#)';
+ callType = DartMethodCallType.directDispatch;
}
- return js
- .call(code, [_visit(node.methodName), _visit(node.argumentList)]);
- }
-
- var type = getStaticType(target);
- var name = node.methodName.name;
- var element = node.methodName.staticElement;
- bool isStatic = element is ExecutableElement && element.isStatic;
- var memberName = _emitMemberName(name, type: type, isStatic: isStatic);
-
- if (DynamicInvoke.get(target)) {
- code = 'dart.$DSEND(#, #, #)';
- } else if (DynamicInvoke.get(node.methodName)) {
- // This is a dynamic call to a statically known target. For example:
- // class Foo { Function bar; }
- // new Foo().bar(); // dynamic call
- code = 'dart.$DCALL(#.#, #)';
- } else if (_requiresStaticDispatch(target, name)) {
- // Object methods require a helper for null checks.
- return js.call('dart.#(#, #)',
- [memberName, _visit(target), _visit(node.argumentList)]);
} else {
- code = '#.#(#)';
+ callTarget = _visit(target);
+ var type = getStaticType(target);
+ var name = node.methodName.name;
+ var element = node.methodName.staticElement;
+ bool isStatic = element is ExecutableElement && element.isStatic;
+ memberName = _emitMemberName(name, type: type, isStatic: isStatic);
+
+ if (DynamicInvoke.get(target)) {
+ callType = DartMethodCallType.dsend;
+ } else if (DynamicInvoke.get(node.methodName)) {
+ // This is a dynamic call to a statically known target. For example:
+ // class Foo { Function bar; }
+ // new Foo().bar(); // dynamic call
+ callType = DartMethodCallType.dcall;
+ } else if (_requiresStaticDispatch(target, name)) {
+ assert(rules.objectMembers[name] is FunctionType);
+ // Object methods require a helper for null checks.
+ callType = DartMethodCallType.staticDispatch;
+ } else {
+ callType = DartMethodCallType.directDispatch;
+ }
}
- return js
- .call(code, [_visit(target), memberName, _visit(node.argumentList)]);
+ return _emitDart(new DartMethodCall(
+ callType, callTarget, memberName, _visit(node.argumentList)));
}
/// Emits code for the `JS(...)` builtin.
@@ -2322,7 +2334,7 @@
void _flushLibraryProperties(List<JS.Statement> body) {
if (_properties.isEmpty) return;
body.add(js.statement('dart.copyProperties(#, { # });',
- [_exportsVar, _properties.map(_emitTopLevelProperty)]));
+ [_exportsVar, _properties.map(_emitTopLevelProperty).map(_emitDart)]));
_properties.clear();
}
@@ -3123,13 +3135,6 @@
otherwise);
}
- JS.Statement _statement(List<JS.Statement> statements) {
- // TODO(jmesserly): empty block singleton?
- if (statements.length == 0) return new JS.Block([]);
- if (statements.length == 1) return statements[0];
- return new JS.Block(statements);
- }
-
/// Visits the catch clause body. This skips the exception type guard, if any.
/// That is handled in [_visitCatch].
@override
@@ -3588,8 +3593,10 @@
var library = unit.library.element.library;
var fields = findFieldsNeedingStorage(unit, _extensionTypes);
var rules = new StrongTypeSystemImpl();
- var codegen =
- new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields);
+ // TODO(ochafik): make this configurable.
+ var backend = new DefaultJsBackend(_statement);
+ var codegen = new JSCodegenVisitor(
+ compiler, rules, library, _extensionTypes, fields, backend);
var module = codegen.emitLibrary(unit);
var out = compiler.getOutputPath(library.source.uri);
var flags = compiler.options;
@@ -3627,3 +3634,10 @@
int get hashCode => identityHashCode(this);
bool operator ==(Object other) => identical(this, other);
}
+
+JS.Statement _statement(List<JS.Statement> statements) {
+ // TODO(jmesserly): empty block singleton?
+ if (statements.length == 0) return new JS.Block([]);
+ if (statements.length == 1) return statements[0];
+ return new JS.Block(statements);
+}
diff --git a/lib/src/codegen/js_codegen_backends.dart b/lib/src/codegen/js_codegen_backends.dart
new file mode 100644
index 0000000..c856118
--- /dev/null
+++ b/lib/src/codegen/js_codegen_backends.dart
@@ -0,0 +1,3467 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// 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 dev_compiler.src.codegen.js_codegen_backends;
+
+import 'dart:collection' show HashSet, HashMap, SplayTreeSet;
+
+import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
+import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator;
+import 'package:analyzer/src/generated/constant.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
+import 'package:analyzer/src/generated/scanner.dart'
+ show StringToken, Token, TokenType;
+import 'package:analyzer/src/task/dart.dart' show PublicNamespaceBuilder;
+import 'package:analyzer/src/task/strong/rules.dart';
+
+import 'ast_builder.dart' show AstBuilder;
+import 'reify_coercions.dart' show CoercionReifier, Tuple2;
+
+// TODO(jmesserly): import from its own package
+import '../js/js_ast.dart' as JS;
+import '../js/js_ast.dart' show js;
+import '../js/dart_nodes.dart';
+
+import '../closure/closure_annotator.dart' show ClosureAnnotator;
+import '../compiler.dart' show AbstractCompiler;
+import '../info.dart';
+import '../options.dart' show CodegenOptions;
+import '../utils.dart';
+
+import 'code_generator.dart';
+import 'js_field_storage.dart';
+import 'js_interop.dart';
+import 'js_names.dart' as JS;
+import 'js_metalet.dart' as JS;
+import 'js_module_item_order.dart';
+import 'js_names.dart';
+import 'js_printer.dart' show writeJsLibrary;
+import 'side_effect_analysis.dart';
+import 'package:collection/equality.dart';
+
+// Various dynamic helpers we call.
+// If renaming these, make sure to check other places like the
+// _runtime.js file and comments.
+// TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can
+// import and generate calls to, rather than dart_runtime.js
+const DPUT = 'dput';
+const DLOAD = 'dload';
+const DINDEX = 'dindex';
+const DSETINDEX = 'dsetindex';
+const DCALL = 'dcall';
+const DSEND = 'dsend';
+
+const ListEquality _listEquality = const ListEquality();
+
+class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator {
+ final AbstractCompiler compiler;
+ final CodegenOptions options;
+ final TypeRules rules;
+ final LibraryElement currentLibrary;
+
+ /// The global extension type table.
+ final HashSet<ClassElement> _extensionTypes;
+
+ /// Information that is precomputed for this library, indicates which fields
+ /// need storage slots.
+ final HashSet<FieldElement> _fieldsNeedingStorage;
+
+ /// The variable for the target of the current `..` cascade expression.
+ ///
+ /// Usually a [SimpleIdentifier], but it can also be other expressions
+ /// that are safe to evaluate multiple times, such as `this`.
+ Expression _cascadeTarget;
+
+ /// The variable for the current catch clause
+ SimpleIdentifier _catchParameter;
+
+ /// In an async* function, this represents the stream controller parameter.
+ JS.TemporaryId _asyncStarController;
+
+ /// Imported libraries, and the temporaries used to refer to them.
+ final _imports = new Map<LibraryElement, JS.TemporaryId>();
+ final _exports = new Set<String>();
+ final _lazyFields = <VariableDeclaration>[];
+ final _properties = <FunctionDeclaration>[];
+ final _privateNames = new HashMap<String, JS.TemporaryId>();
+ final _moduleItems = <JS.Statement>[];
+ final _temps = new HashMap<Element, JS.TemporaryId>();
+ final _qualifiedIds = new List<Tuple2<Element, JS.MaybeQualifiedId>>();
+
+ /// The name for the library's exports inside itself.
+ /// `exports` was chosen as the most similar to ES module patterns.
+ final _dartxVar = new JS.Identifier('dartx');
+ final _exportsVar = new JS.TemporaryId('exports');
+ final _runtimeLibVar = new JS.Identifier('dart');
+ final _namedArgTemp = new JS.TemporaryId('opts');
+
+ final TypeProvider _types;
+
+ ConstFieldVisitor _constField;
+
+ ModuleItemLoadOrder _loader;
+
+ /// _interceptors.JSArray<E>, used for List literals.
+ ClassElement _jsArray;
+
+ /// The default value of the module object. See [visitLibraryDirective].
+ String _jsModuleValue;
+
+ bool _isDartUtils;
+
+ Map<String, DartType> _objectMembers;
+
+ JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary,
+ this._extensionTypes, this._fieldsNeedingStorage)
+ : compiler = compiler,
+ options = compiler.options.codegenOptions,
+ _types = compiler.context.typeProvider {
+ _loader = new ModuleItemLoadOrder(_emitModuleItem);
+
+ var context = compiler.context;
+ var src = context.sourceFactory.forUri('dart:_interceptors');
+ var interceptors = context.computeLibraryElement(src);
+ _jsArray = interceptors.getType('JSArray');
+ _isDartUtils = currentLibrary.source.uri.toString() == 'dart:_utils';
+
+ _objectMembers = getObjectMemberMap(types);
+ }
+
+ TypeProvider get types => rules.provider;
+
+ JS.Visitable emitLibrary(LibraryUnit library) {
+ // Modify the AST to make coercions explicit.
+ new CoercionReifier(library, rules).reify();
+
+ // Build the public namespace for this library. This allows us to do
+ // constant time lookups (contrast with `Element.getChild(name)`).
+ if (currentLibrary.publicNamespace == null) {
+ (currentLibrary as LibraryElementImpl).publicNamespace =
+ new PublicNamespaceBuilder().build(currentLibrary);
+ }
+
+ library.library.directives.forEach(_visit);
+
+ // Rather than directly visit declarations, we instead use [_loader] to
+ // visit them. It has the ability to sort elements on demand, so
+ // dependencies between top level items are handled with a minimal
+ // reordering of the user's input code. The loader will call back into
+ // this visitor via [_emitModuleItem] when it's ready to visit the item
+ // for real.
+ _loader.collectElements(currentLibrary, library.partsThenLibrary);
+
+ var parts = <DartLibraryPart>[];
+
+ for (var unit in library.partsThenLibrary) {
+ var declarations = <DartDeclaration>[];
+
+ _constField = new ConstFieldVisitor(types, unit);
+
+ for (var decl in unit.declarations) {
+ if (decl is TopLevelVariableDeclaration) {
+ // Split variables.
+ for (VariableDeclaration d in decl.variables.variables) {
+ declarations.add(newDartTopLevelValue(d.element, visitVariableDeclaration(d)));
+ }
+ } else if (decl is ClassDeclaration) {
+ declarations.add(visitClassDeclaration(decl));
+ } else if (decl is FunctionTypeAlias) {
+ declarations.add(visitFunctionTypeAlias(decl));
+ } else if (decl is FunctionDeclaration) {
+ declarations.add(visitFunctionDeclaration(decl));
+ }
+ }
+ parts.add(new DartLibraryPart(unit.element, declarations));
+ }
+
+ // Flush any unwritten fields/properties.
+ _flushLazyFields(_moduleItems);
+ _flushLibraryProperties(_moduleItems);
+
+ // Mark all qualified names as qualified or not, depending on if they need
+ // to be loaded lazily or not.
+ for (var elementIdPairs in _qualifiedIds) {
+ var element = elementIdPairs.e0;
+ var id = elementIdPairs.e1;
+ id.setQualified(!_loader.isLoaded(element));
+ }
+
+ if (_exports.isNotEmpty) _moduleItems.add(js.comment('Exports:'));
+
+ // TODO(jmesserly): make these immutable in JS?
+ for (var name in _exports) {
+ _moduleItems.add(js.statement('#.# = #;', [_exportsVar, name, name]));
+ }
+
+ var jsPath = compiler.getModuleName(currentLibrary.source.uri);
+
+ // TODO(jmesserly): it would be great to run the renamer on the body,
+ // then figure out if we really need each of these parameters.
+ // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34
+ var params = [_exportsVar, _runtimeLibVar];
+ var processImport =
+ (LibraryElement library, JS.TemporaryId temp, List list) {
+ params.add(temp);
+ list.add(js.string(compiler.getModuleName(library.source.uri), "'"));
+ };
+
+ var needsDartRuntime = !_isDartUtils;
+
+ var imports = <JS.Expression>[];
+ var moduleStatements = <JS.Statement>[];
+ if (needsDartRuntime) {
+ imports.add(js.string('dart/_runtime'));
+
+ var dartxImport =
+ js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]);
+ moduleStatements.add(dartxImport);
+ }
+ moduleStatements.addAll(_moduleItems);
+
+ _imports.forEach((library, temp) {
+ if (_loader.libraryIsLoaded(library)) {
+ processImport(library, temp, imports);
+ }
+ });
+
+ var lazyImports = <JS.Expression>[];
+ _imports.forEach((library, temp) {
+ if (!_loader.libraryIsLoaded(library)) {
+ processImport(library, temp, lazyImports);
+ }
+ });
+
+ var module =
+ js.call("function(#) { 'use strict'; #; }", [params, moduleStatements]);
+
+ var moduleDef = js.statement("dart_library.library(#, #, #, #, #)", [
+ js.string(jsPath, "'"),
+ _jsModuleValue ?? new JS.LiteralNull(),
+ js.commentExpression(
+ "Imports", new JS.ArrayInitializer(imports, multiline: true)),
+ js.commentExpression("Lazy imports",
+ new JS.ArrayInitializer(lazyImports, multiline: true)),
+ module
+ ]);
+
+ // TODO(jmesserly): scriptTag support.
+ // Enable this if we know we're targetting command line environment?
+ // It doesn't work in browser.
+ // var jsBin = compiler.options.runnerOptions.v8Binary;
+ // String scriptTag = null;
+ // if (library.library.scriptTag != null) scriptTag = '/usr/bin/env $jsBin';
+ return new JS.Program(<JS.Statement>[moduleDef]);
+ }
+
+ void _emitModuleItem(AstNode node) {
+ // Attempt to group adjacent fields/properties.
+ if (node is! VariableDeclaration) _flushLazyFields(_moduleItems);
+ if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems);
+
+ var code = _visit(node);
+ if (code != null) _moduleItems.add(code);
+ }
+
+ @override
+ void visitLibraryDirective(LibraryDirective node) {
+ assert(_jsModuleValue == null);
+
+ var jsName = findAnnotation(node.element, isJSAnnotation);
+ _jsModuleValue =
+ getConstantField(jsName, 'name', types.stringType)?.toStringValue();
+ }
+
+ @override
+ void visitImportDirective(ImportDirective node) {
+ // Nothing to do yet, but we'll want to convert this to an ES6 import once
+ // we have support for modules.
+ }
+
+ @override void visitPartDirective(PartDirective node) {}
+ @override void visitPartOfDirective(PartOfDirective node) {}
+
+ @override
+ void visitExportDirective(ExportDirective node) {
+ var exportName = _libraryName(node.uriElement);
+
+ var currentLibNames = currentLibrary.publicNamespace.definedNames;
+
+ var args = [_exportsVar, exportName];
+ if (node.combinators.isNotEmpty) {
+ var shownNames = <JS.Expression>[];
+ var hiddenNames = <JS.Expression>[];
+
+ var show = node.combinators.firstWhere((c) => c is ShowCombinator,
+ orElse: () => null) as ShowCombinator;
+ var hide = node.combinators.firstWhere((c) => c is HideCombinator,
+ orElse: () => null) as HideCombinator;
+ if (show != null) {
+ shownNames.addAll(show.shownNames
+ .map((i) => i.name)
+ .where((s) => !currentLibNames.containsKey(s))
+ .map((s) => js.string(s, "'")));
+ }
+ if (hide != null) {
+ hiddenNames.addAll(hide.hiddenNames.map((i) => js.string(i.name, "'")));
+ }
+ args.add(new JS.ArrayInitializer(shownNames));
+ args.add(new JS.ArrayInitializer(hiddenNames));
+ }
+ _moduleItems.add(js.statement('dart.export_(#);', [args]));
+ }
+
+ JS.Identifier _initSymbol(JS.Identifier id) {
+ var s =
+ js.statement('const # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
+ _moduleItems.add(s);
+ return id;
+ }
+
+ // TODO(jmesserly): this is a temporary workaround for `Symbol` in core,
+ // until we have better name tracking.
+ String get _SYMBOL {
+ var name = currentLibrary.name;
+ if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol';
+ return 'Symbol';
+ }
+
+ bool isPublic(String name) => !name.startsWith('_');
+
+ @override
+ visitAsExpression(AsExpression node) {
+ var from = getStaticType(node.expression);
+ var to = node.type.type;
+
+ var fromExpr = _visit(node.expression);
+
+ // Skip the cast if it's not needed.
+ if (rules.isSubTypeOf(from, to)) return fromExpr;
+
+ // All Dart number types map to a JS double.
+ if (_isNumberInJS(from) && _isNumberInJS(to)) {
+ // Make sure to check when converting to int.
+ if (from != _types.intType && to == _types.intType) {
+ return js.call('dart.asInt(#)', [fromExpr]);
+ }
+
+ // A no-op in JavaScript.
+ return fromExpr;
+ }
+
+ return js.call('dart.as(#, #)', [fromExpr, _emitTypeName(to)]);
+ }
+
+ @override
+ visitIsExpression(IsExpression node) {
+ // Generate `is` as `dart.is` or `typeof` depending on the RHS type.
+ JS.Expression result;
+ var type = node.type.type;
+ var lhs = _visit(node.expression);
+ var typeofName = _jsTypeofName(type);
+ if (typeofName != null) {
+ result = js.call('typeof # == #', [lhs, js.string(typeofName, "'")]);
+ } else {
+ // Always go through a runtime helper, because implicit interfaces.
+ result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]);
+ }
+
+ if (node.notOperator != null) {
+ return js.call('!#', result);
+ }
+ return result;
+ }
+
+ String _jsTypeofName(DartType t) {
+ if (_isNumberInJS(t)) return 'number';
+ if (t == _types.stringType) return 'string';
+ if (t == _types.boolType) return 'boolean';
+ return null;
+ }
+
+ @override
+ visitFunctionTypeAlias(FunctionTypeAlias node) {
+ var element = node.element;
+ var type = element.type;
+ var name = element.name;
+
+ var fnType = annotateTypeDef(
+ js.statement('const # = dart.typedef(#, () => #);', [
+ name,
+ js.string(name, "'"),
+ _emitTypeName(type, lowerTypedef: true)
+ ]),
+ node.element);
+
+ return _finishClassDef(type, fnType);
+ }
+
+ @override
+ JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type);
+
+ @override
+ JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
+ var element = node.element;
+
+ // Forward all generative constructors from the base class.
+ var body = <JS.Method>[];
+
+ var supertype = element.supertype;
+ if (!supertype.isObject) {
+ for (var ctor in element.constructors) {
+ var parentCtor = supertype.lookUpConstructor(ctor.name, ctor.library);
+ var fun = js.call('function() { super.#(...arguments); }',
+ [_constructorName(parentCtor)]) as JS.Fun;
+ body.add(new JS.Method(_constructorName(ctor), fun));
+ }
+ }
+
+ var classDecl = new JS.ClassDeclaration(new JS.ClassExpression(
+ new JS.Identifier(element.name), _classHeritage(element), body));
+
+ return _finishClassDef(element.type, classDecl);
+ }
+
+ JS.Statement _emitJsType(String dartClassName, DartObject jsName) {
+ var jsTypeName =
+ getConstantField(jsName, 'name', types.stringType)?.toStringValue();
+
+ if (jsTypeName != null && jsTypeName != dartClassName) {
+ // We export the JS type as if it was a Dart type. For example this allows
+ // `dom.InputElement` to actually be HTMLInputElement.
+ // TODO(jmesserly): if we had the JS name on the Element, we could just
+ // generate it correctly when we refer to it.
+ if (isPublic(dartClassName)) _addExport(dartClassName);
+ return js.statement('const # = #;', [dartClassName, jsTypeName]);
+ }
+ return null;
+ }
+
+ @override
+ JS.Statement visitClassDeclaration(ClassDeclaration node) {
+ var classElem = node.element;
+ var type = classElem.type;
+ var jsName = findAnnotation(classElem, isJSAnnotation);
+
+ if (jsName != null) return _emitJsType(node.name.name, jsName);
+
+ var ctors = <ConstructorDeclaration>[];
+ var fields = <FieldDeclaration>[];
+ var methods = <MethodDeclaration>[];
+ for (var member in node.members) {
+ if (member is ConstructorDeclaration) {
+ ctors.add(member);
+ } else if (member is FieldDeclaration && !member.isStatic) {
+ fields.add(member);
+ } else if (member is MethodDeclaration) {
+ methods.add(member);
+ }
+ }
+
+ var classExpr = new JS.ClassExpression(new JS.Identifier(type.name),
+ _classHeritage(classElem), _emitClassMethods(node, ctors, fields));
+
+ String jsPeerName;
+ var jsPeer = findAnnotation(classElem, isJsPeerInterface);
+ if (jsPeer != null) {
+ jsPeerName =
+ getConstantField(jsPeer, 'name', types.stringType)?.toStringValue();
+ }
+
+ return new DartClassDeclaration(
+ element: node.element,
+ jsPeerName: jsPeerName,
+ parentRef: parentRef,
+ mixinRefs: mixinRefs,
+ implementedRefs: implementedRefs,
+ genericTypeNames: genericTypeNames,
+ members: members);
+ }
+
+ convertTypeRef(DartType type) => new DartTypeRef(type);
+
+ @override
+ DartClassDeclaration visitEnumDeclaration(EnumDeclaration node) {
+ var element = node.element;
+ var type = element.type;
+ var name = js.string(type.name);
+ var id = newDartTypeExpression(type);
+
+ // Generate a class per section 13 of the spec.
+ // TODO(vsm): Generate any accompanying metadata
+ var members = <DartCallableDeclaration>[];
+
+ // Create constructor and initialize index
+ members.add(newDartConstructor(node.element,
+ js.call('function(index) { this.index = index; }') as JS.Fun));
+
+ var fields = new List<ConstFieldElementImpl>.from(
+ element.fields.where((f) => f.type == type));
+
+ // Create toString() method
+ var properties = new List<JS.Property>();
+ for (var i = 0; i < fields.length; ++i) {
+ properties.add(new JS.Property(
+ js.number(i), js.string('${type.name}.${fields[i].name}')));
+ }
+ var nameMap = new JS.ObjectInitializer(properties, multiline: true);
+ members.add(newDartSyntheticMethod('toString', types.stringType, {},
+ js.call('function() { return #[this.index]; }', nameMap) as JS.Fun));
+
+ int i = 0;
+ for (ConstFieldElementImpl f in element.fields) {
+ if (f.type == type) {
+ members.add(newDartField(f,
+ js.call('dart.const(new #(#));',
+ [id, js.number(i)])));
+ i++;
+ }
+ }
+
+ // Create enum class
+ var result = <JS.Statement>[js.statement('#', classExpr)];
+
+ // Create static fields for each enum value
+ for (var i = 0; i < fields.length; ++i) {
+ result.add(js.statement('#.# = dart.const(new #(#));',
+ [id, fields[i].name, id, js.number(i)]));
+ }
+
+ // Create static values list
+ var values = new JS.ArrayInitializer(new List<JS.Expression>.from(
+ fields.map((f) => js.call('#.#', [id, f.name]))));
+ result.add(js.statement('#.values = dart.const(dart.list(#, #));',
+ [id, values, _emitTypeName(type)]));
+
+ if (isPublic(type.name)) _addExport(type.name);
+ return _statement(result);
+ }
+
+ /// Given a class element and body, complete the class declaration.
+ /// This handles generic type parameters, laziness (in library-cycle cases),
+ /// and ensuring dependencies are loaded first.
+ JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) {
+ var name = type.name;
+ var genericName = '$name\$';
+
+ JS.Statement genericDef = null;
+ if (type.typeParameters.isNotEmpty) {
+ genericDef = _emitGenericClassDef(type, body);
+ }
+
+ // The base class and all mixins must be declared before this class.
+ if (!_loader.isLoaded(type.element)) {
+ // TODO(jmesserly): the lazy class def is a simple solution for now.
+ // We may want to consider other options in the future.
+
+ if (genericDef != null) {
+ return js.statement(
+ '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }',
+ [genericDef, _exportsVar, _propertyName(name), genericName]);
+ }
+
+ return js.statement(
+ 'dart.defineLazyClass(#, { get #() { #; return #; } });',
+ [_exportsVar, _propertyName(name), body, name]);
+ }
+
+ if (isPublic(name)) _addExport(name);
+
+ if (genericDef != null) {
+ var dynType = fillDynamicTypeArgs(type, types);
+ var genericInst = _emitTypeName(dynType, lowerGeneric: true);
+ return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
+ }
+ return body;
+ }
+
+ JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) {
+ var name = type.name;
+ var genericName = '$name\$';
+ var typeParams = type.typeParameters.map((p) => p.name);
+ if (isPublic(name)) _exports.add(genericName);
+ return js.statement('const # = dart.generic(function(#) { #; return #; });',
+ [genericName, typeParams, body, name]);
+ }
+
+ final _hasDeferredSupertype = new HashSet<ClassElement>();
+
+ bool _deferIfNeeded(DartType type, ClassElement current) {
+ if (type is ParameterizedType) {
+ var typeArguments = type.typeArguments;
+ for (var typeArg in typeArguments) {
+ var typeElement = typeArg.element;
+ // FIXME(vsm): This does not track mutual recursive dependences.
+ if (current == typeElement || _deferIfNeeded(typeArg, current)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ JS.Expression _classHeritage(ClassElement element) {
+ var type = element.type;
+ if (type.isObject) return null;
+
+ // Assume we can load eagerly, until proven otherwise.
+ _loader.startTopLevel(element);
+
+ // Find the super type
+ JS.Expression heritage;
+ var supertype = type.superclass;
+ if (_deferIfNeeded(supertype, element)) {
+ // Fall back to raw type.
+ supertype = fillDynamicTypeArgs(supertype.element.type, rules.provider);
+ _hasDeferredSupertype.add(element);
+ }
+ heritage = _emitTypeName(supertype);
+
+ if (type.mixins.isNotEmpty) {
+ var mixins = type.mixins.map(_emitTypeName).toList();
+ mixins.insert(0, heritage);
+ heritage = js.call('dart.mixin(#)', [mixins]);
+ }
+
+ _loader.finishTopLevel(element);
+ return heritage;
+ }
+
+ List<JS.Method> _emitClassMethods(ClassDeclaration node,
+ List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
+ var element = node.element;
+ var type = element.type;
+ var isObject = type.isObject;
+
+ // Iff no constructor is specified for a class C, it implicitly has a
+ // default constructor `C() : super() {}`, unless C is class Object.
+ var jsMethods = <DartCallableDeclaration>[];
+ if (ctors.isEmpty && !isObject) {
+ jsMethods.add(_emitImplicitConstructor(node, fields));
+ }
+
+ bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null;
+
+ bool hasIterator = false;
+ for (var m in node.members) {
+ if (m is ConstructorDeclaration) {
+ jsMethods.add(_emitConstructor(m, type, fields, isObject));
+ } else if (m is MethodDeclaration) {
+ jsMethods.add(_emitMethodDeclaration(type, m));
+
+ if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') {
+ hasIterator = true;
+ jsMethods.add(_emitIterable(type));
+ }
+ }
+ }
+
+ // If the type doesn't have an `iterator`, but claims to implement Iterable,
+ // we inject the adaptor method here, as it's less code size to put the
+ // helper on a parent class. This pattern is common in the core libraries
+ // (e.g. IterableMixin<E> and IterableBase<E>).
+ //
+ // (We could do this same optimization for any interface with an `iterator`
+ // method, but that's more expensive to check for, so it doesn't seem worth
+ // it. The above case for an explicit `iterator` method will catch those.)
+ if (!hasJsPeer && !hasIterator && _implementsIterable(type)) {
+ jsMethods.add(_emitIterable(type));
+ }
+
+ return jsMethods.where((m) => m != null).toList(growable: false);
+ }
+
+ bool _implementsIterable(InterfaceType t) =>
+ t.interfaces.any((i) => i.element.type == types.iterableType);
+
+ /// Support for adapting dart:core Iterable to ES6 versions.
+ ///
+ /// This lets them use for-of loops transparently:
+ /// <https://github.com/lukehoban/es6features#iterators--forof>
+ ///
+ /// This will return `null` if the adapter was already added on a super type,
+ /// otherwise it returns the adapter code.
+ // TODO(jmesserly): should we adapt `Iterator` too?
+ JS.Method _emitIterable(InterfaceType t) {
+ // If a parent had an `iterator` (concrete or abstract) or implements
+ // Iterable, we know the adapter is already there, so we can skip it as a
+ // simple code size optimization.
+ var parent = t.lookUpGetterInSuperclass('iterator', t.element.library);
+ if (parent != null) return null;
+ var parentType = findSupertype(t, _implementsIterable);
+ if (parentType != null) return null;
+
+ // Otherwise, emit the adapter method, which wraps the Dart iterator in
+ // an ES6 iterator.
+ return new JS.Method(
+ js.call('$_SYMBOL.iterator'),
+ js.call('function() { return new dart.JsIterator(this.#); }',
+ [_emitMemberName('iterator', type: t)]) as JS.Fun);
+ }
+
+ JS.Expression _instantiateAnnotation(Annotation node) {
+ var element = node.element;
+ if (element is ConstructorElement) {
+ return _emitInstanceCreationExpression(element, element.returnType,
+ node.constructorName, node.arguments, true);
+ } else {
+ return _visit(node.name);
+ }
+ }
+
+ /// Emit class members that need to come after the class declaration, such
+ /// as static fields. See [_emitClassMethods] for things that are emitted
+ /// inside the ES6 `class { ... }` node.
+ JS.Statement _finishClassMembers(
+ ClassElement classElem,
+ JS.ClassExpression cls,
+ List<ConstructorDeclaration> ctors,
+ List<FieldDeclaration> fields,
+ List<MethodDeclaration> methods,
+ List<Annotation> metadata,
+ String jsPeerName) {
+ var name = classElem.name;
+ var body = <JS.Statement>[];
+
+ if (_extensionTypes.contains(classElem)) {
+ var dartxNames = <JS.Expression>[];
+ for (var m in methods) {
+ if (!m.isAbstract && !m.isStatic && m.element.isPublic) {
+ dartxNames.add(_elementMemberName(m.element, allowExtensions: false));
+ }
+ }
+ if (dartxNames.isNotEmpty) {
+ body.add(js.statement('dart.defineExtensionNames(#)',
+ [new JS.ArrayInitializer(dartxNames, multiline: true)]));
+ }
+ }
+
+ body.add(new JS.ClassDeclaration(cls));
+
+ // TODO(jmesserly): we should really just extend native Array.
+ if (jsPeerName != null && classElem.typeParameters.isNotEmpty) {
+ body.add(js.statement('dart.setBaseClass(#, dart.global.#);',
+ [classElem.name, _propertyName(jsPeerName)]));
+ }
+
+ // Deferred Superclass
+ if (_hasDeferredSupertype.contains(classElem)) {
+ body.add(js.statement('#.prototype.__proto__ = #.prototype;',
+ [name, _emitTypeName(classElem.type.superclass)]));
+ }
+
+ // Interfaces
+ if (classElem.interfaces.isNotEmpty) {
+ body.add(js.statement('#[dart.implements] = () => #;', [
+ name,
+ new JS.ArrayInitializer(new List<JS.Expression>.from(
+ classElem.interfaces.map(_emitTypeName)))
+ ]));
+ }
+
+ // Named constructors
+ for (ConstructorDeclaration member in ctors) {
+ if (member.name != null && member.factoryKeyword == null) {
+ body.add(js.statement('dart.defineNamedConstructor(#, #);',
+ [name, _emitMemberName(member.name.name, isStatic: true)]));
+ }
+ }
+
+ // Instance fields, if they override getter/setter pairs
+ for (FieldDeclaration member in fields) {
+ for (VariableDeclaration fieldDecl in member.fields.variables) {
+ var field = fieldDecl.element as FieldElement;
+ if (_fieldsNeedingStorage.contains(field)) {
+ body.add(_overrideField(field));
+ }
+ }
+ }
+
+ // Emit the signature on the class recording the runtime type information
+ var extensions = _extensionsToImplement(classElem);
+ {
+ var tStatics = <JS.Property>[];
+ var tMethods = <JS.Property>[];
+ var sNames = <JS.Expression>[];
+ for (MethodDeclaration node in methods) {
+ if (!(node.isSetter || node.isGetter || node.isAbstract)) {
+ var name = node.name.name;
+ var element = node.element;
+ var inheritedElement =
+ classElem.lookUpInheritedConcreteMethod(name, currentLibrary);
+ if (inheritedElement != null &&
+ inheritedElement.type == element.type) continue;
+ var memberName = _elementMemberName(element);
+ var parts = _emitFunctionTypeParts(element.type);
+ var property =
+ new JS.Property(memberName, new JS.ArrayInitializer(parts));
+ if (node.isStatic) {
+ tStatics.add(property);
+ sNames.add(memberName);
+ } else {
+ tMethods.add(property);
+ }
+ }
+ }
+
+ var tCtors = <JS.Property>[];
+ for (ConstructorDeclaration node in ctors) {
+ var memberName = _constructorName(node.element);
+ var element = node.element;
+ var parts = _emitFunctionTypeParts(element.type, node.parameters);
+ var property =
+ new JS.Property(memberName, new JS.ArrayInitializer(parts));
+ tCtors.add(property);
+ }
+
+ JS.Property build(String name, List<JS.Property> elements) {
+ var o =
+ new JS.ObjectInitializer(elements, multiline: elements.length > 1);
+ var e = js.call('() => #', o);
+ return new JS.Property(_propertyName(name), e);
+ }
+ var sigFields = <JS.Property>[];
+ if (!tCtors.isEmpty) sigFields.add(build('constructors', tCtors));
+ if (!tMethods.isEmpty) sigFields.add(build('methods', tMethods));
+ if (!tStatics.isEmpty) {
+ assert(!sNames.isEmpty);
+ var aNames = new JS.Property(
+ _propertyName('names'), new JS.ArrayInitializer(sNames));
+ sigFields.add(build('statics', tStatics));
+ sigFields.add(aNames);
+ }
+ if (!sigFields.isEmpty || extensions.isNotEmpty) {
+ var sig = new JS.ObjectInitializer(sigFields);
+ var classExpr = new JS.Identifier(name);
+ body.add(js.statement('dart.setSignature(#, #);', [classExpr, sig]));
+ }
+ }
+
+ // If a concrete class implements one of our extensions, we might need to
+ // add forwarders.
+ if (extensions.isNotEmpty) {
+ var methodNames = <JS.Expression>[];
+ for (var e in extensions) {
+ methodNames.add(_elementMemberName(e));
+ }
+ body.add(js.statement('dart.defineExtensionMembers(#, #);', [
+ name,
+ new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4)
+ ]));
+ }
+
+ // TODO(vsm): Make this optional per #268.
+ // Metadata
+ if (metadata.isNotEmpty) {
+ body.add(js.statement('#[dart.metadata] = () => #;', [
+ name,
+ new JS.ArrayInitializer(
+ new List<JS.Expression>.from(metadata.map(_instantiateAnnotation)))
+ ]));
+ }
+
+ return _statement(body);
+ }
+
+ List<ExecutableElement> _extensionsToImplement(ClassElement element) {
+ var members = <ExecutableElement>[];
+ if (_extensionTypes.contains(element)) return members;
+
+ // Collect all extension types we implement.
+ var type = element.type;
+ var types = new Set<ClassElement>();
+ _collectExtensions(type, types);
+ if (types.isEmpty) return members;
+
+ // Collect all possible extension method names.
+ var extensionMembers = new HashSet<String>();
+ for (var t in types) {
+ for (var m in [t.methods, t.accessors].expand((e) => e)) {
+ if (!m.isStatic) extensionMembers.add(m.name);
+ }
+ }
+
+ // Collect all of extension methods this type implements.
+ for (var m in [type.methods, type.accessors].expand((e) => e)) {
+ if (!m.isStatic && !m.isAbstract && extensionMembers.contains(m.name)) {
+ members.add(m);
+ }
+ }
+ return members;
+ }
+
+ /// Collections the type and all supertypes, including interfaces, but
+ /// excluding [Object].
+ void _collectExtensions(InterfaceType type, Set<ClassElement> types) {
+ if (type.isObject) return;
+ var element = type.element;
+ if (_extensionTypes.contains(element)) types.add(element);
+ for (var m in type.mixins.reversed) {
+ _collectExtensions(m, types);
+ }
+ for (var i in type.interfaces) {
+ _collectExtensions(i, types);
+ }
+ _collectExtensions(type.superclass, types);
+ }
+
+ JS.Statement _overrideField(FieldElement e) {
+ var cls = e.enclosingElement;
+ return js.statement('dart.virtualField(#, #)',
+ [cls.name, _emitMemberName(e.name, type: cls.type)]);
+ }
+
+ /// Generates the implicit default constructor for class C of the form
+ /// `C() : super() {}`.
+ DartCallableDeclaration _emitImplicitConstructor(
+ ClassDeclaration node, List<FieldDeclaration> fields) {
+ assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty);
+
+ // If we don't have a method body, skip this.
+ var superCall = _superConstructorCall(node.element);
+ if (fields.isEmpty && superCall == null) return null;
+
+ dynamic body = _initializeFields(node, fields);
+ if (superCall != null) body = [
+ [body, superCall]
+ ];
+ return newDartConstructor(node.element, js.call('function() { #; }', body) as JS.Fun);
+ }
+
+ DartCallableDeclaration _emitConstructor(ConstructorDeclaration node, InterfaceType type,
+ List<FieldDeclaration> fields, bool isObject) {
+ if (_externalOrNative(node)) return null;
+
+ var name = _constructorName(node.element);
+
+ // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
+ var redirect = node.redirectedConstructor;
+ if (redirect != null) {
+ var newKeyword = redirect.staticElement.isFactory ? '' : 'new';
+ // Pass along all arguments verbatim, and let the callee handle them.
+ // 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);
+
+ var fun = js.call('function(#) { return $newKeyword #(#); }',
+ [params, _visit(redirect), params]) as JS.Fun;
+ return annotate(
+ new JS.Method(name, fun, isStatic: true)..sourceInformation = node,
+ node.element);
+ }
+
+ // Factory constructors are essentially static methods.
+ if (node.factoryKeyword != null) {
+ var body = <JS.Statement>[];
+ var init = _emitArgumentInitializers(node, constructor: true);
+ if (init != null) body.add(init);
+ body.add(_visit(node.body));
+ var fun = new JS.Fun(
+ _visit(node.parameters) as List<JS.Parameter>, new JS.Block(body));
+ return newDartConstructor(node.element, fun);
+ }
+
+ // Code generation for Object's constructor.
+ JS.Block body;
+ if (isObject &&
+ node.body is EmptyFunctionBody &&
+ node.constKeyword != null &&
+ node.name == null) {
+ // Implements Dart constructor behavior. Because of V8 `super`
+ // [constructor restrictions]
+ // (https://code.google.com/p/v8/issues/detail?id=3330#c65)
+ // we cannot currently emit actual ES6 constructors with super calls.
+ // Instead we use the same trick as named constructors, and do them as
+ // instance methods that perform initialization.
+ // TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8
+ // settles. See <https://github.com/dart-lang/dev_compiler/issues/51>.
+ // Performance of this pattern is likely to be bad.
+ name = _propertyName('constructor');
+ // Mark the parameter as no-rename.
+ body = js.statement('''{
+ // Get the class name for this instance.
+ let name = this.constructor.name;
+ // Call the default constructor.
+ let result = void 0;
+ if (name in this) result = this[name](...arguments);
+ return result === void 0 ? this : result;
+ }''') as JS.Block;
+ } else {
+ body = _emitConstructorBody(node, fields);
+ }
+
+ // We generate constructors as initializer methods in the class;
+ // this allows use of `super` for instance methods/properties.
+ // It also avoids V8 restrictions on `super` in default constructors.
+ return newDartConstructor(
+ node.element,
+ new JS.Fun(_visit(node.parameters) as List<JS.Parameter>, body));
+ }
+
+ JS.Expression _constructorName(ConstructorElement ctor) {
+ var name = ctor.name;
+ if (name != '') {
+ return _emitMemberName(name, isStatic: true);
+ }
+
+ // Factory default constructors use `new` as their name, for readability
+ // Other default constructors use the class name, as they aren't called
+ // from call sites, but rather from Object's constructor.
+ // TODO(jmesserly): revisit in the context of Dart metaclasses, and cleaning
+ // up constructors to integrate more closely with ES6.
+ return _propertyName(ctor.isFactory ? 'new' : ctor.enclosingElement.name);
+ }
+
+ JS.Block _emitConstructorBody(
+ ConstructorDeclaration node, List<FieldDeclaration> fields) {
+ var body = <JS.Statement>[];
+
+ // Generate optional/named argument value assignment. These can not have
+ // side effects, and may be used by the constructor's initializers, so it's
+ // nice to do them first.
+ // Also for const constructors we need to ensure default values are
+ // available for use by top-level constant initializers.
+ ClassDeclaration cls = node.parent;
+ if (node.constKeyword != null) _loader.startTopLevel(cls.element);
+ var init = _emitArgumentInitializers(node, constructor: true);
+ if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
+ if (init != null) body.add(init);
+
+ // Redirecting constructors: these are not allowed to have initializers,
+ // and the redirecting ctor invocation runs before field initializers.
+ var redirectCall = node.initializers.firstWhere(
+ (i) => i is RedirectingConstructorInvocation,
+ orElse: () => null);
+
+ if (redirectCall != null) {
+ body.add(_visit(redirectCall));
+ return new JS.Block(body);
+ }
+
+ // Generate field initializers.
+ // These are expanded into each non-redirecting constructor.
+ // In the future we may want to create an initializer function if we have
+ // multiple constructors, but it needs to be balanced against readability.
+ body.add(_initializeFields(cls, fields, node));
+
+ var superCall = node.initializers.firstWhere(
+ (i) => i is SuperConstructorInvocation,
+ orElse: () => null) as SuperConstructorInvocation;
+
+ // If no superinitializer is provided, an implicit superinitializer of the
+ // form `super()` is added at the end of the initializer list, unless the
+ // enclosing class is class Object.
+ var jsSuper = _superConstructorCall(cls.element, superCall);
+ if (jsSuper != null) body.add(jsSuper);
+
+ body.add(_visit(node.body));
+ return new JS.Block(body)..sourceInformation = node;
+ }
+
+ @override
+ JS.Statement visitRedirectingConstructorInvocation(
+ RedirectingConstructorInvocation node) {
+ var name = _constructorName(node.staticElement);
+ return js.statement('this.#(#);', [name, _visit(node.argumentList)]);
+ }
+
+ JS.Statement _superConstructorCall(ClassElement element,
+ [SuperConstructorInvocation node]) {
+ ConstructorElement superCtor;
+ if (node != null) {
+ superCtor = node.staticElement;
+ } else {
+ // Get the supertype's unnamed constructor.
+ superCtor = element.supertype.element.unnamedConstructor;
+ if (superCtor == null) {
+ // This will only happen if the code has errors:
+ // we're trying to generate an implicit constructor for a type where
+ // we don't have a default constructor in the supertype.
+ assert(options.forceCompile);
+ return null;
+ }
+ }
+
+ if (superCtor.name == '' && !_shouldCallUnnamedSuperCtor(element)) {
+ return null;
+ }
+
+ var name = _constructorName(superCtor);
+ var args = node != null ? _visit(node.argumentList) : [];
+ return js.statement('super.#(#);', [name, args])..sourceInformation = node;
+ }
+
+ bool _shouldCallUnnamedSuperCtor(ClassElement e) {
+ var supertype = e.supertype;
+ if (supertype == null) return false;
+ if (_hasUnnamedConstructor(supertype.element)) return true;
+ for (var mixin in e.mixins) {
+ if (_hasUnnamedConstructor(mixin.element)) return true;
+ }
+ return false;
+ }
+
+ bool _hasUnnamedConstructor(ClassElement e) {
+ if (e.type.isObject) return false;
+ if (!e.unnamedConstructor.isSynthetic) return true;
+ return e.fields.any((f) => !f.isStatic && !f.isSynthetic);
+ }
+
+ /// Initialize fields. They follow the sequence:
+ ///
+ /// 1. field declaration initializer if non-const,
+ /// 2. field initializing parameters,
+ /// 3. constructor field initializers,
+ /// 4. initialize fields not covered in 1-3
+ JS.Statement _initializeFields(
+ ClassDeclaration cls, List<FieldDeclaration> fieldDecls,
+ [ConstructorDeclaration ctor]) {
+ var unit = cls.getAncestor((a) => a is CompilationUnit) as CompilationUnit;
+ var constField = new ConstFieldVisitor(types, unit);
+ bool isConst = ctor != null && ctor.constKeyword != null;
+ if (isConst) _loader.startTopLevel(cls.element);
+
+ // Run field initializers if they can have side-effects.
+ var fields = new Map<FieldElement, JS.Expression>();
+ var unsetFields = new Map<FieldElement, VariableDeclaration>();
+ for (var declaration in fieldDecls) {
+ for (var fieldNode in declaration.fields.variables) {
+ var element = fieldNode.element;
+ if (constField.isFieldInitConstant(fieldNode)) {
+ unsetFields[element as FieldElement] = fieldNode;
+ } else {
+ fields[element as FieldElement] = _visitInitializer(fieldNode);
+ }
+ }
+ }
+
+ // Initialize fields from `this.fieldName` parameters.
+ if (ctor != null) {
+ for (var p in ctor.parameters.parameters) {
+ var element = p.element;
+ if (element is FieldFormalParameterElement) {
+ fields[element.field] = _visit(p);
+ }
+ }
+
+ // Run constructor field initializers such as `: foo = bar.baz`
+ for (var init in ctor.initializers) {
+ if (init is ConstructorFieldInitializer) {
+ fields[init.fieldName.staticElement as FieldElement] =
+ _visit(init.expression);
+ }
+ }
+ }
+
+ for (var f in fields.keys) unsetFields.remove(f);
+
+ // Initialize all remaining fields
+ unsetFields.forEach((element, fieldNode) {
+ JS.Expression value;
+ if (fieldNode.initializer != null) {
+ value = _visit(fieldNode.initializer);
+ } else {
+ value = new JS.LiteralNull();
+ }
+ fields[element] = value;
+ });
+
+ var body = <JS.Statement>[];
+ fields.forEach((FieldElement e, JS.Expression initialValue) {
+ var access = _emitMemberName(e.name, type: e.enclosingElement.type);
+ body.add(js.statement('this.# = #;', [access, initialValue]));
+ });
+
+ if (isConst) _loader.finishTopLevel(cls.element);
+ return _statement(body);
+ }
+
+ FormalParameterList _parametersOf(node) {
+ // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we
+ // could handle argument initializers more consistently in a separate
+ // lowering pass.
+ if (node is ConstructorDeclaration) return node.parameters;
+ if (node is MethodDeclaration) return node.parameters;
+ if (node is FunctionDeclaration) node = node.functionExpression;
+ return (node as FunctionExpression).parameters;
+ }
+
+ /// Emits argument initializers, which handles optional/named args, as well
+ /// as generic type checks needed due to our covariance.
+ JS.Statement _emitArgumentInitializers(node, {bool constructor: false}) {
+ // Constructor argument initializers are emitted earlier in the code, rather
+ // than always when we visit the function body, so we control it explicitly.
+ if (node is ConstructorDeclaration != constructor) return null;
+
+ var parameters = _parametersOf(node);
+ if (parameters == null) return null;
+
+ var body = <JS.Statement>[];
+ for (var param in parameters.parameters) {
+ var jsParam = _visit(param.identifier);
+
+ if (param.kind == ParameterKind.NAMED) {
+ if (!_isDestructurableNamedParam(param)) {
+ // Parameters will be passed using their real names, not the (possibly
+ // renamed) local variable.
+ var paramName = js.string(param.identifier.name, "'");
+
+ // TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming.
+ body.add(js.statement('let # = # && # in # ? #.# : #;', [
+ jsParam,
+ _namedArgTemp,
+ paramName,
+ _namedArgTemp,
+ _namedArgTemp,
+ paramName,
+ _defaultParamValue(param),
+ ]));
+ }
+ } else if (param.kind == ParameterKind.POSITIONAL) {
+ body.add(js.statement('if (# === void 0) # = #;',
+ [jsParam, jsParam, _defaultParamValue(param)]));
+ }
+
+ // TODO(jmesserly): various problems here, see:
+ // https://github.com/dart-lang/dev_compiler/issues/161
+ var paramType = param.element.type;
+ if (!constructor && _hasTypeParameter(paramType)) {
+ body.add(js.statement(
+ 'dart.as(#, #);', [jsParam, _emitTypeName(paramType)]));
+ }
+ }
+ return body.isEmpty ? null : _statement(body);
+ }
+
+ bool _hasTypeParameter(DartType t) => t is TypeParameterType ||
+ t is ParameterizedType && t.typeArguments.any(_hasTypeParameter);
+
+ JS.Expression _defaultParamValue(FormalParameter param) {
+ if (param is DefaultFormalParameter && param.defaultValue != null) {
+ return _visit(param.defaultValue);
+ } else {
+ return new JS.LiteralNull();
+ }
+ }
+
+ JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
+ if (node.isAbstract || _externalOrNative(node)) {
+ return null;
+ }
+
+ var params = _visit(node.parameters) as List<JS.Parameter>;
+ if (params == null) params = <JS.Parameter>[];
+
+ JS.Fun fn = _emitFunctionBody(params, node.body);
+ 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]))
+ ..sourceInformation = fn.sourceInformation;
+ }
+
+ return annotate(
+ new JS.Method(_elementMemberName(node.element), fn,
+ isGetter: node.isGetter,
+ isSetter: node.isSetter,
+ isStatic: node.isStatic),
+ node.element);
+ }
+
+ @override
+ DartCallableDeclaration visitFunctionDeclaration(FunctionDeclaration node) {
+ assert(node.parent is CompilationUnit);
+
+ if (_externalOrNative(node)) return null;
+
+ if (node.isGetter || node.isSetter) {
+ // Add these later so we can use getter/setter syntax.
+ _properties.add(node);
+ return null;
+ }
+
+ var body = <JS.Statement>[];
+ _flushLibraryProperties(body);
+
+ var name = node.name.name;
+
+ var fn = _visit(node.functionExpression);
+ bool needsTagging = true;
+
+ if (currentLibrary.source.isInSystemLibrary &&
+ _isInlineJSFunction(node.functionExpression)) {
+ fn = _simplifyPassThroughArrowFunCallBody(fn);
+ needsTagging = !_isDartUtils;
+ }
+
+ var id = new JS.Identifier(name);
+ body.add(annotate(new JS.FunctionDeclaration(id, fn), node.element));
+ if (needsTagging) {
+ body.add(_emitFunctionTagged(id, node.element.type, topLevel: true)
+ .toStatement());
+ }
+
+ if (isPublic(name)) _addExport(name);
+ return _statement(body);
+ }
+
+ bool _isInlineJSFunction(FunctionExpression functionExpression) {
+ var body = functionExpression.body;
+ if (body is ExpressionFunctionBody) {
+ return _isJSInvocation(body.expression);
+ } else if (body is BlockFunctionBody) {
+ if (body.block.statements.length == 1) {
+ var stat = body.block.statements.single;
+ if (stat is ReturnStatement) {
+ return _isJSInvocation(stat.expression);
+ }
+ }
+ }
+ return false;
+ }
+
+ bool _isJSInvocation(Expression expr) =>
+ expr is MethodInvocation && isInlineJS(expr.methodName.staticElement);
+
+ // Simplify `(args) => ((x, y) => { ... })(x, y)` to `(args) => { ... }`.
+ // Note: we don't check if the top-level args match the ones passed through
+ // the arrow function, which allows silently passing args through to the
+ // body (which only works if we don't do weird renamings of Dart params).
+ JS.Fun _simplifyPassThroughArrowFunCallBody(JS.Fun fn) {
+ String getIdent(JS.Node node) => node is JS.Identifier ? node.name : null;
+ List<String> getIdents(List params) =>
+ params.map(getIdent).toList(growable: false);
+
+ if (fn.body is JS.Block && fn.body.statements.length == 1) {
+ var stat = fn.body.statements.single;
+ if (stat is JS.Return && stat.value is JS.Call) {
+ JS.Call call = stat.value;
+ if (call.target is JS.ArrowFun) {
+ var passedArgs = getIdents(call.arguments);
+ JS.ArrowFun innerFun = call.target;
+ if (_listEquality.equals(getIdents(innerFun.params), passedArgs)) {
+ return new JS.Fun(fn.params, innerFun.body);
+ }
+ }
+ }
+ }
+ return fn;
+ }
+
+ JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
+ var name = node.name.name;
+ return annotate(
+ new JS.Method(_propertyName(name), _visit(node.functionExpression),
+ isGetter: node.isGetter, isSetter: node.isSetter),
+ node.element);
+ }
+
+ bool _executesAtTopLevel(AstNode node) {
+ var ancestor = node.getAncestor((n) => n is FunctionBody ||
+ (n is FieldDeclaration && n.staticKeyword == null) ||
+ (n is ConstructorDeclaration && n.constKeyword == null));
+ return ancestor == null;
+ }
+
+ bool _typeIsLoaded(DartType type) {
+ if (type is FunctionType && (type.name == '' || type.name == null)) {
+ return (_typeIsLoaded(type.returnType) &&
+ type.optionalParameterTypes.every(_typeIsLoaded) &&
+ type.namedParameterTypes.values.every(_typeIsLoaded) &&
+ type.normalParameterTypes.every(_typeIsLoaded));
+ }
+ if (type.isDynamic || type.isVoid || type.isBottom) return true;
+ if (type is ParameterizedType && !type.typeArguments.every(_typeIsLoaded)) {
+ return false;
+ }
+ return _loader.isLoaded(type.element);
+ }
+
+ JS.Expression _emitFunctionTagged(JS.Expression clos, DartType type,
+ {topLevel: false}) {
+ var name = type.name;
+ var lazy = topLevel && !_typeIsLoaded(type);
+
+ if (type is FunctionType && (name == '' || name == null)) {
+ if (type.returnType.isDynamic &&
+ type.optionalParameterTypes.isEmpty &&
+ type.namedParameterTypes.isEmpty &&
+ type.normalParameterTypes.every((t) => t.isDynamic)) {
+ return js.call('dart.fn(#)', [clos]);
+ }
+ if (lazy) {
+ return js.call('dart.fn(#, () => #)', [clos, _emitFunctionRTTI(type)]);
+ }
+ return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]);
+ }
+ throw 'Function has non function type: $type';
+ }
+
+ @override
+ JS.Expression visitFunctionExpression(FunctionExpression node) {
+ var params = _visit(node.parameters) as List<JS.Parameter>;
+ if (params == null) params = <JS.Parameter>[];
+
+ var parent = node.parent;
+ var inStmt = parent.parent is FunctionDeclarationStatement;
+ if (parent is FunctionDeclaration) {
+ return _emitFunctionBody(params, node.body);
+ } 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);
+
+ String code = canUseArrowFun ? '(#) => #' : 'function(#) { return # }';
+ JS.Node jsBody;
+ var body = node.body;
+ if (body.isGenerator || body.isAsynchronous) {
+ jsBody = _emitGeneratorFunctionBody(params, body);
+ } else if (body is ExpressionFunctionBody) {
+ jsBody = _visit(body.expression);
+ } else {
+ code = canUseArrowFun ? '(#) => { #; }' : 'function(#) { #; }';
+ jsBody = _visit(body);
+ }
+ var clos = js.call(code, [params, jsBody]);
+ if (!inStmt) {
+ var type = getStaticType(node);
+ return _emitFunctionTagged(clos, type,
+ topLevel: _executesAtTopLevel(node));
+ }
+ return clos;
+ }
+ }
+
+ JS.Fun _emitFunctionBody(List<JS.Parameter> params, FunctionBody body) {
+ // sync*, async, async*
+ if (body.isAsynchronous || body.isGenerator) {
+ return new JS.Fun(
+ params,
+ js.statement(
+ '{ return #; }', [_emitGeneratorFunctionBody(params, body)]));
+ }
+ // normal function (sync)
+ return new JS.Fun(params, _visit(body));
+ }
+
+ JS.Expression _emitGeneratorFunctionBody(
+ List<JS.Parameter> params, FunctionBody body) {
+ var kind = body.isSynchronous ? 'sync' : 'async';
+ if (body.isGenerator) kind += 'Star';
+
+ // Transforms `sync*` `async` and `async*` function bodies
+ // using ES6 generators.
+ //
+ // `sync*` wraps a generator in a Dart Iterable<T>:
+ //
+ // function name(<args>) {
+ // return dart.syncStar(function*(<args>) {
+ // <body>
+ // }, T, <args>).bind(this);
+ // }
+ //
+ // We need to include <args> in case any are mutated, so each `.iterator`
+ // gets the same initial values.
+ //
+ // TODO(jmesserly): we could omit the args for the common case where args
+ // are not mutated inside the generator.
+ //
+ // In the future, we might be able to simplify this, see:
+ // https://github.com/dart-lang/dev_compiler/issues/247.
+ //
+ // `async` works the same, but uses the `dart.async` helper.
+ //
+ // In the body of a `sync*` and `async`, `yield`/`await` are both generated
+ // simply as `yield`.
+ //
+ // `async*` uses the `dart.asyncStar` helper, and also has an extra `stream`
+ // argument to the generator, which is used for passing values to the
+ // _AsyncStarStreamController implementation type.
+ // `yield` is specially generated inside `async*`, see visitYieldStatement.
+ // `await` is generated as `yield`.
+ // runtime/_generators.js has an example of what the code is generated as.
+ var savedController = _asyncStarController;
+ List jsParams;
+ if (kind == 'asyncStar') {
+ _asyncStarController = new JS.TemporaryId('stream');
+ jsParams = [_asyncStarController]..addAll(params);
+ } else {
+ _asyncStarController = null;
+ jsParams = params;
+ }
+ JS.Expression gen = new JS.Fun(jsParams, _visit(body), isGenerator: true);
+ if (JS.This.foundIn(gen)) {
+ gen = js.call('#.bind(this)', gen);
+ }
+ _asyncStarController = savedController;
+
+ var T = _emitTypeName(rules.getExpectedReturnType(body));
+ return js.call('dart.#(#)', [
+ kind,
+ [gen, T]..addAll(params)
+ ]);
+ }
+
+ @override
+ JS.Statement visitFunctionDeclarationStatement(
+ FunctionDeclarationStatement node) {
+ var func = node.functionDeclaration;
+ if (func.isGetter || func.isSetter) {
+ return js.comment('Unimplemented function get/set statement: $node');
+ }
+
+ var fn = _visit(func.functionExpression);
+
+ var name = new JS.Identifier(func.name.name);
+ JS.Statement declareFn;
+ if (JS.This.foundIn(fn)) {
+ declareFn = js.statement('const # = #.bind(this);', [name, fn]);
+ } else {
+ declareFn = new JS.FunctionDeclaration(name, fn);
+ }
+ declareFn = annotate(declareFn, node.functionDeclaration.element);
+
+ return new JS.Block([
+ declareFn,
+ _emitFunctionTagged(name, func.element.type).toStatement()
+ ]);
+ }
+
+ /// 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) {
+ var accessor = node.staticElement;
+ if (accessor == null) {
+ return js.commentExpression(
+ 'Unimplemented unknown name', new JS.Identifier(node.name));
+ }
+
+ // Get the original declaring element. If we had a property accessor, this
+ // indirects back to a (possibly synthetic) field.
+ var element = accessor;
+ if (accessor is PropertyAccessorElement) element = accessor.variable;
+
+ _loader.declareBeforeUse(element);
+
+ var name = element.name;
+
+ // type literal
+ if (element is ClassElement ||
+ element is DynamicElementImpl ||
+ element is FunctionTypeAliasElement) {
+ return _emitTypeName(
+ fillDynamicTypeArgs((element as dynamic).type, types));
+ }
+
+ // library member
+ if (element.enclosingElement is CompilationUnitElement) {
+ return _maybeQualifiedName(element);
+ }
+
+ // Unqualified class member. This could mean implicit-this, or implicit
+ // call to a static from the same class.
+ if (element is ClassMemberElement && element is! ConstructorElement) {
+ bool isStatic = element.isStatic;
+ var type = element.enclosingElement.type;
+ var member = _emitMemberName(name, isStatic: isStatic, type: type);
+
+ // For static methods, we add the raw type name, without generics or
+ // library prefix. We don't need those because static calls can't use
+ // the generic type.
+ if (isStatic) {
+ var dynType = _emitTypeName(fillDynamicTypeArgs(type, types));
+ return new JS.PropertyAccess(dynType, member);
+ }
+
+ // For instance members, we add implicit-this.
+ // For method tear-offs, we ensure it's a bound method.
+ var tearOff = element is MethodElement && !inInvocationContext(node);
+ var code = (tearOff) ? 'dart.bind(this, #)' : 'this.#';
+ return js.call(code, member);
+ }
+
+ // initializing formal parameter, e.g. `Point(this.x)`
+ if (element is ParameterElement &&
+ element.isInitializingFormal &&
+ element.isPrivate) {
+ /// Rename private names so they don't shadow the private field symbol.
+ /// The renamer would handle this, but it would prefer to rename the
+ /// temporary used for the private symbol. Instead rename the parameter.
+ return _getTemp(element, '${name.substring(1)}');
+ }
+
+ if (element is TemporaryVariableElement) {
+ if (name[0] == '#') {
+ return new JS.InterpolatedExpression(name.substring(1));
+ } else {
+ return _getTemp(element, name);
+ }
+ }
+
+ return new JS.Identifier(name);
+ }
+
+ JS.TemporaryId _getTemp(Element key, String name) =>
+ _temps.putIfAbsent(key, () => new JS.TemporaryId(name));
+
+ List<Annotation> _parameterMetadata(FormalParameter p) =>
+ (p is NormalFormalParameter)
+ ? p.metadata
+ : (p as DefaultFormalParameter).parameter.metadata;
+
+ JS.ArrayInitializer _emitTypeNames(List<DartType> types,
+ [List<FormalParameter> parameters]) {
+ var result = <JS.Expression>[];
+ for (int i = 0; i < types.length; ++i) {
+ var metadata =
+ parameters != null ? _parameterMetadata(parameters[i]) : [];
+ var typeName = _emitTypeName(types[i]);
+ var value = typeName;
+ // TODO(vsm): Make this optional per #268.
+ if (metadata.isNotEmpty) {
+ metadata = metadata.map(_instantiateAnnotation).toList();
+ value = new JS.ArrayInitializer([typeName]..addAll(metadata));
+ }
+ result.add(value);
+ }
+ return new JS.ArrayInitializer(result);
+ }
+
+ JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) {
+ var properties = <JS.Property>[];
+ types.forEach((name, type) {
+ var key = _propertyName(name);
+ var value = _emitTypeName(type);
+ properties.add(new JS.Property(key, value));
+ });
+ return new JS.ObjectInitializer(properties);
+ }
+
+ /// Emit the pieces of a function type, as an array of return type,
+ /// regular args, and optional/named args.
+ List<JS.Expression> _emitFunctionTypeParts(FunctionType type,
+ [FormalParameterList parameterList]) {
+ var parameters = parameterList?.parameters;
+ var returnType = type.returnType;
+ var parameterTypes = type.normalParameterTypes;
+ var optionalTypes = type.optionalParameterTypes;
+ var namedTypes = type.namedParameterTypes;
+ var rt = _emitTypeName(returnType);
+ var ra = _emitTypeNames(parameterTypes, parameters);
+ if (!namedTypes.isEmpty) {
+ assert(optionalTypes.isEmpty);
+ // TODO(vsm): Pass in annotations here as well.
+ var na = _emitTypeProperties(namedTypes);
+ return [rt, ra, na];
+ }
+ if (!optionalTypes.isEmpty) {
+ assert(namedTypes.isEmpty);
+ var oa = _emitTypeNames(
+ optionalTypes, parameters?.sublist(parameterTypes.length));
+ return [rt, ra, oa];
+ }
+ return [rt, ra];
+ }
+
+ JS.Expression _emitFunctionRTTI(FunctionType type) {
+ var parts = _emitFunctionTypeParts(type);
+ return js.call('dart.definiteFunctionType(#)', [parts]);
+ }
+
+ JS.Expression _maybeQualifiedName(Element e, [String name]) {
+ var libName = _libraryName(e.library);
+ var nameExpr = _propertyName(name ?? e.name);
+
+ // Always qualify:
+ // * mutable top-level fields
+ // * elements from other libraries
+ bool mutableTopLevel = e is TopLevelVariableElement &&
+ !e.isConst &&
+ !_isFinalJSDecl(e.computeNode());
+ bool fromAnotherLibrary = e.library != currentLibrary;
+ if (mutableTopLevel || fromAnotherLibrary) {
+ return new JS.PropertyAccess(libName, nameExpr);
+ }
+
+ var id = new JS.MaybeQualifiedId(libName, nameExpr);
+ _qualifiedIds.add(new Tuple2(e, id));
+ return id;
+ }
+
+ @override
+ JS.Expression visitAssignmentExpression(AssignmentExpression node) {
+ var left = node.leftHandSide;
+ var right = node.rightHandSide;
+ if (node.operator.type == TokenType.EQ) return _emitSet(left, right);
+ var op = node.operator.lexeme;
+ assert(op.endsWith('='));
+ op = op.substring(0, op.length - 1); // remove trailing '='
+ return _emitOpAssign(left, right, op, node.staticElement, context: node);
+ }
+
+ JS.MetaLet _emitOpAssign(
+ Expression left, Expression right, String op, MethodElement element,
+ {Expression context}) {
+ if (op == '??') {
+ // Desugar `l ??= r` as ((x) => x == null ? l = r : x)(l)
+ // Note that if `x` contains subexpressions, we need to ensure those
+ // are also evaluated only once. This is similar to desguaring for
+ // postfix expressions like `i++`.
+
+ // Handle the left hand side, to ensure each of its subexpressions are
+ // evaluated only once.
+ var vars = <String, JS.Expression>{};
+ var x = _bindLeftHandSide(vars, left, context: left);
+ // Capture the result of evaluating the left hand side in a temp.
+ var t = _bindValue(vars, 't', x, context: x);
+ return new JS.MetaLet(vars, [
+ js.call('# == null ? # : #', [_visit(t), _emitSet(x, right), _visit(t)])
+ ]);
+ }
+
+ // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
+ // (for example, x is IndexExpression) we evaluate those once.
+ var vars = <String, JS.Expression>{};
+ var lhs = _bindLeftHandSide(vars, left, context: context);
+ var inc = AstBuilder.binaryExpression(lhs, op, right);
+ inc.staticElement = element;
+ inc.staticType = getStaticType(left);
+ return new JS.MetaLet(vars, [_emitSet(lhs, inc)]);
+ }
+
+ JS.Expression _emitSet(Expression lhs, Expression rhs) {
+ if (lhs is IndexExpression) {
+ var target = _getTarget(lhs);
+ if (_useNativeJsIndexer(target.staticType)) {
+ return js.call(
+ '#[#] = #', [_visit(target), _visit(lhs.index), _visit(rhs)]);
+ }
+ return _emitSend(target, '[]=', [lhs.index, rhs]);
+ }
+
+ Expression target = null;
+ SimpleIdentifier id;
+ if (lhs is PropertyAccess) {
+ if (lhs.operator.lexeme == '?.') {
+ return _emitNullSafeSet(lhs, rhs);
+ }
+
+ target = _getTarget(lhs);
+ id = lhs.propertyName;
+ } else if (lhs is PrefixedIdentifier) {
+ target = lhs.prefix;
+ id = lhs.identifier;
+ }
+
+ if (target != null && DynamicInvoke.get(target)) {
+ return js.call('dart.$DPUT(#, #, #)', [
+ _visit(target),
+ _emitMemberName(id.name, type: getStaticType(target)),
+ _visit(rhs)
+ ]);
+ }
+
+ return _visit(rhs).toAssignExpression(_visit(lhs));
+ }
+
+ JS.Expression _emitNullSafeSet(PropertyAccess node, Expression right) {
+ // Emit `obj?.prop = expr` as:
+ //
+ // (_ => _ == null ? null : _.prop = expr)(obj).
+ //
+ // We could use a helper, e.g.: `nullSafeSet(e1, _ => _.v = e2)`
+ //
+ // However with MetaLet, we get clean code in statement or void context,
+ // or when one of the expressions is stateless, which seems common.
+ var vars = <String, JS.Expression>{};
+ var left = _bindValue(vars, 'l', node.target);
+ var body = js.call('# == null ? null : #',
+ [_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]);
+ return new JS.MetaLet(vars, [body]);
+ }
+
+ @override
+ JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) {
+ var initArgs = _emitArgumentInitializers(node.parent);
+ var ret = new JS.Return(_visit(node.expression));
+ return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]);
+ }
+
+ @override
+ JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]);
+
+ @override
+ JS.Block visitBlockFunctionBody(BlockFunctionBody node) {
+ var initArgs = _emitArgumentInitializers(node.parent);
+ var stmts = _visitList(node.block.statements) as List<JS.Statement>;
+ if (initArgs != null) stmts.insert(0, initArgs);
+ return new JS.Block(stmts);
+ }
+
+ @override
+ JS.Block visitBlock(Block node) =>
+ new JS.Block(_visitList(node.statements) as List<JS.Statement>,
+ isScope: true);
+
+ @override
+ JS.Expression visitMethodInvocation(MethodInvocation node) {
+ if (node.operator != null && node.operator.lexeme == '?.') {
+ return _emitNullSafe(node);
+ }
+
+ var target = _getTarget(node);
+ var result = _emitForeignJS(node);
+ if (result != null) return result;
+
+ String code;
+
+ var methodName = _visit(node.methodName);
+ var arguments = _visit(node.argumentList);
+ if (target == null || isLibraryPrefix(target)) {
+ return newDartMethodCall(null, methodName, arguments);
+ if (DynamicInvoke.get(node.methodName)) {
+ code = 'dart.$DCALL(#, #)';
+ } else {
+ code = '#(#)';
+ }
+ return js.call(
+ code, [_visit(node.methodName), _visit(node.argumentList)]);
+ }
+
+ var type = getStaticType(target);
+ var name = node.methodName.name;
+ var element = node.methodName.staticElement;
+ bool isStatic = element is ExecutableElement && element.isStatic;
+ var memberName = _emitMemberName(name, type: type, isStatic: isStatic);
+
+ if (DynamicInvoke.get(target)) {
+ code = 'dart.$DSEND(#, #, #)';
+ } else if (DynamicInvoke.get(node.methodName)) {
+ // This is a dynamic call to a statically known target. For example:
+ // class Foo { Function bar; }
+ // new Foo().bar(); // dynamic call
+ code = 'dart.$DCALL(#.#, #)';
+ } else if (_requiresStaticDispatch(target, name)) {
+ assert(rules.objectMembers[name] is FunctionType);
+ // Object methods require a helper for null checks.
+ return js.call('dart.#(#, #)',
+ [memberName, _visit(target), _visit(node.argumentList)]);
+ } else {
+ code = '#.#(#)';
+ }
+
+ return js.call(
+ code, [_visit(target), memberName, _visit(node.argumentList)]);
+ }
+
+ /// Emits code for the `JS(...)` builtin.
+ _emitForeignJS(MethodInvocation node) {
+ var e = node.methodName.staticElement;
+ if (isInlineJS(e)) {
+ var args = node.argumentList.arguments;
+ // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer`
+ var code = args[1];
+ var templateArgs;
+ var source;
+ if (code is StringInterpolation) {
+ if (args.length > 2) {
+ throw new ArgumentError(
+ "Can't mix template args and string interpolation in JS calls.");
+ }
+ templateArgs = <Expression>[];
+ source = code.elements.map((element) {
+ if (element is InterpolationExpression) {
+ templateArgs.add(element.expression);
+ return '#';
+ } else {
+ return (element as InterpolationString).value;
+ }
+ }).join();
+ } else {
+ templateArgs = args.skip(2);
+ source = (code as StringLiteral).stringValue;
+ }
+
+ var template = js.parseForeignJS(source);
+ var result = template.instantiate(_visitList(templateArgs));
+ // `throw` is emitted as a statement by `parseForeignJS`.
+ assert(result is JS.Expression || node.parent is ExpressionStatement);
+ return result;
+ }
+ return null;
+ }
+
+ @override
+ JS.Expression visitFunctionExpressionInvocation(
+ FunctionExpressionInvocation node) {
+ var code;
+ if (DynamicInvoke.get(node.function)) {
+ code = 'dart.$DCALL(#, #)';
+ } else {
+ code = '#(#)';
+ }
+ return js.call(code, [_visit(node.function), _visit(node.argumentList)]);
+ }
+
+ @override
+ List<JS.Expression> visitArgumentList(ArgumentList node) {
+ var args = <JS.Expression>[];
+ var named = <JS.Property>[];
+ for (var arg in node.arguments) {
+ if (arg is NamedExpression) {
+ named.add(_visit(arg));
+ } else if (arg is MethodInvocation && isJsSpreadInvocation(arg)) {
+ args.add(
+ new JS.RestParameter(_visit(arg.argumentList.arguments.single)));
+ } else {
+ args.add(_visit(arg));
+ }
+ }
+ if (named.isNotEmpty) {
+ args.add(new JS.ObjectInitializer(named));
+ }
+ return args;
+ }
+
+ @override
+ JS.Property visitNamedExpression(NamedExpression node) {
+ assert(node.parent is ArgumentList);
+ return new JS.Property(
+ _propertyName(node.name.label.name), _visit(node.expression));
+ }
+
+ bool _isNamedParam(FormalParameter param) =>
+ param.kind == ParameterKind.NAMED;
+
+ /// We cannot destructure named params that clash with JS reserved names:
+ /// see discussion in https://github.com/dart-lang/dev_compiler/issues/392.
+ bool _isDestructurableNamedParam(FormalParameter param) =>
+ _isNamedParam(param) &&
+ !invalidVariableName(param.identifier.name) &&
+ options.destructureNamedParams;
+
+ @override
+ List<JS.Parameter> visitFormalParameterList(FormalParameterList node) =>
+ _emitFormalParameterList(node);
+
+ List<JS.Parameter> _emitFormalParameterList(FormalParameterList node,
+ {bool allowDestructuring: true}) {
+ var result = <JS.Parameter>[];
+
+ var namedVars = <JS.DestructuredVariable>[];
+ var destructure = allowDestructuring &&
+ node.parameters.where(_isNamedParam).every(_isDestructurableNamedParam);
+ var hasNamedArgsConflictingWithObjectProperties = false;
+ var needsOpts = false;
+
+ for (FormalParameter param in node.parameters) {
+ if (param.kind == ParameterKind.NAMED) {
+ if (destructure) {
+ if (_jsObjectProperties.contains(param.identifier.name)) {
+ hasNamedArgsConflictingWithObjectProperties = true;
+ }
+ namedVars.add(new JS.DestructuredVariable(
+ name: _visit(param.identifier),
+ defaultValue: _defaultParamValue(param)));
+ } else {
+ needsOpts = true;
+ }
+ } else {
+ result.add(_visit(param));
+ }
+ }
+
+ if (needsOpts) {
+ result.add(_namedArgTemp);
+ } else if (namedVars.isNotEmpty) {
+ // Note: `var {valueOf} = {}` extracts `Object.prototype.valueOf`, so
+ // in case there are conflicting names we create an object without
+ // any prototype.
+ var defaultOpts = hasNamedArgsConflictingWithObjectProperties
+ ? js.call('Object.create(null)')
+ : js.call('{}');
+ result.add(new JS.DestructuredVariable(
+ structure: new JS.ObjectBindingPattern(namedVars),
+ defaultValue: defaultOpts));
+ }
+ return result;
+ }
+
+ /// See ES6 spec (and `Object.getOwnPropertyNames(Object.prototype)`):
+ /// http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-object-prototype-object
+ /// http://www.ecma-international.org/ecma-262/6.0/#sec-additional-properties-of-the-object.prototype-object
+ static final Set<String> _jsObjectProperties = new Set<String>()
+ ..addAll([
+ "constructor",
+ "toString",
+ "toLocaleString",
+ "valueOf",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "__defineGetter__",
+ "__lookupGetter__",
+ "__defineSetter__",
+ "__lookupSetter__",
+ "__proto__"
+ ]);
+
+ @override
+ JS.Statement visitExpressionStatement(ExpressionStatement node) =>
+ _visit(node.expression).toStatement();
+
+ @override
+ JS.EmptyStatement visitEmptyStatement(EmptyStatement node) =>
+ new JS.EmptyStatement();
+
+ @override
+ JS.Statement visitAssertStatement(AssertStatement node) =>
+ // TODO(jmesserly): only emit in checked mode.
+ js.statement('dart.assert(#);', _visit(node.condition));
+
+ @override
+ JS.Statement visitReturnStatement(ReturnStatement node) {
+ var e = node.expression;
+ if (e == null) return new JS.Return();
+ return (_visit(e) as JS.Expression).toReturn();
+ }
+
+ @override
+ JS.Statement visitYieldStatement(YieldStatement node) {
+ JS.Expression jsExpr = _visit(node.expression);
+ var star = node.star != null;
+ if (_asyncStarController != null) {
+ // async* yields are generated differently from sync* yields. `yield e`
+ // becomes:
+ //
+ // if (stream.add(e)) return;
+ // yield;
+ //
+ // `yield* e` becomes:
+ //
+ // if (stream.addStream(e)) return;
+ // yield;
+ var helperName = star ? 'addStream' : 'add';
+ return js.statement('{ if(#.#(#)) return; #; }',
+ [_asyncStarController, helperName, jsExpr, new JS.Yield(null)]);
+ }
+ // A normal yield in a sync*
+ return jsExpr.toYieldStatement(star: star);
+ }
+
+ @override
+ JS.Expression visitAwaitExpression(AwaitExpression node) {
+ return new JS.Yield(_visit(node.expression));
+ }
+
+ /// Emits static fields.
+ ///
+ /// Instance fields are emitted in [_initializeFields].
+ ///
+ /// These are generally treated the same as top-level fields, see
+ /// [visitTopLevelVariableDeclaration].
+ @override
+ visitFieldDeclaration(FieldDeclaration node) {
+ if (!node.isStatic) return;
+
+ for (var f in node.fields.variables) {
+ _loader.loadDeclaration(f, f.element);
+ }
+ }
+
+ _addExport(String name) {
+ if (!_exports.add(name)) throw 'Duplicate top level name found: $name';
+ }
+
+ @override
+ JS.Statement visitVariableDeclarationStatement(
+ VariableDeclarationStatement node) {
+ // Special case a single variable with an initializer.
+ // This helps emit cleaner code for things like:
+ // var result = []..add(1)..add(2);
+ if (node.variables.variables.length == 1) {
+ var v = node.variables.variables.single;
+ if (v.initializer != null) {
+ var name = new JS.Identifier(v.name.name);
+ return _visit(v.initializer).toVariableDeclaration(name);
+ }
+ }
+ return _visit(node.variables).toStatement();
+ }
+
+ @override
+ visitVariableDeclarationList(VariableDeclarationList node) {
+ return new JS.VariableDeclarationList(
+ 'let', _visitList(node.variables) as List<JS.VariableInitialization>);
+ }
+
+ @override
+ visitVariableDeclaration(VariableDeclaration node) {
+ if (node.element is PropertyInducingElement) return _emitStaticField(node);
+
+ var name = new JS.Identifier(node.name.name);
+ return new JS.VariableInitialization(name, _visitInitializer(node));
+ }
+
+ bool _isFinalJSDecl(AstNode field) => field is VariableDeclaration &&
+ field.isFinal &&
+ _isJSInvocation(field.initializer);
+
+ /// Emits a static or top-level field.
+ JS.Statement _emitStaticField(VariableDeclaration field) {
+ PropertyInducingElement element = field.element;
+ assert(element.isStatic);
+
+ bool eagerInit;
+ JS.Expression jsInit;
+ if (field.isConst || _constField.isFieldInitConstant(field)) {
+ // If the field is constant, try and generate it at the top level.
+ _loader.startTopLevel(element);
+ jsInit = _visitInitializer(field);
+ _loader.finishTopLevel(element);
+ eagerInit = _loader.isLoaded(element);
+ } else {
+ jsInit = _visitInitializer(field);
+ eagerInit = false;
+ }
+
+ // Treat `final x = JS('', '...')` as a const (non-lazy) to help compile
+ // runtime helpers.
+ var isJSTopLevel = field.isFinal && _isFinalJSDecl(field);
+ if (isJSTopLevel) eagerInit = true;
+
+ var fieldName = field.name.name;
+ if ((field.isConst && eagerInit && element is TopLevelVariableElement) ||
+ isJSTopLevel) {
+ // constant fields don't change, so we can generate them as `let`
+ // but add them to the module's exports. However, make sure we generate
+ // anything they depend on first.
+
+ if (isPublic(fieldName)) _addExport(fieldName);
+ var declKeyword = field.isConst || field.isFinal ? 'const' : 'let';
+ return annotateVariable(
+ js.statement(
+ '$declKeyword # = #;', [new JS.Identifier(fieldName), jsInit]),
+ field.element);
+ }
+
+ if (eagerInit && !JS.invalidStaticFieldName(fieldName)) {
+ return annotateVariable(
+ js.statement('# = #;', [_visit(field.name), jsInit]), field.element);
+ }
+
+ var body = <JS.Statement>[];
+ if (_lazyFields.isNotEmpty) {
+ var existingTarget = _lazyFields[0].element.enclosingElement;
+ if (existingTarget != element.enclosingElement) {
+ _flushLazyFields(body);
+ }
+ }
+
+ _lazyFields.add(field);
+
+ return _statement(body);
+ }
+
+ JS.Expression _visitInitializer(VariableDeclaration node) {
+ var value = _visit(node.initializer);
+ // explicitly initialize to null, to avoid getting `undefined`.
+ // TODO(jmesserly): do this only for vars that aren't definitely assigned.
+ return value ?? new JS.LiteralNull();
+ }
+
+ void _flushLazyFields(List<JS.Statement> body) {
+ if (_lazyFields.isEmpty) return;
+ body.add(_emitLazyFields(_lazyFields));
+ _lazyFields.clear();
+ }
+
+ JS.Statement _emitLazyFields(List<VariableDeclaration> fields) {
+ var methods = [];
+ for (var node in fields) {
+ var name = node.name.name;
+ var element = node.element;
+ var access = _emitMemberName(name, type: element.type, isStatic: true);
+ methods.add(annotate(
+ new JS.Method(
+ access,
+ js.call('function() { return #; }', _visit(node.initializer))
+ as JS.Fun,
+ isGetter: true),
+ _findAccessor(element, getter: true)));
+
+ // TODO(jmesserly): use a dummy setter to indicate writable.
+ if (!node.isFinal) {
+ methods.add(annotate(
+ new JS.Method(access, js.call('function(_) {}') as JS.Fun,
+ isSetter: true),
+ _findAccessor(element, getter: false)));
+ }
+ }
+
+ JS.Expression objExpr = _exportsVar;
+ var target = _lazyFields[0].element.enclosingElement;
+ if (target is ClassElement) {
+ objExpr = new JS.Identifier(target.type.name);
+ }
+
+ return js.statement(
+ 'dart.defineLazyProperties(#, { # });', [objExpr, methods]);
+ }
+
+ PropertyAccessorElement _findAccessor(VariableElement element,
+ {bool getter}) {
+ var parent = element.enclosingElement;
+ if (parent is ClassElement) {
+ return getter
+ ? parent.getGetter(element.name)
+ : parent.getSetter(element.name);
+ }
+ return null;
+ }
+
+ void _flushLibraryProperties(List<JS.Statement> body) {
+ if (_properties.isEmpty) return;
+ body.add(js.statement('dart.copyProperties(#, { # });',
+ [_exportsVar, _properties.map(_emitTopLevelProperty)]));
+ _properties.clear();
+ }
+
+ JS.Expression _emitConstructorName(
+ ConstructorElement element, DartType type, SimpleIdentifier name) {
+ var typeName = _emitTypeName(type);
+ if (name != null || element.isFactory) {
+ var namedCtor = _constructorName(element);
+ return new JS.PropertyAccess(typeName, namedCtor);
+ }
+ return typeName;
+ }
+
+ @override
+ visitConstructorName(ConstructorName node) {
+ return _emitConstructorName(node.staticElement, node.type.type, node.name);
+ }
+
+ JS.Expression _emitInstanceCreationExpression(
+ ConstructorElement element,
+ DartType type,
+ SimpleIdentifier name,
+ ArgumentList argumentList,
+ bool isConst) {
+ JS.Expression emitNew() {
+ JS.Expression ctor;
+ bool isFactory = false;
+ // var element = node.staticElement;
+ if (element == null) {
+ // TODO(jmesserly): this only happens if we had a static error.
+ // Should we generate a throw instead?
+ ctor = _emitTypeName(type);
+ if (name != null) {
+ ctor = new JS.PropertyAccess(ctor, _propertyName(name.name));
+ }
+ } else {
+ ctor = _emitConstructorName(element, type, name);
+ isFactory = element.isFactory;
+ }
+ var args = _visit(argumentList) as List<JS.Expression>;
+ return isFactory ? new JS.Call(ctor, args) : new JS.New(ctor, args);
+ }
+ if (isConst) return _emitConst(emitNew);
+ return emitNew();
+ }
+
+ @override
+ visitInstanceCreationExpression(InstanceCreationExpression node) {
+ var element = node.staticElement;
+ var constructor = node.constructorName;
+ var name = constructor.name;
+ var type = constructor.type.type;
+ return _emitInstanceCreationExpression(
+ element, type, name, node.argumentList, node.isConst);
+ }
+
+ /// True if this type is built-in to JS, and we use the values unwrapped.
+ /// For these types we generate a calling convention via static
+ /// "extension methods". This allows types to be extended without adding
+ /// extensions directly on the prototype.
+ bool _isJSBuiltinType(DartType t) =>
+ typeIsPrimitiveInJS(t) || t == _types.stringType;
+
+ bool typeIsPrimitiveInJS(DartType t) =>
+ _isNumberInJS(t) || t == _types.boolType;
+
+ bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) =>
+ typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT);
+
+ bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t);
+
+ bool _isNonNullableExpression(Expression expr) {
+ // TODO(vsm): Revisit whether we really need this when we get
+ // better non-nullability in the type system.
+ // TODO(jmesserly): we do recursive calls in a few places. This could
+ // leads to O(depth) cost for calling this function. We could store the
+ // resulting value if that becomes an issue, so we maintain the invariant
+ // that each node is visited once.
+
+ if (expr is Literal && expr is! NullLiteral) return true;
+ if (expr is IsExpression) return true;
+ if (expr is ThisExpression) return true;
+ if (expr is SuperExpression) return true;
+ if (expr is ParenthesizedExpression) {
+ return _isNonNullableExpression(expr.expression);
+ }
+ if (expr is SimpleIdentifier) {
+ // Type literals are not null.
+ Element e = expr.staticElement;
+ if (e is ClassElement || e is FunctionTypeAliasElement) return true;
+ }
+ DartType type = null;
+ if (expr is BinaryExpression) {
+ switch (expr.operator.type) {
+ case TokenType.EQ_EQ:
+ case TokenType.BANG_EQ:
+ case TokenType.AMPERSAND_AMPERSAND:
+ case TokenType.BAR_BAR:
+ return true;
+ case TokenType.QUESTION_QUESTION:
+ return _isNonNullableExpression(expr.rightOperand);
+ }
+ type = getStaticType(expr.leftOperand);
+ } else if (expr is PrefixExpression) {
+ if (expr.operator.type == TokenType.BANG) return true;
+ type = getStaticType(expr.operand);
+ } else if (expr is PostfixExpression) {
+ type = getStaticType(expr.operand);
+ }
+ if (type != null && _isJSBuiltinType(type)) {
+ return true;
+ }
+ if (expr is MethodInvocation) {
+ // TODO(vsm): This logic overlaps with the resolver.
+ // Where is the best place to put this?
+ var e = expr.methodName.staticElement;
+ if (isInlineJS(e)) {
+ // Fix types for JS builtin calls.
+ //
+ // This code was taken from analyzer. It's not super sophisticated:
+ // only looks for the type name in dart:core, so we just copy it here.
+ //
+ // TODO(jmesserly): we'll likely need something that can handle a wider
+ // variety of types, especially when we get to JS interop.
+ var args = expr.argumentList.arguments;
+ var first = args.isNotEmpty ? args.first : null;
+ if (first is SimpleStringLiteral) {
+ var types = first.stringValue;
+ if (!types.split('|').contains('Null')) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ JS.Expression notNull(Expression expr) {
+ if (expr == null) return null;
+ var jsExpr = _visit(expr);
+ if (_isNonNullableExpression(expr)) return jsExpr;
+ return js.call('dart.notNull(#)', jsExpr);
+ }
+
+ @override
+ JS.Expression visitBinaryExpression(BinaryExpression node) {
+ var op = node.operator;
+ var left = node.leftOperand;
+ var right = node.rightOperand;
+
+ var leftType = getStaticType(left);
+ var rightType = getStaticType(right);
+
+ var code;
+ if (op.type.isEqualityOperator) {
+ // If we statically know LHS or RHS is null we can generate a clean check.
+ // We can also do this if both sides are the same primitive type.
+ if (_canUsePrimitiveEquality(left, right)) {
+ code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #';
+ } else if (left is SuperExpression) {
+ return _emitSend(left, op.lexeme, [right]);
+ } else {
+ var bang = op.type == TokenType.BANG_EQ ? '!' : '';
+ code = '${bang}dart.equals(#, #)';
+ }
+ return js.call(code, [_visit(left), _visit(right)]);
+ }
+
+ if (op.type.lexeme == '??') {
+ // TODO(jmesserly): leave RHS for debugging?
+ // This should be a hint or warning for dead code.
+ if (_isNonNullableExpression(left)) return _visit(left);
+
+ var vars = <String, JS.Expression>{};
+ // Desugar `l ?? r` as `l != null ? l : r`
+ var l = _visit(_bindValue(vars, 'l', left, context: left));
+ return new JS.MetaLet(vars, [
+ js.call('# != null ? # : #', [l, l, _visit(right)])
+ ]);
+ }
+
+ if (binaryOperationIsPrimitive(leftType, rightType) ||
+ leftType == _types.stringType && op.type == TokenType.PLUS) {
+ // special cases where we inline the operation
+ // these values are assumed to be non-null (determined by the checker)
+ // TODO(jmesserly): it would be nice to just inline the method from core,
+ // instead of special cases here.
+ if (op.type == TokenType.TILDE_SLASH) {
+ // `a ~/ b` is equivalent to `(a / b).truncate()`
+ var div = AstBuilder.binaryExpression(left, '/', right)
+ ..staticType = node.staticType;
+ return _emitSend(div, 'truncate', []);
+ } else {
+ // TODO(vsm): When do Dart ops not map to JS?
+ code = '# $op #';
+ }
+ return js.call(code, [notNull(left), notNull(right)]);
+ }
+
+ return _emitSend(left, op.lexeme, [right]);
+ }
+
+ /// If the type [t] is [int] or [double], returns [num].
+ /// Otherwise returns [t].
+ DartType _canonicalizeNumTypes(DartType t) {
+ var numType = types.numType;
+ if (t is InterfaceType && t.superclass == numType) return numType;
+ return t;
+ }
+
+ bool _canUsePrimitiveEquality(Expression left, Expression right) {
+ if (_isNull(left) || _isNull(right)) return true;
+
+ var leftType = _canonicalizeNumTypes(getStaticType(left));
+ var rightType = _canonicalizeNumTypes(getStaticType(right));
+ return _isJSBuiltinType(leftType) && leftType == rightType;
+ }
+
+ bool _isNull(Expression expr) => expr is NullLiteral;
+
+ SimpleIdentifier _createTemporary(String name, DartType type) {
+ // We use an invalid source location to signal that this is a temporary.
+ // See [_isTemporary].
+ // TODO(jmesserly): alternatives are
+ // * (ab)use Element.isSynthetic, which isn't currently used for
+ // LocalVariableElementImpl, so we could repurpose to mean "temp".
+ // * add a new property to LocalVariableElementImpl.
+ // * create a new subtype of LocalVariableElementImpl to mark a temp.
+ var id =
+ new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
+ id.staticElement = new TemporaryVariableElement.forNode(id);
+ id.staticType = type;
+ DynamicInvoke.set(id, type.isDynamic);
+ return id;
+ }
+
+ JS.Expression _emitConst(JS.Expression expr()) {
+ // TODO(jmesserly): emit the constants at top level if possible.
+ // This wasn't quite working, so disabled for now.
+ return js.call('dart.const(#)', expr());
+ }
+
+ /// Returns a new expression, which can be be used safely *once* on the
+ /// left hand side, and *once* on the right side of an assignment.
+ /// For example: `expr1[expr2] += y` can be compiled as
+ /// `expr1[expr2] = expr1[expr2] + y`.
+ ///
+ /// The temporary scope will ensure `expr1` and `expr2` are only evaluated
+ /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`.
+ ///
+ /// If the expression does not end up using `x1` or `x2` more than once, or
+ /// if those expressions can be treated as stateless (e.g. they are
+ /// non-mutated variables), then the resulting code will be simplified
+ /// automatically.
+ ///
+ /// [scope] can be mutated to contain any new temporaries that were created,
+ /// unless [expr] is a SimpleIdentifier, in which case a temporary is not
+ /// needed.
+ Expression _bindLeftHandSide(
+ Map<String, JS.Expression> scope, Expression expr,
+ {Expression context}) {
+ Expression result;
+ if (expr is IndexExpression) {
+ IndexExpression index = expr;
+ result = new IndexExpression.forTarget(
+ _bindValue(scope, 'o', index.target, context: context),
+ index.leftBracket,
+ _bindValue(scope, 'i', index.index, context: context),
+ index.rightBracket);
+ } else if (expr is PropertyAccess) {
+ PropertyAccess prop = expr;
+ result = new PropertyAccess(
+ _bindValue(scope, 'o', _getTarget(prop), context: context),
+ prop.operator,
+ prop.propertyName);
+ } else if (expr is PrefixedIdentifier) {
+ PrefixedIdentifier ident = expr;
+ if (isLibraryPrefix(ident.prefix)) {
+ return expr;
+ }
+ result = new PrefixedIdentifier(
+ _bindValue(scope, 'o', ident.prefix, context: context)
+ as SimpleIdentifier,
+ ident.period,
+ ident.identifier);
+ } else {
+ return expr as SimpleIdentifier;
+ }
+ result.staticType = expr.staticType;
+ DynamicInvoke.set(result, DynamicInvoke.get(expr));
+ return result;
+ }
+
+ /// Creates a temporary to contain the value of [expr]. The temporary can be
+ /// used multiple times in the resulting expression. For example:
+ /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will
+ /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`.
+ ///
+ /// If the expression does not end up using `x` more than once, or if those
+ /// expressions can be treated as stateless (e.g. they are non-mutated
+ /// variables), then the resulting code will be simplified automatically.
+ ///
+ /// [scope] will be mutated to contain the new temporary's initialization.
+ Expression _bindValue(
+ Map<String, JS.Expression> scope, String name, Expression expr,
+ {Expression context}) {
+ // No need to do anything for stateless expressions.
+ if (isStateless(expr, context)) return expr;
+
+ var t = _createTemporary('#$name', getStaticType(expr));
+ scope[name] = _visit(expr);
+ return t;
+ }
+
+ /// Desugars postfix increment.
+ ///
+ /// In the general case [expr] can be one of [IndexExpression],
+ /// [PrefixExpression] or [PropertyAccess] and we need to
+ /// ensure sub-expressions are evaluated once.
+ ///
+ /// We also need to ensure we can return the original value of the expression,
+ /// and that it is only evaluated once.
+ ///
+ /// We desugar this using let*.
+ ///
+ /// For example, `expr1[expr2]++` can be transformed to this:
+ ///
+ /// // psuedocode mix of Scheme and JS:
+ /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
+ ///
+ /// The [JS.JS.MetaLet] nodes automatically simplify themselves if they can.
+ /// For example, if the result value is not used, then `t` goes away.
+ @override
+ JS.Expression visitPostfixExpression(PostfixExpression node) {
+ var op = node.operator;
+ var expr = node.operand;
+
+ var dispatchType = getStaticType(expr);
+ if (unaryOperationIsPrimitive(dispatchType)) {
+ if (_isNonNullableExpression(expr)) {
+ return js.call('#$op', _visit(expr));
+ }
+ }
+
+ assert(op.lexeme == '++' || op.lexeme == '--');
+
+ // Handle the left hand side, to ensure each of its subexpressions are
+ // evaluated only once.
+ var vars = <String, JS.Expression>{};
+ var left = _bindLeftHandSide(vars, expr, context: expr);
+
+ // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
+ // and `x1` is the new value for `x`.
+ var x = _bindValue(vars, 'x', left, context: expr);
+
+ var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
+ var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
+ ..staticElement = node.staticElement
+ ..staticType = getStaticType(expr);
+
+ var body = <JS.Expression>[_emitSet(left, increment), _visit(x)];
+ return new JS.MetaLet(vars, body, statelessResult: true);
+ }
+
+ @override
+ JS.Expression visitPrefixExpression(PrefixExpression node) {
+ var op = node.operator;
+ var expr = node.operand;
+
+ var dispatchType = getStaticType(expr);
+ if (unaryOperationIsPrimitive(dispatchType)) {
+ if (_isNonNullableExpression(expr)) {
+ return js.call('$op#', _visit(expr));
+ } else if (op.lexeme == '++' || op.lexeme == '--') {
+ // We need a null check, so the increment must be expanded out.
+ var vars = <String, JS.Expression>{};
+ var x = _bindLeftHandSide(vars, expr, context: expr);
+
+ var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
+ var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
+ ..staticElement = node.staticElement
+ ..staticType = getStaticType(expr);
+
+ return new JS.MetaLet(vars, [_emitSet(x, increment)]);
+ } else {
+ return js.call('$op#', notNull(expr));
+ }
+ }
+
+ if (op.lexeme == '++' || op.lexeme == '--') {
+ // Increment or decrement requires expansion.
+ // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions
+ // (for example, x is IndexExpression) we evaluate those once.
+ var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
+ return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement,
+ context: expr);
+ }
+
+ return _emitSend(expr, op.lexeme[0], []);
+ }
+
+ // Cascades can contain [IndexExpression], [MethodInvocation] and
+ // [PropertyAccess]. The code generation for those is handled in their
+ // respective visit methods.
+ @override
+ JS.Node visitCascadeExpression(CascadeExpression node) {
+ var savedCascadeTemp = _cascadeTarget;
+
+ var vars = <String, JS.Expression>{};
+ _cascadeTarget = _bindValue(vars, '_', node.target, context: node);
+ var sections = _visitList(node.cascadeSections) as List<JS.Expression>;
+ sections.add(_visit(_cascadeTarget));
+ var result = new JS.MetaLet(vars, sections, statelessResult: true);
+ _cascadeTarget = savedCascadeTemp;
+ return result;
+ }
+
+ @override
+ visitParenthesizedExpression(ParenthesizedExpression node) =>
+ // The printer handles precedence so we don't need to.
+ _visit(node.expression);
+
+ @override
+ visitFormalParameter(FormalParameter node) {
+ var id = visitSimpleIdentifier(node.identifier);
+
+ var isRestArg = findAnnotation(node.element, isJsRestAnnotation) != null;
+ return isRestArg ? new JS.RestParameter(id) : id;
+ }
+
+ @override
+ JS.This visitThisExpression(ThisExpression node) => new JS.This();
+
+ @override
+ JS.Super visitSuperExpression(SuperExpression node) => new JS.Super();
+
+ @override
+ visitPrefixedIdentifier(PrefixedIdentifier node) {
+ if (isLibraryPrefix(node.prefix)) {
+ return _visit(node.identifier);
+ } else {
+ return _emitGet(node.prefix, node.identifier);
+ }
+ }
+
+ @override
+ visitPropertyAccess(PropertyAccess node) {
+ if (node.operator.lexeme == '?.') {
+ return _emitNullSafe(node);
+ }
+ return _emitGet(_getTarget(node), node.propertyName);
+ }
+
+ JS.Expression _emitNullSafe(Expression node) {
+ // Desugar ?. sequence by passing a sequence of callbacks that applies
+ // each operation in sequence:
+ //
+ // obj?.foo()?.bar
+ // -->
+ // nullSafe(obj, _ => _.foo(), _ => _.bar);
+ //
+ // This pattern has the benefit of preserving order, as well as minimizing
+ // code expansion: each `?.` becomes `, _ => _`, plus one helper call.
+ //
+ // TODO(jmesserly): we could desugar with MetaLet instead, which may
+ // lead to higher performing code, but at the cost of readability.
+ var tail = <JS.Expression>[];
+ for (;;) {
+ var op = _getOperator(node);
+ if (op != null && op.lexeme == '?.') {
+ var nodeTarget = _getTarget(node);
+ if (_isNonNullableExpression(nodeTarget)) {
+ node = _stripNullAwareOp(node, nodeTarget);
+ break;
+ }
+
+ var param = _createTemporary('_', nodeTarget.staticType);
+ var baseNode = _stripNullAwareOp(node, param);
+ tail.add(new JS.ArrowFun([_visit(param)], _visit(baseNode)));
+ node = nodeTarget;
+ } else {
+ break;
+ }
+ }
+ if (tail.isEmpty) return _visit(node);
+ return js.call('dart.nullSafe(#, #)', [_visit(node), tail.reversed]);
+ }
+
+ static Token _getOperator(Expression node) {
+ if (node is PropertyAccess) return node.operator;
+ if (node is MethodInvocation) return node.operator;
+ return null;
+ }
+
+ // TODO(jmesserly): this is dropping source location.
+ Expression _stripNullAwareOp(Expression node, Expression newTarget) {
+ if (node is PropertyAccess) {
+ return AstBuilder.propertyAccess(newTarget, node.propertyName);
+ } else {
+ var invoke = node as MethodInvocation;
+ return AstBuilder.methodInvoke(
+ newTarget, invoke.methodName, invoke.argumentList.arguments);
+ }
+ }
+
+ bool _requiresStaticDispatch(Expression target, String memberName) {
+ var type = getStaticType(target);
+ if (!rules.objectMembers.containsKey(memberName)) {
+ return false;
+ }
+ if (!type.isObject &&
+ !_isJSBuiltinType(type) &&
+ _isNonNullableExpression(target)) {
+ return false;
+ }
+ return true;
+ }
+
+ /// Shared code for [PrefixedIdentifier] and [PropertyAccess].
+ JS.Expression _emitGet(Expression target, SimpleIdentifier memberId) {
+ var member = memberId.staticElement;
+ if (member is PropertyAccessorElement) {
+ member = (member as PropertyAccessorElement).variable;
+ }
+ bool isStatic = member is ClassMemberElement && member.isStatic;
+ if (isStatic) {
+ _loader.declareBeforeUse(member);
+ }
+ var name = _emitMemberName(memberId.name,
+ type: getStaticType(target), isStatic: isStatic);
+ if (DynamicInvoke.get(target)) {
+ return js.call('dart.$DLOAD(#, #)', [_visit(target), name]);
+ }
+
+ String code;
+ if (member != null && member is MethodElement && !isStatic) {
+ // Tear-off methods: explicitly bind it.
+ if (target is SuperExpression) {
+ return js.call('dart.bind(this, #, #.#)', [name, _visit(target), name]);
+ } else if (_requiresStaticDispatch(target, memberId.name)) {
+ var type = member.type;
+ var clos = js.call('dart.#.bind(#)', [name, _visit(target)]);
+ return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]);
+ }
+ code = 'dart.bind(#, #)';
+ } else if (_requiresStaticDispatch(target, memberId.name)) {
+ return js.call('dart.#(#)', [name, _visit(target)]);
+ } else {
+ code = '#.#';
+ }
+
+ return js.call(code, [_visit(target), name]);
+ }
+
+ /// Emits a generic send, like an operator method.
+ ///
+ /// **Please note** this function does not support method invocation syntax
+ /// `obj.name(args)` because that could be a getter followed by a call.
+ /// See [visitMethodInvocation].
+ JS.Expression _emitSend(
+ Expression target, String name, List<Expression> args) {
+ var type = getStaticType(target);
+ var memberName = _emitMemberName(name, unary: args.isEmpty, type: type);
+ if (DynamicInvoke.get(target)) {
+ // dynamic dispatch
+ var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name];
+ if (dynamicHelper != null) {
+ return js.call(
+ 'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]);
+ }
+ return js.call('dart.$DSEND(#, #, #)',
+ [_visit(target), memberName, _visitList(args)]);
+ }
+
+ // Generic dispatch to a statically known method.
+ return js.call('#.#(#)', [_visit(target), memberName, _visitList(args)]);
+ }
+
+ @override
+ visitIndexExpression(IndexExpression node) {
+ var target = _getTarget(node);
+ if (_useNativeJsIndexer(target.staticType)) {
+ return new JS.PropertyAccess(_visit(target), _visit(node.index));
+ }
+ return _emitSend(target, '[]', [node.index]);
+ }
+
+ // TODO(jmesserly): ideally we'd check the method and see if it is marked
+ // `external`, but that doesn't work because it isn't in the element model.
+ bool _useNativeJsIndexer(DartType type) =>
+ findAnnotation(type.element, isJSAnnotation) != null;
+
+ /// Gets the target of a [PropertyAccess], [IndexExpression], or
+ /// [MethodInvocation]. These three nodes can appear in a [CascadeExpression].
+ Expression _getTarget(node) {
+ assert(node is IndexExpression ||
+ node is PropertyAccess ||
+ node is MethodInvocation);
+ return node.isCascaded ? _cascadeTarget : node.target;
+ }
+
+ @override
+ visitConditionalExpression(ConditionalExpression node) {
+ return js.call('# ? # : #', [
+ notNull(node.condition),
+ _visit(node.thenExpression),
+ _visit(node.elseExpression)
+ ]);
+ }
+
+ @override
+ visitThrowExpression(ThrowExpression node) {
+ var expr = _visit(node.expression);
+ if (node.parent is ExpressionStatement) {
+ return js.statement('dart.throw(#);', expr);
+ } else {
+ return js.call('dart.throw(#)', expr);
+ }
+ }
+
+ @override
+ visitRethrowExpression(RethrowExpression node) {
+ if (node.parent is ExpressionStatement) {
+ return js.statement('throw #;', _visit(_catchParameter));
+ } else {
+ return js.call('throw #', _visit(_catchParameter));
+ }
+ }
+
+ /// Visits a statement, and ensures the resulting AST handles block scope
+ /// correctly. Essentially, we need to promote a variable declaration
+ /// statement into a block in some cases, e.g.
+ ///
+ /// do var x = 5; while (false); // Dart
+ /// do { let x = 5; } while (false); // JS
+ JS.Statement _visitScope(Statement stmt) {
+ var result = _visit(stmt);
+ if (result is JS.ExpressionStatement &&
+ result.expression is JS.VariableDeclarationList) {
+ return new JS.Block([result]);
+ }
+ return result;
+ }
+
+ @override
+ JS.If visitIfStatement(IfStatement node) {
+ return new JS.If(notNull(node.condition), _visitScope(node.thenStatement),
+ _visitScope(node.elseStatement));
+ }
+
+ @override
+ JS.For visitForStatement(ForStatement node) {
+ var init = _visit(node.initialization);
+ if (init == null) init = _visit(node.variables);
+ var update = _visitListToBinary(node.updaters, ',');
+ if (update != null) update = update.toVoidExpression();
+ return new JS.For(
+ init, notNull(node.condition), update, _visitScope(node.body));
+ }
+
+ @override
+ JS.While visitWhileStatement(WhileStatement node) {
+ return new JS.While(notNull(node.condition), _visitScope(node.body));
+ }
+
+ @override
+ JS.Do visitDoStatement(DoStatement node) {
+ return new JS.Do(_visitScope(node.body), notNull(node.condition));
+ }
+
+ @override
+ JS.Statement visitForEachStatement(ForEachStatement node) {
+ if (node.awaitKeyword != null) {
+ return _emitAwaitFor(node);
+ }
+
+ var init = _visit(node.identifier);
+ if (init == null) {
+ init = js.call('let #', node.loopVariable.identifier.name);
+ }
+ return new JS.ForOf(init, _visit(node.iterable), _visitScope(node.body));
+ }
+
+ JS.Statement _emitAwaitFor(ForEachStatement node) {
+ // Emits `await for (var value in stream) ...`, which desugars as:
+ //
+ // var iter = new StreamIterator<T>(stream);
+ // try {
+ // while (await iter.moveNext()) {
+ // var value = iter.current;
+ // ...
+ // }
+ // } finally {
+ // await iter.cancel();
+ // }
+ //
+ // Like the Dart VM, we call cancel() always, as it's safe to call if the
+ // stream has already been cancelled.
+ //
+ // TODO(jmesserly): we may want a helper if these become common. For now the
+ // full desugaring seems okay.
+ var context = compiler.context;
+ var dart_async = context
+ .computeLibraryElement(context.sourceFactory.forUri('dart:async'));
+ var T = node.loopVariable.element.type;
+ var StreamIterator_T =
+ dart_async.getType('StreamIterator').type.substitute4([T]);
+
+ var createStreamIter = _emitInstanceCreationExpression(
+ StreamIterator_T.element.unnamedConstructor,
+ StreamIterator_T,
+ null,
+ AstBuilder.argumentList([node.iterable]),
+ false);
+ var iter = _visit(_createTemporary('it', StreamIterator_T));
+
+ var init = _visit(node.identifier);
+ if (init == null) {
+ init = js.call(
+ 'let # = #.current', [node.loopVariable.identifier.name, iter]);
+ } else {
+ init = js.call('# = #.current', [init, iter]);
+ }
+ return js.statement(
+ '{'
+ ' let # = #;'
+ ' try {'
+ ' while (#) { #; #; }'
+ ' } finally { #; }'
+ '}',
+ [
+ iter,
+ createStreamIter,
+ new JS.Yield(js.call('#.moveNext()', iter)),
+ init,
+ _visit(node.body),
+ new JS.Yield(js.call('#.cancel()', iter))
+ ]);
+ }
+
+ @override
+ visitBreakStatement(BreakStatement node) {
+ var label = node.label;
+ return new JS.Break(label?.name);
+ }
+
+ @override
+ visitContinueStatement(ContinueStatement node) {
+ var label = node.label;
+ return new JS.Continue(label?.name);
+ }
+
+ @override
+ visitTryStatement(TryStatement node) {
+ return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses),
+ _visit(node.finallyBlock));
+ }
+
+ _visitCatch(NodeList<CatchClause> clauses) {
+ if (clauses == null || clauses.isEmpty) return null;
+
+ // TODO(jmesserly): need a better way to get a temporary variable.
+ // This could incorrectly shadow a user's name.
+ var savedCatch = _catchParameter;
+
+ if (clauses.length == 1 && clauses.single.exceptionParameter != null) {
+ // Special case for a single catch.
+ _catchParameter = clauses.single.exceptionParameter;
+ } else {
+ _catchParameter = _createTemporary('e', types.dynamicType);
+ }
+
+ JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter));
+ for (var clause in clauses.reversed) {
+ catchBody = _catchClauseGuard(clause, catchBody);
+ }
+
+ var catchVarDecl = _visit(_catchParameter);
+ _catchParameter = savedCatch;
+ return new JS.Catch(catchVarDecl, new JS.Block([catchBody]));
+ }
+
+ JS.Statement _catchClauseGuard(CatchClause clause, JS.Statement otherwise) {
+ var then = visitCatchClause(clause);
+
+ // Discard following clauses, if any, as they are unreachable.
+ if (clause.exceptionType == null) return then;
+
+ // TODO(jmesserly): this is inconsistent with [visitIsExpression], which
+ // has special case for typeof.
+ return new JS.If(
+ js.call('dart.is(#, #)', [
+ _visit(_catchParameter),
+ _emitTypeName(clause.exceptionType.type),
+ ]),
+ then,
+ otherwise);
+ }
+
+ JS.Statement _statement(List<JS.Statement> statements) {
+ // TODO(jmesserly): empty block singleton?
+ if (statements.length == 0) return new JS.Block([]);
+ if (statements.length == 1) return statements[0];
+ return new JS.Block(statements);
+ }
+
+ /// Visits the catch clause body. This skips the exception type guard, if any.
+ /// That is handled in [_visitCatch].
+ @override
+ JS.Statement visitCatchClause(CatchClause node) {
+ var body = <JS.Statement>[];
+
+ var savedCatch = _catchParameter;
+ if (node.catchKeyword != null) {
+ var name = node.exceptionParameter;
+ if (name != null && name != _catchParameter) {
+ body.add(js.statement(
+ 'let # = #;', [_visit(name), _visit(_catchParameter)]));
+ _catchParameter = name;
+ }
+ if (node.stackTraceParameter != null) {
+ var stackVar = node.stackTraceParameter.name;
+ body.add(js.statement(
+ 'let # = dart.stackTrace(#);', [stackVar, _visit(name)]));
+ }
+ }
+
+ body.add(
+ new JS.Block(_visitList(node.body.statements) as List<JS.Statement>));
+ _catchParameter = savedCatch;
+ return _statement(body);
+ }
+
+ @override
+ JS.Case visitSwitchCase(SwitchCase node) {
+ var expr = _visit(node.expression);
+ var body = _visitList(node.statements) as List<JS.Statement>;
+ if (node.labels.isNotEmpty) {
+ body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
+ }
+ // TODO(jmesserly): make sure we are statically checking fall through
+ return new JS.Case(expr, new JS.Block(body));
+ }
+
+ @override
+ JS.Default visitSwitchDefault(SwitchDefault node) {
+ var body = _visitList(node.statements) as List<JS.Statement>;
+ if (node.labels.isNotEmpty) {
+ body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
+ }
+ // TODO(jmesserly): make sure we are statically checking fall through
+ return new JS.Default(new JS.Block(body));
+ }
+
+ @override
+ JS.Switch visitSwitchStatement(SwitchStatement node) => new JS.Switch(
+ _visit(node.expression),
+ _visitList(node.members) as List<JS.SwitchClause>);
+
+ @override
+ JS.Statement visitLabeledStatement(LabeledStatement node) {
+ var result = _visit(node.statement);
+ for (var label in node.labels.reversed) {
+ result = new JS.LabeledStatement(label.label.name, result);
+ }
+ return result;
+ }
+
+ @override
+ visitIntegerLiteral(IntegerLiteral node) => js.number(node.value);
+
+ @override
+ visitDoubleLiteral(DoubleLiteral node) => js.number(node.value);
+
+ @override
+ visitNullLiteral(NullLiteral node) => new JS.LiteralNull();
+
+ @override
+ visitSymbolLiteral(SymbolLiteral node) {
+ JS.New emitSymbol() {
+ // TODO(vsm): When we canonicalize, we need to treat private symbols
+ // correctly.
+ var name = js.string(node.components.join('.'), "'");
+ return new JS.New(_emitTypeName(types.symbolType), [name]);
+ }
+ return _emitConst(emitSymbol);
+ }
+
+ @override
+ visitListLiteral(ListLiteral node) {
+ JS.Expression emitList() {
+ JS.Expression list = new JS.ArrayInitializer(
+ _visitList(node.elements) as List<JS.Expression>);
+ ParameterizedType type = node.staticType;
+ var elementType = type.typeArguments.single;
+ if (elementType != types.dynamicType) {
+ // dart.list helper internally depends on _interceptors.JSArray.
+ _loader.declareBeforeUse(_jsArray);
+ list = js.call('dart.list(#, #)', [list, _emitTypeName(elementType)]);
+ }
+ return list;
+ }
+ if (node.constKeyword != null) return _emitConst(emitList);
+ return emitList();
+ }
+
+ @override
+ visitMapLiteral(MapLiteral node) {
+ // TODO(jmesserly): we can likely make these faster.
+ JS.Expression emitMap() {
+ var entries = node.entries;
+ var mapArguments = null;
+ if (entries.isEmpty) {
+ mapArguments = [];
+ } else if (entries.every((e) => e.key is StringLiteral)) {
+ // Use JS object literal notation if possible, otherwise use an array.
+ // We could do this any time all keys are non-nullable String type.
+ // For now, support StringLiteral as the common non-nullable String case.
+ var props = <JS.Property>[];
+ for (var e in entries) {
+ props.add(new JS.Property(_visit(e.key), _visit(e.value)));
+ }
+ mapArguments = new JS.ObjectInitializer(props);
+ } else {
+ var values = <JS.Expression>[];
+ for (var e in entries) {
+ values.add(_visit(e.key));
+ values.add(_visit(e.value));
+ }
+ mapArguments = new JS.ArrayInitializer(values);
+ }
+ // TODO(jmesserly): add generic types args.
+ return js.call('dart.map(#)', [mapArguments]);
+ }
+ if (node.constKeyword != null) return _emitConst(emitMap);
+ return emitMap();
+ }
+
+ @override
+ JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) =>
+ js.escapedString(node.value, node.isSingleQuoted ? "'" : '"');
+
+ @override
+ JS.Expression visitAdjacentStrings(AdjacentStrings node) =>
+ _visitListToBinary(node.strings, '+');
+
+ @override
+ JS.TemplateString visitStringInterpolation(StringInterpolation node) {
+ // Assuming we implement toString() on our objects, we can avoid calling it
+ // in most cases. Builtin types may differ though. We could handle this with
+ // a tagged template.
+ return new JS.TemplateString(_visitList(node.elements));
+ }
+
+ @override
+ String visitInterpolationString(InterpolationString node) {
+ // TODO(jmesserly): this call adds quotes, and then we strip them off.
+ var str = js.escapedString(node.value, '`').value;
+ return str.substring(1, str.length - 1);
+ }
+
+ @override
+ visitInterpolationExpression(InterpolationExpression node) =>
+ _visit(node.expression);
+
+ @override
+ visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value);
+
+ @override
+ JS.Expression visitExpression(Expression node) =>
+ _unimplementedCall('Unimplemented ${node.runtimeType}: $node');
+
+ JS.Expression _unimplementedCall(String comment) {
+ return js.call('dart.throw(#)', [js.escapedString(comment)]);
+ }
+
+ @override
+ visitNode(AstNode node) {
+ // TODO(jmesserly): verify this is unreachable.
+ throw 'Unimplemented ${node.runtimeType}: $node';
+ }
+
+ _visit(AstNode node) {
+ if (node == null) return null;
+ var result = node.accept(this);
+ if (result is JS.Node) result.sourceInformation = node;
+ return result;
+ }
+
+ // TODO(jmesserly): this will need to be a generic method, if we ever want to
+ // self-host strong mode.
+ List /*<T>*/ _visitList /*<T>*/ (Iterable<AstNode> nodes) {
+ if (nodes == null) return null;
+ var result = /*<T>*/ [];
+ for (var node in nodes) result.add(_visit(node));
+ return result;
+ }
+
+ /// Visits a list of expressions, creating a comma expression if needed in JS.
+ JS.Expression _visitListToBinary(List<Expression> nodes, String operator) {
+ if (nodes == null || nodes.isEmpty) return null;
+ return new JS.Expression.binary(
+ _visitList(nodes) as List<JS.Expression>, operator);
+ }
+
+ /// Like [_emitMemberName], but for declaration sites.
+ ///
+ /// Unlike call sites, we always have an element available, so we can use it
+ /// directly rather than computing the relevant options for [_emitMemberName].
+ JS.Expression _elementMemberName(ExecutableElement e,
+ {bool allowExtensions: true}) {
+ String name;
+ if (e is PropertyAccessorElement) {
+ name = e.variable.name;
+ } else {
+ name = e.name;
+ }
+ return _emitMemberName(name,
+ type: (e.enclosingElement as ClassElement).type,
+ unary: e.parameters.isEmpty,
+ isStatic: e.isStatic,
+ allowExtensions: allowExtensions);
+ }
+
+ /// This handles member renaming for private names and operators.
+ ///
+ /// Private names are generated using ES6 symbols:
+ ///
+ /// // At the top of the module:
+ /// let _x = Symbol('_x');
+ /// let _y = Symbol('_y');
+ /// ...
+ ///
+ /// class Point {
+ /// Point(x, y) {
+ /// this[_x] = x;
+ /// this[_y] = y;
+ /// }
+ /// get x() { return this[_x]; }
+ /// get y() { return this[_y]; }
+ /// }
+ ///
+ /// For user-defined operators the following names are allowed:
+ ///
+ /// <, >, <=, >=, ==, -, +, /, ~/, *, %, |, ^, &, <<, >>, []=, [], ~
+ ///
+ /// They generate code like:
+ ///
+ /// x['+'](y)
+ ///
+ /// There are three exceptions: [], []= and unary -.
+ /// The indexing operators we use `get` and `set` instead:
+ ///
+ /// x.get('hi')
+ /// x.set('hi', 123)
+ ///
+ /// This follows the same pattern as EcmaScript 6 Map:
+ /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map>
+ ///
+ /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed
+ /// for this transformation to happen, otherwise binary minus is assumed.
+ ///
+ /// Equality is a bit special, it is generated via the Dart `equals` runtime
+ /// helper, that checks for null. The user defined method is called '=='.
+ ///
+ JS.Expression _emitMemberName(String name,
+ {DartType type,
+ bool unary: false,
+ bool isStatic: false,
+ bool allowExtensions: true}) {
+ // Static members skip the rename steps.
+ if (isStatic) return _propertyName(name);
+
+ if (name.startsWith('_')) {
+ return _privateNames.putIfAbsent(
+ name, () => _initSymbol(new JS.TemporaryId(name)) as JS.TemporaryId);
+ }
+
+ if (name == '[]') {
+ name = 'get';
+ } else if (name == '[]=') {
+ name = 'set';
+ } else if (name == '-' && unary) {
+ name = 'unary-';
+ } else if (name == 'constructor' || name == 'prototype') {
+ // This uses an illegal (in Dart) character for a member, avoiding the
+ // conflict. We could use practically any character for this.
+ name = '+$name';
+ }
+
+ // Dart "extension" methods. Used for JS Array, Boolean, Number, String.
+ if (allowExtensions &&
+ _extensionTypes.contains(type.element) &&
+ !_objectMembers.containsKey(name)) {
+ return js.call('dartx.#', _propertyName(name));
+ }
+
+ return _propertyName(name);
+ }
+
+ bool _externalOrNative(node) =>
+ node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
+
+ FunctionBody _functionBody(node) =>
+ node is FunctionDeclaration ? node.functionExpression.body : node.body;
+
+ /// Choose a canonical name from the library element.
+ /// This never uses the library's name (the identifier in the `library`
+ /// declaration) as it doesn't have any meaningful rules enforced.
+ JS.Identifier _libraryName(LibraryElement library) {
+ if (library == currentLibrary) return _exportsVar;
+ return _imports.putIfAbsent(
+ library, () => new JS.TemporaryId(jsLibraryName(library)));
+ }
+
+ DartType getStaticType(Expression e) => rules.getStaticType(e);
+
+ @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 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.
+ ///
+ /// JSNumber is the type that actually "implements" all numbers, hence it's
+ /// a subtype of int and double (and num). It's in our "dart:_interceptors".
+ bool _isNumberInJS(DartType t) => rules.isSubTypeOf(t, _types.numType);
+}
+
+class JSGenerator extends CodeGenerator {
+ final _extensionTypes = new HashSet<ClassElement>();
+
+ JSGenerator(AbstractCompiler compiler) : super(compiler) {
+ // TODO(jacobr): determine the the set of types with extension methods from
+ // the annotations rather than hard coding the list once the analyzer
+ // supports summaries.
+ var context = compiler.context;
+ var src = context.sourceFactory.forUri('dart:_interceptors');
+ var interceptors = context.computeLibraryElement(src);
+ for (var t in ['JSArray', 'JSString', 'JSNumber', 'JSBool']) {
+ _addExtensionType(interceptors.getType(t).type);
+ }
+ // TODO(jmesserly): manually add `int` and `double`
+ // Unfortunately our current analyzer rejects "implements int".
+ // Fix was landed, so we can remove this hack once we're updated:
+ // https://github.com/dart-lang/sdk/commit/d7cd11f86a02f55269fc8d9843e7758ebeeb81c8
+ _addExtensionType(context.typeProvider.intType);
+ _addExtensionType(context.typeProvider.doubleType);
+ }
+
+ void _addExtensionType(InterfaceType t) {
+ if (t.isObject || !_extensionTypes.add(t.element)) return;
+ t = fillDynamicTypeArgs(t, rules.provider) as InterfaceType;
+ t.interfaces.forEach(_addExtensionType);
+ t.mixins.forEach(_addExtensionType);
+ _addExtensionType(t.superclass);
+ }
+
+ String generateLibrary(LibraryUnit unit) {
+ // Clone the AST first, so we can mutate it.
+ unit = unit.clone();
+ var library = unit.library.element.library;
+ var fields = findFieldsNeedingStorage(unit, _extensionTypes);
+ var codegen =
+ new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields);
+ var module = codegen.emitLibrary(unit);
+ var out = compiler.getOutputPath(library.source.uri);
+ return writeJsLibrary(module, out,
+ emitSourceMaps: options.emitSourceMaps,
+ arrowFnBindThisWorkaround: options.arrowFnBindThisWorkaround);
+ }
+}
+
+/// Choose a canonical name from the library element.
+/// This never uses the library's name (the identifier in the `library`
+/// declaration) as it doesn't have any meaningful rules enforced.
+String jsLibraryName(LibraryElement library) => canonicalLibraryName(library);
+
+/// Shorthand for identifier-like property names.
+/// For now, we emit them as strings and the printer restores them to
+/// identifiers if it can.
+// TODO(jmesserly): avoid the round tripping through quoted form.
+JS.LiteralString _propertyName(String name) => js.string(name, "'");
+
+// TODO(jacobr): we would like to do something like the following
+// but we don't have summary support yet.
+// bool _supportJsExtensionMethod(AnnotatedNode node) =>
+// _getAnnotation(node, "SupportJsExtensionMethod") != null;
+
+/// A special kind of element created by the compiler, signifying a temporary
+/// variable. These objects use instance equality, and should be shared
+/// everywhere in the tree where they are treated as the same variable.
+class TemporaryVariableElement extends LocalVariableElementImpl {
+ TemporaryVariableElement.forNode(Identifier name) : super.forNode(name);
+
+ int get hashCode => identityHashCode(this);
+ bool operator ==(Object other) => identical(this, other);
+}
diff --git a/lib/src/codegen/js_names.dart b/lib/src/codegen/js_names.dart
index c84991f..cd13794 100644
--- a/lib/src/codegen/js_names.dart
+++ b/lib/src/codegen/js_names.dart
@@ -22,6 +22,8 @@
/// be qualified until [setQualified] is called.
///
/// This expression is transparent to visiting after [setQualified].
+///
+/// TODO(ochafik): Remove in favour of [PlaceholderExpression].
class MaybeQualifiedId extends Expression {
Expression _expr;
diff --git a/lib/src/js/dart_nodes.dart b/lib/src/js/dart_nodes.dart
new file mode 100644
index 0000000..7943a32
--- /dev/null
+++ b/lib/src/js/dart_nodes.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+/// The pseudo-nodes defined here are not JS [Node] subclasses, but they are
+/// still [Visitable] as they may contain nested JS [Node].
+///
+/// For instance, a [DartClassDeclaration] will contain JS [Fun] bodies for
+/// its converted methods. A [DartMethodCall] will contain the converted JS
+/// arguments of the method call, etc.
+library dart_ast;
+
+import '../js/js_ast.dart' as JS;
+import 'js_types.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:dev_compiler/src/info.dart';
+import 'package:analyzer/analyzer.dart';
+
+part 'dart_visitor.dart';
+
+enum DartMethodCallType {
+ dsend, dcall, directDispatch, staticDispatch
+}
+
+abstract class DartNode {
+ var sourceInformation;
+ acceptDart(DartVisitor visitor);
+}
+
+class DartMethodCall extends JS.Node with DartNode {
+ /// Can be this, an expression or null (for global functions).
+ final JS.Expression target;
+ //final SimpleIdentifier memberName;
+ final JS.Expression memberName;
+ final List<JS.Expression> arguments;
+ /// Prepare for generic method calls.
+ final List<TypeRef> typeArguments;
+ /// If true, some dynamic call will be needed (which might involve a reference
+ /// to the signature).
+ final DartMethodCallType callType;
+ DartMethodCall(this.callType, this.target, this.memberName, this.arguments,
+ [this.typeArguments = const<TypeRef>[]]);
+
+ @override accept(JS.NodeVisitor visitor) => null;
+ @override acceptDart(DartVisitor visitor) =>
+ visitor.visitDartMethodCall(this);
+
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ target?.accept(visitor);
+ arguments.forEach((a) => a.accept(visitor));
+ typeArguments.forEach((a) => a.accept(visitor));
+ }
+}
+
+/// TODO(ochafik): Store [LibraryElement]?
+class DartLibrary extends DartNode {
+ final LibraryUnit libraryUnit;
+ final List<DartLibraryPart> parts;
+ DartLibrary(this.libraryUnit, this.parts);
+
+ @override
+ acceptDart(DartVisitor visitor) => visitor.visitDartLibrary(this);
+}
+
+class DartLibraryPart extends DartNode {
+ /// Note: has uri.
+ final CompilationUnitElement compilationUnitElement;
+ final List<DartDeclaration> declarations;
+ DartLibraryPart(this.compilationUnitElement, this.declarations);
+
+ @override
+ acceptDart(DartVisitor visitor) => visitor.visitDartLibraryPart(this);
+}
+
+abstract class DartDeclaration extends JS.Visitable with DartNode {
+ Element get element;
+ // String get name => element.name;
+ List<String> get genericTypeNames;
+}
+
+/// TODO(ochafik): Remove this once all top-level statements are migrated.
+class OpaqueDartDeclaration extends DartDeclaration {
+ JS.Statement statement;
+ OpaqueDartDeclaration(this.statement);
+
+ Element get element => null;
+ List<String> get genericTypeNames => const[];
+
+ @override accept(JS.NodeVisitor visitor) => statement.accept(visitor);
+ @override acceptDart(DartVisitor visitor) => visitor.visitOpaqueDartDeclaration(this);
+
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ statement.visitChildren(visitor);
+ }
+}
+
+class DartTypedef extends DartDeclaration {
+ final FunctionTypeAliasElement element;
+ final TypeRef returnType;
+ final List<TypeRef> paramTypes;
+ List<String> get genericTypeNames => const<String>[];
+
+ DartTypedef(this.element, this.returnType, this.paramTypes);
+
+ @override
+ acceptDart(DartVisitor visitor) => visitor.visitDartTypedef(this);
+
+ @override accept(JS.NodeVisitor visitor) => null;
+
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ returnType?.accept(visitor);
+ paramTypes.forEach((a) => a.accept(visitor));
+ }
+}
+
+/// JS node that gathers the JS output of a Dart class.
+/// This output is kept abstract in this node and needs to be lowered down to
+/// "normal" JS nodes like [ClassDeclaration] and potentially other support
+/// statements / function calls.
+///
+/// TODO(ochafik): Store [ClassElement]?
+class DartClassDeclaration extends DartDeclaration {
+ final ClassElement element;
+ final JS.Expression jsPeerName;
+ final TypeRef parentRef;
+ final TypeRef deferredParentRef;
+ final List<TypeRef> mixinRefs;
+ final List<TypeRef> implementedRefs;
+ final List<String> genericTypeNames;
+ final List<DartCallableDeclaration> members;
+ final List<JS.Expression> dartxNames;
+ final List<JS.Expression> overrideFields;
+ final List<JS.Expression> extensionNames;
+ final List<JS.Expression> metadataExpressions;
+ final List<JS.Expression> constructorNames;
+ JS.Expression signature;
+ JS.Expression deferredSuperType;
+
+ DartClassDeclaration(
+ {this.element,
+ this.jsPeerName,
+ this.parentRef,
+ this.deferredParentRef,
+ this.mixinRefs,
+ this.implementedRefs,
+ this.genericTypeNames,
+ this.dartxNames,
+ this.overrideFields,
+ this.extensionNames,
+ this.metadataExpressions,
+ this.constructorNames,
+ this.signature,
+ this.deferredSuperType,
+ this.members});
+
+ @override
+ acceptDart(DartVisitor visitor) => visitor.visitDartClassDeclaration(this);
+
+ @override accept(JS.NodeVisitor visitor) => null;
+
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ parentRef?.accept(visitor);
+ mixinRefs.forEach((a) => a.accept(visitor));
+ implementedRefs.forEach((a) => a.accept(visitor));
+ members.forEach((a) => a.accept(visitor));
+ }
+}
+
+enum DartCallableKind {
+ constructor,
+ namedConstructor,
+ function,
+ /// Note that the body of static fields is lazy.
+ value,
+ getter,
+ setter
+}
+
+enum DartCallableStorage {
+ topLevel,
+ instanceMember,
+ classMember
+}
+
+class DartModifiers {
+ // final bool isStatic;
+ final bool isConst;
+ final bool isFinal;
+ DartModifiers(
+ {this.isConst : false,
+ this.isFinal : false});
+}
+
+/// Representation of a "callable" Dart element such as fields, top-level values
+/// and functions, constructors, methods, accessors.
+/// TODO(ochafik): Store [ExecutableElement]?
+class DartCallableDeclaration extends DartDeclaration {
+ /// A [ClassMemberElement] or [VariableElement].
+ final Element element;
+ final JS.Expression name;
+ final DartCallableStorage storage;
+ final DartCallableKind kind;
+ List<String> get genericTypeNames => const<String>[];
+ /// Can be a [Fun] for executable declarations (getters, functions), or any
+ /// expression for fields / top-level vars.
+ final JS.Expression body;
+
+ /// Left intentionally mutable for now. Some passes will update this.
+ String comment;
+
+ DartCallableDeclaration._(this.element, this.kind, this.storage, this.name, this.body) {
+ // assert(element is PropertyAccessorElement || element is || element is VariableElement);
+ // Check body type.
+ assert(body is JS.Fun);
+ // assert((body is JS.Fun) || (kind == DartCallableKind.value));
+ // Check params.
+ switch (kind) {
+ case DartCallableKind.getter:
+ assert(name != null);
+ assert(body is JS.Fun && (body as JS.Fun).params.isEmpty);
+ break;
+ case DartCallableKind.setter:
+ assert(name != null);
+ assert(body is JS.Fun && (body as JS.Fun).params.length == 1);
+ break;
+ case DartCallableKind.function:
+ assert(name != null);
+ assert(body is JS.Fun);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @override
+ acceptDart(DartVisitor visitor) => visitor.visitDartCallableDeclaration(this);
+
+ @override accept(JS.NodeVisitor visitor) => null;
+
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ body?.accept(visitor);
+ }
+}
+
+// // Dart AST creation helpers.
+// // TODO(ochafik): Simplify? Remove? Beautify?
+//
+// /// [element] is a [ClassElement] or a [ConstructorElement].
+// /// [body] is null for redirected factory constructors.
+DartCallableDeclaration newDartConstructor(Element element, JS.Expression name, JS.Expression body) =>
+ new DartCallableDeclaration._(
+ element,
+ DartCallableKind.constructor,
+ DartCallableStorage.topLevel,
+ name,
+ body);
+
+DartCallableDeclaration newDartNamedConstructor(Element element, JS.Expression name, JS.Expression body) =>
+ new DartCallableDeclaration._(
+ element,
+ DartCallableKind.constructor,
+ DartCallableStorage.topLevel,
+ name,
+ body);
+
+// DartCallableDeclaration newDartField(FieldElement element, JS.Expression body) =>
+// new DartCallableDeclaration._(
+// element,
+// DartCallableKind.value,
+// element.isStatic ? DartCallableStorage.classMember : DartCallableStorage.instanceMember,
+// body);
+//
+// DartCallableDeclaration newDartTopLevelValue(TopLevelVariableElement element, JS.Expression body) =>
+// new DartCallableDeclaration._(element, DartCallableKind.value, DartCallableStorage.topLevel, body);
+//
+// DartCallableDeclaration newDartTopLevelFunction(FunctionElement element, JS.Fun body) =>
+// new DartCallableDeclaration._(element, DartCallableKind.function, DartCallableStorage.topLevel, body);
+//
+DartCallableDeclaration newDartMethod(ExecutableElement element, JS.Expression name, JS.Fun body) =>
+ new DartCallableDeclaration._(
+ element,
+ DartCallableKind.function,
+ element?.isStatic == true ? DartCallableStorage.classMember : DartCallableStorage.instanceMember,
+ name,
+ body);
+//
+// DartCallableDeclaration newDartSyntheticMethod(String name, DartType returnType, Map<String, DartType> paramTypes, JS.Fun body) {
+// var params = <ParameterElement>[];
+// paramTypes.forEach((name, type) {
+// params.add(new ParameterElementImpl(name, -1)..synthetic = true..type = type);
+// });
+// var element = new MethodElementImpl(name, -1)
+// ..synthetic = true
+// ..returnType = returnType
+// ..parameters = params;
+// element.type = new FunctionTypeImpl(element);
+// return newDartMethod(element, body);
+// }
+//
+// DartCallableDeclaration newDartGetter(PropertyAccessorElement element, JS.Fun body) =>
+// new DartCallableDeclaration._(
+// element,
+// DartCallableKind.getter,
+// element.isStatic ? DartCallableStorage.classMember : DartCallableStorage.instanceMember,
+// body);
+//
+// DartCallableDeclaration newDartSetter(PropertyAccessorElement element, JS.Fun body) =>
+// new DartCallableDeclaration._(
+// element,
+// DartCallableKind.setter,
+// element.isStatic ? DartCallableStorage.classMember : DartCallableStorage.instanceMember,
+// body);
+//
+// JS.Expression newDartTypeExpression(DartType type) =>
+// new JS.PlaceholderExpression(new DartTypeRef(type));
diff --git a/lib/src/js/dart_visitor.dart b/lib/src/js/dart_visitor.dart
new file mode 100644
index 0000000..5517561
--- /dev/null
+++ b/lib/src/js/dart_visitor.dart
@@ -0,0 +1,14 @@
+part of dart_ast;
+
+abstract class DartVisitor<T> {
+ T visit(DartNode node) => node.acceptDart(this);
+
+ T visitDartMethodCall(DartMethodCall node);
+ T visitDartLibrary(DartLibrary node);
+ T visitDartLibraryPart(DartLibraryPart node);
+ T visitDartDeclaration(DartDeclaration node);
+ T visitOpaqueDartDeclaration(OpaqueDartDeclaration node);
+ T visitDartTypedef(DartTypedef node);
+ T visitDartClassDeclaration(DartClassDeclaration node);
+ T visitDartCallableDeclaration(DartCallableDeclaration node);
+}
diff --git a/lib/src/js/js_ast.dart b/lib/src/js/js_ast.dart
index 1c94817..f1595cf 100644
--- a/lib/src/js/js_ast.dart
+++ b/lib/src/js/js_ast.dart
@@ -8,6 +8,7 @@
import 'precedence.dart';
import 'characters.dart' as charCodes;
+import 'js_types.dart';
part 'nodes.dart';
part 'builder.dart';
diff --git a/lib/src/js/js_types.dart b/lib/src/js/js_types.dart
new file mode 100644
index 0000000..9058594
--- /dev/null
+++ b/lib/src/js/js_types.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// 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_types;
+
+import '../closure/closure_type.dart';
+
+import '../js/js_ast.dart' as JS;
+import '../js/precedence.dart';
+import 'dart_nodes.dart';
+import 'package:analyzer/src/generated/element.dart';
+
+/// JavaScript type reference.
+abstract class TypeRef extends JS.Expression {
+ @override accept(JS.NodeVisitor visitor) => visitor.visitTypeRef(this);
+ @override void visitChildren(JS.NodeVisitor visitor) {}
+
+ int get precedenceLevel => EXPRESSION;
+}
+
+abstract class TypeRefVisitor<T> {
+ T visitTypeRef(TypeRef typeRef);
+ T visitClosureTypeRef(ClosureTypeRef typeRef);
+ T visitImportedTypeRef(ImportedTypeRef typeRef);
+ T visitTypeParamRef(TypeParamRef typeRef);
+ T visitGenericTypeRef(GenericTypeRef typeRef);
+}
+
+class DartTypeRef extends TypeRef {
+ final DartType type;
+ DartTypeRef(this.type);
+}
+
+/// Used for interop (imported Closure libs) and for basic types.
+/// TODO(ochafik): Merge [ClosureType] in this hierarchy.
+class ClosureTypeRef extends TypeRef {
+ final ClosureType type;
+ ClosureTypeRef(this.type);
+ // TODO(ochafik): Visit sub-types here.
+ @override void visitChildren(JS.NodeVisitor visitor) {}
+}
+
+/// Similar in mind to [MaybeQualifiedId], but not a JS [Node].
+class ImportedTypeRef extends TypeRef {
+ final String library;
+ /// Note: the same type might be imported multiple times with different prefixes(?),
+ /// so a pass might just walk through the imported refs and coalesce them, maybe respecting
+ /// some of the original prefixes, then outputting modules as it wants / replacing
+ /// those refs as needed.
+ final String originalPrefix;
+ final String name;
+ ImportedTypeRef(this.library, this.originalPrefix, this.name);
+}
+/// Reference to a type parameter defined in a Dart class / method.
+class TypeParamRef extends TypeRef {
+ final DartDeclaration owner;
+ final String name;
+ TypeParamRef(this.owner, this.name);
+}
+class GenericTypeRef extends TypeRef {
+ final TypeRef rawType;
+ final List<TypeRef> typeParams;
+ GenericTypeRef(this.rawType, this.typeParams);
+ @override void visitChildren(JS.NodeVisitor visitor) {
+ rawType.accept(visitor);
+ typeParams.forEach((p) => p.accept(visitor));
+ }
+}
+class OpaqueTypeRef extends TypeRef {
+ final JS.Expression expression;
+ OpaqueTypeRef(this.expression);
+
+
+ @override accept(JS.NodeVisitor visitor) =>
+ expression.accept(visitor);
+
+ @override void visitChildren(JS.NodeVisitor visitor) =>
+ expression.visitChildren(visitor);
+}
diff --git a/lib/src/js/nodes.dart b/lib/src/js/nodes.dart
index af0fc12..9659534 100644
--- a/lib/src/js/nodes.dart
+++ b/lib/src/js/nodes.dart
@@ -92,6 +92,9 @@
T visitArrayBindingPattern(ArrayBindingPattern node);
T visitObjectBindingPattern(ObjectBindingPattern node);
T visitDestructuredVariable(DestructuredVariable node);
+
+ T visitTypeRef(TypeRef node);
+ T visitPlaceholderExpression(PlaceholderExpression node);
}
class BaseVisitor<T> implements NodeVisitor<T> {
@@ -221,9 +224,18 @@
T visitObjectBindingPattern(ObjectBindingPattern node)
=> visitBindingPattern(node);
T visitDestructuredVariable(DestructuredVariable node) => visitNode(node);
+
+ T visitTypeRef(TypeRef node) => null;
+ T visitPlaceholderExpression(PlaceholderExpression node) =>
+ node.data.accept(this);
}
-abstract class Node {
+abstract class Visitable {
+ accept(NodeVisitor visitor);
+ void visitChildren(NodeVisitor visitor);
+}
+
+abstract class Node extends Visitable {
/// Sets the source location of this node. For performance reasons, we allow
/// setting this after construction.
Object sourceInformation;
@@ -232,9 +244,6 @@
/// Closure annotation of this node.
ClosureAnnotation get closureAnnotation => _closureAnnotation;
- accept(NodeVisitor visitor);
- void visitChildren(NodeVisitor visitor);
-
// Shallow clone of node. Does not clone positions since the only use of this
// private method is create a copy with a new position.
Node _clone();
@@ -688,6 +697,28 @@
[new VariableInitialization(name, this)]).toStatement();
}
+/// Mutable placeholder expression that provides an entry point for simple
+/// AST transforms / expansions.
+///
+/// The data might not be JS [Node] but may contain JS nodes and be visitable.
+class PlaceholderExpression extends Expression {
+ var data;
+ PlaceholderExpression(this.data);
+
+ int get precedenceLevel =>
+ data is Expression ? (data as Expression).precedenceLevel : EXPRESSION;
+
+ @override
+ accept(NodeVisitor visitor) => visitor.visitPlaceholderExpression(this);
+
+ @override
+ void visitChildren(NodeVisitor visitor) {}
+
+ @override
+ Node _clone() =>
+ new PlaceholderExpression(data is Node ? (data as Node)._clone() : data);
+}
+
class LiteralExpression extends Expression {
final String template;
final List<Expression> inputs;
@@ -1022,13 +1053,17 @@
int get precedenceLevel => UNARY;
}
-abstract class Parameter implements Expression, VariableBinding {}
+abstract class Parameter implements Expression, VariableBinding {
+ JsType get type;
+}
class Identifier extends Expression implements Parameter, VariableBinding {
final String name;
final bool allowRename;
+ final JsType type;
- Identifier(this.name, {this.allowRename: true}) {
+ // TODO(ochafik): Make type non-optional.
+ Identifier(this.name, {this.allowRename: true, this.type}) {
assert(_identifierRE.hasMatch(name));
}
static RegExp _identifierRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
@@ -1043,8 +1078,10 @@
// This is an expression for convenience in the AST.
class RestParameter extends Expression implements Parameter {
final Identifier parameter;
+ final JsType type;
- RestParameter(this.parameter);
+ // TODO(ochafik): Make type non-optional.
+ RestParameter(this.parameter, [this.type]);
RestParameter _clone() => new RestParameter(parameter);
accept(NodeVisitor visitor) => visitor.visitRestParameter(this);
@@ -1105,21 +1142,28 @@
int get precedenceLevel => PRIMARY_LOW_PRECEDENCE;
}
+abstract class JsType {}
+
abstract class FunctionExpression extends Expression {
List<Parameter> get params;
+
get body; // Expression or block
+ JsType get returnType; // Type of the body or of its return type.
}
class Fun extends FunctionExpression {
final List<Parameter> params;
final Block body;
+ final JsType returnType;
/** Whether this is a JS generator (`function*`) that may contain `yield`. */
final bool isGenerator;
final AsyncModifier asyncModifier;
Fun(this.params, this.body, {this.isGenerator: false,
- this.asyncModifier: const AsyncModifier.sync()});
+ this.asyncModifier: const AsyncModifier.sync(),
+ // TODO(ochafik): Make this non-optional.
+ this.returnType});
accept(NodeVisitor visitor) => visitor.visitFun(this);
@@ -1137,10 +1181,13 @@
class ArrowFun extends FunctionExpression {
final List<Parameter> params;
final body; // Expression or Block
+ final JsType returnType;
bool _closesOverThis; // lazy initialized
- ArrowFun(this.params, this.body);
+ ArrowFun(this.params, this.body,
+ // TODO(ochafik): Make this non-optional.
+ {this.returnType});
accept(NodeVisitor visitor) => visitor.visitArrowFun(this);
diff --git a/lib/src/js/printer.dart b/lib/src/js/printer.dart
index e0e2c14..4de9aaa 100644
--- a/lib/src/js/printer.dart
+++ b/lib/src/js/printer.dart
@@ -6,6 +6,7 @@
class JavaScriptPrintingOptions {
+ final bool outputTypeAnnotations;
final bool shouldCompressOutput;
final bool minifyLocalVariables;
final bool preferSemicolonToNewlineInMinifiedOutput;
@@ -24,6 +25,7 @@
this.preferSemicolonToNewlineInMinifiedOutput: false,
this.allowKeywordsInProperties: false,
this.allowSingleLineIfStatements: false,
+ this.outputTypeAnnotations: false,
this.arrowFnBindThisWorkaround: false});
}
@@ -1327,6 +1329,23 @@
out("await ");
visit(node.expression);
}
+
+ @override
+ visitPlaceholderExpression(PlaceholderExpression node) {
+ var data = node.data;
+ if (data is Node) {
+ visit(data);
+ } else {
+ // Note: in debug we could still visit data.
+ throw new ArgumentError("Cannot pretty print non-JS-node data: $data");
+ }
+ }
+
+ @override
+ visitTypeRef(TypeRef node) {
+ throw new ArgumentError(
+ "Cannot pretty print type ref $node: it must be converted to JS nodes.");
+ }
}
// Collects all the var declarations in the function. We need to do this in a
diff --git a/lib/src/js/template.dart b/lib/src/js/template.dart
index 0dea734..9343168 100644
--- a/lib/src/js/template.dart
+++ b/lib/src/js/template.dart
@@ -857,6 +857,11 @@
makeVars.map((m) => m(arguments)).toList());
};
}
+
+ Instantiator visitTypeRef(TypeRef node) =>
+ TODO('visitTypeRef');
+ Instantiator visitPlaceholderExpression(PlaceholderExpression node) =>
+ TODO('visitPlaceholderExpression');
}
/**