Skip to content

Commit c1e97b4

Browse files
author
John Messerly
committed
fix #603, support mock objects
if noSuchMethod is used to implement an abstract member, this will be detected and appropriate code will be generated. R=leafp@google.com Review URL: https://codereview.chromium.org/2158173003 .
1 parent 144d3db commit c1e97b4

31 files changed

+1384
-46
lines changed

pkg/dev_compiler/lib/runtime/dart_sdk.js

+23-23
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ dart_library.library('dart_sdk', null, /* Imports */[
4040
let JSArrayOfFormatter = () => (JSArrayOfFormatter = dart.constFn(_interceptors.JSArray$(_debugger.Formatter)))();
4141
let LinkedHashSetOfNameValuePair = () => (LinkedHashSetOfNameValuePair = dart.constFn(collection.LinkedHashSet$(_debugger.NameValuePair)))();
4242
let SetOfString = () => (SetOfString = dart.constFn(core.Set$(core.String)))();
43-
let IterableOfNameValuePair = () => (IterableOfNameValuePair = dart.constFn(core.Iterable$(_debugger.NameValuePair)))();
4443
let ListOfNameValuePair = () => (ListOfNameValuePair = dart.constFn(core.List$(_debugger.NameValuePair)))();
4544
let JSArrayOfJsonMLFormatter = () => (JSArrayOfJsonMLFormatter = dart.constFn(_interceptors.JSArray$(_debugger.JsonMLFormatter)))();
4645
let JSArray = () => (JSArray = dart.constFn(_interceptors.JSArray$()))();
@@ -482,7 +481,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
482481
let dynamicToList = () => (dynamicToList = dart.constFn(dart.definiteFunctionType(core.List, [dart.dynamic])))();
483482
let TypeToString = () => (TypeToString = dart.constFn(dart.definiteFunctionType(core.String, [core.Type])))();
484483
let dynamicAndStringTobool = () => (dynamicAndStringTobool = dart.constFn(dart.definiteFunctionType(core.bool, [dart.dynamic, core.String])))();
485-
let dynamicAnddynamicTodynamic$ = () => (dynamicAnddynamicTodynamic$ = dart.constFn(dart.definiteFunctionType(dart.dynamic, [dart.dynamic, dart.dynamic])))();
484+
let intAnddynamicTovoid = () => (intAnddynamicTovoid = dart.constFn(dart.definiteFunctionType(dart.void, [core.int, dart.dynamic])))();
486485
let dynamicTobool$ = () => (dynamicTobool$ = dart.constFn(dart.definiteFunctionType(core.bool, [dart.dynamic])))();
487486
let dynamicAnddynamicTovoid = () => (dynamicAnddynamicTovoid = dart.constFn(dart.definiteFunctionType(dart.void, [dart.dynamic, dart.dynamic])))();
488487
let VoidTodynamic$ = () => (VoidTodynamic$ = dart.constFn(dart.definiteFunctionType(dart.dynamic, [])))();
@@ -498,6 +497,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
498497
let FunctionTovoid = () => (FunctionTovoid = dart.constFn(dart.definiteFunctionType(dart.void, [core.Function])))();
499498
let StringAndStringToString = () => (StringAndStringToString = dart.constFn(dart.definiteFunctionType(core.String, [core.String, core.String])))();
500499
let TypeAndStringTodynamic = () => (TypeAndStringTodynamic = dart.constFn(dart.definiteFunctionType(dart.dynamic, [core.Type, core.String])))();
500+
let dynamicAnddynamicTodynamic$ = () => (dynamicAnddynamicTodynamic$ = dart.constFn(dart.definiteFunctionType(dart.dynamic, [dart.dynamic, dart.dynamic])))();
501501
let ListOfEToListOfE = () => (ListOfEToListOfE = dart.constFn(dart.definiteFunctionType(E => [core.List$(E), [core.List$(E)]])))();
502502
let StringTovoid = () => (StringTovoid = dart.constFn(dart.definiteFunctionType(dart.void, [core.String])))();
503503
let _IsolateContextAndFunctionTodynamic = () => (_IsolateContextAndFunctionTodynamic = dart.constFn(dart.definiteFunctionType(dart.dynamic, [_isolate_helper._IsolateContext, core.Function])))();
@@ -1318,15 +1318,15 @@ dart_library.library('dart_sdk', null, /* Imports */[
13181318
if (dart.test(dart.hasMethod(obj, f))) return dart.bind(obj, f, void 0);
13191319
return obj[f];
13201320
}
1321-
return dart.noSuchMethod(obj, new dart._Invocation(core.String._check(field), [], {isGetter: true}));
1321+
return dart.noSuchMethod(obj, new dart.InvocationImpl(core.String._check(field), [], {isGetter: true}));
13221322
};
13231323
dart.dput = function(obj, field, value) {
13241324
let f = dart._canonicalMember(obj, field);
13251325
dart._trackCall(obj, f);
13261326
if (f != null) {
13271327
return obj[f] = value;
13281328
}
1329-
return dart.noSuchMethod(obj, new dart._Invocation(core.String._check(field), [value], {isSetter: true}));
1329+
return dart.noSuchMethod(obj, new dart.InvocationImpl(core.String._check(field), [value], {isSetter: true}));
13301330
};
13311331
dart._checkApply = function(type, actuals) {
13321332
if (actuals.length < type.args.length) return false;
@@ -1368,7 +1368,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
13681368
if (args.length > 0 && args[args.length - 1].__proto__ == Object.prototype) {
13691369
namedArgs = args.pop();
13701370
}
1371-
return dart.noSuchMethod(originalTarget, new dart._Invocation(name, args, {namedArguments: namedArgs, isMethod: true}));
1371+
return dart.noSuchMethod(originalTarget, new dart.InvocationImpl(name, args, {namedArguments: namedArgs, isMethod: true}));
13721372
}
13731373
if (!(f instanceof Function)) {
13741374
if (f != null) {
@@ -1449,7 +1449,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
14491449
dart._callMethod = function(obj, name, typeArgs, args, displayName) {
14501450
let symbol = dart._canonicalMember(obj, name);
14511451
if (symbol == null) {
1452-
return dart.noSuchMethod(obj, new dart._Invocation(core.String._check(displayName), core.List._check(args), {isMethod: true}));
1452+
return dart.noSuchMethod(obj, new dart.InvocationImpl(core.String._check(displayName), core.List._check(args), {isMethod: true}));
14531453
}
14541454
let f = obj != null ? obj[symbol] : null;
14551455
let ftype = dart.getMethodType(obj, symbol);
@@ -2249,7 +2249,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
22492249
return dart.test(this.isGetter) || dart.test(this.isSetter);
22502250
}
22512251
};
2252-
dart._Invocation = class _Invocation extends core.Invocation {
2252+
dart.InvocationImpl = class InvocationImpl extends core.Invocation {
22532253
new(memberName, positionalArguments, opts) {
22542254
let namedArguments = opts && 'namedArguments' in opts ? opts.namedArguments : null;
22552255
let isMethod = opts && 'isMethod' in opts ? opts.isMethod : false;
@@ -2260,15 +2260,15 @@ dart_library.library('dart_sdk', null, /* Imports */[
22602260
this.isGetter = isGetter;
22612261
this.isSetter = isSetter;
22622262
this.memberName = dart._dartSymbol(memberName);
2263-
this.namedArguments = dart._Invocation._namedArgsToSymbols(namedArguments);
2263+
this.namedArguments = dart.InvocationImpl._namedArgsToSymbols(namedArguments);
22642264
}
22652265
static _namedArgsToSymbols(namedArgs) {
22662266
if (namedArgs == null) return dart.map();
22672267
return MapOfSymbol$dynamic().fromIterable(dart.getOwnPropertyNames(namedArgs), {key: dart._dartSymbol, value: dart.fn(k => namedArgs[k], dynamicTodynamic$())});
22682268
}
22692269
};
2270-
dart.setSignature(dart._Invocation, {
2271-
constructors: () => ({new: dart.definiteFunctionType(dart._Invocation, [core.String, core.List], {namedArguments: dart.dynamic, isMethod: core.bool, isGetter: core.bool, isSetter: core.bool})}),
2270+
dart.setSignature(dart.InvocationImpl, {
2271+
constructors: () => ({new: dart.definiteFunctionType(dart.InvocationImpl, [core.String, core.List], {namedArguments: dart.dynamic, isMethod: core.bool, isGetter: core.bool, isSetter: core.bool})}),
22722272
statics: () => ({_namedArgsToSymbols: dart.definiteFunctionType(core.Map$(core.Symbol, dart.dynamic), [dart.dynamic])}),
22732273
names: ['_namedArgsToSymbols']
22742274
});
@@ -2450,24 +2450,24 @@ dart_library.library('dart_sdk', null, /* Imports */[
24502450
return dart.notNull(this.end) - dart.notNull(this.start);
24512451
}
24522452
get maxPowerOfSubsetSize() {
2453-
return (dart.notNull(math.log(core.num._check(dart.dsend(this.length, '-', 0.5)))) / dart.notNull(math.log(_debugger._maxSpanLength)))[dartx.truncate]();
2453+
return (dart.notNull(math.log(dart.notNull(this.length) - 0.5)) / dart.notNull(math.log(_debugger._maxSpanLength)))[dartx.truncate]();
24542454
}
24552455
get subsetSize() {
2456-
return math.pow(_debugger._maxSpanLength, core.num._check(this.maxPowerOfSubsetSize));
2456+
return dart.asInt(math.pow(_debugger._maxSpanLength, this.maxPowerOfSubsetSize));
24572457
}
24582458
asMap() {
2459-
return this.iterable[dartx.skip](this.start)[dartx.take](core.int._check(this.length))[dartx.toList]()[dartx.asMap]();
2459+
return this.iterable[dartx.skip](this.start)[dartx.take](this.length)[dartx.toList]()[dartx.asMap]();
24602460
}
24612461
children() {
24622462
let ret = JSArrayOfNameValuePair().of([]);
2463-
if (dart.test(dart.dsend(this.length, '<=', _debugger._maxSpanLength))) {
2464-
dart.dsend(this.asMap(), 'forEach', dart.fn((i, element) => {
2465-
ret[dartx.add](new _debugger.NameValuePair({name: dart.toString(dart.dsend(i, '+', this.start)), value: element}));
2466-
}, dynamicAnddynamicTodynamic$()));
2463+
if (dart.notNull(this.length) <= dart.notNull(_debugger._maxSpanLength)) {
2464+
this.asMap()[dartx.forEach](dart.fn((i, element) => {
2465+
ret[dartx.add](new _debugger.NameValuePair({name: dart.toString(dart.notNull(i) + dart.notNull(this.start)), value: element}));
2466+
}, intAnddynamicTovoid()));
24672467
} else {
2468-
for (let i = this.start; dart.notNull(i) < dart.notNull(this.end); i = dart.notNull(i) + dart.notNull(core.int._check(this.subsetSize))) {
2469-
let subSpan = new _debugger.IterableSpan(i, dart.asInt(math.min(core.num)(this.end, core.num._check(dart.dsend(this.subsetSize, '+', i)))), this.iterable);
2470-
if (dart.equals(subSpan.length, 1)) {
2468+
for (let i = this.start; dart.notNull(i) < dart.notNull(this.end); i = dart.notNull(i) + dart.notNull(this.subsetSize)) {
2469+
let subSpan = new _debugger.IterableSpan(i, math.min(core.int)(this.end, dart.notNull(this.subsetSize) + dart.notNull(i)), this.iterable);
2470+
if (subSpan.length == 1) {
24712471
ret[dartx.add](new _debugger.NameValuePair({name: dart.toString(i), value: this.iterable[dartx.elementAt](i)}));
24722472
} else {
24732473
ret[dartx.add](new _debugger.NameValuePair({name: dart.str`[${i}...${dart.notNull(subSpan.end) - 1}]`, value: subSpan, hideName: true}));
@@ -2480,8 +2480,8 @@ dart_library.library('dart_sdk', null, /* Imports */[
24802480
dart.setSignature(_debugger.IterableSpan, {
24812481
constructors: () => ({new: dart.definiteFunctionType(_debugger.IterableSpan, [core.int, core.int, core.Iterable])}),
24822482
methods: () => ({
2483-
asMap: dart.definiteFunctionType(dart.dynamic, []),
2484-
children: dart.definiteFunctionType(dart.dynamic, [])
2483+
asMap: dart.definiteFunctionType(core.Map$(core.int, dart.dynamic), []),
2484+
children: dart.definiteFunctionType(core.List$(_debugger.NameValuePair), [])
24852485
})
24862486
});
24872487
_debugger.ClassMetadata = class ClassMetadata extends core.Object {
@@ -2833,7 +2833,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
28332833
}
28342834
children(object) {
28352835
let ret = LinkedHashSetOfNameValuePair().new();
2836-
ret.addAll(IterableOfNameValuePair()._check(new _debugger.IterableSpan(0, core.int._check(dart.dload(object, 'length')), core.Iterable._check(object)).children()));
2836+
ret.addAll(new _debugger.IterableSpan(0, core.int._check(dart.dload(object, 'length')), core.Iterable._check(object)).children());
28372837
this.addMetadataChildren(object, ret);
28382838
return ret.toList();
28392839
}

pkg/dev_compiler/lib/src/compiler/code_generator.dart

+149-2
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@ class CodeGenerator extends GeneralizingAstVisitor
545545
var from = getStaticType(fromExpr);
546546
var to = node.type.type;
547547

548-
var jsFrom = _visit(fromExpr);
548+
JS.Expression jsFrom = _visit(fromExpr);
549+
if (_inWhitelistCode(node)) return jsFrom;
549550

550551
// Skip the cast if it's not needed.
551552
if (rules.isSubtypeOf(from, to)) return jsFrom;
@@ -565,7 +566,6 @@ class CodeGenerator extends GeneralizingAstVisitor
565566
var type = _emitType(to,
566567
nameType: options.nameTypeTests || options.hoistTypeTests,
567568
hoistType: options.hoistTypeTests);
568-
if (_inWhitelistCode(node)) return jsFrom;
569569
if (isReifiedCoercion(node)) {
570570
return js.call('#._check(#)', [type, jsFrom]);
571571
} else {
@@ -1311,6 +1311,8 @@ class CodeGenerator extends GeneralizingAstVisitor
13111311
}
13121312
}
13131313

1314+
jsMethods.addAll(_implementMockInterfaces(type));
1315+
13141316
// If the type doesn't have an `iterator`, but claims to implement Iterable,
13151317
// we inject the adaptor method here, as it's less code size to put the
13161318
// helper on a parent class. This pattern is common in the core libraries
@@ -1330,6 +1332,151 @@ class CodeGenerator extends GeneralizingAstVisitor
13301332
return jsMethods.where((m) => m != null).toList(growable: false);
13311333
}
13321334

1335+
Iterable<ExecutableElement> _collectMockMethods(InterfaceType type) {
1336+
var element = type.element;
1337+
if (!_hasNoSuchMethod(element)) {
1338+
return [];
1339+
}
1340+
1341+
// Collect all unimplemented members.
1342+
//
1343+
// Initially, we track abstract and concrete members separately, then
1344+
// remove concrete from the abstract set. This is done because abstract
1345+
// members are allowed to "override" concrete ones in Dart.
1346+
// (In that case, it will still be treated as a concrete member and can be
1347+
// called at run time.)
1348+
var abstractMembers = new Map<String, ExecutableElement>();
1349+
var concreteMembers = new HashSet<String>();
1350+
1351+
void visit(InterfaceType type, bool isAbstract) {
1352+
if (type == null) return;
1353+
visit(type.superclass, isAbstract);
1354+
for (var m in type.mixins) visit(m, isAbstract);
1355+
for (var i in type.interfaces) visit(i, true);
1356+
1357+
var members = <ExecutableElement>[]
1358+
..addAll(type.methods)
1359+
..addAll(type.accessors);
1360+
for (var m in members) {
1361+
if (isAbstract || m.isAbstract) {
1362+
// Inconsistent signatures are disallowed, even with nSM, so we don't
1363+
// need to worry too much about which abstract member we save.
1364+
abstractMembers[m.name] = m;
1365+
} else {
1366+
concreteMembers.add(m.name);
1367+
}
1368+
}
1369+
}
1370+
1371+
visit(type, false);
1372+
1373+
concreteMembers.forEach(abstractMembers.remove);
1374+
return abstractMembers.values;
1375+
}
1376+
1377+
Iterable<JS.Method> _implementMockInterfaces(InterfaceType type) {
1378+
// TODO(jmesserly): every type with nSM will generate new stubs for all
1379+
// abstract members. For example:
1380+
//
1381+
// class C { m(); noSuchMethod(...) { ... } }
1382+
// class D extends C { m(); noSuchMethod(...) { ... } }
1383+
//
1384+
// We'll generate D.m even though it is not necessary.
1385+
//
1386+
// Doing better is a bit tricky, as our current codegen strategy for the
1387+
// mock methods encodes information about the number of arguments (and type
1388+
// arguments) that D expects.
1389+
return _collectMockMethods(type).map(_implementMockMethod);
1390+
}
1391+
1392+
/// Given a class C that implements method M from interface I, but does not
1393+
/// declare M, this will generate an implementation that forwards to
1394+
/// noSuchMethod.
1395+
///
1396+
/// For example:
1397+
///
1398+
/// class Cat {
1399+
/// bool eatFood(String food) => true;
1400+
/// }
1401+
/// class MockCat implements Cat {
1402+
/// noSuchMethod(Invocation invocation) => 3;
1403+
/// }
1404+
///
1405+
/// It will generate an `eatFood` that looks like:
1406+
///
1407+
/// eatFood(food) {
1408+
/// return core.bool.as(this.noSuchMethod(
1409+
/// new dart.InvocationImpl('eatFood', [food])));
1410+
/// }
1411+
JS.Method _implementMockMethod(ExecutableElement method) {
1412+
var positionalArgs = <JS.Identifier>[]
1413+
..addAll(
1414+
method.type.normalParameterNames.map((a) => new JS.Identifier(a)))
1415+
..addAll(
1416+
method.type.optionalParameterNames.map((a) => new JS.Identifier(a)));
1417+
1418+
var fnArgs = positionalArgs.toList();
1419+
1420+
var invocationProps = <JS.Property>[];
1421+
addProperty(String name, JS.Expression value) {
1422+
invocationProps.add(new JS.Property(js.string(name), value));
1423+
}
1424+
1425+
if (method.type.namedParameterTypes.isNotEmpty) {
1426+
fnArgs.add(namedArgumentTemp);
1427+
addProperty('namedArguments', namedArgumentTemp);
1428+
}
1429+
1430+
if (method is MethodElement) {
1431+
addProperty('isMethod', js.boolean(true));
1432+
} else {
1433+
var property = method as PropertyAccessorElement;
1434+
if (property.isGetter) {
1435+
addProperty('isGetter', js.boolean(true));
1436+
} else if (property.isSetter) {
1437+
addProperty('isSetter', js.boolean(true));
1438+
}
1439+
}
1440+
1441+
var fnBody =
1442+
js.call('this.noSuchMethod(new dart.InvocationImpl(#, #, #))', [
1443+
_elementMemberName(method),
1444+
new JS.ArrayInitializer(positionalArgs),
1445+
new JS.ObjectInitializer(invocationProps)
1446+
]);
1447+
1448+
if (!method.returnType.isDynamic) {
1449+
fnBody = js.call('#._check(#)', [_emitType(method.returnType), fnBody]);
1450+
}
1451+
1452+
var fn = new JS.Fun(fnArgs, js.statement('{ return #; }', [fnBody]),
1453+
typeParams: _emitTypeFormals(method.type.typeFormals));
1454+
1455+
// TODO(jmesserly): generic type arguments will get dropped.
1456+
// We have a similar issue with `dgsend` helpers.
1457+
return new JS.Method(
1458+
_elementMemberName(method,
1459+
useExtension:
1460+
_extensionTypes.isNativeClass(method.enclosingElement)),
1461+
_makeGenericFunction(fn),
1462+
isGetter: method is PropertyAccessorElement && method.isGetter,
1463+
isSetter: method is PropertyAccessorElement && method.isSetter,
1464+
isStatic: false);
1465+
}
1466+
1467+
/// Return `true` if the given [classElement] has a noSuchMethod() method
1468+
/// distinct from the one declared in class Object, as per the Dart Language
1469+
/// Specification (section 10.4).
1470+
// TODO(jmesserly): this was taken from error_verifier.dart
1471+
bool _hasNoSuchMethod(ClassElement classElement) {
1472+
// TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all
1473+
// methods, up through the class hierarchy.
1474+
MethodElement method = classElement.lookUpMethod(
1475+
FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library);
1476+
var definingClass = method?.enclosingElement;
1477+
return definingClass != null && !definingClass.type.isObject;
1478+
}
1479+
13331480
/// This is called whenever a derived class needs to introduce a new field,
13341481
/// shadowing a field or getter/setter pair on its parent.
13351482
///

pkg/dev_compiler/lib/src/js_ast/nodes.dart

+1
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,7 @@ class Fun extends FunctionExpression {
11751175
final Block body;
11761176
@override final List<Identifier> typeParams;
11771177
@override final TypeRef returnType;
1178+
11781179
/** Whether this is a JS generator (`function*`) that may contain `yield`. */
11791180
final bool isGenerator;
11801181

pkg/dev_compiler/pubspec.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ packages:
234234
name: test
235235
url: "https://pub.dartlang.org"
236236
source: hosted
237-
version: "0.12.13+5"
237+
version: "0.12.15+1"
238238
typed_data:
239239
description:
240240
name: typed_data
@@ -289,4 +289,4 @@ packages:
289289
url: "https://pub.dartlang.org"
290290
source: hosted
291291
version: "2.1.9"
292-
sdk: ">=1.16.0 <1.19.0"
292+
sdk: ">=1.16.0 <1.20.0"

pkg/dev_compiler/test/browser/language_tests.js

-1
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@
259259
'nan_identical_test': skip_fail,
260260
'nested_switch_label_test': skip_fail,
261261
'no_such_method_empty_selector_test': fail,
262-
'no_such_method_subtype_test': fail,
263262
'number_identifier_test_05_multi': skip_fail,
264263
'number_identity2_test': skip_fail,
265264
'numbers_test': skip_fail,

0 commit comments

Comments
 (0)