Compute static types for for-in loops

Change-Id: I47e98eaf6df7d04286cb737bfdff135e6c0d34d2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100843
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/ir/cached_static_type.dart b/pkg/compiler/lib/src/ir/cached_static_type.dart
index 6067b5e..ea00634 100644
--- a/pkg/compiler/lib/src/ir/cached_static_type.dart
+++ b/pkg/compiler/lib/src/ir/cached_static_type.dart
@@ -8,12 +8,14 @@
 import 'package:kernel/type_algebra.dart' as ir;
 import 'package:kernel/type_environment.dart' as ir;
 import 'static_type_base.dart';
+import 'static_type_cache.dart';
 import 'static_type_provider.dart';
 
 /// Class that provides the static type of expression using the visitor pattern
 /// and a precomputed cache for complex expression type.
 class CachedStaticType extends StaticTypeBase implements StaticTypeProvider {
-  final Map<ir.Expression, ir.DartType> _cache;
+  final StaticTypeCache _cache;
+
   @override
   final ThisInterfaceType thisType;
 
@@ -28,6 +30,13 @@
     return type;
   }
 
+  @override
+  ir.DartType getForInIteratorType(ir.ForInStatement node) {
+    ir.DartType type = _cache.getForInIteratorType(node);
+    assert(type != null, "No for-in iterator type found for ${node}.");
+    return type;
+  }
+
   ir.DartType _getStaticType(ir.Expression node) {
     ir.DartType type = _cache[node];
     assert(type != null, "No static type cached for ${node.runtimeType}.");
diff --git a/pkg/compiler/lib/src/ir/impact.dart b/pkg/compiler/lib/src/ir/impact.dart
index d635b78..8799d9b 100644
--- a/pkg/compiler/lib/src/ir/impact.dart
+++ b/pkg/compiler/lib/src/ir/impact.dart
@@ -15,7 +15,7 @@
 import 'runtime_type_analysis.dart';
 import 'scope.dart';
 import 'static_type.dart';
-import 'static_type_base.dart';
+import 'static_type_cache.dart';
 import 'util.dart';
 
 /// Interface for collecting world impact data.
@@ -77,9 +77,11 @@
 
   void registerThrow();
 
-  void registerSyncForIn(ir.DartType iterableType);
+  void registerSyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation);
 
-  void registerAsyncForIn(ir.DartType iterableType);
+  void registerAsyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation);
 
   void registerCatch();
 
@@ -200,16 +202,6 @@
       ir.ClassHierarchy classHierarchy, this.variableScopeModel)
       : super(typeEnvironment, classHierarchy);
 
-  ClassRelation _computeClassRelationFromType(ir.DartType type) {
-    if (type is ThisInterfaceType) {
-      return ClassRelation.thisExpression;
-    } else if (type is ExactInterfaceType) {
-      return ClassRelation.exact;
-    } else {
-      return ClassRelation.subtype;
-    }
-  }
-
   @override
   void handleIntLiteral(ir.IntLiteral node) {
     registerIntLiteral(node.value);
@@ -378,11 +370,14 @@
   }
 
   @override
-  void handleForInStatement(ir.ForInStatement node, ir.DartType iterableType) {
+  void handleForInStatement(ir.ForInStatement node, ir.DartType iterableType,
+      ir.DartType iteratorType) {
     if (node.isAsync) {
-      registerAsyncForIn(iterableType);
+      registerAsyncForIn(iterableType, iteratorType,
+          computeClassRelationFromType(iteratorType));
     } else {
-      registerSyncForIn(iterableType);
+      registerSyncForIn(iterableType, iteratorType,
+          computeClassRelationFromType(iteratorType));
     }
   }
 
@@ -536,7 +531,7 @@
       registerLocalFunctionInvocation(receiver.variable.parent,
           positionArguments, namedArguments, typeArguments);
     } else {
-      ClassRelation relation = _computeClassRelationFromType(receiverType);
+      ClassRelation relation = computeClassRelationFromType(receiverType);
 
       ir.Member interfaceTarget = node.interfaceTarget;
       if (interfaceTarget == null) {
@@ -584,7 +579,7 @@
   @override
   void handlePropertyGet(
       ir.PropertyGet node, ir.DartType receiverType, ir.DartType resultType) {
-    ClassRelation relation = _computeClassRelationFromType(receiverType);
+    ClassRelation relation = computeClassRelationFromType(receiverType);
     if (node.interfaceTarget != null) {
       registerInstanceGet(receiverType, relation, node.interfaceTarget);
     } else {
@@ -601,7 +596,7 @@
   @override
   void handlePropertySet(
       ir.PropertySet node, ir.DartType receiverType, ir.DartType valueType) {
-    ClassRelation relation = _computeClassRelationFromType(receiverType);
+    ClassRelation relation = computeClassRelationFromType(receiverType);
     if (node.interfaceTarget != null) {
       registerInstanceSet(receiverType, relation, node.interfaceTarget);
     } else {
@@ -682,7 +677,7 @@
     }
     node.accept(this);
     return new ImpactBuilderData(
-        impactData, typeMapsForTesting, cachedStaticTypes);
+        impactData, typeMapsForTesting, getStaticTypeCache());
   }
 }
 
@@ -693,7 +688,7 @@
 class ImpactBuilderData {
   final ImpactData impactData;
   final Map<ir.Expression, TypeMap> typeMapsForTesting;
-  final Map<ir.Expression, ir.DartType> cachedStaticTypes;
+  final StaticTypeCache cachedStaticTypes;
 
   ImpactBuilderData(
       this.impactData, this.typeMapsForTesting, this.cachedStaticTypes);
diff --git a/pkg/compiler/lib/src/ir/impact_data.dart b/pkg/compiler/lib/src/ir/impact_data.dart
index 543d3ed..79a7960 100644
--- a/pkg/compiler/lib/src/ir/impact_data.dart
+++ b/pkg/compiler/lib/src/ir/impact_data.dart
@@ -259,13 +259,21 @@
   }
 
   @override
-  void registerAsyncForIn(ir.DartType iterableType) {
-    _registerTypeUse(iterableType, _TypeUseKind.asyncForIn);
+  void registerAsyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation) {
+    _data._forInData ??= [];
+    _data._forInData.add(new _ForInData(
+        iterableType, iteratorType, iteratorClassRelation,
+        isAsync: true));
   }
 
   @override
-  void registerSyncForIn(ir.DartType iterableType) {
-    _registerTypeUse(iterableType, _TypeUseKind.syncForIn);
+  void registerSyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation) {
+    _data._forInData ??= [];
+    _data._forInData.add(new _ForInData(
+        iterableType, iteratorType, iteratorClassRelation,
+        isAsync: false));
   }
 
   @override
@@ -506,6 +514,7 @@
   List<double> _doubleLiterals;
   List<int> _intLiterals;
   List<_RuntimeTypeUse> _runtimeTypeUses;
+  List<_ForInData> _forInData;
 
   // TODO(johnniwinther): Remove these when CFE provides constants.
   List<ir.Constructor> _constructorNodes;
@@ -868,12 +877,6 @@
           case _TypeUseKind.catchType:
             registry.registerCatchType(data.type);
             break;
-          case _TypeUseKind.asyncForIn:
-            registry.registerAsyncForIn(data.type);
-            break;
-          case _TypeUseKind.syncForIn:
-            registry.registerSyncForIn(data.type);
-            break;
           case _TypeUseKind.asCast:
             registry.registerAsCast(data.type);
             break;
@@ -997,6 +1000,17 @@
             data.node, data.kind, data.receiverType, data.argumentType);
       }
     }
+    if (_forInData != null) {
+      for (_ForInData data in _forInData) {
+        if (data.isAsync) {
+          registry.registerAsyncForIn(
+              data.iterableType, data.iteratorType, data.iteratorClassRelation);
+        } else {
+          registry.registerSyncForIn(
+              data.iterableType, data.iteratorType, data.iteratorClassRelation);
+        }
+      }
+    }
 
     // TODO(johnniwinther): Remove these when CFE provides constants.
     if (_constructorNodes != null) {
@@ -1415,8 +1429,6 @@
 enum _TypeUseKind {
   parameterCheck,
   catchType,
-  asyncForIn,
-  syncForIn,
   asCast,
   implicitCast,
   isCheck,
@@ -1606,3 +1618,34 @@
     sink.end(tag);
   }
 }
+
+class _ForInData {
+  static const String tag = '_ForInData';
+
+  final ir.DartType iterableType;
+  final ir.DartType iteratorType;
+  final ClassRelation iteratorClassRelation;
+  final bool isAsync;
+
+  _ForInData(this.iterableType, this.iteratorType, this.iteratorClassRelation,
+      {this.isAsync});
+
+  factory _ForInData.fromDataSource(DataSource source) {
+    source.begin(tag);
+    ir.DartType iterableType = source.readDartTypeNode();
+    ir.DartType iteratorType = source.readDartTypeNode(allowNull: true);
+    ClassRelation iteratorClassRelation = source.readEnum(ClassRelation.values);
+    bool isAsync = source.readBool();
+    return new _ForInData(iterableType, iteratorType, iteratorClassRelation,
+        isAsync: isAsync);
+  }
+
+  void toDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeDartTypeNode(iterableType);
+    sink.writeDartTypeNode(iteratorType);
+    sink.writeEnum(iteratorClassRelation);
+    sink.writeBool(isAsync);
+    sink.end(tag);
+  }
+}
diff --git a/pkg/compiler/lib/src/ir/static_type.dart b/pkg/compiler/lib/src/ir/static_type.dart
index 7aee79e..a1bc702 100644
--- a/pkg/compiler/lib/src/ir/static_type.dart
+++ b/pkg/compiler/lib/src/ir/static_type.dart
@@ -12,6 +12,7 @@
 import 'runtime_type_analysis.dart';
 import 'scope.dart';
 import 'static_type_base.dart';
+import 'static_type_cache.dart';
 
 /// Enum values for how the target of a static type should be interpreted.
 enum ClassRelation {
@@ -27,6 +28,16 @@
   exact,
 }
 
+ClassRelation computeClassRelationFromType(ir.DartType type) {
+  if (type is ThisInterfaceType) {
+    return ClassRelation.thisExpression;
+  } else if (type is ExactInterfaceType) {
+    return ClassRelation.exact;
+  } else {
+    return ClassRelation.subtype;
+  }
+}
+
 /// Visitor that computes and caches the static type of expression while
 /// visiting the full tree at expression level.
 ///
@@ -35,7 +46,8 @@
 /// adds 'handleX' hooks for subclasses to handle individual expressions using
 /// the readily compute static types of subexpressions.
 abstract class StaticTypeVisitor extends StaticTypeBase {
-  Map<ir.Expression, ir.DartType> _cache = {};
+  final Map<ir.Expression, ir.DartType> _expressionTypeCache = {};
+  Map<ir.ForInStatement, ir.DartType> _forInIteratorTypeCache;
   Map<ir.Expression, TypeMap> typeMapsForTesting;
   Map<ir.PropertyGet, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
 
@@ -46,7 +58,9 @@
   StaticTypeVisitor(ir.TypeEnvironment typeEnvironment, this.hierarchy)
       : super(typeEnvironment);
 
-  Map<ir.Expression, ir.DartType> get cachedStaticTypes => _cache;
+  StaticTypeCache getStaticTypeCache() {
+    return new StaticTypeCache(_expressionTypeCache, _forInIteratorTypeCache);
+  }
 
   /// If `true`, the effect of executing assert statements is taken into account
   /// when computing the static type.
@@ -141,6 +155,13 @@
     visitNodes(node.fields);
   }
 
+  ir.InterfaceType getInterfaceTypeOf(ir.DartType type) {
+    while (type is ir.TypeParameterType) {
+      type = (type as ir.TypeParameterType).parameter.bound;
+    }
+    return type is ir.InterfaceType ? type : null;
+  }
+
   /// Returns the static type of the expression as an instantiation of
   /// [superclass].
   ///
@@ -215,8 +236,8 @@
   @override
   ir.DartType visitPropertyGet(ir.PropertyGet node) {
     ir.DartType receiverType = visitNode(node.receiver);
-    ir.DartType resultType =
-        _cache[node] = _computePropertyGetType(node, receiverType);
+    ir.DartType resultType = _expressionTypeCache[node] =
+        _computePropertyGetType(node, receiverType);
     receiverType = _narrowInstanceReceiver(node.interfaceTarget, receiverType);
     handlePropertyGet(node, receiverType, resultType);
     if (node.name.name == Identifiers.runtimeType_) {
@@ -297,7 +318,7 @@
     receiverType = getTypeAsInstanceOf(receiverType, superclass);
     ir.DartType resultType = ir.Substitution.fromInterfaceType(receiverType)
         .substituteType(node.target.getterType);
-    _cache[node] = resultType;
+    _expressionTypeCache[node] = resultType;
     handleDirectPropertyGet(node, receiverType, resultType);
     return resultType;
   }
@@ -326,7 +347,7 @@
               node.target.function.typeParameters, node.arguments.types)
           .substituteType(returnType);
     }
-    _cache[node] = returnType;
+    _expressionTypeCache[node] = returnType;
     handleDirectMethodInvocation(node, receiverType, argumentTypes, returnType);
     return returnType;
   }
@@ -500,7 +521,7 @@
       ir.VariableGet get = new ir.VariableGet(variable)..parent = parent;
       // Visit the newly created variable get.
       handleVariableGet(get, argumentType);
-      cachedStaticTypes[get] = argumentType;
+      _expressionTypeCache[get] = argumentType;
 
       if (checkedParameterType == null) {
         return get;
@@ -682,7 +703,7 @@
             .promote(right.variable, right.variable.type, isTrue: true);
       }
     }
-    _cache[node] = returnType;
+    _expressionTypeCache[node] = returnType;
     handleMethodInvocation(node, receiverType, argumentTypes, returnType);
     return returnType;
   }
@@ -701,7 +722,7 @@
             typeEnvironment.isSubtypeOf(promotedType, node.promotedType),
         "Unexpected promotion of ${node.variable} in ${node.parent}. "
         "Expected ${node.promotedType}, found $promotedType");
-    _cache[node] = promotedType;
+    _expressionTypeCache[node] = promotedType;
     handleVariableGet(node, promotedType);
     return promotedType;
   }
@@ -748,7 +769,7 @@
     ir.DartType returnType = ir.Substitution.fromPairs(
             node.target.function.typeParameters, node.arguments.types)
         .substituteType(node.target.function.returnType);
-    _cache[node] = returnType;
+    _expressionTypeCache[node] = returnType;
     handleStaticInvocation(node, argumentTypes, returnType);
     return returnType;
   }
@@ -763,7 +784,7 @@
         ? new ExactInterfaceType.from(node.target.enclosingClass.rawType)
         : new ExactInterfaceType(
             node.target.enclosingClass, node.arguments.types);
-    _cache[node] = resultType;
+    _expressionTypeCache[node] = resultType;
     handleConstructorInvocation(node, argumentTypes, resultType);
     return resultType;
   }
@@ -788,7 +809,7 @@
             .substituteType(node.interfaceTarget.getterType);
       }
     }
-    _cache[node] = resultType;
+    _expressionTypeCache[node] = resultType;
     handleSuperPropertyGet(node, resultType);
     return resultType;
   }
@@ -824,7 +845,7 @@
               node.arguments.types)
           .substituteType(returnType);
     }
-    _cache[node] = returnType;
+    _expressionTypeCache[node] = returnType;
     handleSuperMethodInvocation(node, argumentTypes, returnType);
     return returnType;
   }
@@ -943,7 +964,7 @@
   ir.DartType visitInstantiation(ir.Instantiation node) {
     ir.FunctionType expressionType = visitNode(node.expression);
     ir.DartType resultType = _computeInstantiationType(node, expressionType);
-    _cache[node] = resultType;
+    _expressionTypeCache[node] = resultType;
     handleInstantiation(node, expressionType, resultType);
     return resultType;
   }
@@ -1172,16 +1193,42 @@
     typeMap = beforeLoop;
   }
 
-  void handleForInStatement(ir.ForInStatement node, ir.DartType iterableType) {}
+  void handleForInStatement(ir.ForInStatement node, ir.DartType iterableType,
+      ir.DartType iteratorType) {}
 
   @override
   Null visitForInStatement(ir.ForInStatement node) {
+    // For sync for-in [iterableType] is a subtype of `Iterable`, for async
+    // for-in [iterableType] is a subtype of `Stream`.
     ir.DartType iterableType = visitNode(node.iterable);
+    ir.DartType iteratorType = const ir.DynamicType();
+    if (node.isAsync) {
+      ir.InterfaceType streamInterfaceType = getInterfaceTypeOf(iterableType);
+      ir.InterfaceType streamType = typeEnvironment.getTypeAsInstanceOf(
+          streamInterfaceType, typeEnvironment.coreTypes.streamClass);
+      if (streamType != null) {
+        iteratorType = new ir.InterfaceType(
+            typeEnvironment.coreTypes.streamIteratorClass,
+            streamType.typeArguments);
+      }
+    } else {
+      ir.InterfaceType iterableInterfaceType = getInterfaceTypeOf(iterableType);
+      ir.Member member = hierarchy.getInterfaceMember(
+          iterableInterfaceType.classNode, new ir.Name(Identifiers.iterator));
+      if (member != null) {
+        iteratorType = ir.Substitution.fromInterfaceType(
+                typeEnvironment.getTypeAsInstanceOf(
+                    iterableInterfaceType, member.enclosingClass))
+            .substituteType(member.getterType);
+      }
+    }
+    _forInIteratorTypeCache ??= {};
+    _forInIteratorTypeCache[node] = iteratorType;
     TypeMap beforeLoop = typeMap =
         typeMap.remove(variableScopeModel.getScopeFor(node).assignedVariables);
     visitNode(node.variable);
     visitNode(node.body);
-    handleForInStatement(node, iterableType);
+    handleForInStatement(node, iterableType, iteratorType);
     typeMap = beforeLoop;
   }
 
diff --git a/pkg/compiler/lib/src/ir/static_type_cache.dart b/pkg/compiler/lib/src/ir/static_type_cache.dart
new file mode 100644
index 0000000..64cf6b8
--- /dev/null
+++ b/pkg/compiler/lib/src/ir/static_type_cache.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:kernel/ast.dart' as ir;
+import 'package:kernel/class_hierarchy.dart' as ir;
+import 'package:kernel/core_types.dart' as ir;
+import 'package:kernel/type_algebra.dart' as ir;
+import 'package:kernel/type_environment.dart' as ir;
+
+import '../serialization/serialization.dart';
+
+class StaticTypeCache {
+  static const String tag = 'static-type-cache';
+
+  final Map<ir.Expression, ir.DartType> _expressionTypes;
+  final Map<ir.ForInStatement, ir.DartType> _forInIteratorTypes;
+
+  const StaticTypeCache(
+      [this._expressionTypes = const {}, this._forInIteratorTypes]);
+
+  factory StaticTypeCache.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    Map<ir.Expression, ir.DartType> expressionTypes =
+        source.readTreeNodeMap(source.readDartTypeNode);
+    Map<ir.ForInStatement, ir.DartType> forInIteratorTypes =
+        source.readTreeNodeMap(source.readDartTypeNode, emptyAsNull: true);
+    source.end(tag);
+    return new StaticTypeCache(expressionTypes, forInIteratorTypes);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeTreeNodeMap(_expressionTypes, sink.writeDartTypeNode);
+    sink.writeTreeNodeMap(_forInIteratorTypes, sink.writeDartTypeNode,
+        allowNull: true);
+    sink.end(tag);
+  }
+
+  ir.DartType operator [](ir.Expression node) => _expressionTypes[node];
+
+  ir.DartType getForInIteratorType(ir.ForInStatement node) {
+    return _forInIteratorTypes != null ? _forInIteratorTypes[node] : null;
+  }
+}
diff --git a/pkg/compiler/lib/src/ir/static_type_provider.dart b/pkg/compiler/lib/src/ir/static_type_provider.dart
index 2901f34..ee0db10 100644
--- a/pkg/compiler/lib/src/ir/static_type_provider.dart
+++ b/pkg/compiler/lib/src/ir/static_type_provider.dart
@@ -11,4 +11,5 @@
 /// Interface for accessing static types on expressions.
 abstract class StaticTypeProvider {
   ir.DartType getStaticType(ir.Expression node);
+  ir.DartType getForInIteratorType(ir.ForInStatement node);
 }
diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
index 61847ff..bb595f5 100644
--- a/pkg/compiler/lib/src/js_model/closure.dart
+++ b/pkg/compiler/lib/src/js_model/closure.dart
@@ -12,6 +12,7 @@
 import '../elements/types.dart';
 import '../ir/closure.dart';
 import '../ir/element_map.dart';
+import '../ir/static_type_cache.dart';
 import '../js_model/element_map.dart';
 import '../js_model/env.dart';
 import '../ordered_typeset.dart';
@@ -1065,7 +1066,7 @@
   ClosureMemberData(this.definition, this.memberThisType);
 
   @override
-  Map<ir.Expression, ir.DartType> get staticTypes {
+  StaticTypeCache get staticTypes {
     // The cached types are stored in the data for enclosing member.
     throw new UnsupportedError("ClosureMemberData.staticTypes");
   }
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index 1b9694b..be39539 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -33,6 +33,7 @@
 import '../ir/types.dart';
 import '../ir/visitors.dart';
 import '../ir/static_type_base.dart';
+import '../ir/static_type_cache.dart';
 import '../ir/static_type_provider.dart';
 import '../ir/util.dart';
 import '../js/js.dart' as js;
@@ -1143,7 +1144,7 @@
   @override
   StaticTypeProvider getStaticTypeProvider(MemberEntity member) {
     MemberDefinition memberDefinition = members.getData(member).definition;
-    Map<ir.Expression, ir.DartType> cachedStaticTypes;
+    StaticTypeCache cachedStaticTypes;
     ir.InterfaceType thisType;
     switch (memberDefinition.kind) {
       case MemberKind.regular:
@@ -1168,7 +1169,7 @@
       case MemberKind.closureField:
       case MemberKind.signature:
       case MemberKind.generatorBody:
-        cachedStaticTypes = const {};
+        cachedStaticTypes = const StaticTypeCache();
         break;
     }
 
diff --git a/pkg/compiler/lib/src/js_model/env.dart b/pkg/compiler/lib/src/js_model/env.dart
index 4bd4808..9017330 100644
--- a/pkg/compiler/lib/src/js_model/env.dart
+++ b/pkg/compiler/lib/src/js_model/env.dart
@@ -14,6 +14,7 @@
 import '../elements/indexed.dart';
 import '../elements/types.dart';
 import '../ir/element_map.dart';
+import '../ir/static_type_cache.dart';
 import '../ir/visitors.dart';
 import '../js_model/element_map.dart';
 import '../ordered_typeset.dart';
@@ -520,7 +521,7 @@
 
   ClassTypeVariableAccess get classTypeVariableAccess;
 
-  Map<ir.Expression, ir.DartType> get staticTypes;
+  StaticTypeCache get staticTypes;
 
   JMemberData();
 
@@ -559,7 +560,7 @@
   final MemberDefinition definition;
 
   @override
-  final Map<ir.Expression, ir.DartType> staticTypes;
+  final StaticTypeCache staticTypes;
 
   JMemberDataImpl(this.node, this.definition, this.staticTypes);
 
@@ -659,7 +660,7 @@
   FunctionType _type;
 
   FunctionDataImpl(ir.Member node, this.functionNode,
-      MemberDefinition definition, Map<ir.Expression, ir.DartType> staticTypes)
+      MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, definition, staticTypes);
 
   factory FunctionDataImpl.readFromDataSource(DataSource source) {
@@ -676,8 +677,8 @@
     }
     MemberDefinition definition =
         new MemberDefinition.readFromDataSource(source);
-    Map<ir.Expression, ir.DartType> staticTypes =
-        source.readTreeNodeMap(() => source.readDartTypeNode());
+    StaticTypeCache staticTypes =
+        new StaticTypeCache.readFromDataSource(source);
     source.end(tag);
     return new FunctionDataImpl(node, functionNode, definition, staticTypes);
   }
@@ -688,7 +689,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    sink.writeTreeNodeMap(staticTypes, sink.writeDartTypeNode);
+    staticTypes.writeToDataSink(sink);
     sink.end(tag);
   }
 
@@ -744,7 +745,7 @@
   }
 
   @override
-  Map<ir.Expression, ir.DartType> get staticTypes => const {};
+  StaticTypeCache get staticTypes => const StaticTypeCache();
 
   @override
   FunctionType getFunctionType(covariant JsKernelToElementMap elementMap) {
@@ -840,7 +841,7 @@
   }
 
   @override
-  Map<ir.Expression, ir.DartType> get staticTypes => const {};
+  StaticTypeCache get staticTypes => const StaticTypeCache();
 }
 
 abstract class JConstructorData extends FunctionData {
@@ -858,7 +859,7 @@
   JConstructorBody constructorBody;
 
   JConstructorDataImpl(ir.Member node, ir.FunctionNode functionNode,
-      MemberDefinition definition, Map<ir.Expression, ir.DartType> staticTypes)
+      MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, functionNode, definition, staticTypes);
 
   factory JConstructorDataImpl.readFromDataSource(DataSource source) {
@@ -875,8 +876,8 @@
     }
     MemberDefinition definition =
         new MemberDefinition.readFromDataSource(source);
-    Map<ir.Expression, ir.DartType> staticTypes =
-        source.readTreeNodeMap(() => source.readDartTypeNode());
+    StaticTypeCache staticTypes =
+        new StaticTypeCache.readFromDataSource(source);
     source.end(tag);
     return new JConstructorDataImpl(
         node, functionNode, definition, staticTypes);
@@ -889,7 +890,7 @@
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
     assert(constructorBody == null);
-    sink.writeTreeNodeMap(staticTypes, sink.writeDartTypeNode);
+    staticTypes.writeToDataSink(sink);
     sink.end(tag);
   }
 
@@ -921,7 +922,7 @@
   static const String tag = 'constructor-body-data';
 
   ConstructorBodyDataImpl(ir.Member node, ir.FunctionNode functionNode,
-      MemberDefinition definition, Map<ir.Expression, ir.DartType> staticTypes)
+      MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, functionNode, definition, staticTypes);
 
   factory ConstructorBodyDataImpl.readFromDataSource(DataSource source) {
@@ -938,8 +939,8 @@
     }
     MemberDefinition definition =
         new MemberDefinition.readFromDataSource(source);
-    Map<ir.Expression, ir.DartType> staticTypes =
-        source.readTreeNodeMap(() => source.readDartTypeNode());
+    StaticTypeCache staticTypes =
+        new StaticTypeCache.readFromDataSource(source);
     source.end(tag);
     return new ConstructorBodyDataImpl(
         node, functionNode, definition, staticTypes);
@@ -951,7 +952,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    sink.writeTreeNodeMap(staticTypes, sink.writeDartTypeNode);
+    staticTypes.writeToDataSink(sink);
     sink.end(tag);
   }
 
@@ -977,8 +978,8 @@
   DartType _type;
   ConstantExpression _constantExpression;
 
-  JFieldDataImpl(ir.Field node, MemberDefinition definition,
-      Map<ir.Expression, ir.DartType> staticTypes)
+  JFieldDataImpl(
+      ir.Field node, MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, definition, staticTypes);
 
   factory JFieldDataImpl.readFromDataSource(DataSource source) {
@@ -986,8 +987,8 @@
     ir.Member node = source.readMemberNode();
     MemberDefinition definition =
         new MemberDefinition.readFromDataSource(source);
-    Map<ir.Expression, ir.DartType> staticTypes =
-        source.readTreeNodeMap(() => source.readDartTypeNode());
+    StaticTypeCache staticTypes =
+        new StaticTypeCache.readFromDataSource(source);
     source.end(tag);
     return new JFieldDataImpl(node, definition, staticTypes);
   }
@@ -998,7 +999,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    sink.writeTreeNodeMap(staticTypes, sink.writeDartTypeNode);
+    staticTypes.writeToDataSink(sink);
     sink.end(tag);
   }
 
diff --git a/pkg/compiler/lib/src/kernel/element_map_impl.dart b/pkg/compiler/lib/src/kernel/element_map_impl.dart
index 7a2b12a..bb8f069 100644
--- a/pkg/compiler/lib/src/kernel/element_map_impl.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_impl.dart
@@ -34,6 +34,7 @@
 import '../ir/impact.dart';
 import '../ir/impact_data.dart';
 import '../ir/static_type.dart';
+import '../ir/static_type_cache.dart';
 import '../ir/scope.dart';
 import '../ir/types.dart';
 import '../ir/visitors.dart';
@@ -1389,14 +1390,13 @@
         typeMapsForTesting[member] = builder.typeMapsForTesting = {};
       }
       node.accept(builder);
-      memberData.staticTypes = builder.cachedStaticTypes;
+      memberData.staticTypes = builder.getStaticTypeCache();
       return builder.impactBuilder;
     }
   }
 
-  Map<ir.Expression, ir.DartType> getCachedStaticTypes(KMember member) {
-    Map<ir.Expression, ir.DartType> staticTypes =
-        members.getData(member).staticTypes;
+  StaticTypeCache getCachedStaticTypes(KMember member) {
+    StaticTypeCache staticTypes = members.getData(member).staticTypes;
     assert(staticTypes != null, "No static types cached for $member.");
     return staticTypes;
   }
diff --git a/pkg/compiler/lib/src/kernel/env.dart b/pkg/compiler/lib/src/kernel/env.dart
index 0f004e3..c76aaaa 100644
--- a/pkg/compiler/lib/src/kernel/env.dart
+++ b/pkg/compiler/lib/src/kernel/env.dart
@@ -19,6 +19,7 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../ir/element_map.dart';
+import '../ir/static_type_cache.dart';
 import '../ir/visitors.dart';
 import '../ir/util.dart';
 import '../js_model/element_map.dart';
@@ -669,7 +670,7 @@
 abstract class KMemberData {
   ir.Member get node;
 
-  Map<ir.Expression, ir.DartType> staticTypes;
+  StaticTypeCache staticTypes;
 
   Iterable<ConstantValue> getMetadata(IrToElementMap elementMap);
 
@@ -688,7 +689,7 @@
   Iterable<ConstantValue> _metadata;
 
   @override
-  Map<ir.Expression, ir.DartType> staticTypes;
+  StaticTypeCache staticTypes;
 
   KMemberDataImpl(this.node);
 
diff --git a/pkg/compiler/lib/src/kernel/kernel_impact.dart b/pkg/compiler/lib/src/kernel/kernel_impact.dart
index e32ea87..6041116 100644
--- a/pkg/compiler/lib/src/kernel/kernel_impact.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_impact.dart
@@ -739,23 +739,31 @@
   }
 
   @override
-  void registerSyncForIn(ir.DartType iterableType) {
-    // TODO(johnniwinther): Use receiver constraints for the dynamic uses in
-    // strong mode.
+  void registerSyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation) {
+    Object receiverConstraint =
+        _computeReceiverConstraint(iteratorType, iteratorClassRelation);
     impactBuilder.registerFeature(Feature.SYNC_FOR_IN);
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.iterator));
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.current));
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.moveNext));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.iterator, receiverConstraint, const []));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.current, receiverConstraint, const []));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.moveNext, receiverConstraint, const []));
   }
 
   @override
-  void registerAsyncForIn(ir.DartType iterableType) {
-    // TODO(johnniwinther): Use receiver constraints for the dynamic uses in
-    // strong mode.
+  void registerAsyncForIn(ir.DartType iterableType, ir.DartType iteratorType,
+      ClassRelation iteratorClassRelation) {
+    Object receiverConstraint =
+        _computeReceiverConstraint(iteratorType, iteratorClassRelation);
     impactBuilder.registerFeature(Feature.ASYNC_FOR_IN);
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.cancel));
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.current));
-    impactBuilder.registerDynamicUse(new DynamicUse(Selectors.moveNext));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.cancel, receiverConstraint, const []));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.current, receiverConstraint, const []));
+    impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
+        Selectors.moveNext, receiverConstraint, const []));
   }
 
   @override
diff --git a/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart b/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
index 81c7601..e7f67fb 100644
--- a/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
+++ b/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
@@ -63,6 +63,7 @@
 class Class4b implements Class4a {
   /*element: Class4b.field4:emitted,get=simple*/
   @pragma('dart2js:noElision')
+  @override
   int field4;
 }
 
diff --git a/tests/compiler/dart2js/impact/data/async.dart b/tests/compiler/dart2js/impact/data/async.dart
index 69792ef..bde8eb7 100644
--- a/tests/compiler/dart2js/impact/data/async.dart
+++ b/tests/compiler/dart2js/impact/data/async.dart
@@ -219,9 +219,9 @@
 
 /*element: testAsyncForIn:
  dynamic=[
-  cancel(0),
-  current,
-  moveNext(0)],
+  _StreamIterator.cancel(0),
+  _StreamIterator.current,
+  _StreamIterator.moveNext(0)],
  static=[
   StreamIterator.(1),
   _asyncAwait(2),
@@ -243,9 +243,9 @@
 
 /*element: testAsyncForInTyped:
  dynamic=[
-  cancel(0),
-  current,
-  moveNext(0)],
+  _StreamIterator.cancel(0),
+  _StreamIterator.current,
+  _StreamIterator.moveNext(0)],
  static=[
   StreamIterator.(1),
   _asyncAwait(2),
diff --git a/tests/compiler/dart2js/impact/data/statements.dart b/tests/compiler/dart2js/impact/data/statements.dart
index 3b5e240..7a89d28 100644
--- a/tests/compiler/dart2js/impact/data/statements.dart
+++ b/tests/compiler/dart2js/impact/data/statements.dart
@@ -67,9 +67,9 @@
 
 /*element: testForIn:
  dynamic=[
-  current,
-  iterator,
-  moveNext(0)],
+  Iterator.current,
+  Iterator.iterator,
+  Iterator.moveNext(0)],
  static=[checkConcurrentModificationError(2)],
  type=[
   impl:Iterable<dynamic>,
@@ -84,9 +84,9 @@
 
 /*element: testForInTyped:
  dynamic=[
-  current,
-  iterator,
-  moveNext(0)],
+  Iterator.current,
+  Iterator.iterator,
+  Iterator.moveNext(0)],
  static=[checkConcurrentModificationError(2)],
  type=[
   impl:Iterable<dynamic>,
diff --git a/tests/compiler/dart2js/inference/data/field_type.dart b/tests/compiler/dart2js/inference/data/field_type.dart
index 4f499a9..d97d431 100644
--- a/tests/compiler/dart2js/inference/data/field_type.dart
+++ b/tests/compiler/dart2js/inference/data/field_type.dart
@@ -24,7 +24,9 @@
   test18();
   test19();
   test20();
+  test20b();
   test21();
+  test21b();
   test22();
   test23();
   test24();
@@ -460,26 +462,22 @@
 }
 
 class A20 {
-  /*element: A20.f20:[null|exact=JSUInt31]*/
+  /*element: A20.f20:[null]*/
   var f20;
 
   /*element: A20.:[exact=A20]*/
   A20() {
     dynamic a = this;
-    // TODO(johnniwinther): Fix ast equivalence on instance fields in for.
     /*iterator: [exact=A20]*/
-    /*current: [exact=A20]*/
-    /*moveNext: [exact=A20]*/
+    /*current: [empty]*/
+    /*moveNext: [empty]*/
     for (/*update: [exact=A20]*/ f20 in a) {}
   }
 
-  /*element: A20.iterator:[exact=A20]*/
   get iterator => this;
 
-  /*element: A20.current:[exact=JSUInt31]*/
   get current => 42;
 
-  /*element: A20.moveNext:Value([exact=JSBool], value: false)*/
   bool moveNext() => false;
 }
 
@@ -488,6 +486,37 @@
   new A20();
 }
 
+class A20b extends Iterable implements Iterator {
+  /*element: A20b.f20b:[null|exact=JSUInt31]*/
+  var f20b;
+
+  /*element: A20b.:[exact=A20b]*/
+  A20b() {
+    dynamic a = this;
+    /*iterator: [exact=A20b]*/
+    /*current: [exact=A20b]*/
+    /*moveNext: [exact=A20b]*/
+    for (/*update: [exact=A20b]*/ f20b in a) {}
+  }
+
+  /*element: A20b.iterator:[exact=A20b]*/
+  @override
+  get iterator => this;
+
+  /*element: A20b.current:[exact=JSUInt31]*/
+  @override
+  get current => 42;
+
+  /*element: A20b.moveNext:Value([exact=JSBool], value: false)*/
+  @override
+  bool moveNext() => false;
+}
+
+/*element: test20b:[null]*/
+test20b() {
+  new A20b();
+}
+
 class A21 {
   /*element: A21.f21:[null|exact=JSUInt31]*/
   var f21;
@@ -496,14 +525,14 @@
   A21() {
     dynamic a = this;
     /*iterator: [exact=A21]*/
-    /*current: [null]*/
-    /*moveNext: [null]*/
+    /*current: [empty]*/
+    /*moveNext: [empty]*/
     for (
         // ignore: unused_local_variable
         var i in a) {}
     /*update: [exact=A21]*/ f21 = 42;
   }
-  /*element: A21.iterator:[null]*/
+
   get iterator => null;
 }
 
@@ -512,6 +541,32 @@
   new A21();
 }
 
+class A21b extends Iterable {
+  /*element: A21b.f21:[null|exact=JSUInt31]*/
+  var f21;
+
+  /*element: A21b.:[exact=A21b]*/
+  A21b() {
+    dynamic a = this;
+    /*iterator: [exact=A21b]*/
+    /*current: [null]*/
+    /*moveNext: [null]*/
+    for (
+        // ignore: unused_local_variable
+        var i in a) {}
+    /*update: [exact=A21b]*/ f21 = 42;
+  }
+
+  /*element: A21b.iterator:[null]*/
+  @override
+  get iterator => null;
+}
+
+/*element: test21b:[null]*/
+test21b() {
+  new A21b();
+}
+
 class A22 {
   /*element: A22.f22a:[exact=JSUInt31]*/
   var f22a;
@@ -617,6 +672,7 @@
 /*element: B24.:[exact=B24]*/
 class B24 extends A24 {
   /*element: B24.bar24:[exact=JSUInt31]*/
+  @override
   bar24() => 42;
 }
 
@@ -683,6 +739,7 @@
 
 /*element: B27.:[exact=B27]*/
 class B27 extends A27 {
+  @override
   set f27b(/*[exact=JSUInt31]*/ value) {}
 }
 
diff --git a/tests/compiler/dart2js/member_usage/data/general.dart b/tests/compiler/dart2js/member_usage/data/general.dart
index 465f141..9954b67 100644
--- a/tests/compiler/dart2js/member_usage/data/general.dart
+++ b/tests/compiler/dart2js/member_usage/data/general.dart
@@ -34,43 +34,75 @@
 /*element: C.:invoke*/
 class C extends A {
   /*element: C.method1:invoke*/
+  @override
   method1() {}
 
   /*element: B.method2:invoke*/
+  @override
   method2() {}
+
+  @override
   method4() {}
 
   /*element: C.getter:read*/
+  @override
   get getter => 42;
+
+  @override
   set setter(_) {}
 }
 
 /*element: D.:invoke*/
 class D implements B {
+  @override
   method1() {}
 
   /*element: D.method2:invoke*/
+  @override
   method2() {}
+
+  @override
   method5() {}
+
+  @override
   get getter => 42;
 
   /*element: D.setter=:write*/
+  @override
   set setter(_) {}
 }
 
 class E implements A {
+  @override
   method1() {}
+
+  @override
   method2() {}
+
+  @override
   method4() {}
+
+  @override
   get getter => 42;
+
+  @override
   set setter(_) {}
 }
 
 class F extends B {
+  @override
   method1() {}
+
+  @override
   method2() {}
+
+  @override
   method5() {}
+
+  @override
   get getter => 42;
+
+  @override
   set setter(_) {}
 }
 
@@ -134,12 +166,15 @@
 /*element: P.:invoke*/
 class P implements O {
   /*element: P.method1:invoke*/
+  @override
   method1() {}
 
   /*element: P.getter:read*/
+  @override
   get getter => 42;
 
   /*element: P.setter=:write*/
+  @override
   set setter(_) {}
 }
 
diff --git a/tests/compiler/dart2js/rti/data/generic_methods_dynamic_05.dart b/tests/compiler/dart2js/rti/data/generic_methods_dynamic_05.dart
index dcd1539..142ac30 100644
--- a/tests/compiler/dart2js/rti/data/generic_methods_dynamic_05.dart
+++ b/tests/compiler/dart2js/rti/data/generic_methods_dynamic_05.dart
@@ -5,7 +5,7 @@
 // Test derived from language_2/generic_methods_dynamic_test/05
 
 /*omit.class: global#JSArray:deps=[List],explicit=[JSArray],needsArgs*/
-/*strong.class: global#JSArray:deps=[ArrayIterator,List],direct,explicit=[JSArray,JSArray.E,JSArray<ArrayIterator.E>],implicit=[JSArray.E],needsArgs*/
+/*strong.class: global#JSArray:deps=[ArrayIterator,List],explicit=[JSArray,JSArray.E,JSArray<ArrayIterator.E>],implicit=[JSArray.E],indirect,needsArgs*/
 
 /*omit.class: global#List:deps=[C.bar],explicit=[List,List<B>],needsArgs*/
 /*strong.class: global#List:deps=[C.bar],explicit=[List,List<B>,List<String>],indirect,needsArgs*/
diff --git a/tests/compiler/dart2js/rti/data/local_function_list_literal.dart b/tests/compiler/dart2js/rti/data/local_function_list_literal.dart
index 7cbb508..aa06e82 100644
--- a/tests/compiler/dart2js/rti/data/local_function_list_literal.dart
+++ b/tests/compiler/dart2js/rti/data/local_function_list_literal.dart
@@ -4,7 +4,7 @@
 
 import 'package:expect/expect.dart';
 
-/*strong.class: global#JSArray:deps=[ArrayIterator,List],direct,explicit=[JSArray,JSArray.E,JSArray<ArrayIterator.E>],implicit=[JSArray.E],needsArgs*/
+/*strong.class: global#JSArray:deps=[ArrayIterator,List],explicit=[JSArray,JSArray.E,JSArray<ArrayIterator.E>],implicit=[JSArray.E],indirect,needsArgs*/
 /*omit.class: global#JSArray:deps=[List],explicit=[JSArray],needsArgs*/
 
 /*strong.element: method:implicit=[method.T],indirect,needsArgs*/
diff --git a/tests/compiler/dart2js/static_type/data/for_in.dart b/tests/compiler/dart2js/static_type/data/for_in.dart
index 3168538..a97c74b 100644
--- a/tests/compiler/dart2js/static_type/data/for_in.dart
+++ b/tests/compiler/dart2js/static_type/data/for_in.dart
@@ -6,9 +6,17 @@
   Iterable<Class> next;
 }
 
+abstract class Class2<E> implements Iterable<E> {
+  @override
+  Iterator<E> iterator;
+}
+
 main() {
   forIn1(null);
   forIn2(null);
+  forIn3(null);
+  forIn4(null);
+  forIn5(null);
 }
 
 forIn1(dynamic c) {
@@ -36,3 +44,30 @@
     /*Class*/ c.next;
   }
 }
+
+forIn3(o) {
+  /*dynamic*/ o;
+  for (var e in /*dynamic*/ o) {
+    /*dynamic*/ e;
+    /*dynamic*/ o;
+  }
+  /*dynamic*/ o;
+}
+
+forIn4(o) {
+  /*dynamic*/ o;
+  for (int e in /*dynamic*/ o) {
+    /*int*/ e;
+    /*dynamic*/ o;
+  }
+  /*dynamic*/ o;
+}
+
+forIn5(Class2<int> o) {
+  /*Class2<int>*/ o;
+  for (var e in /*Class2<int>*/ o) {
+    /*int*/ e;
+    /*Class2<int>*/ o;
+  }
+  /*Class2<int>*/ o;
+}
diff --git a/tests/compiler/dart2js/static_type/static_type_test.dart b/tests/compiler/dart2js/static_type/static_type_test.dart
index 1f935d8..8999060 100644
--- a/tests/compiler/dart2js/static_type/static_type_test.dart
+++ b/tests/compiler/dart2js/static_type/static_type_test.dart
@@ -9,6 +9,7 @@
 import 'package:compiler/src/elements/entities.dart';
 import 'package:compiler/src/ir/cached_static_type.dart';
 import 'package:compiler/src/ir/static_type_base.dart';
+import 'package:compiler/src/ir/static_type_cache.dart';
 import 'package:compiler/src/kernel/element_map_impl.dart';
 import 'package:compiler/src/kernel/kernel_strategy.dart';
 import 'package:kernel/ast.dart' as ir;
@@ -49,8 +50,7 @@
       {bool verbose: false}) {
     KernelFrontEndStrategy frontendStrategy = compiler.frontendStrategy;
     KernelToElementMapImpl elementMap = frontendStrategy.elementMap;
-    Map<ir.TreeNode, ir.DartType> staticTypeCache =
-        elementMap.getCachedStaticTypes(member);
+    StaticTypeCache staticTypeCache = elementMap.getCachedStaticTypes(member);
     ir.Member node = elementMap.getMemberNode(member);
     new StaticTypeIrComputer(
             compiler.reporter,