Index: pkg/dev_compiler/lib/src/compiler/code_generator.dart |
diff --git a/pkg/dev_compiler/lib/src/compiler/code_generator.dart b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
index 92534e23d3ba8e73ca274278369f063bbae5fb2d..1e4d5ac82e61901124bd5dc315dc20f96ee5857d 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
@@ -910,10 +910,9 @@ class CodeGenerator extends GeneralizingAstVisitor |
className = _emitTopLevelName(classElem); |
} |
- var extensions = _extensionsToImplement(classElem); |
var savedClassProperties = _classProperties; |
_classProperties = |
- new ClassPropertyModel.build(virtualFields, classElem, extensions); |
+ new ClassPropertyModel.build(_extensionTypes, virtualFields, classElem); |
var classExpr = _emitClassExpression( |
classElem, _emitClassMethods(node, ctors, fields), |
@@ -935,9 +934,8 @@ class CodeGenerator extends GeneralizingAstVisitor |
_defineNamedConstructors(ctors, body, className, isCallableTransitive); |
_emitVirtualFieldSymbols(classElem, body); |
- _emitClassSignature( |
- methods, allFields, classElem, ctors, extensions, className, body); |
- _defineExtensionMembers(extensions, className, body); |
+ _emitClassSignature(methods, allFields, classElem, ctors, className, body); |
+ _defineExtensionMembers(className, body); |
_emitClassMetadata(node.metadata, className, body); |
JS.Statement classDef = _statement(body); |
@@ -1484,13 +1482,14 @@ class CodeGenerator extends GeneralizingAstVisitor |
if (m.isStatic) continue; |
for (VariableDeclaration field in m.fields.variables) { |
if (virtualFields.containsKey(field.element)) { |
- jsMethods.addAll(_emitVirtualFieldAccessor(field, virtualFields)); |
+ jsMethods.addAll(_emitVirtualFieldAccessor(field)); |
} |
} |
} |
} |
- jsMethods.addAll(_implementMockInterfaces(type)); |
+ jsMethods.addAll(_classProperties.mockMembers.values |
+ .map((e) => _implementMockMember(e, type))); |
// If the type doesn't have an `iterator`, but claims to implement Iterable, |
// we inject the adaptor method here, as it's less code size to put the |
@@ -1511,64 +1510,6 @@ class CodeGenerator extends GeneralizingAstVisitor |
return jsMethods.where((m) => m != null).toList(growable: false); |
} |
- Iterable<ExecutableElement> _collectMockMethods(InterfaceType type) { |
- var element = type.element; |
- if (!_hasNoSuchMethod(element)) { |
- return []; |
- } |
- |
- // Collect all unimplemented members. |
- // |
- // Initially, we track abstract and concrete members separately, then |
- // remove concrete from the abstract set. This is done because abstract |
- // members are allowed to "override" concrete ones in Dart. |
- // (In that case, it will still be treated as a concrete member and can be |
- // called at run time.) |
- var abstractMembers = new Map<String, ExecutableElement>(); |
- var concreteMembers = new HashSet<String>(); |
- |
- void visit(InterfaceType type, bool isAbstract) { |
- if (type == null) return; |
- visit(type.superclass, isAbstract); |
- for (var m in type.mixins) visit(m, isAbstract); |
- for (var i in type.interfaces) visit(i, true); |
- |
- var members = <ExecutableElement>[] |
- ..addAll(type.methods) |
- ..addAll(type.accessors); |
- for (var m in members) { |
- if (isAbstract || m.isAbstract) { |
- // Inconsistent signatures are disallowed, even with nSM, so we don't |
- // need to worry too much about which abstract member we save. |
- abstractMembers[m.name] = m; |
- } else { |
- concreteMembers.add(m.name); |
- } |
- } |
- } |
- |
- visit(type, false); |
- |
- concreteMembers.forEach(abstractMembers.remove); |
- return abstractMembers.values; |
- } |
- |
- Iterable<JS.Method> _implementMockInterfaces(InterfaceType type) { |
- // TODO(jmesserly): every type with nSM will generate new stubs for all |
- // abstract members. For example: |
- // |
- // class C { m(); noSuchMethod(...) { ... } } |
- // class D extends C { m(); noSuchMethod(...) { ... } } |
- // |
- // We'll generate D.m even though it is not necessary. |
- // |
- // Doing better is a bit tricky, as our current codegen strategy for the |
- // mock methods encodes information about the number of arguments (and type |
- // arguments) that D expects. |
- return _collectMockMethods(type) |
- .map((method) => _implementMockMethod(method, type)); |
- } |
- |
/// Given a class C that implements method M from interface I, but does not |
/// declare M, this will generate an implementation that forwards to |
/// noSuchMethod. |
@@ -1588,7 +1529,7 @@ class CodeGenerator extends GeneralizingAstVisitor |
/// return core.bool.as(this.noSuchMethod( |
/// new dart.InvocationImpl('eatFood', args))); |
/// } |
- JS.Method _implementMockMethod(ExecutableElement method, InterfaceType type) { |
+ JS.Method _implementMockMember(ExecutableElement method, InterfaceType type) { |
var invocationProps = <JS.Property>[]; |
addProperty(String name, JS.Expression value) { |
invocationProps.add(new JS.Property(js.string(name), value)); |
@@ -1646,19 +1587,6 @@ class CodeGenerator extends GeneralizingAstVisitor |
isStatic: false); |
} |
- /// Return `true` if the given [classElement] has a noSuchMethod() method |
- /// distinct from the one declared in class Object, as per the Dart Language |
- /// Specification (section 10.4). |
- // TODO(jmesserly): this was taken from error_verifier.dart |
- bool _hasNoSuchMethod(ClassElement classElement) { |
- // TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all |
- // methods, up through the class hierarchy. |
- MethodElement method = classElement.lookUpMethod( |
- FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library); |
- var definingClass = method?.enclosingElement; |
- return definingClass != null && !definingClass.type.isObject; |
- } |
- |
/// This is called whenever a derived class needs to introduce a new field, |
/// shadowing a field or getter/setter pair on its parent. |
/// |
@@ -1666,21 +1594,26 @@ class CodeGenerator extends GeneralizingAstVisitor |
/// 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]; |
+ List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field) { |
+ var element = field.element as FieldElement; |
+ var virtualField = _classProperties.virtualFields[element]; |
var result = <JS.Method>[]; |
- var name = _declareMemberName((field.element as FieldElement).getter); |
- var getter = js.call('function() { return this[#]; }', [virtualField]); |
- result.add(new JS.Method(name, getter, isGetter: true)); |
+ var name = _declareMemberName(element.getter); |
- 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)); |
+ var mocks = _classProperties.mockMembers; |
+ if (!mocks.containsKey(element.name)) { |
+ var getter = js.call('function() { return this[#]; }', [virtualField]); |
+ result.add(new JS.Method(name, getter, isGetter: true)); |
+ } |
+ |
+ if (!mocks.containsKey(element.name + '=')) { |
+ var args = field.isFinal |
+ ? [new JS.Super(), name] |
+ : [new JS.This(), virtualField]; |
+ |
+ result.add(new JS.Method( |
+ name, js.call('function(value) { #[#] = value; }', args), |
+ isSetter: true)); |
} |
return result; |
@@ -1861,20 +1794,25 @@ class CodeGenerator extends GeneralizingAstVisitor |
/// If a concrete class implements one of our extensions, we might need to |
/// add forwarders. |
- void _defineExtensionMembers(List<ExecutableElement> extensions, |
+ void _defineExtensionMembers( |
JS.Expression className, List<JS.Statement> body) { |
- // If a concrete class implements one of our extensions, we might need to |
- // add forwarders. |
- if (extensions.isNotEmpty) { |
- var methodNames = <JS.Expression>[]; |
- for (var e in extensions) { |
- methodNames.add(_declareMemberName(e, useExtension: false)); |
- } |
+ void emitExtenstions( |
vsm
2017/04/07 20:57:35
nit: emitExtenstions -> emitExtensions
|
+ JS.Expression target, Iterable<ExecutableElement> extensions) { |
+ if (extensions.isEmpty) return; |
+ |
+ var names = extensions |
+ .map((e) => _declareMemberName(e, useExtension: false)) |
+ .toList(); |
body.add(_callHelperStatement('defineExtensionMembers(#, #);', [ |
- className, |
- new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4) |
+ target, |
+ new JS.ArrayInitializer(names, multiline: names.length > 4) |
])); |
} |
+ |
+ // Define mixin members (if any) on the mixin class. |
+ var mixinClass = js.call('#.__proto__', [className]); |
+ emitExtenstions(mixinClass, _classProperties.mixinExtensionMembers); |
+ emitExtenstions(className, _classProperties.extensionMembers); |
} |
JS.Property _buildSignatureField(String name, List<JS.Property> elements) { |
@@ -1890,7 +1828,6 @@ class CodeGenerator extends GeneralizingAstVisitor |
List<FieldDeclaration> fields, |
ClassElement classElem, |
List<ConstructorDeclaration> ctors, |
- List<ExecutableElement> extensions, |
JS.Expression className, |
List<JS.Statement> body) { |
if (classElem.interfaces.isNotEmpty) { |
@@ -2039,7 +1976,11 @@ class CodeGenerator extends GeneralizingAstVisitor |
sigFields.add(_buildSignatureField('statics', tStaticMethods)); |
sigFields.add(aNames); |
} |
- if (!sigFields.isEmpty || extensions.isNotEmpty) { |
+ // We set signature here, even if empty, to simplify the work of |
+ // defineExtensionMembers at runtime. See _defineExtensionMembers. |
+ if (!sigFields.isEmpty || |
+ _classProperties.extensionMembers.isNotEmpty || |
+ _classProperties.mixinExtensionMembers.isNotEmpty) { |
var sig = new JS.ObjectInitializer(sigFields); |
body.add(_callHelperStatement('setSignature(#, #);', [className, sig])); |
} |
@@ -2083,34 +2024,6 @@ class CodeGenerator extends GeneralizingAstVisitor |
} |
} |
- List<ExecutableElement> _extensionsToImplement(ClassElement element) { |
- if (_extensionTypes.isNativeClass(element)) return []; |
- |
- // Collect all extension types we implement. |
- var type = element.type; |
- var types = _extensionTypes.collectNativeInterfaces(element); |
- if (types.isEmpty) return []; |
- |
- var members = new Set<ExecutableElement>(); |
- // Collect all possible extension method names. |
- var extensionMembers = new HashSet<String>(); |
- for (var t in types) { |
- for (var m in [t.methods, t.accessors].expand((e) => e)) { |
- if (!m.isStatic && m.isPublic) extensionMembers.add(m.name); |
- } |
- } |
- |
- // Collect all of extension methods this type implements. |
- for (var m in [type.methods, type.accessors].expand((e) => e)) { |
- if (!m.isStatic && !m.isAbstract && extensionMembers.contains(m.name)) { |
- members.add(m); |
- } |
- } |
- members.addAll(_collectMockMethods(type) |
- .where((m) => extensionMembers.contains(m.name))); |
- return members.toList(); |
- } |
- |
/// Generates the implicit default constructor for class C of the form |
/// `C() : super() {}`. |
JS.Method _emitImplicitConstructor( |