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 |