blob: 3f59e61ce147382888b008ea95d16e2ad69b3264 [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.
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/inferrer/typemasks/masks.dart';
import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld;
import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';
import 'type_mask_test_helper.dart';
import '../helpers/element_lookup.dart';
import 'package:compiler/src/util/memory_compiler.dart';
String generateTest(String mapAllocation) {
return """
dynamic anInt = 42;
dynamic aDouble = 42.5;
String aKey = 'aKey';
String anotherKey = 'anotherKey';
String presetKey = 'presetKey';
class A {
final field;
var nonFinalField;
A(this.field);
A.bar(map) : field = null {
nonFinalField = map;
}
receiveIt(map) {
map[aKey] = aDouble;
}
returnIt() {
return mapReturnedFromSelector;
}
useField() {
field[aKey] = aDouble;
}
set callSetter(map) {
map[aKey] = aDouble;
}
operator[](key) {
key[aKey] = aDouble;
}
operator[]=(index, value) {
index[aKey] = anInt;
if (value == mapEscapingTwiceInIndexSet) {
value[aKey] = aDouble;
}
}
}
class B extends A {
B(map) : super.bar(map);
set nonFinalField(value) {
value[aKey] = aDouble;
}
}
class C {
C();
operator[]=(index, value) {
index[aKey] = anInt;
value[aKey] = aDouble;
}
}
var mapInField = $mapAllocation;
var mapPassedToClosure = $mapAllocation;
var mapReturnedFromClosure = $mapAllocation;
var mapPassedToMethod = $mapAllocation;
var mapReturnedFromMethod = $mapAllocation;
var mapUsedWithCascade = $mapAllocation;
var mapUsedInClosure = $mapAllocation;
var mapPassedToSelector = $mapAllocation;
var mapReturnedFromSelector = $mapAllocation;
var mapUsedWithNonOkSelector = $mapAllocation;
var mapUsedWithConstraint = $mapAllocation;
var mapEscapingFromSetter = $mapAllocation;
var mapUsedInLocal = $mapAllocation;
var mapUnset = $mapAllocation;
var mapOnlySetWithConstraint = $mapAllocation;
var mapEscapingInSetterValue = $mapAllocation;
var mapEscapingInIndex = $mapAllocation;
var mapEscapingInIndexSet = $mapAllocation;
var mapEscapingTwiceInIndexSet = $mapAllocation;
var mapPassedAsOptionalParameter = $mapAllocation;
var mapPassedAsNamedParameter = $mapAllocation;
var mapSetInNonFinalField = $mapAllocation;
var mapStoredInList = $mapAllocation;
var mapStoredInListButEscapes = $mapAllocation;
var mapStoredInMap = $mapAllocation;
var mapStoredInMapButEscapes = $mapAllocation;
var mapStoredInRecordWithIndexAccess = $mapAllocation;
var mapStoredInRecordWithNameAccess = $mapAllocation;
var mapStoredInRecordWithoutAccess = $mapAllocation;
var mapStoredInRecordWithDynamicAccess = $mapAllocation;
foo(map) {
map[aKey] = aDouble;
}
bar() {
return mapReturnedFromMethod;
}
takeOptional([map]) {
map[aKey] = aDouble;
}
takeNamed({map}) {
map[aKey] = aDouble;
}
main() {
anInt++;
mapReturnedFromMethod[aKey] = anInt;
bar()[aKey] = aDouble;
mapPassedToMethod[aKey] = anInt;
foo(mapPassedToMethod);
mapPassedToClosure[aKey] = anInt;
((a) => a[aKey] = aDouble)(mapPassedToClosure);
mapReturnedFromClosure[aKey] = anInt;
(() => mapReturnedFromClosure)()[aKey] = aDouble;
mapInField[aKey] = anInt;
A(mapInField).useField();
mapUsedWithCascade[aKey] = anInt;
mapUsedWithCascade..[aKey] = aDouble;
mapUsedInClosure[aKey] = anInt;
(() => mapUsedInClosure[aKey] = aDouble)();
mapPassedToSelector[aKey] = anInt;
A(null).receiveIt(mapPassedToSelector);
mapReturnedFromSelector[aKey] = anInt;
A(null).returnIt()[aKey] = aDouble;
mapUsedWithNonOkSelector[aKey] = anInt;
mapUsedWithNonOkSelector.map((k,v) => v);
mapUsedWithConstraint[aKey] = anInt;
mapUsedWithConstraint[aKey]++;
mapUsedWithConstraint[aKey] += anInt;
mapEscapingFromSetter[aKey] = anInt;
foo((new A(null) as dynamic).field = mapEscapingFromSetter);
mapUsedInLocal[aKey] = anInt;
dynamic a = mapUsedInLocal;
mapUsedInLocal[anotherKey] = aDouble;
// At least use [mapUnset] in a local to pretend it's used.
dynamic b = mapUnset;
mapOnlySetWithConstraint[aKey]++;
mapEscapingInSetterValue[aKey] = anInt;
A(null).callSetter = mapEscapingInSetterValue;
mapEscapingInIndex[aKey] = anInt;
A(null)[mapEscapingInIndex];
A(null)[mapEscapingInIndexSet] = 42;
C()[mapEscapingTwiceInIndexSet] = mapEscapingTwiceInIndexSet;
mapPassedAsOptionalParameter[aKey] = anInt;
takeOptional(mapPassedAsOptionalParameter);
mapPassedAsNamedParameter[aKey] = anInt;
takeNamed(map: mapPassedAsNamedParameter);
mapSetInNonFinalField[aKey] = anInt;
B(mapSetInNonFinalField);
a = [mapStoredInList];
a[0][aKey] = 42;
a = [mapStoredInListButEscapes];
a[0][aKey] = 42;
a.forEach((e) => print(e));
a = {aKey: mapStoredInMap};
a[aKey][aKey] = 42;
a = {aKey: mapStoredInMapButEscapes};
a[aKey][aKey] = 42;
a.forEach((k,v) => print(v));
mapStoredInRecordWithoutAccess[aKey] = anInt;
mapStoredInRecordWithIndexAccess[aKey] = anInt;
mapStoredInRecordWithNameAccess[aKey] = anInt;
final c = (mapStoredInRecordWithoutAccess, mapStoredInRecordWithIndexAccess);
(c.\$2)[aKey] = aDouble;
final d = (name1: mapStoredInRecordWithoutAccess, name2: mapStoredInRecordWithNameAccess);
(d.name2)[aKey] = aDouble;
mapStoredInRecordWithDynamicAccess[aKey] = anInt;
dynamic e = (mapStoredInRecordWithDynamicAccess,);
(e.\$1)[aKey] = aDouble;
}
""";
}
void main() {
runTests() async {
// Test empty literal map
await doTest('<dynamic, dynamic>{}');
// Test preset map of <String,uint32>
await doTest(
'<dynamic, dynamic>{presetKey : anInt}',
keyElementName: "presetKey",
valueElementName: "anInt",
);
// Test preset map of <Double,uint32>
await doTest(
'<dynamic, dynamic>{aDouble : anInt}',
keyElementName: "aDouble",
valueElementName: "anInt",
);
}
asyncTest(() async {
await runTests();
});
}
doTest(
String allocation, {
String? keyElementName,
String? valueElementName,
}) async {
String source = generateTest(allocation);
var result = await runCompiler(memorySourceFiles: {'main.dart': source});
Expect.isTrue(result.isSuccess);
Compiler compiler = result.compiler!;
TypeMask? keyType, valueType;
GlobalTypeInferenceResults results =
compiler.globalInference.resultsForTesting!;
JClosedWorld closedWorld = results.closedWorld;
final commonMasks = closedWorld.abstractValueDomain as CommonMasks;
TypeMask emptyType = TypeMask.nonNullEmpty(commonMasks);
MemberEntity aKey = findMember(closedWorld, 'aKey');
var aKeyType = results.resultOfMember(aKey).type as TypeMask;
if (keyElementName != null) {
MemberEntity keyElement = findMember(closedWorld, keyElementName);
keyType = results.resultOfMember(keyElement).type as TypeMask;
}
if (valueElementName != null) {
MemberEntity valueElement = findMember(closedWorld, valueElementName);
valueType = results.resultOfMember(valueElement).type as TypeMask;
}
if (keyType == null) keyType = emptyType;
if (valueType == null) valueType = emptyType;
checkType(String name, keyType, valueType) {
MemberEntity element = findMember(closedWorld, name);
MapTypeMask mask = results.resultOfMember(element).type as MapTypeMask;
Expect.equals(keyType, simplify(mask.keyType, commonMasks), name);
Expect.equals(valueType, simplify(mask.valueType, commonMasks), name);
}
K(TypeMask other) =>
simplify(keyType!.union(other, commonMasks), commonMasks);
V(TypeMask other) => simplify(
valueType!.union(other, commonMasks).nullable(commonMasks),
commonMasks,
);
checkType('mapInField', K(aKeyType), V(commonMasks.numType));
checkType('mapPassedToMethod', K(aKeyType), V(commonMasks.numType));
checkType('mapReturnedFromMethod', K(aKeyType), V(commonMasks.numType));
checkType('mapUsedWithCascade', K(aKeyType), V(commonMasks.numType));
checkType('mapUsedInClosure', K(aKeyType), V(commonMasks.numType));
checkType('mapPassedToSelector', K(aKeyType), V(commonMasks.numType));
checkType('mapReturnedFromSelector', K(aKeyType), V(commonMasks.numType));
checkType(
'mapUsedWithConstraint',
K(aKeyType),
V(commonMasks.positiveIntType),
);
checkType('mapEscapingFromSetter', K(aKeyType), V(commonMasks.numType));
checkType('mapUsedInLocal', K(aKeyType), V(commonMasks.numType));
checkType('mapEscapingInSetterValue', K(aKeyType), V(commonMasks.numType));
checkType('mapEscapingInIndex', K(aKeyType), V(commonMasks.numType));
checkType(
'mapEscapingInIndexSet',
K(aKeyType),
V(commonMasks.positiveIntType),
);
// TODO(johnniwinther): Reenable this when we don't bail out due to
// (benign) JS calls.
//checkType('mapEscapingTwiceInIndexSet', K(aKeyType), V(commonMasks.numType));
checkType('mapSetInNonFinalField', K(aKeyType), V(commonMasks.numType));
checkType(
'mapPassedToClosure',
K(commonMasks.dynamicType),
V(commonMasks.dynamicType),
);
checkType(
'mapReturnedFromClosure',
K(commonMasks.dynamicType),
V(commonMasks.dynamicType),
);
checkType(
'mapUsedWithNonOkSelector',
K(commonMasks.dynamicType),
V(commonMasks.dynamicType),
);
checkType(
'mapPassedAsOptionalParameter',
K(aKeyType),
V(commonMasks.numType),
);
checkType('mapPassedAsNamedParameter', K(aKeyType), V(commonMasks.numType));
checkType('mapStoredInList', K(aKeyType), V(commonMasks.uint31Type));
checkType(
'mapStoredInListButEscapes',
K(commonMasks.dynamicType),
V(commonMasks.dynamicType),
);
checkType('mapStoredInMap', K(aKeyType), V(commonMasks.uint31Type));
checkType(
'mapStoredInMapButEscapes',
K(commonMasks.dynamicType),
V(commonMasks.dynamicType),
);
checkType(
'mapStoredInRecordWithIndexAccess',
K(aKeyType),
V(commonMasks.numType),
);
checkType(
'mapStoredInRecordWithNameAccess',
K(aKeyType),
V(commonMasks.numType),
);
checkType(
'mapStoredInRecordWithDynamicAccess',
K(aKeyType),
V(commonMasks.numType),
);
checkType(
'mapStoredInRecordWithoutAccess',
K(aKeyType),
V(commonMasks.positiveIntType),
);
checkType('mapUnset', K(emptyType), V(emptyType));
checkType('mapOnlySetWithConstraint', K(aKeyType), V(emptyType));
}