Chromium Code Reviews| Index: lib/src/compiler/code_generator.dart |
| diff --git a/lib/src/compiler/code_generator.dart b/lib/src/compiler/code_generator.dart |
| index c6a700390f429d073f854cae677efa1797b670d9..8e7080e48c124531a71aee0848d386352e6fabeb 100644 |
| --- a/lib/src/compiler/code_generator.dart |
| +++ b/lib/src/compiler/code_generator.dart |
| @@ -545,7 +545,8 @@ class CodeGenerator extends GeneralizingAstVisitor |
| var from = getStaticType(fromExpr); |
| var to = node.type.type; |
| - var jsFrom = _visit(fromExpr); |
| + JS.Expression jsFrom = _visit(fromExpr); |
| + if (_inWhitelistCode(node)) return jsFrom; |
| // Skip the cast if it's not needed. |
| if (rules.isSubtypeOf(from, to)) return jsFrom; |
| @@ -565,7 +566,6 @@ class CodeGenerator extends GeneralizingAstVisitor |
| var type = _emitType(to, |
| nameType: options.nameTypeTests || options.hoistTypeTests, |
| hoistType: options.hoistTypeTests); |
| - if (_inWhitelistCode(node)) return jsFrom; |
| if (isReifiedCoercion(node)) { |
| return js.call('#._check(#)', [type, jsFrom]); |
| } else { |
| @@ -1311,6 +1311,8 @@ class CodeGenerator extends GeneralizingAstVisitor |
| } |
| } |
| + jsMethods.addAll(_implementMockInterfaces(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 |
| // helper on a parent class. This pattern is common in the core libraries |
| @@ -1330,6 +1332,140 @@ 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) { |
|
Leaf
2016/07/18 23:06:06
I don't think this is worth worrying about now, bu
Jennifer Messerly
2016/07/19 20:23:54
Eeeep. Good point. I added a TODO and moar tests
|
| + return _collectMockMethods(type).map(_implementMockMethod); |
| + } |
| + |
| + /// 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. |
| + /// |
| + /// For example: |
| + /// |
| + /// class Cat { |
| + /// bool eatFood(String food) => true; |
| + /// } |
| + /// class MockCat implements Cat { |
| + /// noSuchMethod(Invocation invocation) => 3; |
| + /// } |
| + /// |
| + /// It will generate an `eatFood` that looks like: |
| + /// |
| + /// eatFood(food) { |
| + /// return core.bool.as(this.noSuchMethod( |
| + /// new dart.InvocationImpl('eatFood', [food]))); |
| + /// } |
| + JS.Method _implementMockMethod(ExecutableElement method) { |
| + var positionalArgs = <JS.Identifier>[] |
| + ..addAll( |
| + method.type.normalParameterNames.map((a) => new JS.Identifier(a))) |
| + ..addAll( |
| + method.type.optionalParameterNames.map((a) => new JS.Identifier(a))); |
| + |
| + var fnArgs = positionalArgs.toList(); |
| + |
| + var invocationProps = <JS.Property>[]; |
| + addProperty(String name, JS.Expression value) { |
| + invocationProps.add(new JS.Property(js.string(name), value)); |
| + } |
| + |
| + if (method.type.namedParameterTypes.isNotEmpty) { |
| + fnArgs.add(namedArgumentTemp); |
| + addProperty('namedArguments', namedArgumentTemp); |
| + } |
| + |
| + if (method is MethodElement) { |
| + addProperty('isMethod', js.boolean(true)); |
| + } else { |
| + var property = method as PropertyAccessorElement; |
| + if (property.isGetter) { |
| + addProperty('isGetter', js.boolean(true)); |
| + } else if (property.isSetter) { |
| + addProperty('isSetter', js.boolean(true)); |
| + } |
| + } |
| + |
| + var fnBody = |
| + js.call('this.noSuchMethod(new dart.InvocationImpl(#, #, #))', [ |
| + _elementMemberName(method), |
| + new JS.ArrayInitializer(positionalArgs), |
| + new JS.ObjectInitializer(invocationProps) |
| + ]); |
| + |
| + if (!method.returnType.isDynamic) { |
| + fnBody = js.call('#._check(#)', [_emitType(method.returnType), fnBody]); |
| + } |
| + |
| + var fn = new JS.Fun(fnArgs, js.statement('{ return #; }', [fnBody]), |
| + typeParams: _emitTypeFormals(method.type.typeFormals)); |
| + |
| + // TODO(jmesserly): generic type arguments will get dropped. |
| + // We have a similar issue with `dgsend` helpers. |
| + return new JS.Method( |
| + _elementMemberName(method, |
| + useExtension: |
| + _extensionTypes.isNativeClass(method.enclosingElement)), |
| + _makeGenericFunction(fn), |
| + isGetter: method is PropertyAccessorElement && method.isGetter, |
| + isSetter: method is PropertyAccessorElement && method.isSetter, |
| + 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. |
|
Leaf
2016/07/18 23:06:07
ouch.
Jennifer Messerly
2016/07/19 20:23:54
yeah. called multiple times too, just in this visi
|
| + 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. |
| /// |