Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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'; |
| OLD | NEW |