| Index: lib/src/codegen/js_codegen.dart
|
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
|
| index 39b05cb6877099d2bbd4b4a575399ebb72cff31c..1fcd3a4afb3214ed3e11863aa3845578b97ba1a2 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -27,6 +27,7 @@ import 'package:dev_compiler/src/options.dart';
|
| import 'package:dev_compiler/src/utils.dart';
|
|
|
| import 'code_generator.dart';
|
| +import 'js_field_storage.dart';
|
| import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName;
|
| import 'js_metalet.dart';
|
| import 'js_printer.dart' show writeJsLibrary;
|
| @@ -51,8 +52,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| /// The global extension method table.
|
| final HashMap<String, List<InterfaceType>> _extensionMethods;
|
|
|
| + /// Information that is precomputed for this library, indicates which fields
|
| + /// need storage slots.
|
| + final HashSet<FieldElement> _fieldsNeedingStorage;
|
| +
|
| /// The variable for the target of the current `..` cascade expression.
|
| SimpleIdentifier _cascadeTarget;
|
| +
|
| /// The variable for the current catch clause
|
| SimpleIdentifier _catchParameter;
|
|
|
| @@ -63,9 +69,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| final _lazyFields = <VariableDeclaration>[];
|
| final _properties = <FunctionDeclaration>[];
|
| final _privateNames = new HashMap<String, JSTemporary>();
|
| - final _pendingPrivateNames = <JSTemporary>[];
|
| final _extensionMethodNames = new HashSet<String>();
|
| - final _pendingExtensionMethodNames = <String>[];
|
| + final _pendingSymbols = <JS.Identifier>[];
|
| final _temps = new HashMap<Element, JSTemporary>();
|
|
|
| /// The name for the library's exports inside itself.
|
| @@ -85,7 +90,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| /// Memoized results of [_inLibraryCycle].
|
| final _libraryCycleMemo = new HashMap<LibraryElement, bool>();
|
|
|
| - JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods);
|
| + JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods,
|
| + this._fieldsNeedingStorage);
|
|
|
| LibraryElement get currentLibrary => libraryInfo.library;
|
| TypeProvider get types => rules.provider;
|
| @@ -119,6 +125,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| for (var unit in library.partsThenLibrary) body.add(_visit(unit));
|
|
|
| + // Ensure field slots for any fields we override from other libraries.
|
| + _fieldsNeedingStorage.forEach(_overrideField);
|
| +
|
| assert(_pendingClasses.isEmpty);
|
|
|
| if (_exports.isNotEmpty) body.add(js.comment('Exports:'));
|
| @@ -143,11 +152,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| ]);
|
| }
|
|
|
| - JS.Statement _initPrivateSymbol(JSTemporary tmp) =>
|
| - js.statement('let # = $_SYMBOL(#);', [tmp, js.string(tmp.name, "'")]);
|
| -
|
| - JS.Statement _initExtensionMethodSymbol(String name) => js.statement(
|
| - 'let # = $_SYMBOL(#);', [new JS.Identifier(name), js.string(name, "'")]);
|
| + JS.Statement _initSymbol(JS.Identifier id) =>
|
| + js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
|
|
|
| // TODO(jmesserly): this is a temporary workaround for `Symbol` in core,
|
| // until we have better name tracking.
|
| @@ -171,16 +177,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| if (child is! FunctionDeclaration) _flushLibraryProperties(body);
|
|
|
| var code = _visit(child);
|
| -
|
| if (code != null) {
|
| - if (_pendingPrivateNames.isNotEmpty) {
|
| - body.addAll(_pendingPrivateNames.map(_initPrivateSymbol));
|
| - _pendingPrivateNames.clear();
|
| - }
|
| - if (_pendingExtensionMethodNames.isNotEmpty) {
|
| - body.addAll(
|
| - _pendingExtensionMethodNames.map(_initExtensionMethodSymbol));
|
| - _pendingExtensionMethodNames.clear();
|
| + if (_pendingSymbols.isNotEmpty) {
|
| + body.addAll(_pendingSymbols.map(_initSymbol));
|
| + _pendingSymbols.clear();
|
| }
|
| body.add(code);
|
| }
|
| @@ -190,7 +190,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| _flushLazyFields(body);
|
| _flushLibraryProperties(body);
|
|
|
| - assert(_pendingPrivateNames.isEmpty);
|
| + assert(_pendingSymbols.isEmpty);
|
| return _statement(body);
|
| }
|
|
|
| @@ -317,8 +317,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| @override
|
| JS.Statement visitClassDeclaration(ClassDeclaration node) {
|
| // If we've already emitted this class, skip it.
|
| - var type = node.element.type;
|
| - if (_pendingClasses.remove(node.element) == null) return null;
|
| + 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);
|
| @@ -340,7 +341,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| _classHeritage(node), _emitClassMethods(node, ctors, fields));
|
|
|
| var body =
|
| - _finishClassMembers(node.element, classExpr, ctors, staticFields);
|
| + _finishClassMembers(classElem, classExpr, ctors, fields, staticFields);
|
| currentClass = null;
|
|
|
| return _finishClassDef(type, body);
|
| @@ -551,12 +552,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| var isObject = 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.
|
| + var jsMethods = <JS.Method>[];
|
| if (ctors.isEmpty && !isObject) {
|
| jsMethods.add(_emitImplicitConstructor(node, name, fields));
|
| }
|
| +
|
| for (var member in node.members) {
|
| if (member is ConstructorDeclaration) {
|
| jsMethods.add(_emitConstructor(member, name, fields, isObject));
|
| @@ -585,12 +587,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| /// 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.
|
| + /// inside the ES6 `class { ... }` node.
|
| JS.Statement _finishClassMembers(ClassElement classElem,
|
| JS.ClassExpression cls, List<ConstructorDeclaration> ctors,
|
| - List<FieldDeclaration> staticFields) {
|
| + List<FieldDeclaration> fields, List<FieldDeclaration> staticFields) {
|
| var name = classElem.name;
|
| -
|
| var body = <JS.Statement>[];
|
| body.add(new JS.ClassDeclaration(cls));
|
|
|
| @@ -613,6 +614,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| }
|
|
|
| + // Instance fields, if they override getter/setter pairs
|
| + for (FieldDeclaration member in fields) {
|
| + for (VariableDeclaration fieldDecl in member.fields.variables) {
|
| + var field = fieldDecl.element;
|
| + if (_fieldsNeedingStorage.remove(field)) {
|
| + body.add(_overrideField(field));
|
| + }
|
| + }
|
| + }
|
| +
|
| // Static fields
|
| var lazyStatics = <VariableDeclaration>[];
|
| for (FieldDeclaration member in staticFields) {
|
| @@ -633,6 +644,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return _statement(body);
|
| }
|
|
|
| + JS.Statement _overrideField(FieldElement e) {
|
| + var cls = e.enclosingElement;
|
| + return js.statement('dart.virtualField(#, #)', [
|
| + cls.name,
|
| + _emitMemberName(e.name, type: cls.type)
|
| + ]);
|
| + }
|
| +
|
| /// Generates the implicit default constructor for class C of the form
|
| /// `C() : super() {}`.
|
| JS.Method _emitImplicitConstructor(
|
| @@ -640,7 +659,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // If we don't have a method body, skip this.
|
| if (fields.isEmpty) return null;
|
|
|
| - dynamic body = _initializeFields(fields);
|
| + dynamic body = _initializeFields(node, fields);
|
| var superCall = _superConstructorCall(node);
|
| if (superCall != null) body = [[body, superCall]];
|
| return new JS.Method(
|
| @@ -730,7 +749,8 @@ 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.parent, fields, node.parameters, node.initializers));
|
|
|
| var superCall = node.initializers.firstWhere(
|
| (i) => i is SuperConstructorInvocation, orElse: () => null);
|
| @@ -779,20 +799,21 @@ 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> fields,
|
| + JS.Statement _initializeFields(
|
| + ClassDeclaration classDecl, List<FieldDeclaration> fieldDecls,
|
| [FormalParameterList parameters,
|
| NodeList<ConstructorInitializer> initializers]) {
|
| - var body = <JS.Statement>[];
|
|
|
| // Run field initializers if they can have side-effects.
|
| + var fields = new Map<FieldElement, JS.Expression>();
|
| var unsetFields = new Map<FieldElement, VariableDeclaration>();
|
| - for (var declaration in fields) {
|
| - for (var field in declaration.fields.variables) {
|
| - if (_isFieldInitConstant(field)) {
|
| - unsetFields[field.element] = field;
|
| + for (var declaration in fieldDecls) {
|
| + for (var fieldNode in declaration.fields.variables) {
|
| + var element = fieldNode.element;
|
| + if (_isFieldInitConstant(fieldNode)) {
|
| + unsetFields[element] = fieldNode;
|
| } else {
|
| - body.add(js.statement(
|
| - '# = #;', [_visit(field.name), _visitInitializer(field)]));
|
| + fields[element] = _visitInitializer(fieldNode);
|
| }
|
| }
|
| }
|
| @@ -800,16 +821,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| // Initialize fields from `this.fieldName` parameters.
|
| if (parameters != null) {
|
| for (var p in parameters.parameters) {
|
| - if (p is DefaultFormalParameter) p = p.parameter;
|
| - if (p is FieldFormalParameter) {
|
| - var field = (p.element as FieldFormalParameterElement).field;
|
| - // Use the getter to initialize the field. This is a bit strange, but
|
| - // final fields don't have a setter element that we could use instead.
|
| -
|
| - var memberName =
|
| - _emitMemberName(field.name, type: field.enclosingElement.type);
|
| - body.add(js.statement('this.# = #;', [memberName, _visit(p)]));
|
| - unsetFields.remove(field);
|
| + var element = p.element;
|
| + if (element is FieldFormalParameterElement) {
|
| + fields[element.field] = _visit(p);
|
| }
|
| }
|
| }
|
| @@ -818,30 +832,56 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| if (initializers != null) {
|
| for (var init in initializers) {
|
| if (init is ConstructorFieldInitializer) {
|
| - body.add(js.statement(
|
| - '# = #;', [_visit(init.fieldName), _visit(init.expression)]));
|
| - unsetFields.remove(init.fieldName.staticElement);
|
| + fields[init.fieldName.staticElement] = _visit(init.expression);
|
| }
|
| }
|
| }
|
|
|
| + for (var f in fields.keys) unsetFields.remove(f);
|
| +
|
| // Initialize all remaining fields
|
| - unsetFields.forEach((field, fieldNode) {
|
| + unsetFields.forEach((element, fieldNode) {
|
| JS.Expression value;
|
| if (fieldNode.initializer != null) {
|
| value = _visit(fieldNode.initializer);
|
| } else {
|
| - var type = rules.elementType(field);
|
| + var type = rules.elementType(element);
|
| value = new JS.LiteralNull();
|
| if (rules.maybeNonNullableType(type)) {
|
| value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]);
|
| }
|
| }
|
| - var memberName =
|
| - _emitMemberName(field.name, type: field.enclosingElement.type);
|
| - body.add(js.statement('this.# = #;', [memberName, value]));
|
| + fields[element] = value;
|
| });
|
|
|
| + var classElem = classDecl.element;
|
| + bool classCanBeMixin = !classElem.type.isObject &&
|
| + classElem.supertype.isObject &&
|
| + classElem.mixins.isEmpty;
|
| +
|
| + var body = <JS.Statement>[];
|
| + fields.forEach((FieldElement e, JS.Expression initialValue) {
|
| + var access = _emitMemberName(e.name, type: e.enclosingElement.type);
|
| +
|
| + // Fields on private types can't be extended outside this library.
|
| + // Private fields on public types also can't be extended, as long as the
|
| + // type can't be mixed in.
|
| + bool isSealed = classElem.isPrivate || e.isPrivate && !classCanBeMixin;
|
| +
|
| + JS.Statement init;
|
| + if (isSealed && !_fieldsNeedingStorage.contains(e)) {
|
| + // Concrete field: doesn't override anything else, and is sealed so it
|
| + // isn't and can't be overridden by anything else.
|
| + init = js.statement('this.# = #;', [access, initialValue]);
|
| + } else {
|
| + init = js.statement('dart.initField(#, this, #, #);', [
|
| + classDecl.name.name,
|
| + access,
|
| + initialValue
|
| + ]);
|
| + }
|
| + body.add(init);
|
| + });
|
| return _statement(body);
|
| }
|
|
|
| @@ -2206,10 +2246,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| if (name.startsWith('_')) {
|
| return _privateNames.putIfAbsent(name, () {
|
| var t = new JSTemporary(name);
|
| - _pendingPrivateNames.add(t);
|
| + _pendingSymbols.add(t);
|
| return t;
|
| });
|
| }
|
| +
|
| // Check for extension method:
|
| var extLibrary = _findExtensionLibrary(name, type);
|
|
|
| @@ -2231,10 +2272,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
|
|
| if (extLibrary != null) {
|
| - return js.call('#.#', [
|
| - _libraryName(extLibrary),
|
| - _propertyName(_addExtensionMethodName(name, extLibrary))
|
| - ]);
|
| + return _extensionMethodName(name, extLibrary);
|
| }
|
|
|
| return _propertyName(name);
|
| @@ -2258,18 +2296,18 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return extLibrary;
|
| }
|
|
|
| - String _addExtensionMethodName(String name, LibraryElement extLibrary) {
|
| - var extensionMethodName = '\$$name';
|
| - if (extLibrary == currentLibrary) {
|
| + JS.Expression _extensionMethodName(String name, LibraryElement library) {
|
| + var extName = '\$$name';
|
| + if (library == currentLibrary) {
|
| // TODO(jacobr): need to do a better job ensuring that extension method
|
| // name symbols do not conflict with other symbols before we can let
|
| // user defined libraries define extension methods.
|
| - if (_extensionMethodNames.add(extensionMethodName)) {
|
| - _pendingExtensionMethodNames.add(extensionMethodName);
|
| - _addExport(extensionMethodName);
|
| + if (_extensionMethodNames.add(extName)) {
|
| + _pendingSymbols.add(new JS.Identifier(extName));
|
| + _addExport(extName);
|
| }
|
| }
|
| - return extensionMethodName;
|
| + return js.call('#.#', [_libraryName(library), _propertyName(extName)]);
|
| }
|
|
|
| bool _externalOrNative(node) =>
|
| @@ -2323,7 +2361,8 @@ class JSGenerator extends CodeGenerator {
|
| TypeProvider get types => rules.provider;
|
|
|
| String generateLibrary(LibraryUnit unit, LibraryInfo info) {
|
| - var codegen = new JSCodegenVisitor(info, rules, _extensionMethods);
|
| + var fields = findFieldOverrides(unit);
|
| + var codegen = new JSCodegenVisitor(info, rules, _extensionMethods, fields);
|
| var module = codegen.emitLibrary(unit);
|
| var dir = path.join(outDir, jsOutputPath(info, root));
|
| return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps);
|
|
|