[vm/tfa] Implementation of type arguments tracking in TFA.

Change-Id: I9398186e27ae7a040e249df010ae16fb6ab6da89
Cq-Include-Trybots: luci.dart.try:vm-kernel-win-release-x64-try, vm-kernel-optcounter-threshold-linux-release-x64-try, vm-kernel-precomp-linux-debug-x64-try, vm-kernel-precomp-linux-release-simarm-try, vm-kernel-precomp-linux-release-simarm64-try, vm-kernel-precomp-linux-release-x64-try, vm-kernel-precomp-win-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/74962
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
This commit is contained in:
Samir Jindel 2018-10-11 19:09:42 +00:00 committed by commit-bot@chromium.org
parent 001343ce06
commit 3e7ce992cf
25 changed files with 1757 additions and 182 deletions

View file

@ -42,6 +42,11 @@ abstract class ClassHierarchy {
/// True if the component contains another class that is a subtype of given one.
bool hasProperSubtypes(Class class_);
// Returns the instantition of each generic supertype implemented by this
// class (e.g. getClassAsInstanceOf applied to all superclasses and
// interfaces).
List<Supertype> genericSupertypesOf(Class class_);
/// Returns the least upper bound of two interface types, as defined by Dart
/// 1.0.
///
@ -699,6 +704,15 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
info.directMixers.isNotEmpty;
}
@override
List<Supertype> genericSupertypesOf(Class class_) {
final supertypes = _infoFor[class_].genericSuperTypes;
if (supertypes == null) return const <Supertype>[];
// Multiple supertypes can arise from ambiguous supertypes. The first
// supertype is the real one; the others are purely informational.
return supertypes.values.map((v) => v.first).toList();
}
@override
ClassHierarchy applyTreeChanges(Iterable<Library> removedLibraries,
Iterable<Library> ensureKnownLibraries,
@ -1339,7 +1353,7 @@ class _ClassInfo {
/// Maps generic supertype classes to the instantiation implemented by this
/// class.
///
/// E.g. `List` maps to `List<String>` for a class that directly of indirectly
/// E.g. `List` maps to `List<String>` for a class that directly or indirectly
/// implements `List<String>`.
Map<Class, List<Supertype>> genericSuperTypes;

View file

@ -14,20 +14,54 @@ class InferredType {
static const int flagNullable = 1 << 0;
static const int flagInt = 1 << 1;
InferredType(Class concreteClass, bool nullable, bool isInt)
: this._byReference(getClassReference(concreteClass),
(nullable ? flagNullable : 0) | (isInt ? flagInt : 0));
// For Parameters and Fields, whether a type-check is required at assignment
// (invocation/setter). Not meaningful on other kernel nodes.
static const int flagSkipCheck = 1 << 2;
InferredType._byReference(this._concreteClassReference, this._flags);
// Entire list may be null if no type arguments were inferred.
// Will always be null if `concreteClass` is null.
//
// Each component may be null if that particular type argument was not
// inferred.
//
// Otherwise, a non-null type argument indicates that that particular type
// argument (in the runtime type) is always exactly a particular `DartType`.
final List<DartType> exactTypeArguments;
InferredType(Class concreteClass, bool nullable, bool isInt,
{List<DartType> exactTypeArguments, bool skipCheck: false})
: this._byReference(
getClassReference(concreteClass),
(nullable ? flagNullable : 0) |
(isInt ? flagInt : 0) |
(skipCheck ? flagSkipCheck : 0),
exactTypeArguments);
InferredType._byReference(
this._concreteClassReference, this._flags, this.exactTypeArguments) {
assert(exactTypeArguments == null || _concreteClassReference != null);
}
Class get concreteClass => _concreteClassReference?.asClass;
bool get nullable => (_flags & flagNullable) != 0;
bool get isInt => (_flags & flagInt) != 0;
bool get skipCheck => (_flags & flagSkipCheck) != 0;
@override
String toString() =>
"${concreteClass != null ? concreteClass : (isInt ? 'int' : '!')}${nullable ? '?' : ''}";
String toString() {
final base =
"${concreteClass != null ? concreteClass : (isInt ? 'int' : '!')}";
final suffix = "${nullable ? '?' : ''}";
String typeArgs = "";
if (exactTypeArguments != null) {
typeArgs =
exactTypeArguments.map((t) => t != null ? "$t" : "?").join(", ");
typeArgs = "<" + typeArgs + ">";
}
final skip = skipCheck ? " (skip check)" : "";
return base + suffix + typeArgs + skip;
}
}
/// Repository for [InferredType].
@ -40,6 +74,8 @@ class InferredTypeMetadataRepository extends MetadataRepository<InferredType> {
@override
void writeToBinary(InferredType metadata, Node node, BinarySink sink) {
// TODO(sjindel/tfa): Implement serialization of type arguments when can use
// them for optimizations.
sink.writeCanonicalNameReference(
getCanonicalNameOfClass(metadata.concreteClass));
sink.writeByte(metadata._flags);
@ -47,9 +83,11 @@ class InferredTypeMetadataRepository extends MetadataRepository<InferredType> {
@override
InferredType readFromBinary(Node node, BinarySource source) {
// TODO(sjindel/tfa): Implement serialization of type arguments when can use
// them for optimizations.
final concreteClassReference =
source.readCanonicalNameReference()?.getReference();
final flags = source.readByte();
return new InferredType._byReference(concreteClassReference, flags);
return new InferredType._byReference(concreteClassReference, flags, null);
}
}

View file

@ -171,7 +171,8 @@ class _DirectInvocation extends _Invocation {
assertx(args.values.length == firstParamIndex + 1);
assertx(args.names.isEmpty);
final Type setterArg = args.values[firstParamIndex];
fieldValue.setValue(setterArg, typeFlowAnalysis);
fieldValue.setValue(
setterArg, typeFlowAnalysis, field.isStatic ? null : args.receiver);
return const EmptyType();
case CallKind.Method:
@ -202,7 +203,8 @@ class _DirectInvocation extends _Invocation {
// does not throw exception.
initializerResult = new Type.nullable(initializerResult);
}
fieldValue.setValue(initializerResult, typeFlowAnalysis);
fieldValue.setValue(initializerResult, typeFlowAnalysis,
field.isStatic ? null : args.receiver);
return const EmptyType();
}
@ -251,7 +253,8 @@ class _DirectInvocation extends _Invocation {
final int positionalArguments = args.positionalCount;
final int firstParamIndex = hasReceiverArg(selector.member) ? 1 : 0;
final int firstParamIndex = numTypeParams(selector.member) +
(hasReceiverArg(selector.member) ? 1 : 0);
final int requiredParameters =
firstParamIndex + function.requiredParameterCount;
if (positionalArguments < requiredParameters) {
@ -442,12 +445,7 @@ class _DispatchableInvocation extends _Invocation {
ConcreteType receiver,
Map<Member, _ReceiverTypeBuilder> targets,
TypeFlowAnalysis typeFlowAnalysis) {
DartType receiverDartType = receiver.dartType;
assertx(receiverDartType is! FunctionType);
assertx(receiverDartType is InterfaceType); // TODO(alexmarkov)
Class class_ = (receiverDartType as InterfaceType).classNode;
Class class_ = receiver.classNode;
Member target = typeFlowAnalysis.hierarchyCache.hierarchy
.getDispatchTarget(class_, selector.name, setter: selector.isSetter);
@ -698,9 +696,11 @@ class _InvocationsCache {
class _FieldValue extends _DependencyTracker {
final Field field;
final Type staticType;
final Summary typeGuardSummary;
Type value;
_FieldValue(this.field) : staticType = new Type.fromStatic(field.type) {
_FieldValue(this.field, this.typeGuardSummary)
: staticType = new Type.fromStatic(field.type) {
if (field.initializer == null && _isDefaultValueOfFieldObservable()) {
value = new Type.nullable(const EmptyType());
} else {
@ -708,6 +708,14 @@ class _FieldValue extends _DependencyTracker {
}
}
bool get staticCallSiteSkipCheck {
if (typeGuardSummary != null) {
return (typeGuardSummary.result as TypeCheck).canSkipOnStaticCallSite;
} else {
return false;
}
}
bool _isDefaultValueOfFieldObservable() {
if (field.isStatic) {
return true;
@ -750,7 +758,8 @@ class _FieldValue extends _DependencyTracker {
return value;
}
void setValue(Type newValue, TypeFlowAnalysis typeFlowAnalysis) {
void setValue(
Type newValue, TypeFlowAnalysis typeFlowAnalysis, Type receiverType) {
// Make sure type cones are specialized before putting them into field
// value, in order to ensure that dependency is established between
// cone's base type and corresponding field setter.
@ -776,11 +785,15 @@ class _FieldValue extends _DependencyTracker {
// is established.
//
final hierarchy = typeFlowAnalysis.hierarchyCache;
Type newType = value
.union(
newValue.specialize(hierarchy).intersection(staticType, hierarchy),
hierarchy)
.specialize(hierarchy);
// TODO(sjindel/tfa): Perform narrowing inside 'TypeCheck'.
final narrowedNewValue = typeGuardSummary != null
? typeGuardSummary
.apply(
new Args([receiverType, newValue]), hierarchy, typeFlowAnalysis)
.intersection(staticType, hierarchy)
: newValue.specialize(hierarchy).intersection(staticType, hierarchy);
Type newType =
value.union(narrowedNewValue, hierarchy).specialize(hierarchy);
assertx(newType.isSpecialized);
if (newType != value) {
@ -812,7 +825,7 @@ class _ClassData extends _DependencyTracker implements ClassId<_ClassData> {
/// Flag indicating if this class has a noSuchMethod() method not inherited
/// from Object.
/// Lazy initialized by _ClassHierarchyCache.hasNonTrivialNoSuchMethod().
/// Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod().
bool hasNonTrivialNoSuchMethod;
_ClassData(this._id, this.class_, this.supertypes) {
@ -821,7 +834,7 @@ class _ClassData extends _DependencyTracker implements ClassId<_ClassData> {
ConcreteType _concreteType;
ConcreteType get concreteType =>
_concreteType ??= new ConcreteType(this, class_.rawType);
_concreteType ??= new ConcreteType(this, class_, null);
Type _specializedConeType;
Type get specializedConeType =>
@ -865,11 +878,78 @@ class _ClassData extends _DependencyTracker implements ClassId<_ClassData> {
String dump() => "$this {supers: $supertypes}";
}
class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
final ClosedWorldClassHierarchy hierarchy;
final supertypeOffsetsCache = <SubtypePair, int>{};
final cachedFlattenedTypeArgs = <Class, List<DartType>>{};
final cachedFlattenedTypeArgsForNonGeneric = <Class, List<Type>>{};
RuntimeTypeTranslator closedTypeTranslator;
GenericInterfacesInfoImpl(this.hierarchy) {
closedTypeTranslator = RuntimeTypeTranslator.forClosedTypes(this);
}
List<DartType> flattenedTypeArgumentsFor(Class klass, {bool useCache: true}) {
final cached = useCache ? cachedFlattenedTypeArgs[klass] : null;
if (cached != null) return cached;
final flattenedTypeArguments = List<DartType>.from(
klass.typeParameters.map((t) => new TypeParameterType(t)));
for (final Supertype intf in hierarchy.genericSupertypesOf(klass)) {
int offset = findOverlap(flattenedTypeArguments, intf.typeArguments);
flattenedTypeArguments.addAll(
intf.typeArguments.skip(flattenedTypeArguments.length - offset));
supertypeOffsetsCache[SubtypePair(klass, intf.classNode)] = offset;
}
return flattenedTypeArguments;
}
int genericInterfaceOffsetFor(Class klass, Class iface) {
if (klass == iface) return 0;
final pair = new SubtypePair(klass, iface);
int offset = supertypeOffsetsCache[pair];
if (offset != null) return offset;
flattenedTypeArgumentsFor(klass);
offset = supertypeOffsetsCache[pair];
if (offset == null) {
throw "Invalid call to genericInterfaceOffsetFor.";
}
return offset;
}
List<Type> flattenedTypeArgumentsForNonGeneric(Class klass) {
List<Type> result = cachedFlattenedTypeArgsForNonGeneric[klass];
if (result != null) return result;
List<DartType> flattenedTypeArgs =
flattenedTypeArgumentsFor(klass, useCache: false);
result = new List<Type>(flattenedTypeArgs.length);
for (int i = 0; i < flattenedTypeArgs.length; ++i) {
final translated = closedTypeTranslator.translate(flattenedTypeArgs[i]);
assertx(translated is RuntimeType || translated is AnyType);
result[i] = translated;
}
cachedFlattenedTypeArgsForNonGeneric[klass] = result;
return result;
}
}
class _ClassHierarchyCache implements TypeHierarchy {
final TypeFlowAnalysis _typeFlowAnalysis;
final ClosedWorldClassHierarchy hierarchy;
final TypeEnvironment environment;
final Set<Class> allocatedClasses = new Set<Class>();
final Map<Class, _ClassData> classes = <Class, _ClassData>{};
final GenericInterfacesInfo genericInterfacesInfo;
/// Object.noSuchMethod().
final Member objectNoSuchMethod;
@ -887,10 +967,10 @@ class _ClassHierarchyCache implements TypeHierarchy {
final Map<DynamicSelector, _DynamicTargetSet> _dynamicTargets =
<DynamicSelector, _DynamicTargetSet>{};
_ClassHierarchyCache(this._typeFlowAnalysis, this.hierarchy)
_ClassHierarchyCache(this._typeFlowAnalysis, this.hierarchy,
this.genericInterfacesInfo, this.environment)
: objectNoSuchMethod = hierarchy.getDispatchTarget(
_typeFlowAnalysis.environment.coreTypes.objectClass,
noSuchMethodName) {
environment.coreTypes.objectClass, noSuchMethodName) {
assertx(objectNoSuchMethod != null);
}
@ -943,6 +1023,9 @@ class _ClassHierarchyCache implements TypeHierarchy {
return true;
}
if (superType is DynamicType || superType is VoidType) return true;
if (subType is DynamicType || subType is VoidType) return false;
// TODO(alexmarkov): handle function types properly
if (subType is FunctionType) {
subType = _typeFlowAnalysis.environment.rawFunctionType;
@ -951,16 +1034,21 @@ class _ClassHierarchyCache implements TypeHierarchy {
superType = _typeFlowAnalysis.environment.rawFunctionType;
}
// TODO(alexmarkov): handle generic types properly.
if (subType is TypeParameterType) {
subType = (subType as TypeParameterType).bound;
}
if (superType is TypeParameterType) {
superType = (superType as TypeParameterType).bound;
}
assertx(subType is! TypeParameterType);
assertx(superType is! TypeParameterType);
assertx(subType is InterfaceType, details: subType); // TODO(alexmarkov)
assertx(superType is InterfaceType, details: superType); // TODO(alexmarkov)
// InterfaceTypes should be raw, since we don't handle type arguments
// (although frankly we can't distinguish between raw C and C<dynamic).
assertx((subType as InterfaceType)
.typeArguments
.every((t) => t == const DynamicType()));
assertx((superType as InterfaceType)
.typeArguments
.every((t) => t == const DynamicType()));
Class subClass = (subType as InterfaceType).classNode;
Class superClass = (superType as InterfaceType).classNode;
if (subClass == superClass) {
@ -1056,10 +1144,22 @@ class _ClassHierarchyCache implements TypeHierarchy {
}
}
@override
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
@override
int genericInterfaceOffsetFor(Class klass, Class iface) =>
genericInterfacesInfo.genericInterfaceOffsetFor(klass, iface);
@override
List<Type> flattenedTypeArgumentsForNonGeneric(Class klass) =>
genericInterfacesInfo.flattenedTypeArgumentsForNonGeneric(klass);
@override
String toString() {
StringBuffer buf = new StringBuffer();
buf.write("_ClassHierarchyCache {\n");
buf.write("ClassHierarchyCache {\n");
buf.write(" allocated classes:\n");
allocatedClasses.forEach((c) {
buf.write(" $c\n");
@ -1071,6 +1171,9 @@ class _ClassHierarchyCache implements TypeHierarchy {
buf.write("}\n");
return buf.toString();
}
Class get futureOrClass => environment.coreTypes.futureOrClass;
Class get futureClass => environment.coreTypes.futureClass;
}
class _WorkList {
@ -1196,6 +1299,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
SummaryCollector summaryCollector;
_InvocationsCache _invocationsCache;
_WorkList workList;
GenericInterfacesInfo _genericInterfacesInfo;
final Map<Member, Summary> _summaries = <Member, Summary>{};
final Map<Field, _FieldValue> _fieldValues = <Field, _FieldValue>{};
@ -1204,15 +1308,22 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
final Set<Member> _calledViaInterfaceSelector = new Set<Member>();
final Set<Member> _calledViaThis = new Set<Member>();
TypeFlowAnalysis(this.target, Component component, CoreTypes coreTypes,
ClosedWorldClassHierarchy hierarchy, this.environment, this.libraryIndex,
TypeFlowAnalysis(
this.target,
Component component,
CoreTypes coreTypes,
ClosedWorldClassHierarchy hierarchy,
this._genericInterfacesInfo,
this.environment,
this.libraryIndex,
{PragmaAnnotationParser matcher})
: annotationMatcher =
matcher ?? new ConstantPragmaAnnotationParser(coreTypes) {
nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher);
hierarchyCache = new _ClassHierarchyCache(this, hierarchy);
summaryCollector =
new SummaryCollector(target, environment, this, nativeCodeOracle);
hierarchyCache = new _ClassHierarchyCache(
this, hierarchy, _genericInterfacesInfo, environment);
summaryCollector = new SummaryCollector(
target, environment, this, nativeCodeOracle, hierarchyCache);
_invocationsCache = new _InvocationsCache(this);
workList = new _WorkList(this);
@ -1227,7 +1338,12 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
}
_FieldValue getFieldValue(Field field) {
return _fieldValues[field] ??= new _FieldValue(field);
Summary setterSummary = null;
if (field.isGenericCovariantImpl) {
setterSummary = summaryCollector.createSummary(field,
fieldSummaryType: FieldSummaryType.kFieldGuard);
}
return _fieldValues[field] ??= new _FieldValue(field, setterSummary);
}
void process() {
@ -1249,6 +1365,12 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
Type fieldType(Field field) => _fieldValues[field]?.value;
// True if a the runtime type-check for this field can be skipped on
// statically-typed setter calls. (The type-check is only simulated after
// narrowing by the static parameter type.)
bool fieldStaticCallSiteSkipCheck(Field field) =>
_fieldValues[field]?.staticCallSiteSkipCheck;
Args<Type> argumentTypes(Member member) => _summaries[member]?.argumentTypes;
bool isTearOffTaken(Member member) => _tearOffTaken.contains(member);
@ -1268,6 +1390,12 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
_calledViaDynamicSelector.contains(member) ||
_calledViaInterfaceSelector.contains(member);
// Returns parameters for which a runtime type-check can be skipped on
// statically-typed call-sites. (The type-check is only simulated after
// narrowing by the static parameter type.)
List<VariableDeclaration> staticCallSiteSkipCheckParams(Member member) =>
_summaries[member]?.staticCallSiteSkipCheckParams;
/// ---- Implementation of [CallHandler] interface. ----
@override
@ -1309,7 +1437,13 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
@override
void addDirectFieldAccess(Field field, Type value) {
getFieldValue(field).setValue(value, this);
final fieldValue = getFieldValue(field);
if (field.isStatic) {
fieldValue.setValue(value, this, /*receiver_type=*/ null);
} else {
final receiver = new Type.cone(new InterfaceType(field.parent));
fieldValue.setValue(value, this, receiver);
}
}
@override

View file

@ -55,12 +55,19 @@ class StatementVisitor {
void visitJoin(Join expr) => visitDefault(expr);
void visitUse(Use expr) => visitDefault(expr);
void visitCall(Call expr) => visitDefault(expr);
void visitExtract(Extract expr) => visitDefault(expr);
void visitCreateConcreteType(CreateConcreteType expr) => visitDefault(expr);
void visitCreateRuntimeType(CreateRuntimeType expr) => visitDefault(expr);
void visitTypeCheck(TypeCheck expr) => visitDefault(expr);
}
/// Input parameter of the summary.
class Parameter extends Statement {
final String name;
// 'staticType' is null for type parameters to factory constructors.
final Type staticType;
Type defaultValue;
Type _argumentType = const EmptyType();
@ -261,6 +268,182 @@ class Call extends Statement {
}
}
// Extract a type argument from a ConcreteType (used to extract type arguments
// from receivers of methods).
class Extract extends Statement {
TypeExpr arg;
final Class referenceClass;
final int paramIndex;
Extract(this.arg, this.referenceClass, this.paramIndex);
@override
void accept(StatementVisitor visitor) => visitor.visitExtract(this);
@override
String dump() => "$label = _Extract ($arg[$referenceClass/$paramIndex])";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
Type argType = arg.getComputedType(computedTypes);
Type extractedType;
void extractType(ConcreteType c) {
if (c.typeArgs == null) {
extractedType = const AnyType();
} else {
final interfaceOffset = typeHierarchy.genericInterfaceOffsetFor(
c.classNode, referenceClass);
final extract = c.typeArgs[interfaceOffset + paramIndex];
assertx(extract is AnyType || extract is RuntimeType);
if (extractedType == null || extract == extractedType) {
extractedType = extract;
} else {
extractedType = const AnyType();
}
}
}
// TODO(sjindel/tfa): Support more types here if possible.
if (argType is ConcreteType) {
extractType(argType);
} else if (argType is SetType) {
argType.types.forEach(extractType);
}
return extractedType ?? const AnyType();
}
}
// Instantiate a concrete type with type arguments. For example, used to fill in
// "T = int" in "C<T>" to create "C<int>".
//
// The type arguments are factored against the generic interfaces; for more
// details see 'ClassHierarchyCache.factoredGenericInterfacesOf'.
class CreateConcreteType extends Statement {
final ConcreteType type;
final List<TypeExpr> flattenedTypeArgs;
CreateConcreteType(this.type, this.flattenedTypeArgs);
@override
void accept(StatementVisitor visitor) =>
visitor.visitCreateConcreteType(this);
@override
String dump() {
int numImmediateTypeArgs = type.classNode.typeParameters.length;
return "$label = _CreateConcreteType (${type.classNode} @ "
"${flattenedTypeArgs.take(numImmediateTypeArgs)})";
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
bool hasRuntimeType = false;
final types = new List<Type>(flattenedTypeArgs.length);
for (int i = 0; i < types.length; ++i) {
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
assertx(computed is RuntimeType || computed is AnyType);
if (computed is RuntimeType) hasRuntimeType = true;
types[i] = computed;
}
return new ConcreteType(
type.classId, type.classNode, hasRuntimeType ? types : null);
}
}
// Similar to "CreateConcreteType", but creates a "RuntimeType" rather than a
// "ConcreteType". Unlike a "ConcreteType", none of the type arguments can be
// missing ("AnyType").
class CreateRuntimeType extends Statement {
final Class klass;
final List<TypeExpr> flattenedTypeArgs;
CreateRuntimeType(this.klass, this.flattenedTypeArgs);
@override
void accept(StatementVisitor visitor) => visitor.visitCreateRuntimeType(this);
@override
String dump() => "$label = _CreateRuntimeType ($klass @ "
"${flattenedTypeArgs.take(klass.typeParameters.length)})";
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
final types = new List<RuntimeType>(flattenedTypeArgs.length);
for (int i = 0; i < types.length; ++i) {
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
assertx(computed is RuntimeType || computed is AnyType);
if (computed is AnyType) return const AnyType();
types[i] = computed;
}
return new RuntimeType(new InterfaceType(klass), types);
}
}
// Used to simulate a runtime type-check, to determine when it can be skipped.
// TODO(sjindel/tfa): Unify with Narrow.
class TypeCheck extends Statement {
TypeExpr arg;
TypeExpr type;
bool _canSkip = true;
// True if a the runtime type-check for this parameter can be skipped on
// statically-typed call-sites. (The type-check is only simulated after
// narrowing by the static parameter type.)
bool get canSkipOnStaticCallSite => _canSkip;
final VariableDeclaration parameter;
TypeCheck(this.arg, this.type, this.parameter);
@override
void accept(StatementVisitor visitor) => visitor.visitTypeCheck(this);
@override
String dump() {
String result = "$label = _TypeCheck ($arg against $type)";
if (parameter != null) {
result += " (for parameter ${parameter.name})";
}
return result;
}
@override
Type apply(List<Type> computedTypes, TypeHierarchy typeHierarchy,
CallHandler callHandler) {
Type argType = arg.getComputedType(computedTypes);
Type checkType = type.getComputedType(computedTypes);
// TODO(sjindel/tfa): Narrow the result if possible.
assertx(checkType is AnyType || checkType is RuntimeType);
if (_canSkip) {
if (checkType is AnyType) {
// If we don't know what the RHS of the check is going to be, we can't
// guarantee that it will pass.
if (kPrintTrace) {
tracePrint("TypeCheck failed, type is unknown");
}
_canSkip = false;
} else if (checkType is RuntimeType) {
_canSkip = argType.isSubtypeOfRuntimeType(typeHierarchy, checkType);
if (kPrintTrace && !_canSkip) {
tracePrint("TypeCheck of $argType against $checkType failed.");
}
argType = argType.intersection(
Type.fromStatic(checkType.representedTypeRaw), typeHierarchy);
} else {
assertx(false, details: "Cannot see $checkType on RHS of TypeCheck.");
}
}
return argType;
}
}
/// Summary is a linear sequence of statements representing a type flow in
/// one member, function or initializer.
class Summary {
@ -318,13 +501,19 @@ class Summary {
for (int i = 0; i < positionalArgCount; i++) {
final Parameter param = _statements[i] as Parameter;
final argType = args[i].specialize(typeHierarchy);
param._observeArgumentType(argType, typeHierarchy);
types[i] = argType.intersection(param.staticType, typeHierarchy);
if (param.staticType != null) {
final argType = args[i].specialize(typeHierarchy);
param._observeArgumentType(argType, typeHierarchy);
// TODO(sjindel/tfa): Perform narrowing inside 'TypeCheck'.
types[i] = argType.intersection(param.staticType, typeHierarchy);
} else {
types[i] = args[i];
}
}
for (int i = positionalArgCount; i < positionalParameterCount; i++) {
final Parameter param = _statements[i] as Parameter;
assertx(param.staticType != null);
final argType = param.defaultValue.specialize(typeHierarchy);
param._observeArgumentType(argType, typeHierarchy);
types[i] = argType;
@ -380,4 +569,15 @@ class Summary {
}
return new Args<Type>(argTypes, names: argNames);
}
List<VariableDeclaration> get staticCallSiteSkipCheckParams {
final vars = <VariableDeclaration>[];
for (final statement in _statements) {
if (statement is TypeCheck && statement.canSkipOnStaticCallSite) {
final decl = statement.parameter;
if (decl != null) vars.add(decl);
}
}
return vars;
}
}

View file

@ -11,6 +11,7 @@ import 'package:kernel/target/targets.dart';
import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
import 'package:kernel/ast.dart' as ast show Statement, StatementVisitor;
import 'package:kernel/type_environment.dart' show TypeEnvironment;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'calls.dart';
import 'native_code.dart';
@ -155,6 +156,36 @@ class _SummaryNormalizer extends StatementVisitor {
}
}
}
@override
void visitCreateConcreteType(CreateConcreteType expr) {
for (int i = 0; i < expr.flattenedTypeArgs.length; ++i) {
expr.flattenedTypeArgs[i] =
_normalizeExpr(expr.flattenedTypeArgs[i], true);
if (_inLoop) return;
}
}
@override
void visitCreateRuntimeType(CreateRuntimeType expr) {
for (int i = 0; i < expr.flattenedTypeArgs.length; ++i) {
expr.flattenedTypeArgs[i] =
_normalizeExpr(expr.flattenedTypeArgs[i], true);
if (_inLoop) return;
}
}
@override
void visitTypeCheck(TypeCheck expr) {
expr.arg = _normalizeExpr(expr.arg, true);
if (_inLoop) return;
expr.type = _normalizeExpr(expr.type, true);
}
@override
void visitExtract(Extract expr) {
expr.arg = _normalizeExpr(expr.arg, true);
}
}
/// Detects whether the control flow can pass through the function body and
@ -241,32 +272,43 @@ class _FallthroughDetector extends ast.StatementVisitor<bool> {
bool visitFunctionDeclaration(FunctionDeclaration node) => true;
}
enum FieldSummaryType { kFieldGuard, kInitializer }
/// Create a type flow summary for a member from the kernel AST.
class SummaryCollector extends RecursiveVisitor<TypeExpr> {
final Target target;
final TypeEnvironment _environment;
final EntryPointsListener _entryPointsListener;
final NativeCodeOracle _nativeCodeOracle;
final GenericInterfacesInfo _genericInterfacesInfo;
final Map<TreeNode, Call> callSites = <TreeNode, Call>{};
final _FallthroughDetector _fallthroughDetector = new _FallthroughDetector();
Summary _summary;
Map<VariableDeclaration, Join> _variables;
Map<VariableDeclaration, Join> _variableJoins;
Map<VariableDeclaration, TypeExpr> _variables;
Join _returnValue;
Parameter _receiver;
ConstantAllocationCollector constantAllocationCollector;
RuntimeTypeTranslator _translator;
// Currently only used for factory constructors.
Map<TypeParameter, TypeExpr> _fnTypeVariables;
SummaryCollector(this.target, this._environment, this._entryPointsListener,
this._nativeCodeOracle) {
this._nativeCodeOracle, this._genericInterfacesInfo) {
assertx(_genericInterfacesInfo != null);
constantAllocationCollector = new ConstantAllocationCollector(this);
}
Summary createSummary(Member member) {
Summary createSummary(Member member,
{fieldSummaryType: FieldSummaryType.kInitializer}) {
debugPrint("===== ${member} =====");
assertx(!member.isAbstract);
_variables = <VariableDeclaration, Join>{};
_variableJoins = <VariableDeclaration, Join>{};
_variables = <VariableDeclaration, TypeExpr>{};
_returnValue = null;
_receiver = null;
@ -274,7 +316,10 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
if (member is Field) {
if (hasReceiver) {
_summary = new Summary(parameterCount: 1, positionalParameterCount: 1);
final int numArgs =
fieldSummaryType == FieldSummaryType.kInitializer ? 1 : 2;
_summary = new Summary(
parameterCount: numArgs, positionalParameterCount: numArgs);
// TODO(alexmarkov): subclass cone
_receiver = _declareParameter(
"this", member.enclosingClass.rawType, null,
@ -283,12 +328,25 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
} else {
_summary = new Summary();
}
assertx(member.initializer != null);
_summary.result = _visit(member.initializer);
_translator = new RuntimeTypeTranslator(member.enclosingClass, _summary,
_receiver, null, _genericInterfacesInfo);
if (fieldSummaryType == FieldSummaryType.kInitializer) {
assertx(member.initializer != null);
_summary.result = _visit(member.initializer);
} else {
Parameter valueParam = _declareParameter("value", member.type, null);
TypeExpr runtimeType = _translator.translate(member.type);
final check = new TypeCheck(valueParam, runtimeType, null);
_summary.add(check);
_summary.result = check;
}
} else {
FunctionNode function = member.function;
final firstParamIndex = hasReceiver ? 1 : 0;
final numTypeParameters = numTypeParams(member);
final firstParamIndex = (hasReceiver ? 1 : 0) + numTypeParameters;
_summary = new Summary(
parameterCount: firstParamIndex +
@ -299,6 +357,14 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
requiredParameterCount:
firstParamIndex + function.requiredParameterCount);
if (numTypeParameters > 0) {
_fnTypeVariables = <TypeParameter, TypeExpr>{};
for (int i = 0; i < numTypeParameters; ++i) {
_fnTypeVariables[function.typeParameters[i]] =
_declareParameter(function.typeParameters[i].name, null, null);
}
}
if (hasReceiver) {
// TODO(alexmarkov): subclass cone
_receiver = _declareParameter(
@ -307,6 +373,9 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
_environment.thisType = member.enclosingClass?.thisType;
}
_translator = new RuntimeTypeTranslator(member.enclosingClass, _summary,
_receiver, _fnTypeVariables, _genericInterfacesInfo);
for (VariableDeclaration param in function.positionalParameters) {
_declareParameter(param.name, param.type, param.initializer);
}
@ -316,11 +385,13 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
int count = firstParamIndex;
for (VariableDeclaration param in function.positionalParameters) {
Join v = _declareVariable(param);
Join v =
_declareVariable(param, useTypeCheck: param.isGenericCovariantImpl);
v.values.add(_summary.statements[count++]);
}
for (VariableDeclaration param in function.namedParameters) {
Join v = _declareVariable(param);
Join v =
_declareVariable(param, useTypeCheck: param.isGenericCovariantImpl);
v.values.add(_summary.statements[count++]);
}
assertx(count == _summary.parameterCount);
@ -377,6 +448,11 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
final List<Type> args = <Type>[];
final List<String> names = <String>[];
final numTypeParameters = numTypeParams(member);
for (int i = 0; i < numTypeParameters; ++i) {
args.add(const AnyType());
}
if (hasReceiverArg(member)) {
assertx(member.enclosingClass != null);
Type receiver = new Type.cone(member.enclosingClass.rawType);
@ -422,8 +498,14 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
TypeExpr _visit(TreeNode node) => node.accept(this);
Args<TypeExpr> _visitArguments(TypeExpr receiver, Arguments arguments) {
Args<TypeExpr> _visitArguments(TypeExpr receiver, Arguments arguments,
{bool passTypeArguments: false}) {
final args = <TypeExpr>[];
if (passTypeArguments) {
for (var type in arguments.types) {
args.add(_translator.translate(type));
}
}
if (receiver != null) {
args.add(receiver);
}
@ -451,8 +533,10 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
Parameter _declareParameter(
String name, DartType type, Expression initializer,
{bool isReceiver: false}) {
Type staticType =
isReceiver ? new ConeType(type) : new Type.fromStatic(type);
Type staticType;
if (type != null) {
staticType = isReceiver ? new ConeType(type) : new Type.fromStatic(type);
}
final param = new Parameter(name, staticType);
_summary.add(param);
assertx(param.index < _summary.parameterCount);
@ -474,17 +558,30 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
return param;
}
Join _declareVariable(VariableDeclaration decl, {bool addInitType: false}) {
Join v = new Join(decl.name, decl.type);
_summary.add(v);
_variables[decl] = v;
Join _declareVariable(VariableDeclaration decl,
{bool addInitType: false, bool useTypeCheck: false}) {
Join join = new Join(decl.name, decl.type);
_summary.add(join);
_variableJoins[decl] = join;
TypeExpr variable = join;
if (useTypeCheck) {
TypeExpr runtimeType = _translator.translate(decl.type);
variable = new TypeCheck(variable, runtimeType, decl);
_summary.add(variable);
_summary.add(new Use(variable));
}
_variables[decl] = variable;
if (decl.initializer != null) {
TypeExpr initType = _visit(decl.initializer);
if (addInitType) {
v.values.add(initType);
join.values.add(initType);
}
}
return v;
return join;
}
// TODO(alexmarkov): Avoid declaring variables with static types.
@ -520,7 +617,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
void _addUse(TypeExpr arg) {
if (arg is Narrow) {
_addUse(arg.arg);
} else if (arg is Join || arg is Call) {
} else if (arg is Join || arg is Call || arg is TypeCheck) {
_summary.add(new Use(arg));
} else {
assertx(arg is Type || arg is Parameter);
@ -571,19 +668,24 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
}
void _handleNestedFunctionNode(FunctionNode node) {
var oldReturn = _returnValue;
var oldVariables = _variables;
final oldReturn = _returnValue;
final oldVariableJoins = _variableJoins;
final oldVariables = _variables;
_returnValue = null;
_variables = <VariableDeclaration, Join>{};
_variableJoins = <VariableDeclaration, Join>{};
_variableJoins.addAll(oldVariableJoins);
_variables = <VariableDeclaration, TypeExpr>{};
_variables.addAll(oldVariables);
// Approximate parameters of nested functions with static types.
// TODO(sjindel/tfa): Use TypeCheck for closure parameters.
node.positionalParameters.forEach(_declareVariableWithStaticType);
node.namedParameters.forEach(_declareVariableWithStaticType);
_visit(node.body);
_returnValue = oldReturn;
_variableJoins = oldVariableJoins;
_variables = oldVariables;
}
@ -595,7 +697,16 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
TypeExpr visitAsExpression(AsExpression node) {
TypeExpr operand = _visit(node.operand);
Type type = new Type.fromStatic(node.type);
return _makeNarrow(operand, type);
TypeExpr result = _makeNarrow(operand, type);
TypeExpr runtimeType = _translator.translate(node.type);
if (runtimeType is Statement) {
result = new TypeCheck(operand, runtimeType, /*parameter=*/ null);
_summary.add(result);
}
return result;
}
@override
@ -626,9 +737,10 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
@override
TypeExpr visitConstructorInvocation(ConstructorInvocation node) {
final receiver =
ConcreteType klass =
_entryPointsListener.addAllocatedClass(node.constructedType.classNode);
TypeExpr receiver =
_translator.instantiateConcreteType(klass, node.arguments.types);
final args = _visitArguments(receiver, node.arguments);
_makeCall(node, new DirectSelector(node.target), args);
return receiver;
@ -718,9 +830,12 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
node.expressions.forEach(_visit);
Class concreteClass =
target.concreteListLiteralClass(_environment.coreTypes);
return concreteClass != null
? _entryPointsListener.addAllocatedClass(concreteClass)
: _staticType(node);
if (concreteClass != null) {
return _translator.instantiateConcreteType(
_entryPointsListener.addAllocatedClass(concreteClass),
[node.typeArgument]);
}
return _staticType(node);
}
@override
@ -738,9 +853,12 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
}
Class concreteClass =
target.concreteMapLiteralClass(_environment.coreTypes);
return concreteClass != null
? _entryPointsListener.addAllocatedClass(concreteClass)
: _staticType(node);
if (concreteClass != null) {
return _translator.instantiateConcreteType(
_entryPointsListener.addAllocatedClass(concreteClass),
[node.keyType, node.valueType]);
}
return _staticType(node);
}
@override
@ -917,7 +1035,8 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
@override
TypeExpr visitStaticInvocation(StaticInvocation node) {
final args = _visitArguments(null, node.arguments);
final args = _visitArguments(null, node.arguments,
passTypeArguments: node.target.isFactory);
final target = node.target;
assertx((target is! Field) && !target.isGetter && !target.isSetter);
return _makeCall(node, new DirectSelector(target), args);
@ -969,7 +1088,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
@override
TypeExpr visitVariableGet(VariableGet node) {
Join v = _variables[node.variable];
final v = _variables[node.variable];
if (v == null) {
throw 'Unable to find variable ${node.variable}';
}
@ -984,7 +1103,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
@override
TypeExpr visitVariableSet(VariableSet node) {
Join v = _variables[node.variable];
Join v = _variableJoins[node.variable];
assertx(v != null, details: node);
TypeExpr value = _visit(node.value);
@ -1221,6 +1340,141 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
}
}
class RuntimeTypeTranslator extends DartTypeVisitor<TypeExpr> {
final Class enclosingClass;
final Summary summary;
final Map<TypeParameter, TypeExpr> functionTypeVariables;
final Map<DartType, TypeExpr> typesCache = <DartType, TypeExpr>{};
final TypeExpr receiver;
final GenericInterfacesInfo genericInterfacesInfo;
RuntimeTypeTranslator(this.enclosingClass, this.summary, this.receiver,
this.functionTypeVariables, this.genericInterfacesInfo) {}
// Create a type translator which can be used only for types with no free type
// variables.
RuntimeTypeTranslator.forClosedTypes(this.genericInterfacesInfo)
: enclosingClass = null,
summary = null,
functionTypeVariables = null,
receiver = null {}
TypeExpr instantiateConcreteType(ConcreteType type, List<DartType> typeArgs) {
if (typeArgs.isEmpty) return type;
// This function is very similar to 'visitInterfaceType', but with
// many small differences.
final klass = type.classNode;
final substitution = Substitution.fromPairs(klass.typeParameters, typeArgs);
final flattenedTypeArgs =
genericInterfacesInfo.flattenedTypeArgumentsFor(klass);
final flattenedTypeExprs = new List<TypeExpr>(flattenedTypeArgs.length);
bool createConcreteType = true;
bool allAnyType = true;
for (int i = 0; i < flattenedTypeArgs.length; ++i) {
final typeExpr =
translate(substitution.substituteType(flattenedTypeArgs[i]));
if (typeExpr != const AnyType()) allAnyType = false;
if (typeExpr is Statement) createConcreteType = false;
flattenedTypeExprs[i] = typeExpr;
}
if (allAnyType) return type;
if (createConcreteType) {
return new ConcreteType(type.classId, type.classNode,
new List<Type>.from(flattenedTypeExprs));
} else {
final instantiate = new CreateConcreteType(type, flattenedTypeExprs);
summary.add(instantiate);
return instantiate;
}
}
// Creates a TypeExpr representing the set of types which can flow through a
// given DartType.
//
// Will return AnyType, RuntimeType or Statement.
TypeExpr translate(DartType type) {
final cached = typesCache[type];
if (cached != null) return cached;
// During type translation, loops can arise via super-bounded types:
//
// class A<T> extends Comparable<A<T>> {}
//
// Creating the factored type arguments of A will lead to an infinite loop.
// We break such loops by inserting an 'AnyType' in place of the currently
// processed type, ensuring we try to build 'A<T>' in the process of
// building 'A<T>'.
typesCache[type] = const AnyType();
final result = type.accept(this);
assertx(result is AnyType || result is RuntimeType || result is Statement);
typesCache[type] = result;
return result;
}
@override
TypeExpr defaultDartType(DartType node) => const AnyType();
@override
TypeExpr visitDynamicType(DynamicType type) => new RuntimeType(type, null);
@override
TypeExpr visitVoidType(VoidType type) => new RuntimeType(type, null);
@override
TypeExpr visitBottomType(BottomType type) => new RuntimeType(type, null);
@override
visitTypedefType(TypedefType node) => translate(node.unalias);
@override
visitInterfaceType(InterfaceType type) {
if (type.typeArguments.isEmpty) return new RuntimeType(type, null);
final substitution = Substitution.fromPairs(
type.classNode.typeParameters, type.typeArguments);
final flattenedTypeArgs =
genericInterfacesInfo.flattenedTypeArgumentsFor(type.classNode);
final flattenedTypeExprs = new List<TypeExpr>(flattenedTypeArgs.length);
bool createRuntimeType = true;
for (var i = 0; i < flattenedTypeArgs.length; ++i) {
final typeExpr =
translate(substitution.substituteType(flattenedTypeArgs[i]));
if (typeExpr == const AnyType()) return const AnyType();
if (typeExpr is! RuntimeType) createRuntimeType = false;
flattenedTypeExprs[i] = typeExpr;
}
if (createRuntimeType) {
return new RuntimeType(new InterfaceType(type.classNode),
new List<RuntimeType>.from(flattenedTypeExprs));
} else {
final instantiate =
new CreateRuntimeType(type.classNode, flattenedTypeExprs);
summary.add(instantiate);
return instantiate;
}
}
@override
visitTypeParameterType(TypeParameterType type) {
if (functionTypeVariables != null) {
final result = functionTypeVariables[type.parameter];
if (result != null) return result;
}
if (type.parameter.parent is! Class) return const AnyType();
assertx(type.parameter.parent == enclosingClass);
assertx(receiver != null);
final extract = new Extract(receiver, enclosingClass,
enclosingClass.typeParameters.indexOf(type.parameter));
summary.add(extract);
return extract;
}
}
class EmptyEntryPointsListener implements EntryPointsListener {
final Map<Class, IntClassId> _classIds = <Class, IntClassId>{};
int _classIdCounter = 0;
@ -1234,7 +1488,7 @@ class EmptyEntryPointsListener implements EntryPointsListener {
@override
ConcreteType addAllocatedClass(Class c) {
final classId = (_classIds[c] ??= new IntClassId(++_classIdCounter));
return new ConcreteType(classId, c.rawType);
return new ConcreteType(classId, c, null);
}
@override
@ -1246,16 +1500,21 @@ class EmptyEntryPointsListener implements EntryPointsListener {
class CreateAllSummariesVisitor extends RecursiveVisitor<Null> {
final TypeEnvironment _environment;
final SummaryCollector _summaryColector;
final SummaryCollector _summaryCollector;
CreateAllSummariesVisitor(Target target, this._environment)
: _summaryColector = new SummaryCollector(target, _environment,
new EmptyEntryPointsListener(), new NativeCodeOracle(null, null));
CreateAllSummariesVisitor(
Target target, this._environment, GenericInterfacesInfo hierarchy)
: _summaryCollector = new SummaryCollector(
target,
_environment,
new EmptyEntryPointsListener(),
new NativeCodeOracle(null, null),
hierarchy);
@override
defaultMember(Member m) {
if (!m.isAbstract && !(m is Field && m.initializer == null)) {
_summaryColector.createSummary(m);
_summaryCollector.createSummary(m);
}
}
}

View file

@ -41,18 +41,20 @@ Component transformComponent(
onAmbiguousSupertypes: ignoreAmbiguousSupertypes);
final types = new TypeEnvironment(coreTypes, hierarchy, strongMode: true);
final libraryIndex = new LibraryIndex.all(component);
final genericInterfacesInfo = new GenericInterfacesInfoImpl(hierarchy);
if (kDumpAllSummaries) {
Statistics.reset();
new CreateAllSummariesVisitor(target, types).visitComponent(component);
new CreateAllSummariesVisitor(target, types, genericInterfacesInfo)
.visitComponent(component);
Statistics.print("All summaries statistics");
}
Statistics.reset();
final analysisStopWatch = new Stopwatch()..start();
final typeFlowAnalysis = new TypeFlowAnalysis(
target, component, coreTypes, hierarchy, types, libraryIndex,
final typeFlowAnalysis = new TypeFlowAnalysis(target, component, coreTypes,
hierarchy, genericInterfacesInfo, types, libraryIndex,
matcher: matcher);
Procedure main = component.mainMethod;
@ -127,7 +129,7 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
component.addMetadataRepository(_procedureAttributesMetadata);
}
InferredType _convertType(Type type) {
InferredType _convertType(Type type, {bool skipCheck: false}) {
assertx(type != null);
Class concreteClass;
@ -148,15 +150,25 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
}
}
List<DartType> typeArgs;
if (type is ConcreteType && type.typeArgs != null) {
typeArgs = type.typeArgs
.take(type.numImmediateTypeArgs)
.map((t) => t is AnyType ? null : (t as RuntimeType).representedType)
.toList();
}
if ((concreteClass != null) || !nullable || isInt) {
return new InferredType(concreteClass, nullable, isInt);
return new InferredType(concreteClass, nullable, isInt,
exactTypeArguments: typeArgs, skipCheck: skipCheck);
}
return null;
}
void _setInferredType(TreeNode node, Type type) {
final inferredType = _convertType(type);
void _setInferredType(TreeNode node, Type type, {bool skipCheck: false}) {
assertx(skipCheck == false || node is VariableDeclaration || node is Field);
final inferredType = _convertType(type, skipCheck: skipCheck);
if (inferredType != null) {
_inferredTypeMetadata.mapping[node] = inferredType;
}
@ -182,12 +194,17 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
void _annotateMember(Member member) {
if (_typeFlowAnalysis.isMemberUsed(member)) {
if (member is Field) {
_setInferredType(member, _typeFlowAnalysis.fieldType(member));
_setInferredType(member, _typeFlowAnalysis.fieldType(member),
skipCheck: _typeFlowAnalysis.fieldStaticCallSiteSkipCheck(member));
} else {
Args<Type> argTypes = _typeFlowAnalysis.argumentTypes(member);
assertx(argTypes != null);
final int firstParamIndex = hasReceiverArg(member) ? 1 : 0;
final skipCheckParams = new Set<VariableDeclaration>.from(
_typeFlowAnalysis.staticCallSiteSkipCheckParams(member));
final int firstParamIndex =
numTypeParams(member) + (hasReceiverArg(member) ? 1 : 0);
final positionalParams = member.function.positionalParameters;
assertx(argTypes.positionalCount ==
@ -195,7 +212,8 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
for (int i = 0; i < positionalParams.length; i++) {
_setInferredType(
positionalParams[i], argTypes.values[firstParamIndex + i]);
positionalParams[i], argTypes.values[firstParamIndex + i],
skipCheck: skipCheckParams.contains(positionalParams[i]));
}
// TODO(dartbug.com/32292): make sure parameters are sorted in kernel
@ -205,7 +223,8 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
final param = findNamedParameter(member.function, names[i]);
assertx(param != null);
_setInferredType(param,
argTypes.values[firstParamIndex + positionalParams.length + i]);
argTypes.values[firstParamIndex + positionalParams.length + i],
skipCheck: skipCheckParams.contains(param));
}
// TODO(alexmarkov): figure out how to pass receiver type.

View file

@ -11,14 +11,40 @@ import 'package:kernel/ast.dart';
import 'utils.dart';
abstract class GenericInterfacesInfo {
// Return a type arguments vector which contains the immediate type parameters
// to 'klass' as well as the type arguments to all generic supertypes of
// 'klass', instantiated in terms of the type parameters on 'klass'.
//
// The offset into this vector from which a specific generic supertype's type
// arguments can be found is given by 'genericInterfaceOffsetFor'.
List<DartType> flattenedTypeArgumentsFor(Class klass);
// Return the offset into the flattened type arguments vector from which a
// specific generic supertype's type arguments can be found. The flattened
// type arguments vector is given by 'flattenedTypeArgumentsFor'.
int genericInterfaceOffsetFor(Class klass, Class iface);
// Similar to 'flattenedTypeArgumentsFor', but works for non-generic classes
// which may have recursive substitutions, e.g. 'class num implements
// Comparable<num>'.
//
// Since there are no free type variables in the result, 'RuntimeType' is
// returned instead of 'DartType'.
List<Type> flattenedTypeArgumentsForNonGeneric(Class klass);
}
/// Abstract interface to type hierarchy information used by types.
abstract class TypeHierarchy {
abstract class TypeHierarchy implements GenericInterfacesInfo {
/// Test if [subType] is a subtype of [superType].
bool isSubtype(DartType subType, DartType superType);
/// Return a more specific type for the type cone with [base] root.
/// May return EmptyType, AnyType, ConcreteType or a SetType.
Type specializeTypeCone(DartType base);
Class get futureOrClass;
Class get futureClass;
}
/// Basic normalization of Dart types.
@ -97,6 +123,12 @@ abstract class Type extends TypeExpr {
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) => false;
// Returns 'true' if this type will definitely pass a runtime type-check
// against 'runtimeType'. Returns 'false' if the test might fail (e.g. due to
// an approximation).
bool isSubtypeOfRuntimeType(
TypeHierarchy typeHierarchy, RuntimeType runtimeType);
@override
Type getComputedType(List<Type> types) => this;
@ -118,6 +150,7 @@ abstract class Type extends TypeExpr {
/// Order of precedence between types for evaluation of union/intersection.
enum TypeOrder {
RuntimeType,
Empty,
Nullable,
Any,
@ -147,6 +180,10 @@ class EmptyType extends Type {
@override
Type intersection(Type other, TypeHierarchy typeHierarchy) => this;
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) {
return true;
}
}
/// Nullable type represents a union of a (non-nullable) type and the `null`
@ -173,6 +210,9 @@ class NullableType extends Type {
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) =>
baseType.isSubtypeOf(typeHierarchy, dartType);
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) =>
baseType.isSubtypeOfRuntimeType(typeHierarchy, other);
@override
int get order => TypeOrder.Nullable.index;
@ -212,6 +252,7 @@ class NullableType extends Type {
/// Type representing any instance except `null`.
/// Semantically equivalent to ConeType of Object, but more efficient.
/// Can also represent a set of types, the set of all types.
class AnyType extends Type {
const AnyType();
@ -242,6 +283,10 @@ class AnyType extends Type {
}
return other;
}
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) {
return typeHierarchy.isSubtype(const DynamicType(), other._type);
}
}
/// SetType is a union of concrete types T1, T2, ..., Tn, where n >= 2.
@ -263,7 +308,7 @@ class SetType extends Type {
int _computeHashCode() {
int hash = 1237;
for (var t in types) {
hash = (((hash * 31) & kHashMask) + t.classId.hashCode) & kHashMask;
hash = (((hash * 31) & kHashMask) + t.hashCode) & kHashMask;
}
return hash;
}
@ -272,7 +317,7 @@ class SetType extends Type {
bool operator ==(other) {
if ((other is SetType) && (types.length == other.types.length)) {
for (int i = 0; i < types.length; i++) {
if (types[i].classId != other.types[i].classId) {
if (types[i] != other.types[i]) {
return false;
}
}
@ -288,6 +333,9 @@ class SetType extends Type {
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) =>
types.every((ConcreteType t) => t.isSubtypeOf(typeHierarchy, dartType));
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) =>
types.every((t) => t.isSubtypeOfRuntimeType(typeHierarchy, other));
@override
int get order => TypeOrder.Set.index;
@ -307,8 +355,13 @@ class SetType extends Type {
types.add(t2);
++i2;
} else {
assertx(t1 == t2);
types.add(t1);
if (t1 == t2) {
types.add(t1);
} else {
// TODO(sjindel/tfa): Merge the type arguments vectors.
// (e.g., Map<?, int> vs Map<String, int> can become Map<?, int>)
types.add(t1.raw);
}
++i1;
++i2;
}
@ -335,8 +388,14 @@ class SetType extends Type {
} else if (relation > 0) {
++i2;
} else {
assertx(t1 == t2);
types.add(t1);
if (t1.typeArgs == null && t2.typeArgs == null) {
types.add(t1);
} else {
final intersect = t1.intersection(t2, null);
if (intersect is! EmptyType) {
types.add(intersect);
}
}
++i1;
++i2;
}
@ -380,7 +439,13 @@ class SetType extends Type {
return new SetType(list);
}
} else if (other is ConcreteType) {
return types.contains(other) ? other : const EmptyType();
for (var type in types) {
if (type == other) return other;
if (type.classId == other.classId) {
return type.intersection(other, typeHierarchy);
}
}
return EmptyType();
} else if (other is ConeType) {
return typeHierarchy
.specializeTypeCone(other.dartType)
@ -410,6 +475,14 @@ class ConeType extends Type {
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) =>
typeHierarchy.isSubtype(this.dartType, dartType);
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) {
if (!typeHierarchy.isSubtype(dartType, other._type)) return false;
if (dartType is InterfaceType) {
return (dartType as InterfaceType).classNode.typeParameters.isEmpty;
}
return true;
}
@override
int get hashCode => (dartType.hashCode + 37) & kHashMask;
@ -446,7 +519,7 @@ class ConeType extends Type {
return other;
}
} else if (other is ConcreteType) {
if (typeHierarchy.isSubtype(other.dartType, this.dartType)) {
if (typeHierarchy.isSubtype(other.classNode.rawType, this.dartType)) {
return this;
}
}
@ -471,7 +544,7 @@ class ConeType extends Type {
return this;
}
} else if (other is ConcreteType) {
if (typeHierarchy.isSubtype(other.dartType, this.dartType)) {
if (typeHierarchy.isSubtype(other.classNode.rawType, this.dartType)) {
return other;
} else {
return const EmptyType();
@ -503,37 +576,138 @@ class IntClassId extends ClassId<IntClassId> {
/// or `null` object).
class ConcreteType extends Type implements Comparable<ConcreteType> {
final ClassId classId;
final DartType dartType;
final Class classNode;
int _hashCode;
ConcreteType(this.classId, this.dartType) {
// TODO(alexmarkov): support generics & closures
assertx(dartType is InterfaceType);
assertx(!(dartType as InterfaceType).classNode.isAbstract);
assertx((dartType as InterfaceType)
.typeArguments
.every((t) => t == const DynamicType()));
// May be null if there are no type arguments constraints. The type arguments
// should represent type sets, i.e. `AnyType` or `RuntimeType`. The type
// arguments vector is factored against the generic interfaces implemented by
// the class (see [TypeHierarchy.flattenedTypeArgumentsFor]).
//
// The 'typeArgs' vector is null for non-generic classes, even if they
// implement a generic interface.
//
// 'numImmediateTypeArgs' is the length of the prefix of 'typeArgs' which
// holds the type arguments to the class itself.
final int numImmediateTypeArgs;
final List<Type> typeArgs;
ConcreteType(this.classId, this.classNode, [List<Type> typeArgs_])
: typeArgs = typeArgs_,
numImmediateTypeArgs =
typeArgs_ != null ? classNode.typeParameters.length : 0 {
// TODO(alexmarkov): support closures
assertx(!classNode.isAbstract);
assertx(typeArgs == null || classNode.typeParameters.isNotEmpty);
assertx(typeArgs == null || typeArgs.any((t) => t is RuntimeType));
}
ConcreteType get raw => new ConcreteType(classId, classNode, null);
@override
Class getConcreteClass(TypeHierarchy typeHierarchy) =>
(dartType as InterfaceType).classNode;
Class getConcreteClass(TypeHierarchy typeHierarchy) => classNode;
@override
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) =>
typeHierarchy.isSubtype(this.dartType, dartType);
typeHierarchy.isSubtype(classNode.rawType, dartType);
bool isSubtypeOfRuntimeType(
TypeHierarchy typeHierarchy, RuntimeType runtimeType) {
if (!typeHierarchy.isSubtype(this.classNode.rawType, runtimeType._type)) {
return false;
}
InterfaceType runtimeDartType;
if (runtimeType._type is InterfaceType) {
runtimeDartType = runtimeType._type;
if (runtimeDartType.typeArguments.isEmpty) return true;
if (runtimeDartType.classNode == typeHierarchy.futureOrClass) {
if (typeHierarchy.isSubtype(
classNode.rawType, typeHierarchy.futureClass.rawType) ||
classNode == typeHierarchy.futureOrClass) {
final RuntimeType lhs =
typeArgs == null ? RuntimeType(DynamicType(), null) : typeArgs[0];
return lhs.isSubtypeOfRuntimeType(
typeHierarchy, runtimeType.typeArgs[0]);
} else {
return isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[0]);
}
}
} else {
// The TypeHierarchy result may be inaccurate only if there are type
// arguments which it doesn't examine.
return true;
}
List<Type> usableTypeArgs = typeArgs;
if (usableTypeArgs == null) {
if (classNode.typeParameters.isEmpty) {
usableTypeArgs =
typeHierarchy.flattenedTypeArgumentsForNonGeneric(classNode);
} else {
return false;
}
}
final interfaceOffset = typeHierarchy.genericInterfaceOffsetFor(
classNode, runtimeDartType.classNode);
assertx(usableTypeArgs.length - interfaceOffset >=
runtimeType.numImmediateTypeArgs);
for (int i = 0; i < runtimeType.numImmediateTypeArgs; ++i) {
if (usableTypeArgs[i + interfaceOffset] == const AnyType()) return false;
assertx(usableTypeArgs[i + interfaceOffset] is RuntimeType);
if (!usableTypeArgs[i + interfaceOffset]
.isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[i])) {
return false;
}
}
return true;
}
@override
int get hashCode => (classId.hashCode ^ 0x1234) & kHashMask;
int get hashCode => _hashCode ??= _computeHashCode();
int _computeHashCode() {
int hash = classId.hashCode ^ 0x1234 & kHashMask;
// We only need to hash the first type arguments vector, since the type
// arguments of the implemented interfaces are implied by it.
for (int i = 0; i < numImmediateTypeArgs; ++i) {
hash = (((hash * 31) & kHashMask) + typeArgs[i].hashCode) & kHashMask;
}
return hash;
}
@override
bool operator ==(other) =>
(other is ConcreteType) && (this.classId == other.classId);
bool operator ==(other) {
if (other is ConcreteType) {
if (this.classId != other.classId ||
this.numImmediateTypeArgs != other.numImmediateTypeArgs) {
return false;
}
if (this.typeArgs != null) {
for (int i = 0; i < numImmediateTypeArgs; ++i) {
if (this.typeArgs[i] != other.typeArgs[i]) {
return false;
}
}
}
return true;
} else {
return false;
}
}
// Note that this may return 0 for concrete types which are not equal if the
// difference is only in type arguments.
@override
int compareTo(ConcreteType other) => classId.compareTo(other.classId);
@override
String toString() => "_T (${dartType})";
String toString() => typeArgs == null
? "_T (${classNode})"
: "_T (${classNode}<${typeArgs.take(numImmediateTypeArgs).join(', ')}>)";
@override
int get order => TypeOrder.Concrete.index;
@ -546,13 +720,14 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
if (other is ConcreteType) {
if (this == other) {
return this;
} else {
assertx(this.classId != other.classId);
final List<ConcreteType> types =
(this.classId.compareTo(other.classId) < 0)
? <ConcreteType>[this, other]
: <ConcreteType>[other, this];
} else if (this.classId != other.classId) {
final types = (this.classId.compareTo(other.classId) < 0)
? <ConcreteType>[this, other]
: <ConcreteType>[other, this];
return new SetType(types);
} else {
assertx(typeArgs != null || other.typeArgs != null);
return raw;
}
} else {
throw 'Unexpected type $other';
@ -567,11 +742,201 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
if (other is ConcreteType) {
if (this == other) {
return this;
} else {
return const EmptyType();
}
if (this.classId != other.classId) {
return EmptyType();
}
assertx(typeArgs != null || other.typeArgs != null);
if (typeArgs == null) {
return other;
} else if (other.typeArgs == null) {
return this;
}
final mergedTypeArgs = new List<Type>(typeArgs.length);
bool hasRuntimeType = false;
for (int i = 0; i < typeArgs.length; ++i) {
final merged =
typeArgs[i].intersection(other.typeArgs[i], typeHierarchy);
if (merged is EmptyType) {
return EmptyType();
} else if (merged is RuntimeType) {
hasRuntimeType = true;
}
mergedTypeArgs[i] = merged;
}
if (!hasRuntimeType) return raw;
return new ConcreteType(classId, classNode, mergedTypeArgs);
} else {
throw 'Unexpected type $other';
}
}
}
// Unlike the other 'Type's, this represents a single type, not a set of
// values. It is used as the right-hand-side of type-tests.
//
// The type arguments are represented in a form that is factored against the
// generic interfaces implemented by the type to enable efficient type-test
// against its interfaces. See 'TypeHierarchy.flattenedTypeArgumentsFor' for
// more details.
//
// This factored representation can have cycles for some types:
//
// class num implements Comparable<num> {}
// class A<T> extends Comparable<A<T>> {}
//
// To avoid these cycles, we approximate generic super-bounded types (the second
// case), so the representation for 'A<String>' would be simply 'AnyType'.
// However, approximating non-generic types like 'int' and 'num' (the first
// case) would be too coarse, so we leave an null 'typeArgs' field for these
// types. As a result, when doing an 'isSubtypeOfRuntimeType' against
// their interfaces (e.g. 'int' vs 'Comparable<int>') we approximate the result
// as 'false'.
//
// So, the invariant about 'typeArgs' is that they will be 'null' iff the class
// is non-generic, and non-null (with at least one vector) otherwise.
class RuntimeType extends Type {
final DartType _type; // Doesn't contain type args.
final int numImmediateTypeArgs;
final List<RuntimeType> typeArgs;
RuntimeType(DartType type, this.typeArgs)
: _type = type,
numImmediateTypeArgs =
type is InterfaceType ? type.classNode.typeParameters.length : 0 {
if (_type is InterfaceType && numImmediateTypeArgs > 0) {
assertx(typeArgs != null);
assertx(typeArgs.length >= numImmediateTypeArgs);
assertx((_type as InterfaceType)
.typeArguments
.every((t) => t == const DynamicType()));
} else {
assertx(typeArgs == null);
}
}
int get order => TypeOrder.RuntimeType.index;
DartType get representedTypeRaw => _type;
DartType get representedType {
if (_type is InterfaceType && typeArgs != null) {
final klass = (_type as InterfaceType).classNode;
final typeArguments = typeArgs
.take(klass.typeParameters.length)
.map((pt) => pt.representedType)
.toList();
return new InterfaceType(klass, typeArguments);
} else {
return _type;
}
}
@override
int get hashCode {
int hash = _type.hashCode ^ 0x1234 & kHashMask;
// Only hash by the type arguments of the class. The type arguments of
// supertypes are are implied by them.
for (int i = 0; i < numImmediateTypeArgs; ++i) {
hash = (((hash * 31) & kHashMask) + typeArgs[i].hashCode) & kHashMask;
}
return hash;
}
@override
operator ==(other) {
if (other is RuntimeType) {
if (other._type != _type) return false;
assertx(numImmediateTypeArgs == other.numImmediateTypeArgs);
return typeArgs == null || listEquals(typeArgs, other.typeArgs);
}
return false;
}
@override
String toString() {
final head = _type is InterfaceType
? "${(_type as InterfaceType).classNode}"
: "$_type";
if (numImmediateTypeArgs == 0) return head;
final typeArgsStrs =
typeArgs.take(numImmediateTypeArgs).map((t) => "$t").join(", ");
return "_TS {$head<$typeArgsStrs>}";
}
@override
bool get isSpecialized =>
throw "ERROR: RuntimeType does not support isSpecialized.";
@override
bool isSubtypeOf(TypeHierarchy typeHierarchy, DartType dartType) =>
throw "ERROR: RuntimeType does not support isSubtypeOf.";
@override
Type union(Type other, TypeHierarchy typeHierarchy) =>
throw "ERROR: RuntimeType does not support union.";
// This only works between "type-set" representations ('AnyType' and
// 'RuntimeType') and is used when merging type arguments.
@override
Type intersection(Type other, TypeHierarchy typeHierarchy) {
if (other is AnyType) {
return this;
} else if (other is RuntimeType) {
return this == other ? this : const EmptyType();
}
throw "ERROR: RuntimeType cannot intersect with ${other.runtimeType}";
}
@override
Type specialize(TypeHierarchy typeHierarchy) =>
throw "ERROR: RuntimeType does not support specialize.";
@override
Class getConcreteClass(TypeHierarchy typeHierarchy) =>
throw "ERROR: ConcreteClass does not support getConcreteClass.";
bool isSubtypeOfRuntimeType(
TypeHierarchy typeHierarchy, RuntimeType runtimeType) {
if (!typeHierarchy.isSubtype(this._type, runtimeType._type)) return false;
// The typeHierarchy result maybe be inaccurate only if there are type
// arguments which need to be examined.
if (_type is! InterfaceType || runtimeType.numImmediateTypeArgs == 0) {
return true;
}
final thisClass = (_type as InterfaceType).classNode;
final otherClass = (runtimeType._type as InterfaceType).classNode;
if (otherClass == typeHierarchy.futureOrClass) {
if (thisClass == typeHierarchy.futureClass ||
thisClass == typeHierarchy.futureOrClass) {
return typeArgs[0]
.isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[0]);
} else {
return isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[0]);
}
}
List<Type> usableTypeArgs = typeArgs;
if (usableTypeArgs == null) {
assertx(thisClass.typeParameters.isEmpty);
usableTypeArgs =
typeHierarchy.flattenedTypeArgumentsForNonGeneric(thisClass);
}
final interfaceOffset =
typeHierarchy.genericInterfaceOffsetFor(thisClass, otherClass);
assertx(usableTypeArgs.length - interfaceOffset >=
runtimeType.numImmediateTypeArgs);
for (int i = 0; i < runtimeType.numImmediateTypeArgs; ++i) {
if (!usableTypeArgs[interfaceOffset + i]
.isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[i])) {
return false;
}
}
return true;
}
}

View file

@ -7,7 +7,14 @@
library vm.transformations.type_flow.utils;
import 'package:kernel/ast.dart'
show Constructor, FunctionNode, Member, VariableDeclaration;
show
Class,
Constructor,
DartType,
Procedure,
FunctionNode,
Member,
VariableDeclaration;
const bool kPrintTrace =
const bool.fromEnvironment('global.type.flow.print.trace');
@ -51,6 +58,14 @@ const int kHashMask = 0x3fffffff;
bool hasReceiverArg(Member member) =>
member.isInstanceMember || (member is Constructor);
// Type arguments to procedures is only supported for factory constructors of
// generic classes at the moment.
//
// TODO(sjindel/tfa): Extend suport to normal generic functions.
int numTypeParams(Member member) => member is Procedure && member.isFactory
? member.function.typeParameters.length
: 0;
/// Returns true if elements in [list] are in strictly increasing order.
/// List with duplicates is considered not sorted.
bool isSorted(List list) {
@ -148,3 +163,43 @@ class Statistics {
""");
}
}
int typeArgumentsHash(List<DartType> typeArgs) {
int hash = 1237;
for (var t in typeArgs) {
hash = (((hash * 31) & kHashMask) + t.hashCode) & kHashMask;
}
return hash;
}
class SubtypePair {
final Class subtype;
final Class supertype;
SubtypePair(this.subtype, this.supertype);
int get hashCode {
return subtype.hashCode ^ supertype.hashCode;
}
bool operator ==(Object other) {
if (other is SubtypePair) {
return subtype == other.subtype && supertype == other.supertype;
}
return false;
}
}
// Returns the smallest index 'i' such that 'list.skip(i)' is a prefix of
// 'sublist'.
int findOverlap(List list, List sublist) {
for (int i = 0; i < list.length; ++i)
outer:
{
for (int j = 0; j < sublist.length && i + j < list.length; ++j) {
if (list[i + j] != sublist[j]) continue outer;
}
return i;
}
return list.length;
}

View file

@ -11,6 +11,7 @@ import 'package:kernel/type_environment.dart';
import 'package:test/test.dart';
import 'package:vm/transformations/type_flow/native_code.dart';
import 'package:vm/transformations/type_flow/summary_collector.dart';
import 'package:vm/transformations/type_flow/analysis.dart';
import 'annotation_matcher.dart';
import 'package:kernel/target/targets.dart';
@ -29,7 +30,8 @@ class PrintSummaries extends RecursiveVisitor<Null> {
environment,
new EmptyEntryPointsListener(),
new NativeCodeOracle(
null, new ExpressionPragmaAnnotationParser(coreTypes)));
null, new ExpressionPragmaAnnotationParser(coreTypes)),
new GenericInterfacesInfoImpl(environment.hierarchy));
String print(TreeNode node) {
visitLibrary(node);

View file

@ -39,8 +39,9 @@ main() {
final testCasesDir = new Directory(
pkgVmDir + '/testcases/transformations/type_flow/transformer');
for (var entry
in testCasesDir.listSync(recursive: true, followLinks: false)) {
for (var entry in testCasesDir
.listSync(recursive: true, followLinks: false)
.reversed) {
if (entry.path.endsWith(".dart")) {
test(entry.path, () => runTestCase(entry.uri));
}

View file

@ -26,6 +26,19 @@ class TestTypeHierarchy implements TypeHierarchy {
reason: "specializeTypeCone($base) is not defined");
return result;
}
List<DartType> flattenedTypeArgumentsFor(Class klass) =>
throw "flattenedTypeArgumentsFor is not supported in the types test.";
int genericInterfaceOffsetFor(Class klass, Class iface) =>
throw "genericInterfaceOffsetFor is not supported in the types test.";
List<Type> flattenedTypeArgumentsForNonGeneric(Class klass) =>
throw "flattenedTypeArgumentsFor is not supported in the types test.";
Class get futureOrClass =>
throw "futureOrClass not supported in the types test.";
Class get futureClass => throw "futureClass not supported in the types test.";
}
main() {
@ -75,10 +88,10 @@ main() {
final empty = new EmptyType();
final any = new AnyType();
final concreteT1 = new ConcreteType(const IntClassId(1), t1);
final concreteT2 = new ConcreteType(const IntClassId(2), t2);
final concreteT3 = new ConcreteType(const IntClassId(3), t3);
final concreteT4 = new ConcreteType(const IntClassId(4), t4);
final concreteT1 = new ConcreteType(const IntClassId(1), t1.classNode);
final concreteT2 = new ConcreteType(const IntClassId(2), t2.classNode);
final concreteT3 = new ConcreteType(const IntClassId(3), t3.classNode);
final concreteT4 = new ConcreteType(const IntClassId(4), t4.classNode);
final coneT1 = new ConeType(t1);
final coneT2 = new ConeType(t2);
final coneT3 = new ConeType(t3);
@ -261,7 +274,6 @@ main() {
final t1a = new InterfaceType(c1);
final t1b = new InterfaceType(c1);
final t2 = new InterfaceType(c2);
final t3 = new InterfaceType(c3);
final f1a = new FunctionType([t1a], const VoidType());
final f1b = new FunctionType([t1b], const VoidType());
final f2 = new FunctionType([t1a, t1a], const VoidType());
@ -292,27 +304,27 @@ main() {
eq(new EmptyType(), new EmptyType());
ne(new EmptyType(), new AnyType());
ne(new EmptyType(), new ConcreteType(cid1, t1a));
ne(new EmptyType(), new ConcreteType(cid1, c1));
ne(new EmptyType(), new ConeType(t1a));
ne(new EmptyType(),
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]));
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]));
ne(new EmptyType(), new NullableType(new EmptyType()));
eq(new AnyType(), new AnyType());
ne(new AnyType(), new ConcreteType(cid1, t1a));
ne(new AnyType(), new ConcreteType(cid1, c1));
ne(new AnyType(), new ConeType(t1a));
ne(new AnyType(),
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]));
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]));
ne(new AnyType(), new NullableType(new EmptyType()));
eq(new ConcreteType(cid1, t1a), new ConcreteType(cid1, t1b));
ne(new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2));
ne(new ConcreteType(cid1, t1a), new ConeType(t1a));
ne(new ConcreteType(cid1, t1a), new ConeType(t2));
ne(new ConcreteType(cid1, t1a),
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]));
ne(new ConcreteType(cid1, t1a),
new NullableType(new ConcreteType(cid1, t1a)));
eq(new ConcreteType(cid1, c1), new ConcreteType(cid1, c1));
ne(new ConcreteType(cid1, c1), new ConcreteType(cid2, c2));
ne(new ConcreteType(cid1, c1), new ConeType(t1a));
ne(new ConcreteType(cid1, c1), new ConeType(t2));
ne(new ConcreteType(cid1, c1),
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]));
ne(new ConcreteType(cid1, c1),
new NullableType(new ConcreteType(cid1, c1)));
eq(new ConeType(t1a), new ConeType(t1b));
eq(new ConeType(f1a), new ConeType(f1b));
@ -320,34 +332,34 @@ main() {
ne(new ConeType(f1a), new ConeType(f2));
ne(new ConeType(t1a), new ConeType(f1a));
ne(new ConeType(t1a),
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]));
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]));
ne(new ConeType(t1a), new NullableType(new ConeType(t1a)));
eq(new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]),
new SetType([new ConcreteType(cid1, t1b), new ConcreteType(cid2, t2)]));
eq(new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]),
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]));
eq(
new SetType([
new ConcreteType(cid1, t1a),
new ConcreteType(cid2, t2),
new ConcreteType(cid3, t3)
new ConcreteType(cid1, c1),
new ConcreteType(cid2, c2),
new ConcreteType(cid3, c3)
]),
new SetType([
new ConcreteType(cid1, t1b),
new ConcreteType(cid2, t2),
new ConcreteType(cid3, t3)
new ConcreteType(cid1, c1),
new ConcreteType(cid2, c2),
new ConcreteType(cid3, c3)
]));
ne(
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]),
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]),
new SetType([
new ConcreteType(cid1, t1a),
new ConcreteType(cid2, t2),
new ConcreteType(cid3, t3)
new ConcreteType(cid1, c1),
new ConcreteType(cid2, c2),
new ConcreteType(cid3, c3)
]));
ne(new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]),
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid3, t3)]));
ne(new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]),
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid3, c3)]));
ne(
new SetType([new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)]),
new SetType([new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)]),
new NullableType(new SetType(
[new ConcreteType(cid1, t1a), new ConcreteType(cid2, t2)])));
[new ConcreteType(cid1, c1), new ConcreteType(cid2, c2)])));
});
}

View file

@ -0,0 +1,40 @@
------------ #lib::C:: ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::C::foo ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
t1 = _Extract (%this[#lib::C/0])
t2 = _Instantiate (#lib::D @ [t1])
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::D:: ------------
%this = _Parameter #0 [_T (#lib::D<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::E:: ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Call direct [#lib::C::] (%this)
RESULT: _T {}?
------------ #lib::E::foo ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1* = _Call direct [#lib::C::foo] (%this)
RESULT: t1
------------ #lib::E::bar ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Extract (%this[#lib::E/1])
t2 = _Instantiate (#lib::D @ [t1])
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::E::baz ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Extract (%this[#lib::E/2])
t2 = _Instantiate (#lib::D @ [t1])
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::main ------------
t0 = _Call direct [#lib::C::] (_T (#lib::C<_TS (dart.core::int)>))
t1 = _Call [#lib::C::foo] (_T (#lib::C<_TS (dart.core::int)>))
t2 = _Call direct [#lib::E::] (_T (#lib::E<_TS (dart.core::String), _TS (dart.core::int), _TS (dart.core::String)>))
t3 = _Call [#lib::E::foo] (_T (#lib::E<_TS (dart.core::String), _TS (dart.core::int), _TS (dart.core::String)>))
RESULT: _T {}?

View file

@ -0,0 +1 @@
../transformer/class_generics_basic.dart

View file

@ -0,0 +1,109 @@
------------ #lib::C:: ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::C::foo ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
t1 = _Extract (%this[#lib::C/0])
t2 = _CreateConcreteType (#lib::D @ (t1))
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::C::id1 ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
%x = _Parameter #1 [_T (dart.core::Object)+?]
t2 = _Extract (%this[#lib::C/0])
t3 = _TypeCheck (%x against t2) (for parameter x)
RESULT: t3
------------ #lib::C::id2 ------------
%this = _Parameter #0 [_T (#lib::C<dynamic>)+]
%x = _Parameter #1 [_T (dart.core::Object)+?]
t2 = _Extract (%this[#lib::C/0])
t3 = _TypeCheck (%x against t2) (for parameter x)
RESULT: t3
------------ #lib::D:: ------------
%this = _Parameter #0 [_T (#lib::D<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::E:: ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Call direct [#lib::C::] (%this)
RESULT: _T {}?
------------ #lib::E::foo ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1* = _Call direct [#lib::C::foo] (%this)
RESULT: t1
------------ #lib::E::bar ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Extract (%this[#lib::E/0])
t2 = _CreateConcreteType (#lib::D @ (t1))
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::E::baz ------------
%this = _Parameter #0 [_T (#lib::E<dynamic, dynamic>)+]
t1 = _Extract (%this[#lib::E/1])
t2 = _CreateConcreteType (#lib::D @ (t1))
t3 = _Call direct [#lib::D::] (t2)
RESULT: t2
------------ #lib::X:: ------------
%this = _Parameter #0 [_T (#lib::X)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::Y:: ------------
%this = _Parameter #0 [_T (#lib::Y)+]
t1 = _Call direct [#lib::X::] (%this)
RESULT: _T {}?
------------ #lib::Z:: ------------
%this = _Parameter #0 [_T (#lib::Z)+]
t1 = _Call direct [#lib::X::] (%this)
RESULT: _T {}?
------------ #lib::I:: ------------
%this = _Parameter #0 [_T (#lib::I<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::J:: ------------
%this = _Parameter #0 [_T (#lib::J)+]
t1 = _Call direct [#lib::I::] (%this)
RESULT: _T {}?
------------ #lib::K:: ------------
%this = _Parameter #0 [_T (#lib::K<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::C2:: ------------
%this = _Parameter #0 [_T (#lib::C2<dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::C2::id3 ------------
%this = _Parameter #0 [_T (#lib::C2<dynamic>)+]
%x = _Parameter #1 [_T (dart.core::Comparable<dynamic>)+?]
t2 = _Extract (%this[#lib::C2/0])
t3 = _CreateRuntimeType (dart.core::Comparable @ (t2))
t4 = _TypeCheck (%x against t3) (for parameter x)
RESULT: t4
------------ #lib::C2::id4 ------------
%this = _Parameter #0 [_T (#lib::C2<dynamic>)+]
%x = _Parameter #1 [_T (#lib::K<dynamic>)+?]
t2 = _Extract (%this[#lib::C2/0])
t3 = _CreateRuntimeType (#lib::I @ (t2))
t4 = _CreateRuntimeType (#lib::K @ (t3))
t5 = _TypeCheck (%x against t4) (for parameter x)
RESULT: t5
------------ #lib::main ------------
t0 = _Call direct [#lib::C::] (_T (#lib::C<dart.core::int>))
t1* = _Call [#lib::C::foo] (_T (#lib::C<dart.core::int>))
t2 = _Call direct [#lib::E::] (_T (#lib::E<dart.core::int, dart.core::String>))
t3* = _Call [#lib::E::foo] (_T (#lib::E<dart.core::int, dart.core::String>))
t4 = _Call direct [#lib::E::] (_T (#lib::E<dart.core::int, dart.core::String>))
t5* = _Call [#lib::E::bar] (_T (#lib::E<dart.core::int, dart.core::String>))
t6 = _Call direct [#lib::E::] (_T (#lib::E<dart.core::int, dart.core::String>))
t7* = _Call [#lib::E::baz] (_T (#lib::E<dart.core::int, dart.core::String>))
t8 = _Call direct [#lib::C::] (_T (#lib::C<#lib::Y>))
t9 = _Call direct [#lib::Y::] (_T (#lib::Y))
t10 = _Call [#lib::C::id1] (_T (#lib::C<#lib::Y>), _T (#lib::Y))
t11 = _Call direct [#lib::Z::] (_T (#lib::Z))
t12 = _Call [#lib::C::id2] (_T (#lib::C<#lib::Y>), _T (#lib::Z))
t13 = _Call direct [#lib::C2::] (_T (#lib::C2<dart.core::num>))
t14 = _Call [#lib::C2::id3] (_T (#lib::C2<dart.core::num>), _T (dart.core::double)+)
t15 = _Call direct [#lib::K::] (_T (#lib::K<#lib::J>))
t16 = _Call [#lib::C2::id4] (_T (#lib::C2<dart.core::num>), _T (#lib::K<#lib::J>))
used = _Join [dynamic] (_T {}?, t1, t3, t5, t7)
RESULT: used

View file

@ -0,0 +1 @@
../transformer/class_generics_case1.dart

View file

@ -0,0 +1,44 @@
------------ #lib::Element:: ------------
%this = _Parameter #0 [_T (#lib::Element)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::MockHashMap:: ------------
%K = _Parameter #0 [null]
%V = _Parameter #1 [null]
t2 = _CreateConcreteType (#lib::_NotRealHashMap @ (%K, %V))
t3 = _Call direct [#lib::_NotRealHashMap::] (t2)
RESULT: t2
------------ #lib::_NotRealHashMap:: ------------
%this = _Parameter #0 [_T (#lib::_NotRealHashMap<dynamic, dynamic>)+]
t1 = _Call direct [dart.core::Object::] (%this)
RESULT: _T {}?
------------ #lib::_NotRealHashMap::setEntry ------------
%this = _Parameter #0 [_T (#lib::_NotRealHashMap<dynamic, dynamic>)+]
%key = _Parameter #1 [_T (dart.core::Object)+?]
%value = _Parameter #2 [_T (dart.core::Object)+?]
t3 = _Extract (%this[#lib::_NotRealHashMap/0])
t4 = _TypeCheck (%key against t3) (for parameter key)
t5 = _Extract (%this[#lib::_NotRealHashMap/1])
t6 = _TypeCheck (%value against t5) (for parameter value)
RESULT: _T {}?
------------ #lib::InheritedElement:: ------------
%this = _Parameter #0 [_T (#lib::InheritedElement)+]
t1 = _Call direct [#lib::Element::] (%this)
RESULT: _T {}?
------------ #lib::InheritedElement::setDependencies ------------
%this = _Parameter #0 [_T (#lib::InheritedElement)+]
%dependent = _Parameter #1 [_T (#lib::Element)+?]
%value = _Parameter #2 [_T (dart.core::Object)+?]
t3* = _Call virtual get [#lib::InheritedElement::_dependents] (%this)
t4 = _Call [#lib::MockHashMap::setEntry] (t3, %dependent, %value)
RESULT: _T {}?
------------ #lib::InheritedElement::_dependents ------------
%this = _Parameter #0 [_T (#lib::InheritedElement)+]
t1* = _Call direct [#lib::MockHashMap::] (#lib::Element, dart.core::Object)
RESULT: t1
------------ #lib::main ------------
t0 = _Call direct [#lib::InheritedElement::] (_T (#lib::InheritedElement))
t1 = _Call [#lib::InheritedElement::setDependencies] (_T (#lib::InheritedElement), _T (#lib::InheritedElement), _T (dart.core::_Smi))
t2 = _Call direct [#lib::Element::] (_T (#lib::Element))
t3 = _Call [#lib::InheritedElement::setDependencies] (_T (#lib::InheritedElement), _T (#lib::Element), _T {}?)
RESULT: _T {}?

View file

@ -0,0 +1,55 @@
// Copyright (c) 2018, 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.
class C<T> {
foo() => D<T>();
dynamic id1(T x) => x;
dynamic id2(T x) => x;
}
class D<T> {}
class E<S, T> extends C<T> {
foo() => super.foo();
bar() => D<S>();
baz() => D<T>();
}
class X {}
class Y extends X {}
class Z extends X {}
class I<T> {}
class J extends I<int> {}
class K<T> {}
class C2<T> {
dynamic id3(Comparable<T> x) => x;
dynamic id4(K<I<T>> x) => x;
}
main() {
// Test that type arguments are instantiated correctly on concrete types.
dynamic used;
used = C<int>().foo();
used = E<int, String>().foo();
used = E<int, String>().bar();
used = E<int, String>().baz();
// Test that narrow against type-parameters works.
C<X> c = new C<Y>();
c.id1(Y());
c.id2(Z());
// Test that generic supertypes of non-generic types are handled correctly.
C2<num> c2 = new C2<num>();
c2.id3(3.0);
c2.id4(K<J>());
return used;
}

View file

@ -0,0 +1,78 @@
library #lib;
import self as self;
import "dart:core" as core;
class C<T extends core::Object = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T>
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method foo() → dynamic
return new self::D::•<self::C::T>();
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method id1([@vm.inferred-type.metadata=#lib::Y (skip check)] generic-covariant-impl self::C::T x) → dynamic
return x;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method id2([@vm.inferred-type.metadata=#lib::Z] generic-covariant-impl self::C::T x) → dynamic
return x;
}
class D<T extends core::Object = dynamic> extends core::Object {
synthetic constructor •() → self::D<self::D::T>
: super core::Object::•()
;
}
class E<S extends core::Object = dynamic, T extends core::Object = dynamic> extends self::C<self::E::T> {
synthetic constructor •() → self::E<self::E::S, self::E::T>
: super self::C::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method foo() → dynamic
return [@vm.inferred-type.metadata=#lib::D<dart.core::String>] super.{self::C::foo}();
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method bar() → dynamic
return new self::D::•<self::E::S>();
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method baz() → dynamic
return new self::D::•<self::E::T>();
}
abstract class X extends core::Object {
synthetic constructor •() → self::X
: super core::Object::•()
;
}
class Y extends self::X {
synthetic constructor •() → self::Y
: super self::X::•()
;
}
class Z extends self::X {
synthetic constructor •() → self::Z
: super self::X::•()
;
}
abstract class I<T extends core::Object = dynamic> extends core::Object {
}
abstract class J extends self::I<core::int> {
}
class K<T extends core::Object = dynamic> extends core::Object {
synthetic constructor •() → self::K<self::K::T>
: super core::Object::•()
;
}
class C2<T extends core::Object = dynamic> extends core::Object {
synthetic constructor •() → self::C2<self::C2::T>
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method id3([@vm.inferred-type.metadata=dart.core::_Double (skip check)] generic-covariant-impl core::Comparable<self::C2::T> x) → dynamic
return x;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method id4([@vm.inferred-type.metadata=#lib::K<#lib::J> (skip check)] generic-covariant-impl self::K<self::I<self::C2::T>> x) → dynamic
return x;
}
static method main() → dynamic {
dynamic used;
used = [@vm.direct-call.metadata=#lib::C::foo] [@vm.inferred-type.metadata=#lib::D<dart.core::int>] new self::C::•<core::int>().{self::C::foo}();
used = [@vm.direct-call.metadata=#lib::E::foo] [@vm.inferred-type.metadata=#lib::D<dart.core::String>] new self::E::•<core::int, core::String>().{self::E::foo}();
used = [@vm.direct-call.metadata=#lib::E::bar] [@vm.inferred-type.metadata=#lib::D<dart.core::int>] new self::E::•<core::int, core::String>().{self::E::bar}();
used = [@vm.direct-call.metadata=#lib::E::baz] [@vm.inferred-type.metadata=#lib::D<dart.core::String>] new self::E::•<core::int, core::String>().{self::E::baz}();
self::C<self::X> c = new self::C::•<self::Y>();
[@vm.call-site-attributes.metadata=receiverType:#lib::C<#lib::X>] [@vm.direct-call.metadata=#lib::C::id1] c.{self::C::id1}(new self::Y::•());
[@vm.call-site-attributes.metadata=receiverType:#lib::C<#lib::X>] [@vm.direct-call.metadata=#lib::C::id2] c.{self::C::id2}(new self::Z::•());
self::C2<core::num> c2 = new self::C2::•<core::num>();
[@vm.call-site-attributes.metadata=receiverType:#lib::C2<dart.core::num>] [@vm.direct-call.metadata=#lib::C2::id3] c2.{self::C2::id3}(3.0);
[@vm.call-site-attributes.metadata=receiverType:#lib::C2<dart.core::num>] [@vm.direct-call.metadata=#lib::C2::id4] c2.{self::C2::id4}(new self::K::•<self::J>());
return used;
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2018, 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.
//
// This test checks that TFA works as expected on an example imitating the
// InheritedElement.setDependencies hotspot in Flutter. The example is modified
// to use a custom class 'MockHashMap' rather than the regular 'HashMap' since
// we want to print out the inferred type of the '_dependents' field, which
// would be a 'SetType' under the regular 'HashMap' (and set types aren't
// translated into 'InferredType'). Also, []= is the target of a truly-dynamic
// call, and we want to make sure there is only one call-site in this example
// (call-site level info is not available yet).
import 'dart:collection';
class Element {}
abstract class MockHashMap<K, V> {
factory MockHashMap() {
return _NotRealHashMap<K, V>();
}
void setEntry(K key, V value);
}
class _NotRealHashMap<K, V> implements MockHashMap<K, V> {
void setEntry(K key, V value) {}
}
class InheritedElement extends Element {
// The inferred type for '_dependents' needs to be concrete and have exact
// type arguments.
final MockHashMap<Element, Object> _dependents =
MockHashMap<Element, Object>();
void setDependencies(Element dependent, Object value) {
_dependents.setEntry(dependent, value);
}
}
main() {
var ie = InheritedElement();
ie.setDependencies(ie, 0);
ie.setDependencies(Element(), null);
}

View file

@ -0,0 +1,35 @@
library #lib;
import self as self;
import "dart:core" as core;
class Element extends core::Object {
synthetic constructor •() → self::Element
: super core::Object::•()
;
}
abstract class MockHashMap<K extends core::Object = dynamic, V extends core::Object = dynamic> extends core::Object {
static factory •<K extends core::Object = dynamic, V extends core::Object = dynamic>() → self::MockHashMap<self::MockHashMap::•::K, self::MockHashMap::•::V> {
return new self::_NotRealHashMap::•<self::MockHashMap::•::K, self::MockHashMap::•::V>();
}
abstract method setEntry(generic-covariant-impl self::MockHashMap::K key, generic-covariant-impl self::MockHashMap::V value) → void;
}
class _NotRealHashMap<K extends core::Object = dynamic, V extends core::Object = dynamic> extends core::Object implements self::MockHashMap<self::_NotRealHashMap::K, self::_NotRealHashMap::V> {
synthetic constructor •() → self::_NotRealHashMap<self::_NotRealHashMap::K, self::_NotRealHashMap::V>
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method setEntry([@vm.inferred-type.metadata=! (skip check)] generic-covariant-impl self::_NotRealHashMap::K key, [@vm.inferred-type.metadata=dart.core::_Smi? (skip check)] generic-covariant-impl self::_NotRealHashMap::V value) → void {}
}
class InheritedElement extends self::Element {
[@vm.inferred-type.metadata=#lib::_NotRealHashMap<#lib::Element, dart.core::Object>] [@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false] final field self::MockHashMap<self::Element, core::Object> _dependents = [@vm.inferred-type.metadata=#lib::_NotRealHashMap<#lib::Element, dart.core::Object>] self::MockHashMap::•<self::Element, core::Object>();
synthetic constructor •() → self::InheritedElement
: super self::Element::•()
;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method setDependencies([@vm.inferred-type.metadata=!] self::Element dependent, [@vm.inferred-type.metadata=dart.core::_Smi?] core::Object value) → void {
[@vm.call-site-attributes.metadata=receiverType:#lib::MockHashMap<#lib::Element, dart.core::Object>] [@vm.direct-call.metadata=#lib::_NotRealHashMap::setEntry] [@vm.direct-call.metadata=#lib::InheritedElement::_dependents] [@vm.inferred-type.metadata=#lib::_NotRealHashMap<#lib::Element, dart.core::Object>] this.{self::InheritedElement::_dependents}.{self::MockHashMap::setEntry}(dependent, value);
}
}
static method main() → dynamic {
self::InheritedElement ie = new self::InheritedElement::•();
[@vm.direct-call.metadata=#lib::InheritedElement::setDependencies] ie.{self::InheritedElement::setDependencies}(ie, 0);
[@vm.direct-call.metadata=#lib::InheritedElement::setDependencies] ie.{self::InheritedElement::setDependencies}(new self::Element::•(), null);
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2018, 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 'dart:async';
class C<T> {
void test2c(FutureOr<T> x) {}
void test3c(Future<T> x) {}
void test4c(FutureOr<T> x) {}
void test2r(C<FutureOr<T>> x) {}
void test3r(C<Future<T>> x) {}
void test4r(C<FutureOr<T>> x) {}
void test5r(C<Future<T>> x) {}
void test6r(C<FutureOr<T>> x) {}
void test7r(C<T> x) {}
void test8r(C<T> x) {}
}
main() {
dynamic c = C<int>();
c.test2c(3);
c.test3c(Future.value(3));
c.test4c(Future.value(3));
c.test2r(C<int>());
c.test3r(C<Future<int>>());
c.test4r(C<Future<int>>());
c.test5r(C<FutureOr<int>>());
c.test6r(C<FutureOr<int>>());
c.test7r(C<FutureOr<int>>());
c.test8r(C<Future<int>>());
}

View file

@ -0,0 +1,33 @@
library #lib;
import self as self;
import "dart:core" as core;
import "dart:async" as asy;
class C<T extends core::Object = dynamic> extends core::Object {
synthetic constructor •() → self::C<self::C::T>
: super core::Object::•()
;
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test2c([@vm.inferred-type.metadata=dart.core::_Smi (skip check)] generic-covariant-impl asy::FutureOr<self::C::T> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test3c([@vm.inferred-type.metadata=dart.async::_Future<dart.core::int> (skip check)] generic-covariant-impl asy::Future<self::C::T> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test4c([@vm.inferred-type.metadata=dart.async::_Future<dart.core::int> (skip check)] generic-covariant-impl asy::FutureOr<self::C::T> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test2r([@vm.inferred-type.metadata=#lib::C<dart.core::int> (skip check)] generic-covariant-impl self::C<asy::FutureOr<self::C::T>> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test3r([@vm.inferred-type.metadata=#lib::C<dart.async::Future<dart.core::int>> (skip check)] generic-covariant-impl self::C<asy::Future<self::C::T>> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test4r([@vm.inferred-type.metadata=#lib::C<dart.async::Future<dart.core::int>> (skip check)] generic-covariant-impl self::C<asy::FutureOr<self::C::T>> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test5r([@vm.inferred-type.metadata=#lib::C<dart.async::FutureOr<dart.core::int>>] generic-covariant-impl self::C<asy::Future<self::C::T>> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test6r([@vm.inferred-type.metadata=#lib::C<dart.async::FutureOr<dart.core::int>> (skip check)] generic-covariant-impl self::C<asy::FutureOr<self::C::T>> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test7r([@vm.inferred-type.metadata=#lib::C<dart.async::FutureOr<dart.core::int>>] generic-covariant-impl self::C<self::C::T> x) → void {}
[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test8r([@vm.inferred-type.metadata=#lib::C<dart.async::Future<dart.core::int>>] generic-covariant-impl self::C<self::C::T> x) → void {}
}
static method main() → dynamic {
dynamic c = new self::C::•<core::int>();
[@vm.direct-call.metadata=#lib::C::test2c] c.test2c(3);
[@vm.direct-call.metadata=#lib::C::test3c] c.test3c([@vm.inferred-type.metadata=dart.async::_Future<dart.core::int>] asy::Future::value<core::int>(3));
[@vm.direct-call.metadata=#lib::C::test4c] c.test4c([@vm.inferred-type.metadata=dart.async::_Future<dart.core::int>] asy::Future::value<core::int>(3));
[@vm.direct-call.metadata=#lib::C::test2r] c.test2r(new self::C::•<core::int>());
[@vm.direct-call.metadata=#lib::C::test3r] c.test3r(new self::C::•<asy::Future<core::int>>());
[@vm.direct-call.metadata=#lib::C::test4r] c.test4r(new self::C::•<asy::Future<core::int>>());
[@vm.direct-call.metadata=#lib::C::test5r] c.test5r(new self::C::•<asy::FutureOr<core::int>>());
[@vm.direct-call.metadata=#lib::C::test6r] c.test6r(new self::C::•<asy::FutureOr<core::int>>());
[@vm.direct-call.metadata=#lib::C::test7r] c.test7r(new self::C::•<asy::FutureOr<core::int>>());
[@vm.direct-call.metadata=#lib::C::test8r] c.test8r(new self::C::•<asy::Future<core::int>>());
}

View file

@ -14,11 +14,11 @@ class B extends self::A {
;
}
[@vm.inferred-type.metadata=dart.core::Null?]static field core::Function unknown;
static method foo1_a1([@vm.inferred-type.metadata=dart.async::_Future] dynamic x) → void {}
static method foo1_a1([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] dynamic x) → void {}
static method foo1_a2([@vm.inferred-type.metadata=#lib::B] dynamic x) → void {}
static method foo1_a3([@vm.inferred-type.metadata=dart.async::_Future] dynamic x) → void {}
static method foo1_a3([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] dynamic x) → void {}
static method foo1_a4([@vm.inferred-type.metadata=#lib::B] dynamic x) → void {}
static method foo1([@vm.inferred-type.metadata=dart.async::_Future] asy::Future<self::A> a1, [@vm.inferred-type.metadata=#lib::B] self::A a2, [@vm.inferred-type.metadata=dart.async::_Future] asy::FutureOr<self::A> a3, [@vm.inferred-type.metadata=#lib::B] asy::FutureOr<self::A> a4) → void {
static method foo1([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future<self::A> a1, [@vm.inferred-type.metadata=#lib::B] self::A a2, [@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::FutureOr<self::A> a3, [@vm.inferred-type.metadata=#lib::B] asy::FutureOr<self::A> a4) → void {
self::foo1_a1(a1);
self::foo1_a2(a2);
self::foo1_a3(a3);
@ -37,6 +37,6 @@ static method foo2([@vm.inferred-type.metadata=dart.async::_Future?] asy::Future
static method getDynamic() → dynamic
return [@vm.call-site-attributes.metadata=receiverType:dart.core::Function] self::unknown.call();
static method main(core::List<core::String> args) → dynamic {
self::foo1([@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value<self::B>(new self::B::•()), new self::B::•(), [@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value<self::B>(new self::B::•()), new self::B::•());
self::foo1([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future::value<self::B>(new self::B::•()), new self::B::•(), [@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future::value<self::B>(new self::B::•()), new self::B::•());
self::foo2(self::getDynamic() as{TypeError} asy::Future<self::A>, self::getDynamic() as{TypeError} self::A, self::getDynamic() as{TypeError} asy::FutureOr<self::A>, self::getDynamic() as{TypeError} asy::FutureOr<self::A>);
}

View file

@ -28,8 +28,8 @@ class Q<T extends core::Object = dynamic> extends core::Object {
: self::Q::result = result, super core::Object::•()
;
}
static method foo1([@vm.inferred-type.metadata=dart.core::_GrowableList] core::List<self::T1> list) → dynamic {
[@vm.direct-call.metadata=#lib::T3::run] [@vm.direct-call.metadata=#lib::T1::go??] [@vm.inferred-type.metadata=#lib::T3] [@vm.direct-call.metadata=#lib::Q::result??] [@vm.direct-call.metadata=dart._internal::ListIterable::first] [@vm.direct-call.metadata=dart.collection::_ListBase&Object&ListMixin::map] [@vm.inferred-type.metadata=dart._internal::MappedListIterable] list.{core::Iterable::map}<self::Q<self::T1>>((self::T1 t1) → self::Q<self::T1> => new self::Q::•<self::T1>(t1)).{core::Iterable::first}.{self::Q::result}.{self::T1::go}().{self::T3::run}();
static method foo1([@vm.inferred-type.metadata=dart.core::_GrowableList<#lib::T1>] core::List<self::T1> list) → dynamic {
[@vm.direct-call.metadata=#lib::T3::run] [@vm.direct-call.metadata=#lib::T1::go??] [@vm.inferred-type.metadata=#lib::T3] [@vm.direct-call.metadata=#lib::Q::result??] [@vm.direct-call.metadata=dart._internal::ListIterable::first] [@vm.direct-call.metadata=dart.collection::_ListBase&Object&ListMixin::map] [@vm.inferred-type.metadata=dart._internal::MappedListIterable<#lib::T1, ?>] list.{core::Iterable::map}<self::Q<self::T1>>((self::T1 t1) → self::Q<self::T1> => new self::Q::•<self::T1>(t1)).{core::Iterable::first}.{self::Q::result}.{self::T1::go}().{self::T3::run}();
}
static method foo2NewValue() → self::Q<dynamic>
return new self::Q::•<self::T2>(new self::T2::•());

View file

@ -48,11 +48,11 @@ class B extends self::A {
return new self::T1::•();
}
no-such-method-forwarder get bar() → dynamic
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] no-such-method-forwarder method foo() → dynamic
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("foo", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("foo", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] no-such-method-forwarder method bazz([@vm.inferred-type.metadata=dart.core::_Smi] dynamic a1, [@vm.inferred-type.metadata=dart.core::_Smi] dynamic a2, [@vm.inferred-type.metadata=dart.core::_Smi] dynamic a3, [[@vm.inferred-type.metadata=dart.core::_Smi] dynamic a4 = null, [@vm.inferred-type.metadata=dart.core::Null?] dynamic a5 = null]) → dynamic
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("bazz", const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>[a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::B::noSuchMethod] [@vm.inferred-type.metadata=#lib::T1] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withoutType("bazz", const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>[a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
}
abstract class C extends core::Object {
synthetic constructor •() → self::C
@ -67,11 +67,11 @@ class D extends self::C implements self::A {
: super self::C::•()
;
no-such-method-forwarder get bar() → dynamic
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] no-such-method-forwarder method foo() → dynamic
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("foo", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("foo", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] no-such-method-forwarder method bazz([@vm.inferred-type.metadata=dart.core::_Smi] dynamic a1, [@vm.inferred-type.metadata=dart.core::_Smi] dynamic a2, [@vm.inferred-type.metadata=dart.core::_Smi] dynamic a3, [[@vm.inferred-type.metadata=dart.core::_Smi] dynamic a4 = null, [@vm.inferred-type.metadata=dart.core::Null?] dynamic a5 = null]) → dynamic
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("bazz", const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>[a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::C::noSuchMethod] [@vm.inferred-type.metadata=#lib::T2] this.{self::C::noSuchMethod}(new core::_InvocationMirror::_withoutType("bazz", const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>[a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
}
class E extends core::Object implements self::A {
synthetic constructor •() → self::E
@ -81,7 +81,7 @@ class E extends core::Object implements self::A {
return new self::T4::•();
}
no-such-method-forwarder get bar() → dynamic
return [@vm.direct-call.metadata=#lib::E::noSuchMethod] [@vm.inferred-type.metadata=#lib::T4] this.{self::E::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
return [@vm.direct-call.metadata=#lib::E::noSuchMethod] [@vm.inferred-type.metadata=#lib::T4] this.{self::E::noSuchMethod}(new core::_InvocationMirror::_withoutType("get:bar", const <core::Type>[], const <dynamic>[], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol, dynamic>] core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}), false)) as{TypeError} dynamic;
}
class F extends core::Object {
synthetic constructor •() → self::F