| 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..37edc3ab45ff714d7e8b3e3d314be3a4ed0fbed9 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,151 @@ 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(_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.
|
| + 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.
|
| ///
|
|
|