Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Side by Side Diff: lib/src/compiler/code_generator.dart

Issue 2158173003: fix #603, support mock objects (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:collection' show HashMap, HashSet; 5 import 'dart:collection' show HashMap, HashSet;
6 import 'dart:math' show min, max; 6 import 'dart:math' show min, max;
7 7
8 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; 8 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
9 import 'package:analyzer/dart/ast/ast.dart'; 9 import 'package:analyzer/dart/ast/ast.dart';
10 import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; 10 import 'package:analyzer/dart/ast/token.dart' show Token, TokenType;
(...skipping 527 matching lines...) Expand 10 before | Expand all | Expand 10 after
538 emitExport(export); 538 emitExport(export);
539 } 539 }
540 } 540 }
541 541
542 @override 542 @override
543 visitAsExpression(AsExpression node) { 543 visitAsExpression(AsExpression node) {
544 Expression fromExpr = node.expression; 544 Expression fromExpr = node.expression;
545 var from = getStaticType(fromExpr); 545 var from = getStaticType(fromExpr);
546 var to = node.type.type; 546 var to = node.type.type;
547 547
548 var jsFrom = _visit(fromExpr); 548 JS.Expression jsFrom = _visit(fromExpr);
549 if (_inWhitelistCode(node)) return jsFrom;
549 550
550 // Skip the cast if it's not needed. 551 // Skip the cast if it's not needed.
551 if (rules.isSubtypeOf(from, to)) return jsFrom; 552 if (rules.isSubtypeOf(from, to)) return jsFrom;
552 553
553 // All Dart number types map to a JS double. 554 // All Dart number types map to a JS double.
554 if (_isNumberInJS(from) && _isNumberInJS(to)) { 555 if (_isNumberInJS(from) && _isNumberInJS(to)) {
555 // Make sure to check when converting to int. 556 // Make sure to check when converting to int.
556 if (from != types.intType && to == types.intType) { 557 if (from != types.intType && to == types.intType) {
557 // TODO(jmesserly): fuse this with notNull check. 558 // TODO(jmesserly): fuse this with notNull check.
558 return js.call('dart.asInt(#)', jsFrom); 559 return js.call('dart.asInt(#)', jsFrom);
559 } 560 }
560 561
561 // A no-op in JavaScript. 562 // A no-op in JavaScript.
562 return jsFrom; 563 return jsFrom;
563 } 564 }
564 565
565 var type = _emitType(to, 566 var type = _emitType(to,
566 nameType: options.nameTypeTests || options.hoistTypeTests, 567 nameType: options.nameTypeTests || options.hoistTypeTests,
567 hoistType: options.hoistTypeTests); 568 hoistType: options.hoistTypeTests);
568 if (_inWhitelistCode(node)) return jsFrom;
569 if (isReifiedCoercion(node)) { 569 if (isReifiedCoercion(node)) {
570 return js.call('#._check(#)', [type, jsFrom]); 570 return js.call('#._check(#)', [type, jsFrom]);
571 } else { 571 } else {
572 return js.call('#.as(#)', [type, jsFrom]); 572 return js.call('#.as(#)', [type, jsFrom]);
573 } 573 }
574 } 574 }
575 575
576 bool isReifiedCoercion(AstNode node) { 576 bool isReifiedCoercion(AstNode node) {
577 // TODO(sra): Find a better way to recognize reified coercion, since we 577 // TODO(sra): Find a better way to recognize reified coercion, since we
578 // can't set the isSynthetic attribute. 578 // can't set the isSynthetic attribute.
(...skipping 725 matching lines...) Expand 10 before | Expand all | Expand 10 after
1304 } 1304 }
1305 if (m.isStatic) continue; 1305 if (m.isStatic) continue;
1306 for (VariableDeclaration field in m.fields.variables) { 1306 for (VariableDeclaration field in m.fields.variables) {
1307 if (virtualFields.containsKey(field.element)) { 1307 if (virtualFields.containsKey(field.element)) {
1308 jsMethods.addAll(_emitVirtualFieldAccessor(field, virtualFields)); 1308 jsMethods.addAll(_emitVirtualFieldAccessor(field, virtualFields));
1309 } 1309 }
1310 } 1310 }
1311 } 1311 }
1312 } 1312 }
1313 1313
1314 jsMethods.addAll(_implementMockInterfaces(type));
1315
1314 // If the type doesn't have an `iterator`, but claims to implement Iterable, 1316 // If the type doesn't have an `iterator`, but claims to implement Iterable,
1315 // we inject the adaptor method here, as it's less code size to put the 1317 // we inject the adaptor method here, as it's less code size to put the
1316 // helper on a parent class. This pattern is common in the core libraries 1318 // helper on a parent class. This pattern is common in the core libraries
1317 // (e.g. IterableMixin<E> and IterableBase<E>). 1319 // (e.g. IterableMixin<E> and IterableBase<E>).
1318 // 1320 //
1319 // (We could do this same optimization for any interface with an `iterator` 1321 // (We could do this same optimization for any interface with an `iterator`
1320 // method, but that's more expensive to check for, so it doesn't seem worth 1322 // method, but that's more expensive to check for, so it doesn't seem worth
1321 // it. The above case for an explicit `iterator` method will catch those.) 1323 // it. The above case for an explicit `iterator` method will catch those.)
1322 if (!hasJsPeer && !hasIterator && _implementsIterable(type)) { 1324 if (!hasJsPeer && !hasIterator && _implementsIterable(type)) {
1323 jsMethods.add(_emitIterable(type)); 1325 jsMethods.add(_emitIterable(type));
1324 } 1326 }
1325 1327
1326 // Add all of the super helper methods 1328 // Add all of the super helper methods
1327 jsMethods.addAll(_superHelpers); 1329 jsMethods.addAll(_superHelpers);
1328 _superHelpers.clear(); 1330 _superHelpers.clear();
1329 1331
1330 return jsMethods.where((m) => m != null).toList(growable: false); 1332 return jsMethods.where((m) => m != null).toList(growable: false);
1331 } 1333 }
1332 1334
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) {
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
1378 return _collectMockMethods(type).map(_implementMockMethod);
1379 }
1380
1381 /// Given a class C that implements method M from interface I, but does not
1382 /// declare M, this will generate an implementation that forwards to
1383 /// noSuchMethod.
1384 ///
1385 /// For example:
1386 ///
1387 /// class Cat {
1388 /// bool eatFood(String food) => true;
1389 /// }
1390 /// class MockCat implements Cat {
1391 /// noSuchMethod(Invocation invocation) => 3;
1392 /// }
1393 ///
1394 /// It will generate an `eatFood` that looks like:
1395 ///
1396 /// eatFood(food) {
1397 /// return core.bool.as(this.noSuchMethod(
1398 /// new dart.InvocationImpl('eatFood', [food])));
1399 /// }
1400 JS.Method _implementMockMethod(ExecutableElement method) {
1401 var positionalArgs = <JS.Identifier>[]
1402 ..addAll(
1403 method.type.normalParameterNames.map((a) => new JS.Identifier(a)))
1404 ..addAll(
1405 method.type.optionalParameterNames.map((a) => new JS.Identifier(a)));
1406
1407 var fnArgs = positionalArgs.toList();
1408
1409 var invocationProps = <JS.Property>[];
1410 addProperty(String name, JS.Expression value) {
1411 invocationProps.add(new JS.Property(js.string(name), value));
1412 }
1413
1414 if (method.type.namedParameterTypes.isNotEmpty) {
1415 fnArgs.add(namedArgumentTemp);
1416 addProperty('namedArguments', namedArgumentTemp);
1417 }
1418
1419 if (method is MethodElement) {
1420 addProperty('isMethod', js.boolean(true));
1421 } else {
1422 var property = method as PropertyAccessorElement;
1423 if (property.isGetter) {
1424 addProperty('isGetter', js.boolean(true));
1425 } else if (property.isSetter) {
1426 addProperty('isSetter', js.boolean(true));
1427 }
1428 }
1429
1430 var fnBody =
1431 js.call('this.noSuchMethod(new dart.InvocationImpl(#, #, #))', [
1432 _elementMemberName(method),
1433 new JS.ArrayInitializer(positionalArgs),
1434 new JS.ObjectInitializer(invocationProps)
1435 ]);
1436
1437 if (!method.returnType.isDynamic) {
1438 fnBody = js.call('#._check(#)', [_emitType(method.returnType), fnBody]);
1439 }
1440
1441 var fn = new JS.Fun(fnArgs, js.statement('{ return #; }', [fnBody]),
1442 typeParams: _emitTypeFormals(method.type.typeFormals));
1443
1444 // TODO(jmesserly): generic type arguments will get dropped.
1445 // We have a similar issue with `dgsend` helpers.
1446 return new JS.Method(
1447 _elementMemberName(method,
1448 useExtension:
1449 _extensionTypes.isNativeClass(method.enclosingElement)),
1450 _makeGenericFunction(fn),
1451 isGetter: method is PropertyAccessorElement && method.isGetter,
1452 isSetter: method is PropertyAccessorElement && method.isSetter,
1453 isStatic: false);
1454 }
1455
1456 /// Return `true` if the given [classElement] has a noSuchMethod() method
1457 /// distinct from the one declared in class Object, as per the Dart Language
1458 /// Specification (section 10.4).
1459 // TODO(jmesserly): this was taken from error_verifier.dart
1460 bool _hasNoSuchMethod(ClassElement classElement) {
1461 // TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all
1462 // 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
1463 MethodElement method = classElement.lookUpMethod(
1464 FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library);
1465 var definingClass = method?.enclosingElement;
1466 return definingClass != null && !definingClass.type.isObject;
1467 }
1468
1333 /// This is called whenever a derived class needs to introduce a new field, 1469 /// This is called whenever a derived class needs to introduce a new field,
1334 /// shadowing a field or getter/setter pair on its parent. 1470 /// shadowing a field or getter/setter pair on its parent.
1335 /// 1471 ///
1336 /// This is important because otherwise, trying to read or write the field 1472 /// This is important because otherwise, trying to read or write the field
1337 /// would end up calling the getter or setter, and one of those might not even 1473 /// would end up calling the getter or setter, and one of those might not even
1338 /// exist, resulting in a runtime error. Even if they did exist, that's the 1474 /// exist, resulting in a runtime error. Even if they did exist, that's the
1339 /// wrong behavior if a new field was declared. 1475 /// wrong behavior if a new field was declared.
1340 List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field, 1476 List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field,
1341 Map<FieldElement, JS.TemporaryId> virtualFields) { 1477 Map<FieldElement, JS.TemporaryId> virtualFields) {
1342 var virtualField = virtualFields[field.element]; 1478 var virtualField = virtualFields[field.element];
(...skipping 3872 matching lines...) Expand 10 before | Expand all | Expand 10 after
5215 } 5351 }
5216 5352
5217 bool isLibraryPrefix(Expression node) => 5353 bool isLibraryPrefix(Expression node) =>
5218 node is SimpleIdentifier && node.staticElement is PrefixElement; 5354 node is SimpleIdentifier && node.staticElement is PrefixElement;
5219 5355
5220 LibraryElement _getLibrary(AnalysisContext c, String uri) => 5356 LibraryElement _getLibrary(AnalysisContext c, String uri) =>
5221 c.computeLibraryElement(c.sourceFactory.forUri(uri)); 5357 c.computeLibraryElement(c.sourceFactory.forUri(uri));
5222 5358
5223 bool _isDartRuntime(LibraryElement l) => 5359 bool _isDartRuntime(LibraryElement l) =>
5224 l.isInSdk && l.source.uri.toString() == 'dart:_runtime'; 5360 l.isInSdk && l.source.uri.toString() == 'dart:_runtime';
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698