blob: 83ad71cfe4b6242b6e89d117a0cbf666d3e1112c [file] [log] [blame]
// 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;
}
}
}