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) { |
| 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 |
1333 /// This is called whenever a derived class needs to introduce a new field, | 1480 /// This is called whenever a derived class needs to introduce a new field, |
1334 /// shadowing a field or getter/setter pair on its parent. | 1481 /// shadowing a field or getter/setter pair on its parent. |
1335 /// | 1482 /// |
1336 /// This is important because otherwise, trying to read or write the field | 1483 /// 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 | 1484 /// 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 | 1485 /// exist, resulting in a runtime error. Even if they did exist, that's the |
1339 /// wrong behavior if a new field was declared. | 1486 /// wrong behavior if a new field was declared. |
1340 List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field, | 1487 List<JS.Method> _emitVirtualFieldAccessor(VariableDeclaration field, |
1341 Map<FieldElement, JS.TemporaryId> virtualFields) { | 1488 Map<FieldElement, JS.TemporaryId> virtualFields) { |
1342 var virtualField = virtualFields[field.element]; | 1489 var virtualField = virtualFields[field.element]; |
(...skipping 3872 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5215 } | 5362 } |
5216 | 5363 |
5217 bool isLibraryPrefix(Expression node) => | 5364 bool isLibraryPrefix(Expression node) => |
5218 node is SimpleIdentifier && node.staticElement is PrefixElement; | 5365 node is SimpleIdentifier && node.staticElement is PrefixElement; |
5219 | 5366 |
5220 LibraryElement _getLibrary(AnalysisContext c, String uri) => | 5367 LibraryElement _getLibrary(AnalysisContext c, String uri) => |
5221 c.computeLibraryElement(c.sourceFactory.forUri(uri)); | 5368 c.computeLibraryElement(c.sourceFactory.forUri(uri)); |
5222 | 5369 |
5223 bool _isDartRuntime(LibraryElement l) => | 5370 bool _isDartRuntime(LibraryElement l) => |
5224 l.isInSdk && l.source.uri.toString() == 'dart:_runtime'; | 5371 l.isInSdk && l.source.uri.toString() == 'dart:_runtime'; |
OLD | NEW |