| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index c705287d6caa5c606de964b28dfa5bf4cd5f1b24..0d6da39f38732d929bd01e6775e9cd3e87ebb859 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -4,7 +4,7 @@
|
|
|
| library dev_compiler.src.codegen.js_codegen;
|
|
|
| -import 'dart:collection' show HashSet;
|
| +import 'dart:collection' show HashSet, HashMap;
|
| import 'dart:io' show Directory, File;
|
|
|
| import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
|
| @@ -67,9 +67,19 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| final _privateNames = new HashSet<String>();
|
| final _pendingPrivateNames = <String>[];
|
|
|
| + /// Classes we have not emitted yet. Values can be [ClassDeclaration] or
|
| + /// [ClassTypeAlias].
|
| + final _pendingClasses = new HashMap<ClassElement, CompilationUnitMember>();
|
| +
|
| + /// Memoized results of [_lazyClass].
|
| + final _lazyClassMemo = new HashMap<ClassElement, bool>();
|
| +
|
| + /// Memoized results of [_inLibraryCycle].
|
| + final _libraryCycleMemo = new HashMap<LibraryElement, bool>();
|
| +
|
| JSCodegenVisitor(this.libraryInfo, this.rules, this._checkerReporter);
|
|
|
| - Element get currentLibrary => libraryInfo.library;
|
| + LibraryElement get currentLibrary => libraryInfo.library;
|
|
|
| JS.Program emitLibrary(LibraryUnit library) {
|
| var jsDefaultValue = '{}';
|
| @@ -87,14 +97,20 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| var body = <JS.Statement>[];
|
|
|
| - // Visit parts first, since the "part" declarations come before code
|
| - // in the main library's compilation unit.
|
| - for (var unit in library.parts) {
|
| - body.add(_visit(unit));
|
| + // Collect classes we need to emit, used for:
|
| + // * tracks what we've emitted so we don't emit twice
|
| + // * provides a mapping from ClassElement back to the ClassDeclaration.
|
| + for (var unit in library.partsThenLibrary) {
|
| + for (var decl in unit.declarations) {
|
| + if (decl is ClassDeclaration || decl is ClassTypeAlias) {
|
| + _pendingClasses[decl.element] = decl;
|
| + }
|
| + }
|
| }
|
|
|
| - // Visit main library
|
| - body.add(_visit(library.library));
|
| + for (var unit in library.partsThenLibrary) body.add(_visit(unit));
|
| +
|
| + assert(_pendingClasses.isEmpty);
|
|
|
| if (_exports.isNotEmpty) body.add(js.comment('Exports:'));
|
|
|
| @@ -224,57 +240,32 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| @override
|
| JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
|
| + // If we've already emitted this class, skip it.
|
| + var classElem = node.element;
|
| + if (_pendingClasses.remove(classElem) == null) return null;
|
| +
|
| var name = node.name.name;
|
| var heritage =
|
| js.call('dart.mixin(#)', [_visitList(node.withClause.mixinTypes)]);
|
| var classDecl = new JS.ClassDeclaration(
|
| new JS.ClassExpression(new JS.VariableDeclaration(name), heritage, []));
|
| if (isPublic(name)) _exports.add(name);
|
| - return _addTypeParameters(node.typeParameters, name, classDecl);
|
| - }
|
| -
|
| - @override
|
| - visitTypeParameter(TypeParameter node) => new JS.Parameter(node.name.name);
|
| -
|
| - JS.Statement _addTypeParameters(
|
| - TypeParameterList node, String name, JS.Statement clazz) {
|
| - if (node == null) return clazz;
|
| -
|
| - var genericName = '$name\$';
|
| - var genericDef = js.statement(
|
| - 'let # = dart.generic(function(#) { #; return #; });', [
|
| - genericName,
|
| - _visitList(node.typeParameters),
|
| - clazz,
|
| - name
|
| - ]);
|
|
|
| - // TODO(jmesserly): we may not want this to be `dynamic` if the generic
|
| - // has a lower bound, e.g. `<T extends SomeType>`.
|
| - // https://github.com/dart-lang/dart-dev-compiler/issues/38
|
| - var typeArgs =
|
| - new List.filled(node.typeParameters.length, js.call('dart.dynamic'));
|
| -
|
| - var dynInst = js.statement('let # = #(#);', [name, genericName, typeArgs]);
|
| -
|
| - // TODO(jmesserly): is it worth exporting both names? Alternatively we could
|
| - // put the generic type constructor on the <dynamic> instance.
|
| - if (isPublic(name)) _exports.add('${name}\$');
|
| - return new JS.Block([genericDef, dynInst]);
|
| + return _finishClassDef(classElem, classDecl);
|
| }
|
|
|
| @override
|
| JS.Statement visitClassDeclaration(ClassDeclaration node) {
|
| + // If we've already emitted this class, skip it.
|
| + var classElem = node.element;
|
| + if (_pendingClasses.remove(classElem) == null) return null;
|
| if (_getJsNameAnnotation(node) != null) return null;
|
|
|
| currentClass = node;
|
|
|
| - // dart:core Object is a bit special.
|
| - var isObject = node.element.type.isObject;
|
| -
|
| - var body = <JS.Statement>[];
|
| + var name = classElem.name;
|
| + if (isPublic(name)) _exports.add(name);
|
|
|
| - var name = node.name.name;
|
| var ctors = <ConstructorDeclaration>[];
|
| var fields = <FieldDeclaration>[];
|
| var staticFields = <FieldDeclaration>[];
|
| @@ -286,6 +277,212 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| }
|
|
|
| + var classExpr = new JS.ClassExpression(new JS.VariableDeclaration(name),
|
| + _classHeritage(node), _emitClassMethods(node, ctors, fields));
|
| +
|
| + var body = _finishClassMembers(name, classExpr, ctors, staticFields);
|
| + currentClass = null;
|
| +
|
| + return _finishClassDef(classElem, body);
|
| + }
|
| +
|
| + /// 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(ClassElement classElem, JS.Statement body) {
|
| + var name = classElem.name;
|
| + var genericName = '$name\$';
|
| +
|
| + JS.Statement genericDef;
|
| + JS.Expression genericInst;
|
| + if (classElem.typeParameters.isNotEmpty) {
|
| + genericDef = _emitGenericClassDef(classElem, body);
|
| + var dynamicArgs = new List.filled(
|
| + classElem.typeParameters.length, js.call('dart.dynamic'));
|
| +
|
| + var target = genericName;
|
| + if (_needQualifiedName(classElem)) {
|
| + target = js.call('#.#', [_EXPORTS, genericName]);
|
| + }
|
| + genericInst = js.call('#(#)', [target, dynamicArgs]);
|
| + }
|
| +
|
| + // The base class and all mixins must be declared before this class.
|
| + if (_lazyClass(classElem)) {
|
| + // 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,
|
| + _EXPORTS,
|
| + js.string(name, "'"),
|
| + genericName
|
| + ]);
|
| + }
|
| +
|
| + return js.statement(
|
| + 'dart.defineLazyClass(#, { get #() { #; return #; } });', [
|
| + _EXPORTS,
|
| + name,
|
| + body,
|
| + name
|
| + ]);
|
| + }
|
| +
|
| + if (genericDef != null) {
|
| + body = js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
|
| + }
|
| +
|
| + if (classElem.type.isObject) return body;
|
| +
|
| + // If we're not lazy, we still need to ensure our dependencies are
|
| + // generated first.
|
| + var classDefs = <JS.Statement>[];
|
| + _emitClassIfNeeded(classDefs, classElem.supertype.element);
|
| + for (var m in classElem.mixins) {
|
| + _emitClassIfNeeded(classDefs, m.element);
|
| + }
|
| + classDefs.add(body);
|
| + return _statement(classDefs);
|
| + }
|
| +
|
| + void _emitClassIfNeeded(List<JS.Statement> defs, ClassElement base) {
|
| + // We can only emit classes from this library.
|
| + if (base.library != currentLibrary) return;
|
| +
|
| + var baseNode = _pendingClasses[base];
|
| + if (baseNode != null) defs.add(visitClassDeclaration(baseNode));
|
| + }
|
| +
|
| + /// Returns true if the supertype or mixins aren't loaded.
|
| + /// If that is the case, we'll emit a lazy class definition.
|
| + bool _lazyClass(ClassElement cls) {
|
| + if (cls.type.isObject) return false;
|
| +
|
| + assert(cls.library == currentLibrary);
|
| + var result = _lazyClassMemo[cls];
|
| + if (result != null) return result;
|
| +
|
| + result = _classMightNotBeLoaded(cls.supertype.element);
|
| + for (var mixin in cls.mixins) {
|
| + if (result) break;
|
| + result = _classMightNotBeLoaded(mixin.element);
|
| + }
|
| + return _lazyClassMemo[cls] = result;
|
| + }
|
| +
|
| + /// Curated order to minimize lazy classes needed by dart:core and its
|
| + /// transitive SDK imports.
|
| + static const CORELIB_ORDER = const [
|
| + 'dart.core',
|
| + 'dart.collection',
|
| + 'dart._internal'
|
| + ];
|
| +
|
| + /// Returns true if the class might not be loaded.
|
| + ///
|
| + /// If the class is from our library, this can happen because it's lazy.
|
| + ///
|
| + /// If the class is from a different library, it could happen if we're in
|
| + /// a library cycle. In other words, if that different library depends back
|
| + /// on this library via some transitive import path.
|
| + ///
|
| + /// If we could control the global import ordering, we could eliminate some
|
| + /// of these cases, by ordering the imports of the cyclic libraries in an
|
| + /// optimal way. For example, we could order the libraries in a cycle to
|
| + /// minimize laziness. However, we currently assume we cannot control the
|
| + /// order that the cycle of libraries will be loaded in.
|
| + bool _classMightNotBeLoaded(ClassElement cls) {
|
| + if (cls.library == currentLibrary) return _lazyClass(cls);
|
| +
|
| + // The SDK is a special case: we optimize the order to prevent laziness.
|
| + if (cls.library.isInSdk) {
|
| + // SDK is loaded before non-SDK libraies
|
| + if (!currentLibrary.isInSdk) return false;
|
| +
|
| + // Compute the order of both SDK libraries. If unknown, assume it's after.
|
| + var classOrder = CORELIB_ORDER.indexOf(cls.library.name);
|
| + if (classOrder == -1) classOrder = CORELIB_ORDER.length;
|
| +
|
| + var currentOrder = CORELIB_ORDER.indexOf(currentLibrary.name);
|
| + if (currentOrder == -1) currentOrder = CORELIB_ORDER.length;
|
| +
|
| + // If the dart:* library we are currently compiling is loaded after the
|
| + // class's library, then we know the class is available.
|
| + if (classOrder != currentOrder) return currentOrder < classOrder;
|
| +
|
| + // If we don't know the order of the class's library or the current
|
| + // library, do the normal cycle check. (Not all SDK libs are cycles.)
|
| + }
|
| +
|
| + return _inLibraryCycle(cls.library);
|
| + }
|
| +
|
| + /// Returns true if [library] depends on the [currentLibrary] via some
|
| + /// transitive import.
|
| + bool _inLibraryCycle(LibraryElement library) {
|
| + // SDK libs don't depend on things outside the SDK.
|
| + // (We can reach this via the recursive call below.)
|
| + if (library.isInSdk && !currentLibrary.isInSdk) return false;
|
| +
|
| + var result = _libraryCycleMemo[library];
|
| + if (result != null) return result;
|
| +
|
| + result = library == currentLibrary;
|
| + _libraryCycleMemo[library] = result;
|
| + for (var e in library.imports) {
|
| + if (result) break;
|
| + result = _inLibraryCycle(e.importedLibrary);
|
| + }
|
| + for (var e in library.exports) {
|
| + if (result) break;
|
| + result = _inLibraryCycle(e.exportedLibrary);
|
| + }
|
| + return _libraryCycleMemo[library] = result;
|
| + }
|
| +
|
| + JS.Statement _emitGenericClassDef(ClassElement cls, JS.Statement body) {
|
| + var name = cls.name;
|
| + var genericName = '$name\$';
|
| + var typeParams = cls.typeParameters.map((p) => new JS.Parameter(p.name));
|
| + // TODO(jmesserly): is it worth exporting both names? Alternatively we could
|
| + // put the generic type constructor on the <dynamic> instance.
|
| + if (isPublic(name)) _exports.add(genericName);
|
| + return js.statement('let # = dart.generic(function(#) { #; return #; });', [
|
| + genericName,
|
| + typeParams,
|
| + body,
|
| + name
|
| + ]);
|
| + }
|
| +
|
| + JS.Expression _classHeritage(ClassDeclaration node) {
|
| + if (node.element.type.isObject) return null;
|
| +
|
| + JS.Expression heritage = null;
|
| + if (node.extendsClause != null) {
|
| + heritage = _visit(node.extendsClause.superclass);
|
| + } else {
|
| + heritage = _emitTypeName(rules.provider.objectType);
|
| + }
|
| + if (node.withClause != null) {
|
| + var mixins = _visitList(node.withClause.mixinTypes);
|
| + mixins.insert(0, heritage);
|
| + heritage = js.call('dart.mixin(#)', [mixins]);
|
| + }
|
| + return heritage;
|
| + }
|
| +
|
| + /// Emit class members that can be generated as methods.
|
| + /// Anything not handled here will be addressed in [_finishClassMembers].
|
| + List<JS.Method> _emitClassMethods(ClassDeclaration node,
|
| + List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
|
| + var element = node.element;
|
| + var isObject = element.type.isObject;
|
| + var name = node.name.name;
|
| +
|
| var jsMethods = <JS.Method>[];
|
| // Iff no constructor is specified for a class C, it implicitly has a
|
| // default constructor `C() : super() {}`, unless C is class Object.
|
| @@ -304,7 +501,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // Support for adapting dart:core Iterator/Iterable to ES6 versions.
|
| // This lets them use for-of loops transparently.
|
| // https://github.com/lukehoban/es6features#iterators--forof
|
| - if (node.element.library.isDartCore && node.element.name == 'Iterable') {
|
| + if (element.library.isDartCore && element.name == 'Iterable') {
|
| JS.Fun body = js.call('''function() {
|
| var iterator = this.iterator;
|
| return {
|
| @@ -316,23 +513,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }''');
|
| jsMethods.add(new JS.Method(js.call('Symbol.iterator'), body));
|
| }
|
| + return jsMethods.where((m) => m != null).toList(growable: false);
|
| + }
|
|
|
| - JS.Expression heritage = null;
|
| - if (node.extendsClause != null) {
|
| - heritage = _visit(node.extendsClause.superclass);
|
| - } else if (!isObject) {
|
| - heritage = _emitTypeName(rules.provider.objectType);
|
| - }
|
| - if (node.withClause != null) {
|
| - var mixins = _visitList(node.withClause.mixinTypes);
|
| - mixins.insert(0, heritage);
|
| - heritage = js.call('dart.mixin(#)', [mixins]);
|
| - }
|
| - body.add(new JS.ClassDeclaration(new JS.ClassExpression(
|
| - new JS.VariableDeclaration(name), heritage,
|
| - jsMethods.where((m) => m != null).toList(growable: false))));
|
| -
|
| - if (isPublic(name)) _exports.add(name);
|
| + /// Emit class members that need to come after the class declaration, such
|
| + /// as static fields. See [_emitClassMethods] for things that are emitted
|
| + /// insite the ES6 `class { ... }` node.
|
| + JS.Statement _finishClassMembers(String name, JS.ClassExpression cls,
|
| + List<ConstructorDeclaration> ctors, List<FieldDeclaration> staticFields) {
|
| + var body = <JS.Statement>[];
|
| + body.add(new JS.ClassDeclaration(cls));
|
|
|
| // Named constructors
|
| for (ConstructorDeclaration member in ctors) {
|
| @@ -360,9 +550,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| var lazy = _emitLazyFields(name, lazyStatics);
|
| if (lazy != null) body.add(lazy);
|
| -
|
| - currentClass = null;
|
| - return _addTypeParameters(node.typeParameters, name, _statement(body));
|
| + return _statement(body);
|
| }
|
|
|
| /// Generates the implicit default constructor for class C of the form
|
| @@ -742,7 +930,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| JS.Expression _emitTypeName(DartType type) {
|
| var name = type.name;
|
| - var lib = type.element.library;
|
| + var element = type.element;
|
| if (name == '') {
|
| // TODO(jmesserly): remove when we're using coercion reifier.
|
| return _unimplementedCall('Unimplemented type $type');
|
| @@ -751,8 +939,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| var typeArgs = null;
|
| if (type is ParameterizedType) {
|
| // TODO(jmesserly): this is a workaround for an analyzer bug, see:
|
| - // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a626dd8d16881cdb8e8b9c3fa
|
| - if (type is! FunctionType || type.element is FunctionTypeAlias) {
|
| + // https://github.com/dart-lang/dev_compiler/commit/a212d59ad046085a626dd8d16881cdb8e8b9c3fa
|
| + if (type is! FunctionType || element is FunctionTypeAlias) {
|
| var args = type.typeArguments;
|
| if (args.any((a) => a != rules.provider.dynamicType)) {
|
| name = '$name\$';
|
| @@ -762,8 +950,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
|
|
| JS.Expression result;
|
| - if (lib != currentLibrary && lib != null) {
|
| - result = js.call('#.#', [_libraryName(lib), name]);
|
| + if (_needQualifiedName(element)) {
|
| + result = js.call('#.#', [_libraryName(element.library), name]);
|
| } else {
|
| result = new JS.VariableUse(name);
|
| }
|
| @@ -774,6 +962,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return result;
|
| }
|
|
|
| + bool _needQualifiedName(Element element) {
|
| + var lib = element.library;
|
| +
|
| + return lib != null &&
|
| + (lib != currentLibrary ||
|
| + element is ClassElement && _lazyClass(element));
|
| + }
|
| +
|
| JS.Node _emitDPutIfDynamic(
|
| Expression target, SimpleIdentifier id, Expression rhs) {
|
| if (rules.isDynamicTarget(target)) {
|
| @@ -1085,7 +1281,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
|
|
| return js.statement(
|
| - 'dart.defineLazyProperties(#, { # })', [objExpr, methods]);
|
| + 'dart.defineLazyProperties(#, { # });', [objExpr, methods]);
|
| }
|
|
|
| void _flushLibraryProperties(List<JS.Statement> body) {
|
|
|