| Index: lib/src/compiler/code_generator.dart
|
| diff --git a/lib/src/compiler/code_generator.dart b/lib/src/compiler/code_generator.dart
|
| index 2c19b39ef6f2c7ac0a9af6e10978145d953ec503..43de895f24bd03427106230beb2078f5d600b6e6 100644
|
| --- a/lib/src/compiler/code_generator.dart
|
| +++ b/lib/src/compiler/code_generator.dart
|
| @@ -531,9 +531,24 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| }
|
| }
|
|
|
| + JS.Expression className;
|
| + if (classElem.typeParameters.isNotEmpty) {
|
| + // Generic classes will be defined inside a function that closes over the
|
| + // type parameter. So we can use their local variable name directly.
|
| + className = new JS.Identifier(classElem.name);
|
| + } else {
|
| + className = _emitTopLevelName(classElem);
|
| + }
|
| +
|
| + var superclasses = getSuperclasses(classElem);
|
| + var virtualFields = <FieldElement, JS.TemporaryId>{};
|
| + var virtualFieldSymbols = <JS.Statement>[];
|
| + _registerVirtualFields(classElem, className, superclasses, fields,
|
| + virtualFields, virtualFieldSymbols);
|
| +
|
| var allFields = new List.from(fields)..addAll(staticFields);
|
| - var classExpr = _emitClassExpression(
|
| - classElem, _emitClassMethods(node, ctors, fields),
|
| + var classExpr = _emitClassExpression(classElem,
|
| + _emitClassMethods(node, ctors, fields, superclasses, virtualFields),
|
| fields: allFields);
|
|
|
| var body = <JS.Statement>[];
|
| @@ -541,12 +556,12 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| _initExtensionSymbols(classElem, methods, fields, body);
|
|
|
| // Emit the class, e.g. `core.Object = class Object { ... }`
|
| - JS.Expression className = _defineClass(classElem, classExpr, body);
|
| + _defineClass(classElem, className, classExpr, body);
|
|
|
| // Emit things that come after the ES6 `class ... { ... }`.
|
| _setBaseClass(classElem, className, body);
|
| _defineNamedConstructors(ctors, body, className);
|
| - _emitVirtualFields(classElem, fields, className, body);
|
| + _emitVirtualFieldSymbols(virtualFieldSymbols, body);
|
| _emitClassSignature(methods, classElem, ctors, extensions, className, body);
|
| _defineExtensionMembers(extensions, className, body);
|
| _emitClassMetadata(node.metadata, className, body);
|
| @@ -563,19 +578,42 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| return _statement(body);
|
| }
|
|
|
| - JS.Expression _defineClass(ClassElement classElem,
|
| + void _registerVirtualFields(
|
| + ClassElement classElem,
|
| + JS.Expression className,
|
| + List<ClassElement> superclasses,
|
| + List<FieldDeclaration> fields,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields,
|
| + List<JS.Statement> virtualFieldSymbols) {
|
| + for (var field in fields) {
|
| + for (VariableDeclaration field in field.fields.variables) {
|
| + var overrideInfo = checkForPropertyOverride(
|
| + field.element, superclasses, _extensionTypes);
|
| + if (overrideInfo.foundGetter || overrideInfo.foundSetter) {
|
| + var fieldName =
|
| + _emitMemberName(field.element.name, type: classElem.type);
|
| + var virtualField = new JS.TemporaryId(field.element.name);
|
| + virtualFields[field.element] = virtualField;
|
| + virtualFieldSymbols.add(js.statement(
|
| + 'const # = Symbol(#.name + "." + #.toString());',
|
| + [virtualField, className, fieldName]));
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + void _defineClass(ClassElement classElem, JS.Expression className,
|
| JS.ClassExpression classExpr, List<JS.Statement> body) {
|
| - JS.Expression className;
|
| if (classElem.typeParameters.isNotEmpty) {
|
| - // Generic classes will be defined inside a function that closes over the
|
| - // type parameter. So we can use their local variable name directly.
|
| - className = classExpr.name;
|
| body.add(new JS.ClassDeclaration(classExpr));
|
| } else {
|
| - className = _emitTopLevelName(classElem);
|
| body.add(js.statement('# = #;', [className, classExpr]));
|
| }
|
| - return className;
|
| + }
|
| +
|
| + void _emitVirtualFieldSymbols(
|
| + List<JS.Statement> virtualFields, List<JS.Statement> body) {
|
| + body.addAll(virtualFields);
|
| }
|
|
|
| List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) {
|
| @@ -752,8 +790,12 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| return jsMethods;
|
| }
|
|
|
| - List<JS.Method> _emitClassMethods(ClassDeclaration node,
|
| - List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
|
| + List<JS.Method> _emitClassMethods(
|
| + ClassDeclaration node,
|
| + List<ConstructorDeclaration> ctors,
|
| + List<FieldDeclaration> fields,
|
| + List<ClassElement> superclasses,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields) {
|
| var element = node.element;
|
| var type = element.type;
|
| var isObject = type.isObject;
|
| @@ -762,16 +804,16 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| // default constructor `C() : super() {}`, unless C is class Object.
|
| var jsMethods = <JS.Method>[];
|
| if (ctors.isEmpty && !isObject) {
|
| - jsMethods.add(_emitImplicitConstructor(node, fields));
|
| + jsMethods.add(_emitImplicitConstructor(node, fields, virtualFields));
|
| }
|
|
|
| bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null;
|
| - var superclasses = getSuperclasses(element);
|
|
|
| bool hasIterator = false;
|
| for (var m in node.members) {
|
| if (m is ConstructorDeclaration) {
|
| - jsMethods.add(_emitConstructor(m, type, fields, isObject));
|
| + jsMethods
|
| + .add(_emitConstructor(m, type, fields, virtualFields, isObject));
|
| } else if (m is MethodDeclaration) {
|
| jsMethods.add(_emitMethodDeclaration(type, m));
|
|
|
| @@ -783,8 +825,17 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| hasIterator = true;
|
| jsMethods.add(_emitIterable(type));
|
| }
|
| - } else if (m is FieldDeclaration && _extensionTypes.contains(element)) {
|
| - jsMethods.addAll(_emitNativeFieldAccessors(m));
|
| + } else if (m is FieldDeclaration) {
|
| + if (_extensionTypes.contains(element)) {
|
| + jsMethods.addAll(_emitNativeFieldAccessors(m));
|
| + continue;
|
| + }
|
| + if (m.isStatic) continue;
|
| + for (VariableDeclaration field in m.fields.variables) {
|
| + if (virtualFields.containsKey(field.element)) {
|
| + jsMethods.addAll(_emitVirtualFieldAccessor(field, virtualFields));
|
| + }
|
| + }
|
| }
|
| }
|
|
|
| @@ -803,6 +854,38 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| return jsMethods.where((m) => m != null).toList(growable: false);
|
| }
|
|
|
| + /// This is called whenever a derived class needs to introduce a new field,
|
| + /// shadowing a field or getter/setter pair on its parent.
|
| + ///
|
| + /// This is important because otherwise, trying to read or write the field
|
| + /// would end up calling the getter or setter, and one of those might not even
|
| + /// exist, resulting in a runtime error. Even if they did exist, that's the
|
| + /// wrong behavior if a new field was declared.
|
| + List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields) {
|
| + var virtualField = virtualFields[field.element];
|
| + var result = <JS.Method>[];
|
| + var name = _emitMemberName(field.element.name,
|
| + type: (field.element.enclosingElement as ClassElement).type);
|
| + var getter = js.call('function() { return this[#]; }', [virtualField]);
|
| + result.add(new JS.Method(name, getter, isGetter: true));
|
| +
|
| + if (field.isFinal) {
|
| + var setter = js.call('function(value) { super[#] = value; }', [name]);
|
| + result.add(new JS.Method(name, setter, isSetter: true));
|
| + } else {
|
| + var setter =
|
| + js.call('function(value) { this[#] = value; }', [virtualField]);
|
| + result.add(new JS.Method(name, setter, isSetter: true));
|
| + }
|
| +
|
| + return result;
|
| + }
|
| +
|
| + /// Emit a getter or setter that simply forwards to the superclass getter or
|
| + /// setter. This is needed because in ES6, if you only override a getter
|
| + /// (alternatively, a setter), then there is an implicit override of the
|
| + /// setter (alternatively, the getter) that does nothing.
|
| JS.Method _emitSuperAccessorWrapper(MethodDeclaration method,
|
| InterfaceType type, List<ClassElement> superclasses) {
|
| var methodElement = method.element as PropertyAccessorElement;
|
| @@ -911,26 +994,6 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| }
|
| }
|
|
|
| - /// Emits instance fields, if they are virtual
|
| - /// (in other words, they override a getter/setter pair).
|
| - void _emitVirtualFields(
|
| - ClassElement classElement,
|
| - List<FieldDeclaration> fields,
|
| - JS.Expression className,
|
| - List<JS.Statement> body) {
|
| - List<ClassElement> superclasses = getSuperclasses(classElement);
|
| - for (FieldDeclaration member in fields) {
|
| - for (VariableDeclaration field in member.fields.variables) {
|
| - var propertyOverrideResult = checkForPropertyOverride(
|
| - field.element, superclasses, _extensionTypes);
|
| - if (propertyOverrideResult.foundGetter ||
|
| - propertyOverrideResult.foundSetter) {
|
| - body.add(_overrideField(className, field.element));
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| void _defineNamedConstructors(List<ConstructorDeclaration> ctors,
|
| List<JS.Statement> body, JS.Expression className) {
|
| for (ConstructorDeclaration member in ctors) {
|
| @@ -1137,23 +1200,19 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| _collectExtensions(type.superclass, types);
|
| }
|
|
|
| - JS.Statement _overrideField(JS.Expression className, FieldElement e) {
|
| - var cls = e.enclosingElement;
|
| - return js.statement('dart.virtualField(#, #)',
|
| - [className, _emitMemberName(e.name, type: cls.type)]);
|
| - }
|
| -
|
| /// Generates the implicit default constructor for class C of the form
|
| /// `C() : super() {}`.
|
| JS.Method _emitImplicitConstructor(
|
| - ClassDeclaration node, List<FieldDeclaration> fields) {
|
| + ClassDeclaration node,
|
| + List<FieldDeclaration> fields,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields) {
|
| assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty);
|
|
|
| // If we don't have a method body, skip this.
|
| var superCall = _superConstructorCall(node.element);
|
| if (fields.isEmpty && superCall == null) return null;
|
|
|
| - dynamic body = _initializeFields(node, fields);
|
| + dynamic body = _initializeFields(node, fields, virtualFields);
|
| if (superCall != null) {
|
| body = [
|
| [body, superCall]
|
| @@ -1166,8 +1225,12 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| node.element);
|
| }
|
|
|
| - JS.Method _emitConstructor(ConstructorDeclaration node, InterfaceType type,
|
| - List<FieldDeclaration> fields, bool isObject) {
|
| + JS.Method _emitConstructor(
|
| + ConstructorDeclaration node,
|
| + InterfaceType type,
|
| + List<FieldDeclaration> fields,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields,
|
| + bool isObject) {
|
| if (_externalOrNative(node)) return null;
|
|
|
| var name = _constructorName(node.element);
|
| @@ -1238,7 +1301,7 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| } else {
|
| var savedFunction = _currentFunction;
|
| _currentFunction = node.body;
|
| - body = _emitConstructorBody(node, fields);
|
| + body = _emitConstructorBody(node, fields, virtualFields);
|
| _currentFunction = savedFunction;
|
| }
|
|
|
| @@ -1266,7 +1329,9 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| }
|
|
|
| JS.Block _emitConstructorBody(
|
| - ConstructorDeclaration node, List<FieldDeclaration> fields) {
|
| + ConstructorDeclaration node,
|
| + List<FieldDeclaration> fields,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields) {
|
| var body = <JS.Statement>[];
|
| ClassDeclaration cls = node.parent;
|
|
|
| @@ -1295,7 +1360,7 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| // These are expanded into each non-redirecting constructor.
|
| // In the future we may want to create an initializer function if we have
|
| // multiple constructors, but it needs to be balanced against readability.
|
| - body.add(_initializeFields(cls, fields, node));
|
| + body.add(_initializeFields(cls, fields, virtualFields, node));
|
|
|
| var superCall = node.initializers.firstWhere(
|
| (i) => i is SuperConstructorInvocation,
|
| @@ -1370,7 +1435,9 @@ class CodeGenerator extends GeneralizingAstVisitor
|
| /// 3. constructor field initializers,
|
| /// 4. initialize fields not covered in 1-3
|
| JS.Statement _initializeFields(
|
| - ClassDeclaration cls, List<FieldDeclaration> fieldDecls,
|
| + ClassDeclaration cls,
|
| + List<FieldDeclaration> fieldDecls,
|
| + Map<FieldElement, JS.TemporaryId> virtualFields,
|
| [ConstructorDeclaration ctor]) {
|
| bool isConst = ctor != null && ctor.constKeyword != null;
|
| if (isConst) _loader.startTopLevel(cls.element);
|
| @@ -1422,8 +1489,13 @@ class CodeGenerator extends GeneralizingAstVisitor
|
|
|
| var body = <JS.Statement>[];
|
| fields.forEach((FieldElement e, JS.Expression initialValue) {
|
| - var access = _emitMemberName(e.name, type: e.enclosingElement.type);
|
| - body.add(js.statement('this.# = #;', [access, initialValue]));
|
| + if (virtualFields.containsKey(e)) {
|
| + body.add(
|
| + js.statement('this[#] = #;', [virtualFields[e], initialValue]));
|
| + } else {
|
| + var access = _emitMemberName(e.name, type: e.enclosingElement.type);
|
| + body.add(js.statement('this.# = #;', [access, initialValue]));
|
| + }
|
| });
|
|
|
| if (isConst) _loader.finishTopLevel(cls.element);
|
|
|