Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(659)

Unified Diff: lib/src/codegen/js_codegen.dart

Issue 1133593004: fixes #131, use before define from variables to classes (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/runtime/dart_runtime.js ('k') | lib/src/codegen/js_module_item_order.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
« no previous file with comments | « lib/runtime/dart_runtime.js ('k') | lib/src/codegen/js_module_item_order.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698