| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library compiler.src.inferrer.map_tracer; |
| |
| import '../elements/entities.dart'; |
| import '../js_backend/backend.dart' show JavaScriptBackend; |
| import '../universe/selector.dart' show Selector; |
| import 'node_tracer.dart'; |
| import 'type_graph_nodes.dart'; |
| |
| Set<String> okMapSelectorsSet = new Set.from(const <String>[ |
| // From Object. |
| "==", |
| "hashCode", |
| "toString", |
| "noSuchMethod", |
| "runtimeType", |
| // From Map |
| "[]", |
| "isEmpty", |
| "isNotEmpty", |
| "keys", |
| "length", |
| "values", |
| "clear", |
| "containsKey", |
| "containsValue", |
| "forEach", |
| "remove" |
| ]); |
| |
| class MapTracerVisitor extends TracerVisitor { |
| // These lists are used to keep track of newly discovered assignments to |
| // the map. Note that elements at corresponding indices are expected to |
| // belong to the same assignment operation. |
| List<TypeInformation> keyAssignments = <TypeInformation>[]; |
| List<TypeInformation> valueAssignments = <TypeInformation>[]; |
| // This list is used to keep track of assignments of entire maps to |
| // this map. |
| List<MapTypeInformation> mapAssignments = <MapTypeInformation>[]; |
| |
| MapTracerVisitor(tracedType, inferrer) : super(tracedType, inferrer); |
| |
| /** |
| * Returns [true] if the analysis completed successfully, [false] |
| * if it bailed out. In the former case, [keyAssignments] and |
| * [valueAssignments] hold a list of [TypeInformation] nodes that |
| * flow into the key and value types of this map. |
| */ |
| bool run() { |
| analyze(); |
| MapTypeInformation map = tracedType; |
| if (continueAnalyzing) { |
| map.addFlowsIntoTargets(flowsInto); |
| return true; |
| } |
| keyAssignments = valueAssignments = mapAssignments = null; |
| return false; |
| } |
| |
| visitClosureCallSiteTypeInformation(ClosureCallSiteTypeInformation info) { |
| bailout('Passed to a closure'); |
| } |
| |
| visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { |
| super.visitStaticCallSiteTypeInformation(info); |
| MemberEntity called = info.calledElement; |
| if (inferrer.closedWorld.commonElements.isForeign(called) && |
| called.name == JavaScriptBackend.JS) { |
| bailout('Used in JS ${info.debugName}'); |
| } |
| } |
| |
| visitDynamicCallSiteTypeInformation(DynamicCallSiteTypeInformation info) { |
| super.visitDynamicCallSiteTypeInformation(info); |
| Selector selector = info.selector; |
| String selectorName = selector.name; |
| if (currentUser == info.receiver) { |
| if (!okMapSelectorsSet.contains(selectorName)) { |
| if (selector.isCall) { |
| if (selectorName == 'addAll') { |
| // All keys and values from the argument flow into |
| // the map. |
| TypeInformation map = info.arguments.positional[0]; |
| if (map is MapTypeInformation) { |
| inferrer.analyzeMapAndEnqueue(map); |
| mapAssignments.add(map); |
| } else { |
| // If we could select a component from a [TypeInformation], |
| // like the keytype or valuetype in this case, we could |
| // propagate more here. |
| // TODO(herhut): implement selection on [TypeInformation]. |
| bailout('Adding map with unknown typeinfo to current map'); |
| } |
| } else if (selectorName == 'putIfAbsent') { |
| // The first argument is a new key, the result type of |
| // the second argument becomes a new value. |
| // Unfortunately, the type information does not |
| // explicitly track the return type, yet, so we have |
| // to go to dynamic. |
| // TODO(herhut,16507): Use return type of closure in |
| // Map.putIfAbsent. |
| keyAssignments.add(info.arguments.positional[0]); |
| valueAssignments.add(inferrer.types.dynamicType); |
| } else { |
| // It would be nice to handle [Map.keys] and [Map.values], too. |
| // However, currently those calls do not trigger the creation |
| // of a [ListTypeInformation], so I have nowhere to propagate |
| // that information. |
| // TODO(herhut): add support for Map.keys and Map.values. |
| bailout('Map used in a not-ok selector [$selectorName]'); |
| return; |
| } |
| } else if (selector.isIndexSet) { |
| keyAssignments.add(info.arguments.positional[0]); |
| valueAssignments.add(info.arguments.positional[1]); |
| } else if (!selector.isIndex) { |
| bailout('Map used in a not-ok selector [$selectorName]'); |
| return; |
| } |
| } |
| } else if (selector.isCall && |
| (info.hasClosureCallTargets || |
| info.concreteTargets.any((element) => !element.isFunction))) { |
| bailout('Passed to a closure'); |
| return; |
| } |
| } |
| } |