| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index 1b533b6bad1bc1579d895c0d9a16b15dee049f83..31400493aad53d6698c6cd828f6d15dabe0d7a0f 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -24,7 +24,6 @@ import 'package:dev_compiler/src/js/js_ast.dart' as JS;
|
| import 'package:dev_compiler/src/js/js_ast.dart' show js;
|
|
|
| import 'package:dev_compiler/src/checker/rules.dart';
|
| -import 'package:dev_compiler/src/dependency_graph.dart';
|
| import 'package:dev_compiler/src/info.dart';
|
| import 'package:dev_compiler/src/options.dart';
|
| import 'package:dev_compiler/src/utils.dart';
|
| @@ -33,6 +32,7 @@ import 'code_generator.dart';
|
| import 'js_field_storage.dart';
|
| import 'js_names.dart' as JS;
|
| import 'js_metalet.dart' as JS;
|
| +import 'js_module_item_order.dart';
|
| import 'js_printer.dart' show writeJsLibrary;
|
| import 'side_effect_analysis.dart';
|
|
|
| @@ -66,10 +66,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| /// The variable for the current catch clause
|
| SimpleIdentifier _catchParameter;
|
|
|
| - ConstantEvaluator _constEvaluator;
|
| -
|
| - ClassElement _currentClassElement = null;
|
| -
|
| /// Imported libraries, and the temporaries used to refer to them.
|
| final _imports = new Map<LibraryElement, JS.TemporaryId>();
|
| final _exports = new Set<String>();
|
| @@ -77,26 +73,24 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| final _properties = <FunctionDeclaration>[];
|
| final _privateNames = new HashMap<String, JS.TemporaryId>();
|
| final _extensionMethodNames = new HashSet<String>();
|
| - final _pendingStatements = <JS.Statement>[];
|
| + final _moduleItems = <JS.Statement>[];
|
| final _temps = new HashMap<Element, JS.TemporaryId>();
|
| + final _qualifiedIds = new HashMap<Element, JS.MaybeQualifiedId>();
|
| + final _qualifiedGenericIds = new HashMap<Element, JS.MaybeQualifiedId>();
|
|
|
| /// The name for the library's exports inside itself.
|
| /// `exports` was chosen as the most similar to ES module patterns.
|
| final _exportsVar = new JS.TemporaryId('exports');
|
| final _namedArgTemp = new JS.TemporaryId('opts');
|
|
|
| - /// Classes we have not emitted yet. Values can be [ClassDeclaration] or
|
| - /// [ClassTypeAlias].
|
| - final _pendingClasses = new HashMap<Element, CompilationUnitMember>();
|
| -
|
| - /// Memoized results of [_lazyClass].
|
| - final _lazyClassMemo = new HashMap<Element, bool>();
|
| + ConstFieldVisitor _constField;
|
|
|
| - /// Memoized results of [_inLibraryCycle].
|
| - final _libraryCycleMemo = new HashMap<LibraryElement, bool>();
|
| + ModuleItemLoadOrder _loader;
|
|
|
| JSCodegenVisitor(this.options, this.rules, this.libraryInfo,
|
| - this._extensionMethods, this._fieldsNeedingStorage);
|
| + this._extensionMethods, this._fieldsNeedingStorage) {
|
| + _loader = new ModuleItemLoadOrder(_emitModuleItem);
|
| + }
|
|
|
| LibraryElement get currentLibrary => libraryInfo.library;
|
| TypeProvider get types => rules.provider;
|
| @@ -117,30 +111,50 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| if (jsDefaultValue == null) jsDefaultValue = '{}';
|
|
|
| - var body = <JS.Statement>[];
|
| + // TODO(jmesserly): visit scriptTag, directives?
|
| +
|
| + _loader.collectElements(currentLibrary, library.partsThenLibrary);
|
|
|
| - // 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) {
|
| + _constField = new ConstFieldVisitor(types, unit);
|
| +
|
| for (var decl in unit.declarations) {
|
| - if (decl is ClassDeclaration ||
|
| - decl is ClassTypeAlias ||
|
| - decl is FunctionTypeAlias) {
|
| - _pendingClasses[decl.element] = decl;
|
| + if (decl is TopLevelVariableDeclaration) {
|
| + _visit(decl);
|
| + } else {
|
| + _loader.loadDeclaration(decl, decl.element);
|
| + }
|
| + if (decl is ClassDeclaration) {
|
| + // Static fields can be emitted into the top-level code, so they need
|
| + // to potentially be ordered independently of the class.
|
| + for (var member in decl.members) {
|
| + if (member is FieldDeclaration && member.isStatic) {
|
| + for (var f in member.fields.variables) {
|
| + _loader.loadDeclaration(f, f.element);
|
| + }
|
| + }
|
| + }
|
| }
|
| }
|
| }
|
|
|
| - for (var unit in library.partsThenLibrary) body.add(_visit(unit));
|
| + // Flush any unwritten fields/properties.
|
| + _flushLazyFields(_moduleItems);
|
| + _flushLibraryProperties(_moduleItems);
|
|
|
| - assert(_pendingClasses.isEmpty);
|
| + // Mark all qualified names as qualified or not, depending on if they need
|
| + // to be loaded lazily or not.
|
| + unqualifyIfNeeded(Element e, JS.MaybeQualifiedId id) {
|
| + id.setQualified(!_loader.isLoaded(e));
|
| + }
|
| + _qualifiedIds.forEach(unqualifyIfNeeded);
|
| + _qualifiedGenericIds.forEach(unqualifyIfNeeded);
|
|
|
| - if (_exports.isNotEmpty) body.add(js.comment('Exports:'));
|
| + if (_exports.isNotEmpty) _moduleItems.add(js.comment('Exports:'));
|
|
|
| // TODO(jmesserly): make these immutable in JS?
|
| for (var name in _exports) {
|
| - body.add(js.statement('#.# = #;', [_exportsVar, name, name]));
|
| + _moduleItems.add(js.statement('#.# = #;', [_exportsVar, name, name]));
|
| }
|
|
|
| var name = new JS.Identifier(jsLibraryName(currentLibrary));
|
| @@ -162,19 +176,31 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| var name = new JS.Identifier(temp.name);
|
| params.add(temp);
|
| args.add(name);
|
| - var helper = _libraryMightNotBeLoaded(library) ? 'lazyImport' : 'import';
|
| + var helper = _loader.libraryIsLoaded(library) ? 'import' : 'lazyImport';
|
| program.add(js.statement('var # = dart.#(#);', [name, helper, name]));
|
| });
|
|
|
| - program.add(js.statement(
|
| - "(function(#) { 'use strict'; #; })(#);", [params, body, args]));
|
| + program.add(js.statement("(function(#) { 'use strict'; #; })(#);", [
|
| + params,
|
| + _moduleItems,
|
| + args
|
| + ]));
|
|
|
| return new JS.Program(program);
|
| }
|
|
|
| + 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);
|
| + }
|
| +
|
| JS.Identifier _initSymbol(JS.Identifier id) {
|
| var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
|
| - _pendingStatements.add(s);
|
| + _moduleItems.add(s);
|
| return id;
|
| }
|
|
|
| @@ -186,34 +212,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return 'Symbol';
|
| }
|
|
|
| - @override
|
| - JS.Statement visitCompilationUnit(CompilationUnit node) {
|
| - var source = node.element.source;
|
| -
|
| - _constEvaluator = new ConstantEvaluator(source, types);
|
| -
|
| - // TODO(jmesserly): scriptTag, directives.
|
| - var body = <JS.Statement>[];
|
| - for (var child in node.declarations) {
|
| - // Attempt to group adjacent fields/properties.
|
| - if (child is! TopLevelVariableDeclaration) _flushLazyFields(body);
|
| - if (child is! FunctionDeclaration) _flushLibraryProperties(body);
|
| -
|
| - var code = _visit(child);
|
| - if (code != null) {
|
| - _flushPendingStatements(body);
|
| - body.add(code);
|
| - }
|
| - }
|
| -
|
| - // Flush any unwritten fields/properties.
|
| - _flushLazyFields(body);
|
| - _flushLibraryProperties(body);
|
| -
|
| - assert(_pendingStatements.isEmpty);
|
| - return _statement(body);
|
| - }
|
| -
|
| bool isPublic(String name) => !name.startsWith('_');
|
|
|
| /// Conversions that we don't handle end up here.
|
| @@ -280,17 +278,19 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| @override
|
| visitFunctionTypeAlias(FunctionTypeAlias node) {
|
| // If we've already emitted this class, skip it.
|
| - var type = node.element.type;
|
| - if (_pendingClasses.remove(node.element) == null) return null;
|
| + var element = node.element;
|
| + var type = element.type;
|
| + var name = element.name;
|
|
|
| - var name = type.name;
|
| - var result = js.statement('let # = dart.typedef(#, () => #);', [
|
| - new JS.Identifier(name),
|
| + _loader.startTopLevel(element);
|
| + var fnType = js.statement('let # = dart.typedef(#, #);', [
|
| + name,
|
| js.string(name, "'"),
|
| - _emitTypeName(node.element.type, lowerTypedef: true)
|
| + _emitTypeName(type, lowerTypedef: true)
|
| ]);
|
| + _loader.finishTopLevel(element);
|
|
|
| - return _finishClassDef(type, result);
|
| + return _finishClassDef(type, fnType);
|
| }
|
|
|
| @override
|
| @@ -299,14 +299,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| @override
|
| JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
|
| // If we've already emitted this class, skip it.
|
| - var type = node.element.type;
|
| - if (_pendingClasses.remove(node.element) == null) return null;
|
| + var element = node.element;
|
|
|
| - var name = node.name.name;
|
| var classDecl = new JS.ClassDeclaration(new JS.ClassExpression(
|
| - new JS.Identifier(name), _classHeritage(node), []));
|
| + new JS.Identifier(element.name), _classHeritage(element), []));
|
|
|
| - return _finishClassDef(type, classDecl);
|
| + return _finishClassDef(element.type, classDecl);
|
| }
|
|
|
| JS.Statement _emitJsType(String dartClassName, DartObjectImpl jsName) {
|
| @@ -328,29 +326,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // If we've already emitted this class, skip it.
|
| var classElem = node.element;
|
| var type = classElem.type;
|
| - if (_pendingClasses.remove(classElem) == null) return null;
|
| -
|
| var jsName = getAnnotationValue(node, _isJsNameAnnotation);
|
|
|
| if (jsName != null) return _emitJsType(node.name.name, jsName);
|
|
|
| - // Set current class
|
| - assert(_currentClassElement == null);
|
| - _currentClassElement = classElem;
|
| -
|
| var ctors = <ConstructorDeclaration>[];
|
| var fields = <FieldDeclaration>[];
|
| - var staticFields = <FieldDeclaration>[];
|
| for (var member in node.members) {
|
| if (member is ConstructorDeclaration) {
|
| ctors.add(member);
|
| - } else if (member is FieldDeclaration) {
|
| - (member.isStatic ? staticFields : fields).add(member);
|
| + } else if (member is FieldDeclaration && !member.isStatic) {
|
| + fields.add(member);
|
| }
|
| }
|
|
|
| var classExpr = new JS.ClassExpression(new JS.Identifier(type.name),
|
| - _classHeritage(node), _emitClassMethods(node, ctors, fields));
|
| + _classHeritage(classElem), _emitClassMethods(node, ctors, fields));
|
|
|
| String jsPeerName;
|
| var jsPeer = getAnnotationValue(node, _isJsPeerInterface);
|
| @@ -358,12 +349,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| jsPeerName = getConstantField(jsPeer, 'name', types.stringType);
|
| }
|
|
|
| - var body = _finishClassMembers(
|
| - classElem, classExpr, ctors, fields, staticFields, jsPeerName);
|
| -
|
| - // Unset current class
|
| - assert(_currentClassElement == classElem);
|
| - _currentClassElement = null;
|
| + var body =
|
| + _finishClassMembers(classElem, classExpr, ctors, fields, jsPeerName);
|
|
|
| var result = _finishClassDef(type, body);
|
|
|
| @@ -372,7 +359,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // the native JS type eagerly at this point.
|
| // If we wanted to support laziness, we could defer the hookup until
|
| // the end of the Dart library cycle load.
|
| - assert(!_lazyClass(type));
|
| + assert(_loader.isLoaded(classElem));
|
|
|
| // TODO(jmesserly): this copies the dynamic members.
|
| // Probably fine for objects coming from JS, but not if we actually
|
| @@ -399,19 +386,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| var name = type.name;
|
| var genericName = '$name\$';
|
|
|
| - JS.Statement genericDef;
|
| - JS.Expression genericInst;
|
| + JS.Statement genericDef = null;
|
| if (type.typeParameters.isNotEmpty) {
|
| genericDef = _emitGenericClassDef(type, body);
|
| - var target = genericName;
|
| - if (_needQualifiedName(type.element)) {
|
| - target = js.call('#.#', [_exportsVar, genericName]);
|
| - }
|
| - genericInst = js.call('#()', [target]);
|
| }
|
|
|
| // The base class and all mixins must be declared before this class.
|
| - if (_lazyClass(type)) {
|
| + 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.
|
|
|
| @@ -437,118 +418,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| if (isPublic(name)) _addExport(name);
|
|
|
| if (genericDef != null) {
|
| - body = js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
|
| + var dynType = fillDynamicTypeArgs(type, types);
|
| + var genericInst = _emitTypeName(dynType, lowerGeneric: true);
|
| + return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
|
| }
|
| -
|
| - if (type.isObject) return body;
|
| -
|
| - // If we're not lazy, we still need to ensure our dependencies are
|
| - // generated first.
|
| - var classDefs = <JS.Statement>[];
|
| - if (type is InterfaceType) {
|
| - _emitClassIfNeeded(classDefs, type.superclass);
|
| - for (var m in type.element.mixins) {
|
| - _emitClassIfNeeded(classDefs, m);
|
| - }
|
| - } else if (type is FunctionType) {
|
| - _emitClassIfNeeded(classDefs, types.functionType);
|
| - }
|
| - classDefs.add(body);
|
| - return _statement(classDefs);
|
| - }
|
| -
|
| - void _emitClassIfNeeded(List<JS.Statement> defs, DartType base) {
|
| - // We can only emit classes from this library.
|
| - if (base.element.library != currentLibrary) return;
|
| -
|
| - var baseNode = _pendingClasses[base.element];
|
| - 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(DartType type) {
|
| - if (type.isObject) return false;
|
| -
|
| - // Use the element as the key, as those are unique whereas generic types
|
| - // can have their arguments substituted.
|
| - assert(type.element.library == currentLibrary);
|
| - var result = _lazyClassMemo[type.element];
|
| - if (result != null) return result;
|
| -
|
| - if (type is InterfaceType) {
|
| - result = _typeMightNotBeLoaded(type.superclass) ||
|
| - type.mixins.any(_typeMightNotBeLoaded);
|
| - } else if (type is FunctionType) {
|
| - result = _typeMightNotBeLoaded(types.functionType);
|
| - }
|
| - return _lazyClassMemo[type.element] = result;
|
| - }
|
| -
|
| - /// 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 _typeMightNotBeLoaded(DartType type) {
|
| - var library = type.element.library;
|
| - if (library == currentLibrary) return _lazyClass(type);
|
| - return _libraryMightNotBeLoaded(library);
|
| - }
|
| -
|
| - bool _libraryMightNotBeLoaded(LibraryElement library) {
|
| - // The SDK is a special case: we optimize the order to prevent laziness.
|
| - if (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 = corelibOrder.indexOf(library.name);
|
| - if (classOrder == -1) classOrder = corelibOrder.length;
|
| -
|
| - var currentOrder = corelibOrder.indexOf(currentLibrary.name);
|
| - if (currentOrder == -1) currentOrder = corelibOrder.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(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;
|
| + return body;
|
| }
|
|
|
| JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) {
|
| @@ -564,25 +438,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| ]);
|
| }
|
|
|
| - JS.Expression _classHeritage(node) {
|
| - if (node.element.type.isObject) return null;
|
| -
|
| - DartType supertype;
|
| - if (node is ClassDeclaration) {
|
| - var ext = node.extendsClause;
|
| - supertype = ext != null ? ext.superclass.type : types.objectType;
|
| - } else {
|
| - supertype = (node as ClassTypeAlias).superclass.type;
|
| - }
|
| + JS.Expression _classHeritage(ClassElement element) {
|
| + var type = element.type;
|
| + if (type.isObject) return null;
|
|
|
| - JS.Expression heritage = _emitTypeName(supertype);
|
| + // Assume we can load eagerly, until proven otherwise.
|
| + _loader.startTopLevel(element);
|
|
|
| - if (node.withClause != null) {
|
| - var mixins = _visitList(node.withClause.mixinTypes);
|
| + JS.Expression heritage = _emitTypeName(type.superclass);
|
| + 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;
|
| }
|
|
|
| @@ -663,8 +533,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| /// inside the ES6 `class { ... }` node.
|
| JS.Statement _finishClassMembers(ClassElement classElem,
|
| JS.ClassExpression cls, List<ConstructorDeclaration> ctors,
|
| - List<FieldDeclaration> fields, List<FieldDeclaration> staticFields,
|
| - String jsPeerName) {
|
| + List<FieldDeclaration> fields, String jsPeerName) {
|
| var name = classElem.name;
|
| var body = <JS.Statement>[];
|
| body.add(new JS.ClassDeclaration(cls));
|
| @@ -706,23 +575,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| }
|
|
|
| - // Static fields
|
| - var lazyStatics = <VariableDeclaration>[];
|
| - for (FieldDeclaration member in staticFields) {
|
| - for (VariableDeclaration field in member.fields.variables) {
|
| - var fieldName = field.name.name;
|
| - if ((field.isConst || _isFieldInitConstant(field)) &&
|
| - !JS.invalidStaticFieldName(fieldName)) {
|
| - var init = _visit(field.initializer);
|
| - if (init == null) init = new JS.LiteralNull();
|
| - body.add(js.statement('#.# = #;', [name, fieldName, init]));
|
| - } else {
|
| - lazyStatics.add(field);
|
| - }
|
| - }
|
| - }
|
| - var lazy = _emitLazyFields(new JS.Identifier(name), lazyStatics);
|
| - if (lazy != null) body.add(lazy);
|
| return _statement(body);
|
| }
|
|
|
| @@ -744,7 +596,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| var superCall = _superConstructorCall(node);
|
| if (fields.isEmpty && superCall == null) return null;
|
|
|
| - dynamic body = _initializeFields(fields);
|
| + dynamic body = _initializeFields(node, fields);
|
| if (superCall != null) body = [[body, superCall]];
|
| return new JS.Method(
|
| _propertyName(name), js.call('function() { #; }', body));
|
| @@ -833,7 +685,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // 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(fields, node.parameters, node.initializers));
|
| + body.add(_initializeFields(node, fields));
|
|
|
| var superCall = node.initializers.firstWhere(
|
| (i) => i is SuperConstructorInvocation, orElse: () => null);
|
| @@ -897,9 +749,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| /// 2. field initializing parameters,
|
| /// 3. constructor field initializers,
|
| /// 4. initialize fields not covered in 1-3
|
| - JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls,
|
| - [FormalParameterList parameters,
|
| - NodeList<ConstructorInitializer> initializers]) {
|
| + JS.Statement _initializeFields(
|
| + AstNode node, List<FieldDeclaration> fieldDecls) {
|
| + var unit = node.getAncestor((a) => a is CompilationUnit);
|
| + var constField = new ConstFieldVisitor(types, unit);
|
|
|
| // Run field initializers if they can have side-effects.
|
| var fields = new Map<FieldElement, JS.Expression>();
|
| @@ -907,7 +760,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| for (var declaration in fieldDecls) {
|
| for (var fieldNode in declaration.fields.variables) {
|
| var element = fieldNode.element;
|
| - if (_isFieldInitConstant(fieldNode)) {
|
| + if (constField.isFieldInitConstant(fieldNode)) {
|
| unsetFields[element] = fieldNode;
|
| } else {
|
| fields[element] = _visitInitializer(fieldNode);
|
| @@ -916,17 +769,18 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
|
|
| // Initialize fields from `this.fieldName` parameters.
|
| - if (parameters != null) {
|
| + if (node is ConstructorDeclaration) {
|
| + var parameters = node.parameters;
|
| + var initializers = node.initializers;
|
| +
|
| for (var p in parameters.parameters) {
|
| var element = p.element;
|
| if (element is FieldFormalParameterElement) {
|
| fields[element.field] = _visit(p);
|
| }
|
| }
|
| - }
|
|
|
| - // Run constructor field initializers such as `: foo = bar.baz`
|
| - if (initializers != null) {
|
| + // Run constructor field initializers such as `: foo = bar.baz`
|
| for (var init in initializers) {
|
| if (init is ConstructorFieldInitializer) {
|
| fields[init.fieldName.staticElement] = _visit(init.expression);
|
| @@ -1129,14 +983,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // indirects back to a (possibly synthetic) field.
|
| var element = accessor;
|
| if (element is PropertyAccessorElement) element = accessor.variable;
|
| +
|
| + _loader.declareBeforeUse(element);
|
| +
|
| var name = element.name;
|
|
|
| // library member
|
| - if (element.enclosingElement is CompilationUnitElement &&
|
| - (element.library != currentLibrary ||
|
| - element is TopLevelVariableElement && !element.isConst)) {
|
| - var memberName = _emitMemberName(name, isStatic: true);
|
| - return js.call('#.#', [_libraryName(element.library), memberName]);
|
| + if (element.enclosingElement is CompilationUnitElement) {
|
| + return _maybeQualifiedName(
|
| + element, _emitMemberName(name, isStatic: true));
|
| }
|
|
|
| // Unqualified class member. This could mean implicit-this, or implicit
|
| @@ -1150,7 +1005,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // library prefix. We don't need those because static calls can't use
|
| // the generic type.
|
| if (isStatic) {
|
| - return js.call('#.#', [type.name, member]);
|
| + var dynType = _emitTypeName(fillDynamicTypeArgs(type, types));
|
| + return new JS.PropertyAccess(dynType, member);
|
| }
|
|
|
| // For instance members, we add implicit-this.
|
| @@ -1200,7 +1056,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return new JS.ObjectInitializer(properties);
|
| }
|
|
|
| - JS.Expression _emitTypeName(DartType type, {bool lowerTypedef: false}) {
|
| + /// Emits a Dart [type] into code.
|
| + ///
|
| + /// If [lowerTypedef] is set, a typedef will be expanded as if it were a
|
| + /// function type. Similarly if [lowerGeneric] is set, the `List$()` form
|
| + /// will be used instead of `List`. These flags are used when generating
|
| + /// the definitions for typedefs and generic types, respectively.
|
| + JS.Expression _emitTypeName(DartType type,
|
| + {bool lowerTypedef: false, bool lowerGeneric: false}) {
|
| +
|
| // The void and dynamic types are not defined in core.
|
| if (type.isVoid) {
|
| return js.call('dart.void');
|
| @@ -1208,77 +1072,81 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return js.call('dart.dynamic');
|
| }
|
|
|
| + _loader.declareBeforeUse(type.element);
|
| +
|
| + // TODO(jmesserly): like constants, should we hoist function types out of
|
| + // methods? Similar issue with generic types. For all of these, we may want
|
| + // to canonicalize them too, at least when inside the same library.
|
| var name = type.name;
|
| var element = type.element;
|
| - if (name == '' || lowerTypedef && type is FunctionType) {
|
| - if (type is FunctionType) {
|
| - var returnType = type.returnType;
|
| - var parameterTypes = type.normalParameterTypes;
|
| - var optionalTypes = type.optionalParameterTypes;
|
| - var namedTypes = type.namedParameterTypes;
|
| - if (namedTypes.isEmpty) {
|
| - if (optionalTypes.isEmpty) {
|
| - return js.call('dart.functionType(#, #)', [
|
| - _emitTypeName(returnType),
|
| - _emitTypeNames(parameterTypes)
|
| - ]);
|
| - } else {
|
| - return js.call('dart.functionType(#, #, #)', [
|
| - _emitTypeName(returnType),
|
| - _emitTypeNames(parameterTypes),
|
| - _emitTypeNames(optionalTypes)
|
| - ]);
|
| - }
|
| + if (name == '' || lowerTypedef) {
|
| + var fnType = type as FunctionType;
|
| + var returnType = fnType.returnType;
|
| + var parameterTypes = fnType.normalParameterTypes;
|
| + var optionalTypes = fnType.optionalParameterTypes;
|
| + var namedTypes = fnType.namedParameterTypes;
|
| + if (namedTypes.isEmpty) {
|
| + if (optionalTypes.isEmpty) {
|
| + return js.call('dart.functionType(#, #)', [
|
| + _emitTypeName(returnType),
|
| + _emitTypeNames(parameterTypes)
|
| + ]);
|
| } else {
|
| - assert(optionalTypes.isEmpty);
|
| return js.call('dart.functionType(#, #, #)', [
|
| _emitTypeName(returnType),
|
| _emitTypeNames(parameterTypes),
|
| - _emitTypeProperties(namedTypes)
|
| + _emitTypeNames(optionalTypes)
|
| ]);
|
| }
|
| + } else {
|
| + assert(optionalTypes.isEmpty);
|
| + return js.call('dart.functionType(#, #, #)', [
|
| + _emitTypeName(returnType),
|
| + _emitTypeNames(parameterTypes),
|
| + _emitTypeProperties(namedTypes)
|
| + ]);
|
| }
|
| - // TODO(jmesserly): remove when we're using coercion reifier.
|
| - return _unimplementedCall('Unimplemented type $type');
|
| }
|
|
|
| - var typeArgs = null;
|
| + if (type is TypeParameterType) {
|
| + return new JS.Identifier(name);
|
| + }
|
| +
|
| if (type is ParameterizedType) {
|
| var args = type.typeArguments;
|
| var isCurrentClass =
|
| - type is InterfaceType ? type.element == _currentClassElement : false;
|
| + args.isNotEmpty && _loader.isCurrentElement(type.element);
|
| + Iterable jsArgs = null;
|
| if (args.any((a) => a != types.dynamicType)) {
|
| - name = '$name\$';
|
| - typeArgs = args.map(_emitTypeName);
|
| - } else if (args.isNotEmpty && isCurrentClass) {
|
| + jsArgs = args.map(_emitTypeName);
|
| + } else if (lowerGeneric || isCurrentClass) {
|
| // When creating a `new S<dynamic>` we try and use the raw form
|
| // `new S()`, but this does not work if we're inside the same class,
|
| // because `S` refers to the current S<T> we are generating.
|
| - name = '$name\$';
|
| - typeArgs = [];
|
| + jsArgs = [];
|
| + }
|
| + if (jsArgs != null) {
|
| + var genericName = _maybeQualifiedName(
|
| + element, _propertyName('$name\$'), _qualifiedGenericIds);
|
| + return js.call('#(#)', [genericName, jsArgs]);
|
| }
|
| }
|
|
|
| - JS.Expression result;
|
| - if (_needQualifiedName(element)) {
|
| - result = js.call('#.#', [_libraryName(element.library), name]);
|
| - } else {
|
| - result = new JS.Identifier(name);
|
| - }
|
| + return _maybeQualifiedName(element, _propertyName(name));
|
| + }
|
| +
|
| + JS.Expression _maybeQualifiedName(Element e, JS.Expression name,
|
| + [Map<Element, JS.MaybeQualifiedId> idTable]) {
|
| + var libName = _libraryName(e.library);
|
| + if (idTable == null) idTable = _qualifiedIds;
|
|
|
| - if (typeArgs != null) {
|
| - result = js.call('#(#)', [result, typeArgs]);
|
| + // Mutable top-level fields should always be qualified.
|
| + bool mutableTopLevel = e is TopLevelVariableElement && !e.isConst;
|
| + if (e.library != currentLibrary || mutableTopLevel) {
|
| + return new JS.PropertyAccess(libName, name);
|
| }
|
| - return result;
|
| - }
|
|
|
| - bool _needQualifiedName(Element element) {
|
| - var lib = element.library;
|
| - if (lib == null) return false;
|
| - if (lib != currentLibrary) return true;
|
| - if (element is ClassElement) return _lazyClass(element.type);
|
| - if (element is FunctionTypeAliasElement) return _lazyClass(element.type);
|
| - return false;
|
| + return idTable.putIfAbsent(e, () => new JS.MaybeQualifiedId(libName, name));
|
| }
|
|
|
| @override
|
| @@ -1488,25 +1356,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| @override
|
| visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
| - var body = <JS.Statement>[];
|
| -
|
| - for (var field in node.variables.variables) {
|
| - if (field.isConst) {
|
| - // constant fields don't change, so we can generate them as `let`
|
| - // but add them to the module's exports
|
| - var name = field.name.name;
|
| - body.add(js.statement(
|
| - 'let # = #;', [new JS.Identifier(name), _visitInitializer(field)]));
|
| - if (isPublic(name)) _addExport(name);
|
| - } else if (_isFieldInitConstant(field)) {
|
| - body.add(js.statement(
|
| - '# = #;', [_visit(field.name), _visitInitializer(field)]));
|
| - } else {
|
| - _lazyFields.add(field);
|
| - }
|
| + for (var v in node.variables.variables) {
|
| + _loader.loadDeclaration(v, v.element);
|
| }
|
| -
|
| - return _statement(body);
|
| }
|
|
|
| _addExport(String name) {
|
| @@ -1535,11 +1387,58 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
|
|
| @override
|
| - JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) {
|
| + 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));
|
| }
|
|
|
| + /// 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;
|
| + }
|
| +
|
| + var fieldName = field.name.name;
|
| + if (field.isConst && eagerInit && element is TopLevelVariableElement) {
|
| + // 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);
|
| + return js.statement('let # = #;', [new JS.Identifier(fieldName), jsInit]);
|
| + }
|
| +
|
| + if (eagerInit && !JS.invalidStaticFieldName(fieldName)) {
|
| + return js.statement('# = #;', [_visit(field.name), jsInit]);
|
| + }
|
| +
|
| + var body = [];
|
| + 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`.
|
| @@ -1547,27 +1446,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return value != null ? value : new JS.LiteralNull();
|
| }
|
|
|
| - void _flushPendingStatements(List<JS.Statement> body) {
|
| - if (_pendingStatements.isNotEmpty) {
|
| - body.addAll(_pendingStatements);
|
| - _pendingStatements.clear();
|
| - }
|
| - }
|
| -
|
| void _flushLazyFields(List<JS.Statement> body) {
|
| - var code = _emitLazyFields(_exportsVar, _lazyFields);
|
| - if (code != null) {
|
| - // Ensure symbols for private fields are defined.
|
| - _flushPendingStatements(body);
|
| - body.add(code);
|
| - }
|
| + if (_lazyFields.isEmpty) return;
|
| + body.add(_emitLazyFields(_lazyFields));
|
| _lazyFields.clear();
|
| }
|
|
|
| - JS.Statement _emitLazyFields(
|
| - JS.Expression objExpr, List<VariableDeclaration> fields) {
|
| - if (fields.isEmpty) return null;
|
| -
|
| + JS.Statement _emitLazyFields(List<VariableDeclaration> fields) {
|
| var methods = [];
|
| for (var node in fields) {
|
| var name = node.name.name;
|
| @@ -1584,6 +1469,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| }
|
|
|
| + 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]);
|
| }
|
| @@ -1609,10 +1500,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| @override
|
| visitInstanceCreationExpression(InstanceCreationExpression node) {
|
| - var newExpr = js.call(
|
| + emitNew() => js.call(
|
| 'new #(#)', [_visit(node.constructorName), _visit(node.argumentList)]);
|
| - if (node.isConst) return _const(node, newExpr);
|
| - return newExpr;
|
| + if (node.isConst) return _emitConst(node, emitNew);
|
| + return emitNew();
|
| }
|
|
|
| /// True if this type is built-in to JS, and we use the values unwrapped.
|
| @@ -1780,26 +1671,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return id;
|
| }
|
|
|
| - JS.Expression _const(Expression node, JS.Expression expr, [String nameHint]) {
|
| - var value = js.call('dart.const(#)', expr);
|
| -
|
| - // If we're inside a method or function, capture the value into a
|
| - // global temporary, so we don't do the expensive canonicalization step.
|
| - var ancestor = node.getAncestor((n) => n is FunctionBody ||
|
| - (n is FieldDeclaration && n.staticKeyword == null));
|
| - if (ancestor == null) return value;
|
| -
|
| - if (nameHint == null) {
|
| - nameHint = 'const_' + getStaticType(node).name;
|
| - }
|
| -
|
| - // TODO(jmesserly): enable this once we fix
|
| - // https://github.com/dart-lang/dev_compiler/issues/131
|
| - /*var temp = new JSTemporary(nameHint);
|
| - _pendingStatements.add(js.statement('let # = #;', [temp, value]));
|
| - return temp;*/
|
| - assert(nameHint != null); // so it's not marked unused
|
| - return value;
|
| + JS.Expression _emitConst(Expression node, 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
|
| @@ -2277,54 +2152,59 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| @override
|
| visitSymbolLiteral(SymbolLiteral node) {
|
| - // TODO(vsm): When we canonicalize, we need to treat private symbols
|
| - // correctly.
|
| - var name = js.string(node.components.join('.'), "'");
|
| - var nameHint = 'symbol_' + node.components.join('_');
|
| - return _const(
|
| - node, new JS.New(_emitTypeName(types.symbolType), [name]), nameHint);
|
| + 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(node, emitSymbol);
|
| }
|
|
|
| @override
|
| visitListLiteral(ListLiteral node) {
|
| - JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements));
|
| -
|
| - ParameterizedType type = node.staticType;
|
| - if (type.typeArguments.any((a) => a != types.dynamicType)) {
|
| - list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]);
|
| + emitList() {
|
| + JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements));
|
| + ParameterizedType type = node.staticType;
|
| + if (type.typeArguments.any((a) => a != types.dynamicType)) {
|
| + list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]);
|
| + }
|
| + return list;
|
| }
|
| - if (node.constKeyword != null) return _const(node, list);
|
| - return list;
|
| + if (node.constKeyword != null) return _emitConst(node, emitList);
|
| + return emitList();
|
| }
|
|
|
| @override
|
| visitMapLiteral(MapLiteral node) {
|
| // TODO(jmesserly): we can likely make these faster.
|
| - 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 = [];
|
| - for (var e in entries) {
|
| - props.add(new JS.Property(_visit(e.key), _visit(e.value)));
|
| - }
|
| - mapArguments = new JS.ObjectInitializer(props);
|
| - } else {
|
| - var values = [];
|
| - for (var e in entries) {
|
| - values.add(_visit(e.key));
|
| - values.add(_visit(e.value));
|
| + 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 = [];
|
| + for (var e in entries) {
|
| + props.add(new JS.Property(_visit(e.key), _visit(e.value)));
|
| + }
|
| + mapArguments = new JS.ObjectInitializer(props);
|
| + } else {
|
| + var values = [];
|
| + for (var e in entries) {
|
| + values.add(_visit(e.key));
|
| + values.add(_visit(e.value));
|
| + }
|
| + mapArguments = new JS.ArrayInitializer(values);
|
| }
|
| - mapArguments = new JS.ArrayInitializer(values);
|
| + // TODO(jmesserly): add generic types args.
|
| + return js.call('dart.map(#)', [mapArguments]);
|
| }
|
| - // TODO(jmesserly): add generic types args.
|
| - var map = js.call('dart.map(#)', [mapArguments]);
|
| - if (node.constKeyword != null) return _const(node, map);
|
| - return map;
|
| + if (node.constKeyword != null) return _emitConst(node, emitMap);
|
| + return emitMap();
|
| }
|
|
|
| @override
|
| @@ -2375,29 +2255,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| throw 'Unimplemented ${node.runtimeType}: $node';
|
| }
|
|
|
| - // TODO(jmesserly): this is used to determine if the field initialization is
|
| - // side effect free. We should make the check more general, as things like
|
| - // list/map literals/regexp are also side effect free and fairly common
|
| - // to use as field initializers.
|
| - bool _isFieldInitConstant(VariableDeclaration field) =>
|
| - field.initializer == null || _computeConstant(field).isValid;
|
| -
|
| - EvaluationResult _computeConstant(VariableDeclaration field) {
|
| - // If the constant is already computed by ConstantEvaluator, just return it.
|
| - VariableElementImpl element = field.element;
|
| - var result = element.evaluationResult;
|
| - if (result != null) return result;
|
| -
|
| - // ConstantEvaluator will not compute constants for non-const fields
|
| - // at least for cases like `int x = 0;`, so run ConstantVisitor for those.
|
| - // TODO(jmesserly): ideally we'd only do this if we're sure it was skipped
|
| - // by ConstantEvaluator.
|
| - var initializer = field.initializer;
|
| - if (initializer == null) return null;
|
| -
|
| - return _constEvaluator.evaluate(initializer);
|
| - }
|
| -
|
| _visit(AstNode node) {
|
| if (node == null) return null;
|
| var result = node.accept(this);
|
|
|