diff --git a/pkg/kernel/lib/class_hierarchy.dart b/pkg/kernel/lib/class_hierarchy.dart index 6082e811f79..214572a2268 100644 --- a/pkg/kernel/lib/class_hierarchy.dart +++ b/pkg/kernel/lib/class_hierarchy.dart @@ -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 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 genericSupertypesOf(Class class_) { + final supertypes = _infoFor[class_].genericSuperTypes; + if (supertypes == null) return const []; + // 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 removedLibraries, Iterable ensureKnownLibraries, @@ -1339,7 +1353,7 @@ class _ClassInfo { /// Maps generic supertype classes to the instantiation implemented by this /// class. /// - /// E.g. `List` maps to `List` for a class that directly of indirectly + /// E.g. `List` maps to `List` for a class that directly or indirectly /// implements `List`. Map> genericSuperTypes; diff --git a/pkg/vm/lib/metadata/inferred_type.dart b/pkg/vm/lib/metadata/inferred_type.dart index 91200ea2a3b..55e9443a36d 100644 --- a/pkg/vm/lib/metadata/inferred_type.dart +++ b/pkg/vm/lib/metadata/inferred_type.dart @@ -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 exactTypeArguments; + + InferredType(Class concreteClass, bool nullable, bool isInt, + {List 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 { @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 { @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); } } diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index 8a0d6ed388b..903431cc9b5 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -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 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 = {}; + final cachedFlattenedTypeArgs = >{}; + final cachedFlattenedTypeArgsForNonGeneric = >{}; + + RuntimeTypeTranslator closedTypeTranslator; + + GenericInterfacesInfoImpl(this.hierarchy) { + closedTypeTranslator = RuntimeTypeTranslator.forClosedTypes(this); + } + + List flattenedTypeArgumentsFor(Class klass, {bool useCache: true}) { + final cached = useCache ? cachedFlattenedTypeArgs[klass] : null; + if (cached != null) return cached; + + final flattenedTypeArguments = List.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 flattenedTypeArgumentsForNonGeneric(Class klass) { + List result = cachedFlattenedTypeArgsForNonGeneric[klass]; + if (result != null) return result; + + List flattenedTypeArgs = + flattenedTypeArgumentsFor(klass, useCache: false); + result = new List(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 allocatedClasses = new Set(); final Map classes = {}; + final GenericInterfacesInfo genericInterfacesInfo; /// Object.noSuchMethod(). final Member objectNoSuchMethod; @@ -887,10 +967,10 @@ class _ClassHierarchyCache implements TypeHierarchy { final Map _dynamicTargets = {}; - _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 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 flattenedTypeArgumentsFor(Class klass) => + genericInterfacesInfo.flattenedTypeArgumentsFor(klass); + + @override + int genericInterfaceOffsetFor(Class klass, Class iface) => + genericInterfacesInfo.genericInterfaceOffsetFor(klass, iface); + + @override + List 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 _summaries = {}; final Map _fieldValues = {}; @@ -1204,15 +1308,22 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler { final Set _calledViaInterfaceSelector = new Set(); final Set _calledViaThis = new Set(); - 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 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 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 diff --git a/pkg/vm/lib/transformations/type_flow/summary.dart b/pkg/vm/lib/transformations/type_flow/summary.dart index f4a99d28f21..c2e7b407afd 100644 --- a/pkg/vm/lib/transformations/type_flow/summary.dart +++ b/pkg/vm/lib/transformations/type_flow/summary.dart @@ -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 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" to create "C". +// +// The type arguments are factored against the generic interfaces; for more +// details see 'ClassHierarchyCache.factoredGenericInterfacesOf'. +class CreateConcreteType extends Statement { + final ConcreteType type; + final List 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 computedTypes, TypeHierarchy typeHierarchy, + CallHandler callHandler) { + bool hasRuntimeType = false; + final types = new List(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 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 computedTypes, TypeHierarchy typeHierarchy, + CallHandler callHandler) { + final types = new List(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 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(argTypes, names: argNames); } + + List get staticCallSiteSkipCheckParams { + final vars = []; + for (final statement in _statements) { + if (statement is TypeCheck && statement.canSkipOnStaticCallSite) { + final decl = statement.parameter; + if (decl != null) vars.add(decl); + } + } + return vars; + } } diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart index e1030c79e7c..9cd99b06d31 100644 --- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart +++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart @@ -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 visitFunctionDeclaration(FunctionDeclaration node) => true; } +enum FieldSummaryType { kFieldGuard, kInitializer } + /// Create a type flow summary for a member from the kernel AST. class SummaryCollector extends RecursiveVisitor { final Target target; final TypeEnvironment _environment; final EntryPointsListener _entryPointsListener; final NativeCodeOracle _nativeCodeOracle; + final GenericInterfacesInfo _genericInterfacesInfo; final Map callSites = {}; final _FallthroughDetector _fallthroughDetector = new _FallthroughDetector(); Summary _summary; - Map _variables; + Map _variableJoins; + Map _variables; Join _returnValue; Parameter _receiver; ConstantAllocationCollector constantAllocationCollector; + RuntimeTypeTranslator _translator; + + // Currently only used for factory constructors. + Map _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 = {}; + _variableJoins = {}; + _variables = {}; _returnValue = null; _receiver = null; @@ -274,7 +316,10 @@ class SummaryCollector extends RecursiveVisitor { 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 { } 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 { requiredParameterCount: firstParamIndex + function.requiredParameterCount); + if (numTypeParameters > 0) { + _fnTypeVariables = {}; + 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 { _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 { 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 { final List args = []; final List names = []; + 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 _visit(TreeNode node) => node.accept(this); - Args _visitArguments(TypeExpr receiver, Arguments arguments) { + Args _visitArguments(TypeExpr receiver, Arguments arguments, + {bool passTypeArguments: false}) { final args = []; + 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 { 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 { 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 { 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 { } void _handleNestedFunctionNode(FunctionNode node) { - var oldReturn = _returnValue; - var oldVariables = _variables; + final oldReturn = _returnValue; + final oldVariableJoins = _variableJoins; + final oldVariables = _variables; _returnValue = null; - _variables = {}; + _variableJoins = {}; + _variableJoins.addAll(oldVariableJoins); + _variables = {}; _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 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 { @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 { 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 { } 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 { @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 { @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 { @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 { } } +class RuntimeTypeTranslator extends DartTypeVisitor { + final Class enclosingClass; + final Summary summary; + final Map functionTypeVariables; + final Map typesCache = {}; + 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 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(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.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 extends Comparable> {} + // + // 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' in the process of + // building 'A'. + 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(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.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 _classIds = {}; 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 { 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); } } } diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart index 42835d1051e..bc6bb28ec44 100644 --- a/pkg/vm/lib/transformations/type_flow/transformer.dart +++ b/pkg/vm/lib/transformations/type_flow/transformer.dart @@ -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 { 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 { } } + List 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 { 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 argTypes = _typeFlowAnalysis.argumentTypes(member); assertx(argTypes != null); - final int firstParamIndex = hasReceiverArg(member) ? 1 : 0; + final skipCheckParams = new Set.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 { 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 { 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. diff --git a/pkg/vm/lib/transformations/type_flow/types.dart b/pkg/vm/lib/transformations/type_flow/types.dart index c2da60f9625..51a0d82024a 100644 --- a/pkg/vm/lib/transformations/type_flow/types.dart +++ b/pkg/vm/lib/transformations/type_flow/types.dart @@ -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 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'. + // + // Since there are no free type variables in the result, 'RuntimeType' is + // returned instead of 'DartType'. + List 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 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 vs Map can become Map) + 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 { /// or `null` object). class ConcreteType extends Type implements Comparable { 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 typeArgs; + + ConcreteType(this.classId, this.classNode, [List 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 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 { if (other is ConcreteType) { if (this == other) { return this; - } else { - assertx(this.classId != other.classId); - final List types = - (this.classId.compareTo(other.classId) < 0) - ? [this, other] - : [other, this]; + } else if (this.classId != other.classId) { + final types = (this.classId.compareTo(other.classId) < 0) + ? [this, other] + : [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 { 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(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 {} +// class A extends Comparable> {} +// +// To avoid these cycles, we approximate generic super-bounded types (the second +// case), so the representation for 'A' 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') 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 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 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; + } +} diff --git a/pkg/vm/lib/transformations/type_flow/utils.dart b/pkg/vm/lib/transformations/type_flow/utils.dart index 3ce56e80e0a..c3af59a290e 100644 --- a/pkg/vm/lib/transformations/type_flow/utils.dart +++ b/pkg/vm/lib/transformations/type_flow/utils.dart @@ -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 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; +} diff --git a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart index d3d7c9d8c69..1ec8d87cf1b 100644 --- a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart +++ b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart @@ -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 { environment, new EmptyEntryPointsListener(), new NativeCodeOracle( - null, new ExpressionPragmaAnnotationParser(coreTypes))); + null, new ExpressionPragmaAnnotationParser(coreTypes)), + new GenericInterfacesInfoImpl(environment.hierarchy)); String print(TreeNode node) { visitLibrary(node); diff --git a/pkg/vm/test/transformations/type_flow/transformer_test.dart b/pkg/vm/test/transformations/type_flow/transformer_test.dart index e1a90311303..d031f10f769 100644 --- a/pkg/vm/test/transformations/type_flow/transformer_test.dart +++ b/pkg/vm/test/transformations/type_flow/transformer_test.dart @@ -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)); } diff --git a/pkg/vm/test/transformations/type_flow/types_test.dart b/pkg/vm/test/transformations/type_flow/types_test.dart index f00f935e112..213b6d9ba7a 100644 --- a/pkg/vm/test/transformations/type_flow/types_test.dart +++ b/pkg/vm/test/transformations/type_flow/types_test.dart @@ -26,6 +26,19 @@ class TestTypeHierarchy implements TypeHierarchy { reason: "specializeTypeCone($base) is not defined"); return result; } + + List 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 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)]))); }); } diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics.dart.expect b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics.dart.expect new file mode 100644 index 00000000000..82211a9df49 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics.dart.expect @@ -0,0 +1,40 @@ +------------ #lib::C:: ------------ +%this = _Parameter #0 [_T (#lib::C)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::C::foo ------------ +%this = _Parameter #0 [_T (#lib::C)+] +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)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::E:: ------------ +%this = _Parameter #0 [_T (#lib::E)+] +t1 = _Call direct [#lib::C::] (%this) +RESULT: _T {}? +------------ #lib::E::foo ------------ +%this = _Parameter #0 [_T (#lib::E)+] +t1* = _Call direct [#lib::C::foo] (%this) +RESULT: t1 +------------ #lib::E::bar ------------ +%this = _Parameter #0 [_T (#lib::E)+] +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)+] +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 {}? diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart new file mode 120000 index 00000000000..e25bf91838d --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart @@ -0,0 +1 @@ +../transformer/class_generics_basic.dart \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart.expect b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart.expect new file mode 100644 index 00000000000..9e9fb78b085 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_basic.dart.expect @@ -0,0 +1,109 @@ +------------ #lib::C:: ------------ +%this = _Parameter #0 [_T (#lib::C)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::C::foo ------------ +%this = _Parameter #0 [_T (#lib::C)+] +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)+] +%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)+] +%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)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::E:: ------------ +%this = _Parameter #0 [_T (#lib::E)+] +t1 = _Call direct [#lib::C::] (%this) +RESULT: _T {}? +------------ #lib::E::foo ------------ +%this = _Parameter #0 [_T (#lib::E)+] +t1* = _Call direct [#lib::C::foo] (%this) +RESULT: t1 +------------ #lib::E::bar ------------ +%this = _Parameter #0 [_T (#lib::E)+] +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)+] +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)+] +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)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::C2:: ------------ +%this = _Parameter #0 [_T (#lib::C2)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::C2::id3 ------------ +%this = _Parameter #0 [_T (#lib::C2)+] +%x = _Parameter #1 [_T (dart.core::Comparable)+?] +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)+] +%x = _Parameter #1 [_T (#lib::K)+?] +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)) +t1* = _Call [#lib::C::foo] (_T (#lib::C)) +t2 = _Call direct [#lib::E::] (_T (#lib::E)) +t3* = _Call [#lib::E::foo] (_T (#lib::E)) +t4 = _Call direct [#lib::E::] (_T (#lib::E)) +t5* = _Call [#lib::E::bar] (_T (#lib::E)) +t6 = _Call direct [#lib::E::] (_T (#lib::E)) +t7* = _Call [#lib::E::baz] (_T (#lib::E)) +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)) +t14 = _Call [#lib::C2::id3] (_T (#lib::C2), _T (dart.core::double)+) +t15 = _Call direct [#lib::K::] (_T (#lib::K<#lib::J>)) +t16 = _Call [#lib::C2::id4] (_T (#lib::C2), _T (#lib::K<#lib::J>)) +used = _Join [dynamic] (_T {}?, t1, t3, t5, t7) +RESULT: used diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart new file mode 120000 index 00000000000..55d2be4e1be --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart @@ -0,0 +1 @@ +../transformer/class_generics_case1.dart \ No newline at end of file diff --git a/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart.expect b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart.expect new file mode 100644 index 00000000000..fae7252dfcc --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/summary_collector/class_generics_case1.dart.expect @@ -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)+] +t1 = _Call direct [dart.core::Object::] (%this) +RESULT: _T {}? +------------ #lib::_NotRealHashMap::setEntry ------------ +%this = _Parameter #0 [_T (#lib::_NotRealHashMap)+] +%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 {}? diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart new file mode 100644 index 00000000000..2cb9cfba8d7 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart @@ -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 { + foo() => D(); + dynamic id1(T x) => x; + dynamic id2(T x) => x; +} + +class D {} + +class E extends C { + foo() => super.foo(); + bar() => D(); + baz() => D(); +} + +class X {} + +class Y extends X {} + +class Z extends X {} + +class I {} + +class J extends I {} + +class K {} + +class C2 { + dynamic id3(Comparable x) => x; + dynamic id4(K> x) => x; +} + +main() { + // Test that type arguments are instantiated correctly on concrete types. + dynamic used; + used = C().foo(); + used = E().foo(); + used = E().bar(); + used = E().baz(); + + // Test that narrow against type-parameters works. + C c = new C(); + c.id1(Y()); + c.id2(Z()); + + // Test that generic supertypes of non-generic types are handled correctly. + C2 c2 = new C2(); + c2.id3(3.0); + c2.id4(K()); + + return used; +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart.expect new file mode 100644 index 00000000000..7f4b4d54283 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_basic.dart.expect @@ -0,0 +1,78 @@ +library #lib; +import self as self; +import "dart:core" as core; + +class C extends core::Object { + synthetic constructor •() → self::C + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasTearOffUses:false] method foo() → dynamic + return new self::D::•(); +[@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 extends core::Object { + synthetic constructor •() → self::D + : super core::Object::•() + ; +} +class E extends self::C { + synthetic constructor •() → self::E + : super self::C::•() + ; +[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method foo() → dynamic + return [@vm.inferred-type.metadata=#lib::D] super.{self::C::foo}(); +[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method bar() → dynamic + return new self::D::•(); +[@vm.procedure-attributes.metadata=hasDynamicUses:false,hasThisUses:false,hasTearOffUses:false] method baz() → dynamic + return new self::D::•(); +} +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 extends core::Object { +} +abstract class J extends self::I { +} +class K extends core::Object { + synthetic constructor •() → self::K + : super core::Object::•() + ; +} +class C2 extends core::Object { + synthetic constructor •() → self::C2 + : 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 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> x) → dynamic + return x; +} +static method main() → dynamic { + dynamic used; + used = [@vm.direct-call.metadata=#lib::C::foo] [@vm.inferred-type.metadata=#lib::D] new self::C::•().{self::C::foo}(); + used = [@vm.direct-call.metadata=#lib::E::foo] [@vm.inferred-type.metadata=#lib::D] new self::E::•().{self::E::foo}(); + used = [@vm.direct-call.metadata=#lib::E::bar] [@vm.inferred-type.metadata=#lib::D] new self::E::•().{self::E::bar}(); + used = [@vm.direct-call.metadata=#lib::E::baz] [@vm.inferred-type.metadata=#lib::D] new self::E::•().{self::E::baz}(); + self::C c = new self::C::•(); + [@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 c2 = new self::C2::•(); + [@vm.call-site-attributes.metadata=receiverType:#lib::C2] [@vm.direct-call.metadata=#lib::C2::id3] c2.{self::C2::id3}(3.0); + [@vm.call-site-attributes.metadata=receiverType:#lib::C2] [@vm.direct-call.metadata=#lib::C2::id4] c2.{self::C2::id4}(new self::K::•()); + return used; +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart new file mode 100644 index 00000000000..3b211f3bab3 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart @@ -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 { + factory MockHashMap() { + return _NotRealHashMap(); + } + + void setEntry(K key, V value); +} + +class _NotRealHashMap implements MockHashMap { + 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 _dependents = + MockHashMap(); + + void setDependencies(Element dependent, Object value) { + _dependents.setEntry(dependent, value); + } +} + +main() { + var ie = InheritedElement(); + ie.setDependencies(ie, 0); + ie.setDependencies(Element(), null); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart.expect new file mode 100644 index 00000000000..bc476af2aa9 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/class_generics_case1.dart.expect @@ -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 extends core::Object { + static factory •() → self::MockHashMap { + return new self::_NotRealHashMap::•(); + } + abstract method setEntry(generic-covariant-impl self::MockHashMap::K key, generic-covariant-impl self::MockHashMap::V value) → void; +} +class _NotRealHashMap extends core::Object implements self::MockHashMap { + synthetic constructor •() → self::_NotRealHashMap + : 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 _dependents = [@vm.inferred-type.metadata=#lib::_NotRealHashMap<#lib::Element, dart.core::Object>] self::MockHashMap::•(); + 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); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/future.dart b/pkg/vm/testcases/transformations/type_flow/transformer/future.dart new file mode 100644 index 00000000000..7c942a5e698 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/future.dart @@ -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 { + void test2c(FutureOr x) {} + void test3c(Future x) {} + void test4c(FutureOr x) {} + + void test2r(C> x) {} + void test3r(C> x) {} + void test4r(C> x) {} + void test5r(C> x) {} + void test6r(C> x) {} + void test7r(C x) {} + void test8r(C x) {} +} + +main() { + dynamic c = C(); + + c.test2c(3); + c.test3c(Future.value(3)); + c.test4c(Future.value(3)); + + c.test2r(C()); + c.test3r(C>()); + c.test4r(C>()); + c.test5r(C>()); + c.test6r(C>()); + c.test7r(C>()); + c.test8r(C>()); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/future.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/future.dart.expect new file mode 100644 index 00000000000..3998132e860 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/future.dart.expect @@ -0,0 +1,33 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "dart:async" as asy; + +class C extends core::Object { + synthetic constructor •() → self::C + : 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 x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test3c([@vm.inferred-type.metadata=dart.async::_Future (skip check)] generic-covariant-impl asy::Future x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test4c([@vm.inferred-type.metadata=dart.async::_Future (skip check)] generic-covariant-impl asy::FutureOr x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test2r([@vm.inferred-type.metadata=#lib::C (skip check)] generic-covariant-impl self::C> x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test3r([@vm.inferred-type.metadata=#lib::C> (skip check)] generic-covariant-impl self::C> x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test4r([@vm.inferred-type.metadata=#lib::C> (skip check)] generic-covariant-impl self::C> x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test5r([@vm.inferred-type.metadata=#lib::C>] generic-covariant-impl self::C> x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test6r([@vm.inferred-type.metadata=#lib::C> (skip check)] generic-covariant-impl self::C> x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test7r([@vm.inferred-type.metadata=#lib::C>] generic-covariant-impl self::C x) → void {} +[@vm.procedure-attributes.metadata=hasThisUses:false,hasTearOffUses:false] method test8r([@vm.inferred-type.metadata=#lib::C>] generic-covariant-impl self::C x) → void {} +} +static method main() → dynamic { + dynamic c = new self::C::•(); + [@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] asy::Future::value(3)); + [@vm.direct-call.metadata=#lib::C::test4c] c.test4c([@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value(3)); + [@vm.direct-call.metadata=#lib::C::test2r] c.test2r(new self::C::•()); + [@vm.direct-call.metadata=#lib::C::test3r] c.test3r(new self::C::•>()); + [@vm.direct-call.metadata=#lib::C::test4r] c.test4r(new self::C::•>()); + [@vm.direct-call.metadata=#lib::C::test5r] c.test5r(new self::C::•>()); + [@vm.direct-call.metadata=#lib::C::test6r] c.test6r(new self::C::•>()); + [@vm.direct-call.metadata=#lib::C::test7r] c.test7r(new self::C::•>()); + [@vm.direct-call.metadata=#lib::C::test8r] c.test8r(new self::C::•>()); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect index 307529448b2..4a946773e92 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/future_or.dart.expect @@ -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 a1, [@vm.inferred-type.metadata=#lib::B] self::A a2, [@vm.inferred-type.metadata=dart.async::_Future] asy::FutureOr a3, [@vm.inferred-type.metadata=#lib::B] asy::FutureOr a4) → void { +static method foo1([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future a1, [@vm.inferred-type.metadata=#lib::B] self::A a2, [@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::FutureOr a3, [@vm.inferred-type.metadata=#lib::B] asy::FutureOr 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 args) → dynamic { - self::foo1([@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value(new self::B::•()), new self::B::•(), [@vm.inferred-type.metadata=dart.async::_Future] asy::Future::value(new self::B::•()), new self::B::•()); + self::foo1([@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future::value(new self::B::•()), new self::B::•(), [@vm.inferred-type.metadata=dart.async::_Future<#lib::B>] asy::Future::value(new self::B::•()), new self::B::•()); self::foo2(self::getDynamic() as{TypeError} asy::Future, self::getDynamic() as{TypeError} self::A, self::getDynamic() as{TypeError} asy::FutureOr, self::getDynamic() as{TypeError} asy::FutureOr); } diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect index 7f7b453ee2e..43d88942cbc 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/invalidation_set_field2.dart.expect @@ -28,8 +28,8 @@ class Q extends core::Object { : self::Q::result = result, super core::Object::•() ; } -static method foo1([@vm.inferred-type.metadata=dart.core::_GrowableList] core::List 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::T1 t1) → self::Q => new self::Q::•(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 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::T1 t1) → self::Q => new self::Q::•(t1)).{core::Iterable::first}.{self::Q::result}.{self::T1::go}().{self::T3::run}(); } static method foo2NewValue() → self::Q return new self::Q::•(new self::T2::•()); diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect index 691d0129b4d..372e102e709 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/no_such_method.dart.expect @@ -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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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::List::unmodifiable([a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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::List::unmodifiable([a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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::List::unmodifiable([a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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::List::unmodifiable([a1, a2, a3, a4, a5]), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), 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 [], const [], [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView] core::Map::unmodifiable(const {}), false)) as{TypeError} dynamic; } class F extends core::Object { synthetic constructor •() → self::F