In progress
diff --git a/pkg/kernel/bin/transform.dart b/pkg/kernel/bin/transform.dart
index 68e264a..798102a 100755
--- a/pkg/kernel/bin/transform.dart
+++ b/pkg/kernel/bin/transform.dart
@@ -25,7 +25,7 @@
 import 'package:kernel/vm/native_effects.dart';
 import 'package:kernel/vm/constants_native_effects.dart';
 import 'package:kernel/vm/roots.dart';
-// import 'package:kernel/verifier.dart';
+import 'package:kernel/verifier.dart';
 import 'package:kernel/transformations/coq.dart' as coq;
 
 import 'util.dart';
@@ -138,7 +138,7 @@
   // TODO(30631): Fix the verifier so we can check that the transform produced
   // valid output.
   //
-  // verifyProgram(program);
+  verifyProgram(program);
 
   if (format == 'text') {
     writeProgramToText(program, path: output);
diff --git a/pkg/kernel/lib/clone.dart b/pkg/kernel/lib/clone.dart
index e0697f0..d07fd36 100644
--- a/pkg/kernel/lib/clone.dart
+++ b/pkg/kernel/lib/clone.dart
@@ -6,105 +6,11 @@
 import 'ast.dart';
 import 'type_algebra.dart';
 
-typedef String ClassRenamer(Class node);
+typedef Member MemberSubstitutor(TreeNode receiver, Member m, Arguments arguments);
+Member _noopMemberSubstitutor(TreeNode receiver, Member m, Arguments arguments) => m;
 
-String _nopClassRenamer(Class node) => node.name;
-
-class _CloneSubstitutor extends DartTypeVisitor<DartType> {
-  final Map<TreeNode, TreeNode> mapping;
-
-  _CloneSubstitutor(this.mapping);
-
-  int changed = 0;
-
-  Class visitClass(Class node) {
-    if (mapping.containsKey(node)) {
-      changed++;
-      return mapping[node];
-    }
-    return node;
-  }
-
-  DartType visit(DartType node) => node.accept(this);
-
-  @override
-  InterfaceType visitInterfaceType(InterfaceType node) {
-    final oldChanged = changed;
-    final typeArguments = node.typeArguments.map(visit).toList();
-    final classNode = visitClass(node.classNode);
-    if (changed != oldChanged) {
-      changed++;
-      return new InterfaceType(classNode, typeArguments);
-    }
-    return node;
-  }
-
-  @override
-  FunctionType visitFunctionType(FunctionType node) {
-    final oldChanged = changed;
-    final List<TypeParameter> typeParameters = node.typeParameters;
-    final int requiredParameterCount = node.requiredParameterCount;
-    final List<DartType> positionalParameters =
-        node.positionalParameters.map(visit).toList();
-    final List<NamedType> namedParameters =
-        node.namedParameters.map(visitNamedType).toList();
-    final DartType returnType = visit(node.returnType);
-    if (oldChanged != changed) {
-      return new FunctionType(positionalParameters, returnType,
-          requiredParameterCount: requiredParameterCount,
-          namedParameters: namedParameters,
-          typeParameters: typeParameters);
-    }
-    return node;
-  }
-
-  @override
-  TypeParameterType visitTypeParameterType(TypeParameterType node) {
-    // TODO
-    return node;
-  }
-
-  @override
-  TypedefType visitTypedefType(TypedefType node) {
-    // TODO
-    return node;
-  }
-
-  @override
-  InvalidType visitInvalidType(InvalidType node) => node;
-
-  @override
-  DynamicType visitDynamicType(DynamicType node) => node;
-
-  @override
-  VoidType visitVoidType(VoidType node) => node;
-
-  @override
-  BottomType visitBottomType(BottomType node) => node;
-
-  @override
-  VectorType visitVectorType(VectorType node) => node;
-
-  NamedType visitNamedType(NamedType type) {
-    final oldChanged = changed;
-    final DartType t = visit(type.type);
-    if (oldChanged != changed) {
-      return new NamedType(type.name, t);
-    } else {
-      return type;
-    }
-  }
-
-  Supertype visitSupertype(Supertype node) {
-    final oldChanged = changed;
-    final Class classNode = visitClass(node.classNode);
-    final List<DartType> typeArguments = node.typeArguments.map(visit).toList();
-    if (oldChanged != changed) {
-      return new Supertype(classNode, typeArguments);
-    }
-    return node;
-  }
-}
+typedef DartType TypeSubstitutor(DartType m);
+DartType _noopTypeSubstitutor(DartType t) => t;
 
 /// Visitor that return a clone of a tree, maintaining references to cloned
 /// objects.
@@ -118,15 +24,14 @@
       <LabeledStatement, LabeledStatement>{};
   final Map<SwitchCase, SwitchCase> switchCases = <SwitchCase, SwitchCase>{};
   final Map<TypeParameter, DartType> typeSubstitution;
-  final ClassRenamer classRenamer;
 
-  // final Map<TreeNode, TreeNode> mapping = ;
-  final _CloneSubstitutor _cloneSubstitutor =
-      new _CloneSubstitutor(<TreeNode, TreeNode>{});
+  final MemberSubstitutor memberSubstitutor;
+  final TypeSubstitutor typeSubstitutor;
 
   CloneVisitor(
       {Map<TypeParameter, DartType> typeSubstitution,
-      this.classRenamer: _nopClassRenamer})
+       this.memberSubstitutor: _noopMemberSubstitutor,
+       this.typeSubstitutor: _noopTypeSubstitutor})
       : this.typeSubstitution = ensureMutable(typeSubstitution);
 
   static Map<TypeParameter, DartType> ensureMutable(
@@ -144,105 +49,8 @@
   TreeNode visitLibrary(Library node) =>
       throw 'Cloning of libraries is not implemented';
 
-  TreeNode visitClass(Class node) {
-    final mapping = _cloneSubstitutor.mapping;
-
-    final cloned = new Class(
-        name: classRenamer(node),
-        isAbstract: node.isAbstract,
-        fileUri: node.fileUri);
-    mapping[node] = cloned;
-
-    substituteSupertype(Supertype type) {
-      if (type != null) {
-        type = Substitution.fromMap(typeSubstitution).substituteSupertype(type);
-        type = _cloneSubstitutor.visitSupertype(type);
-      }
-      return type;
-    }
-
-    cloned.typeParameters.addAll(node.typeParameters.map((TypeParameter param) {
-      if (typeSubstitution.containsKey(param)) {
-        return null;
-      }
-
-      return visitTypeParameter(param);
-    }).where((p) => p != null));
-
-    cloned.supertype = substituteSupertype(node.supertype);
-    cloned.mixedInType = substituteSupertype(node.mixedInType);
-    cloned.implementedTypes
-        .addAll(node.implementedTypes.map(substituteSupertype));
-
-    cloneConstructor(Constructor node) {
-      return mapping[node] = new Constructor(null,
-          name: node.name,
-          isConst: node.isConst,
-          isExternal: node.isExternal,
-          transformerFlags: node.transformerFlags)
-        ..fileEndOffset = node.fileEndOffset;
-    }
-
-    fillConstructor(Constructor node) {
-      final Constructor nodeClone = mapping[node];
-      nodeClone.function = clone(node.function);
-      nodeClone.function?.parent = nodeClone;
-      nodeClone.initializers = node.initializers.map(clone).toList();
-      setParents(nodeClone.initializers, nodeClone);
-    }
-
-    cloneProcedure(Procedure node) {
-      return mapping[node] = new Procedure(node.name, node.kind, null,
-          isAbstract: node.isAbstract,
-          isStatic: node.isStatic,
-          isExternal: node.isExternal,
-          isConst: node.isConst,
-          transformerFlags: node.transformerFlags,
-          fileUri: node.fileUri)
-        ..fileEndOffset = node.fileEndOffset;
-    }
-
-    fillProcedure(Procedure node) {
-      final Procedure nodeClone = mapping[node];
-      nodeClone.function = clone(node.function);
-      nodeClone.function?.parent = nodeClone;
-    }
-
-    cloneField(Field node) {
-      return mapping[node] = new Field(node.name,
-          type: visitType(node.type),
-          initializer: null,
-          isFinal: node.isFinal,
-          isConst: node.isConst,
-          isStatic: node.isStatic,
-          hasImplicitGetter: node.hasImplicitGetter,
-          hasImplicitSetter: node.hasImplicitSetter,
-          transformerFlags: node.transformerFlags,
-          fileUri: node.fileUri)
-        ..fileEndOffset = node.fileEndOffset;
-    }
-
-    fillField(Field node) {
-      final Field nodeClone = mapping[node];
-      nodeClone.initializer = cloneOptional(node.initializer);
-      nodeClone.initializer?.parent = nodeClone;
-    }
-
-    cloned.constructors.addAll(node.constructors.map(cloneConstructor));
-    setParents(cloned.constructors, cloned);
-
-    cloned.procedures.addAll(node.procedures.map(cloneProcedure));
-    setParents(cloned.procedures, cloned);
-
-    cloned.fields.addAll(node.fields.map(cloneField));
-    setParents(cloned.fields, cloned);
-
-    node.constructors.forEach(fillConstructor);
-    node.procedures.forEach(fillProcedure);
-    node.fields.forEach(fillField);
-
-    return cloned;
-  }
+  TreeNode visitClass(Class node) =>
+      throw 'Clonning of classes is not implemented';
 
   TreeNode clone(TreeNode node) =>
       node.accept(this)..fileOffset = node.fileOffset;
@@ -253,9 +61,9 @@
     return result;
   }
 
-  DartType visitType(DartType type) {
-    return _cloneSubstitutor.visit(substitute(type, typeSubstitution));
-  }
+  DartType visitType(DartType type) =>
+      typeSubstitutor(substitute(type, typeSubstitution));
+
 
   DartType visitOptionalType(DartType type) {
     return type == null ? null : visitType(type);
@@ -272,94 +80,90 @@
     return new VariableSet(variables[node.variable], clone(node.value));
   }
 
-  Member redirect(Member m) =>
-      m == null ? null : (_cloneSubstitutor.mapping[m] ?? m);
+  Member redirect(TreeNode receiver, Member m, Arguments arguments) =>
+      m == null ? null : memberSubstitutor(receiver, m, arguments);
 
   visitPropertyGet(PropertyGet node) {
+    final receiver = clone(node.receiver);
     return new PropertyGet(
-        clone(node.receiver), node.name, redirect(node.interfaceTarget));
+        receiver, node.name, redirect(receiver, node.interfaceTarget, null));
   }
 
   visitPropertySet(PropertySet node) {
-    return new PropertySet(clone(node.receiver), node.name, clone(node.value),
-        redirect(node.interfaceTarget));
+    final receiver = clone(node.receiver);
+    return new PropertySet(receiver, node.name, clone(node.value),
+        redirect(receiver, node.interfaceTarget, null));
   }
 
   visitDirectPropertyGet(DirectPropertyGet node) {
-    return new DirectPropertyGet(clone(node.receiver), redirect(node.target));
+    final receiver = clone(node.receiver);
+    return new DirectPropertyGet(receiver, redirect(receiver, node.target, null));
   }
 
   visitDirectPropertySet(DirectPropertySet node) {
+    final receiver = clone(node.receiver);
     return new DirectPropertySet(
-        clone(node.receiver), redirect(node.target), clone(node.value));
+        receiver, redirect(receiver, node.target, null), clone(node.value));
   }
 
   visitSuperPropertyGet(SuperPropertyGet node) {
-    return new SuperPropertyGet(node.name, redirect(node.interfaceTarget));
+    return new SuperPropertyGet(node.name, redirect(null, node.interfaceTarget, null));
   }
 
   visitSuperPropertySet(SuperPropertySet node) {
     return new SuperPropertySet(
-        node.name, clone(node.value), redirect(node.interfaceTarget));
+        node.name, clone(node.value), redirect(null, node.interfaceTarget, null));
   }
 
   visitStaticGet(StaticGet node) {
-    return new StaticGet(redirect(node.target));
+    return new StaticGet(redirect(null, node.target, null));
   }
 
   visitStaticSet(StaticSet node) {
-    return new StaticSet(redirect(node.target), clone(node.value));
+    return new StaticSet(redirect(null, node.target, null), clone(node.value));
   }
 
   visitMethodInvocation(MethodInvocation node) {
-    return new MethodInvocation(clone(node.receiver), node.name,
-        clone(node.arguments), redirect(node.interfaceTarget));
+    final receiver = clone(node.receiver);
+    final arguments = clone(node.arguments);
+    return new MethodInvocation(receiver, node.name,
+        arguments, redirect(receiver, node.interfaceTarget, arguments));
   }
 
   visitDirectMethodInvocation(DirectMethodInvocation node) {
+    final receiver = clone(node.receiver);
+    final arguments = clone(node.arguments);
     return new DirectMethodInvocation(
-        clone(node.receiver), redirect(node.target), clone(node.arguments));
+        receiver, redirect(receiver, node.target, arguments), arguments);
   }
 
   visitSuperMethodInvocation(SuperMethodInvocation node) {
+    final arguments = clone(node.arguments);
     return new SuperMethodInvocation(
-        node.name, clone(node.arguments), redirect(node.interfaceTarget));
+        node.name, arguments, redirect(null, node.interfaceTarget, clone(node.arguments)));
   }
 
   visitStaticInvocation(StaticInvocation node) {
-    return new StaticInvocation(redirect(node.target), clone(node.arguments),
+    final arguments = clone(node.arguments);
+    return new StaticInvocation(redirect(null, node.target, arguments), arguments,
         isConst: node.isConst);
   }
 
   @override
   visitConstructorInvocation(ConstructorInvocation node) {
-    final target = redirect(node.target);
-    final Arguments arguments = clone(node.arguments);
-
-    final klass = node.target.enclosingClass;
-
-    if (arguments.types.length != target.enclosingClass.typeParameters.length) {
-      var j = 0;
-      for (var i = 0; i < arguments.types.length; i++) {
-        if (!typeSubstitution.containsKey(klass.typeParameters[i])) {
-          arguments.types[j++] = arguments.types[i];
-        }
-      }
-      arguments.types.length = j;
-    }
-
     return new ConstructorInvocation(
-        target, arguments,
+        node.target, clone(node.arguments),
         isConst: node.isConst);
   }
 
   visitFieldInitializer(FieldInitializer node) {
-    return new FieldInitializer(redirect(node.field), clone(node.value));
+    final receiver = clone(node.value);
+    return new FieldInitializer(redirect(receiver, node.field, null), receiver);
   }
 
   visitSuperInitializer(SuperInitializer node) {
     return new SuperInitializer(
-        redirect(node.target), visitArguments(node.arguments));
+        redirect(null, node.target, node.arguments), visitArguments(node.arguments));
   }
 
   visitNot(Not node) {
@@ -640,6 +444,10 @@
   }
 
   visitTypeParameter(TypeParameter node) {
+    if (typeSubstitution.containsKey(node)) {
+      return null;
+    }
+
     var newNode = new TypeParameter(node.name);
     typeSubstitution[node] = new TypeParameterType(newNode);
     newNode.bound = visitType(node.bound);
@@ -649,7 +457,7 @@
   TreeNode cloneFunctionNodeBody(FunctionNode node) => cloneOptional(node.body);
 
   visitFunctionNode(FunctionNode node) {
-    var typeParameters = node.typeParameters.map(clone).toList();
+    var typeParameters = node.typeParameters.map(visitTypeParameter).where((node) => node != null).toList();
     var positional = node.positionalParameters.map(clone).toList();
     var named = node.namedParameters.map(clone).toList();
     return new FunctionNode(cloneFunctionNodeBody(node),
diff --git a/pkg/kernel/lib/transformations/treeshaker.dart b/pkg/kernel/lib/transformations/treeshaker.dart
index 7ebe688..45f23be 100644
--- a/pkg/kernel/lib/transformations/treeshaker.dart
+++ b/pkg/kernel/lib/transformations/treeshaker.dart
@@ -1247,7 +1247,7 @@
         }
       }
     }
-    throw 'External procedure has no @ExternalName("...") annotation!';
+    throw 'External procedure ${procedure} has no @ExternalName("...") annotation! (${procedure.annotations})';
   }
   return null;
 }
diff --git a/pkg/kernel/lib/transformations/vip_instantiate_generics.dart b/pkg/kernel/lib/transformations/vip_instantiate_generics.dart
index cec8abc..c8c9805 100644
--- a/pkg/kernel/lib/transformations/vip_instantiate_generics.dart
+++ b/pkg/kernel/lib/transformations/vip_instantiate_generics.dart
@@ -62,7 +62,7 @@
   final Set<InterfaceType> nestedTypes = new Set<InterfaceType>();
   final Set<MethodInstantiation> invocations = new Set<MethodInstantiation>();
 
-  final Set<Instantiation> instantiations = new Set<Instantiation>();
+  final Set<TypeKindVector> instantiations = new Set<TypeKindVector>();
 
   Generic(this.original, this.typeParameters);
 
@@ -79,19 +79,23 @@
   }
 
   void dump([String prefix = '']) {
+    if (instantiations.isEmpty) {
+      return;
+    }
+
     print('${prefix}-- ${this}');
-    for (var type in nestedTypes) {
-      print('${prefix}  type ${type}');
-    }
-    for (var invocation in invocations) {
-      print('${prefix}  invoke ${invocation}');
-    }
+    print('${prefix}## ${instantiations}');
+    // for (var type in nestedTypes) {
+    //  print('${prefix}  type ${type}');
+    // }
+    // for (var invocation in invocations) {
+    //  print('${prefix}  invoke ${invocation}');
+    // }
   }
 }
 
 class GenericClass extends Generic<Class> {
-  final List<GenericProcedure> nestedGenerics =
-      new List<GenericProcedure>();
+  final List<GenericProcedure> nestedGenerics = new List<GenericProcedure>();
 
   GenericClass(Class original, Set<TypeParameter> typeParameters)
       : super(original, typeParameters);
@@ -116,6 +120,7 @@
 class FixPointGenericInstantiator extends RecursiveVisitor {
   final Program program;
   final TypeKindAnalyzer typeKindAnalyzer;
+  TypeEnvironment environment;
 
   final Set<Instantiation> instantiations = new Set<Instantiation>();
   List<Instantiation> worklist = <Instantiation>[];
@@ -129,27 +134,23 @@
 
   FixPointGenericInstantiator(CoreTypes coreTypes, this.program)
       : typeKindAnalyzer = new TypeKindAnalyzer(program, coreTypes) {
-    processProgram();
-
-    for (var generic in generics.values) {
-      generic.dump();
-    }
-
-    processWorklist();
-    print(instantiations);
+    _typeSubstitutor = new TypeSubstitutor(this);
   }
 
   void processWorklist() {
     while (worklist.isNotEmpty) {
       final current = worklist;
-      worklist = <Instantiation<Generic>>[];
+      worklist = <Instantiation>[];
 
       for (var insn in current) {
         if (insn.klass is GenericClass) {
           instantiateClass(insn);
-        } else {
-          assert(insn.klass is String, "expected String, got ${insn.klass}");
+        } else if (insn.klass is String) {
           instantiateMethods(insn);
+        } else if (insn.klass is Procedure) {
+          instantiateProcedure(insn);
+        } else {
+          throw "Unexexpected ${insn}";
         }
       }
     }
@@ -178,38 +179,74 @@
 
   void instantiateMethods(Instantiation<String> insn) {
     for (var generic in genericMethods[insn.klass] ?? const <Null>[]) {
-      generic.instantiations.add(insn);
+      instantiateProcedureImpl(generic, insn.types);
+    }
+  }
 
-      final s = Substitution.fromMap(makeSubstitution(
-          generic.original.function.typeParameters, insn.types));
+  void instantiateProcedure(Instantiation<Procedure> insn) {
+    final generic = generics[insn.klass] as GenericProcedure;
+    instantiateProcedureImpl(generic, insn.types);
+  }
 
-      final hostS = [Substitution.empty];
-      if (generic.parent != null) {
-        hostS.addAll(generic.parent.instantiations.map(substitutionFromClassInstantiation));
-      }
+  void instantiateProcedureImpl(GenericProcedure generic, TypeKindVector v) {
+    generic.instantiations.add(v);
 
-      for (var s2 in hostS) {
-        final s3 = Substitution.combine(s, s2);
-        for (var type in generic.nestedTypes) {
-          final newType = s3.substituteType(type);
-          if (type != newType) {
-            recordClassInstantiation(newType);
-          }
+    final s = Substitution.fromMap(
+        makeSubstitution(generic.original.function.typeParameters, v.kinds));
+
+    final hostS = [Substitution.empty];
+    if (generic.parent != null) {
+      hostS.addAll(generic.parent.instantiations
+          .map((v) => substitutionForClassInstantiation(generic.parent, v)));
+    }
+
+    for (var s2 in hostS) {
+      final s3 = Substitution.combine(s, s2);
+      for (var type in generic.nestedTypes) {
+        final newType = s3.substituteType(type);
+        if (type != newType) {
+          recordClassInstantiation(newType);
         }
       }
     }
   }
 
-  Substitution substitutionFromClassInstantiation(Instantiation<GenericClass> insn) => Substitution.fromMap(
-        makeSubstitution(insn.klass.original.typeParameters, insn.types));
+  Substitution substitutionFromClassInstantiation(
+          Instantiation<GenericClass> insn) =>
+      Substitution.fromMap(makeSubstitution(
+          insn.klass.original.typeParameters, insn.types.kinds));
 
-  Substitution Function(Instantiation) substitutionFromMethodInstantiation(GenericProcedure generic) {
-    return (Instantiation insn) => Substitution.fromMap(makeSubstitution(generic.original.function.typeParameters, insn.types));
+  Substitution substitutionForClassInstantiation(
+          GenericClass klass, TypeKindVector v) =>
+      Substitution
+          .fromMap(makeSubstitution(klass.original.typeParameters, v.kinds));
+
+  Substitution Function(TypeKindVector) substitutionFromMethodInstantiation(
+      GenericProcedure generic) {
+    return (TypeKindVector v) => Substitution.fromMap(
+        makeSubstitution(generic.original.function.typeParameters, v.kinds));
   }
 
+  List<DartType> substituteList(List<DartType> types, Substitution s) {
+    List<DartType> result = null;
+    for (var i = 0; i < types.length; i++) {
+      final type = types[i];
+      final newType = s.substituteType(type);
+      if (type != newType && result == null) {
+        result = new List<DartType>(types.length);
+        result.setRange(0, i, types);
+      }
+
+      if (result != null) {
+        result[i] = newType;
+      }
+    }
+
+    return result;
+  }
 
   void instantiateClass(Instantiation<GenericClass> insn) {
-    insn.klass.instantiations.add(insn);
+    insn.klass.instantiations.add(insn.types);
 
     final s = substitutionFromClassInstantiation(insn);
     for (var type in insn.klass.nestedTypes) {
@@ -219,8 +256,18 @@
       }
     }
 
+    for (var invoke in insn.klass.invocations) {
+      final args = substituteList(invoke.typeArguments, s);
+      if (args == null) {
+        continue;
+      }
+      assert(anyPrimitives(args));
+      recordMethodInstantiation(new MethodInstantiation(invoke.name, args));
+    }
+
     for (var generic in insn.klass.nestedGenerics) {
-      for (var s2 in [Substitution.empty]..addAll(generic.instantiations.map(substitutionFromMethodInstantiation(generic)))) {
+      for (var s2 in [Substitution.empty]..addAll(generic.instantiations
+          .map(substitutionFromMethodInstantiation(generic)))) {
         final s3 = Substitution.combine(s, s2);
         for (var type in generic.nestedTypes) {
           final newType = s3.substituteType(type);
@@ -230,6 +277,15 @@
         }
       }
     }
+
+    for (var procedure in insn.klass.original.procedures) {
+      if (procedure.isFactory) {
+        GenericProcedure generic = generics[procedure];
+        if (generic.instantiations.add(insn.types)) {
+          instantiateProcedureImpl(generic, insn.types);
+        }
+      }
+    }
   }
 
   GenericClass currentClass;
@@ -264,6 +320,9 @@
                   currentMethod.typeParameters.contains(dt.parameter)))
           ? currentMethod
           : currentClass;
+      if (method.name == 'runUnary') {
+        print('${current} <-- ${method}');
+      }
       current.invocations.add(method);
     } else if (anyPrimitives(method.typeArguments)) {
       // Instantiate eagerly.
@@ -274,8 +333,8 @@
   void recordClassInstantiation(InterfaceType type) {
     final generic = toGenericClass(type.classNode);
     final insn = new Instantiation<GenericClass>(
-        generic, typesToKinds(type.typeArguments));
-    assert(insn.types.any((k) => k != TypeKind.Reference));
+        generic, new TypeKindVector(typesToKinds(type.typeArguments)));
+    assert(insn.types.kinds.any((k) => k != TypeKind.Reference));
 
     // Check if already instantiated.
     if (!instantiations.add(insn)) {
@@ -285,10 +344,13 @@
     worklist.add(insn);
   }
 
-  void recordMethodInstantiation(MethodInstantiation method) {
-    final insn = new Instantiation<String>(
-        method.name, typesToKinds(method.typeArguments));
-    assert(insn.types.any((k) => k != TypeKind.Reference));
+  void recordMethodInstantiation<T>(MethodInstantiation<T> method) {
+    final kinds = new TypeKindVector(typesToKinds(method.typeArguments));
+    final name = method.name;
+    final insn = name is String
+        ? new Instantiation<String>(name, kinds)
+        : new Instantiation<Procedure>(name as Procedure, kinds);
+    assert(insn.types.kinds.any((k) => k != TypeKind.Reference));
 
     if (!instantiations.add(insn)) {
       return;
@@ -343,7 +405,566 @@
     currentClass = null;
   }
 
+  static String _letter(TypeKind kind) {
+    switch (kind) {
+      case TypeKind.Reference:
+        return "R";
+      case TypeKind.Double:
+        return "D";
+      case TypeKind.Integer:
+        return "I";
+    }
+  }
+
+  static String _abbreviateList(List<TypeKind> v) => v.map(_letter).join();
+
+  static String _abbreviate(TypeKindVector v) => _abbreviateList(v.kinds);
+
+  static String _className(Instantiation<Class> insn) =>
+      "${insn.klass.name}%${_abbreviate(insn.types)}";
+
+  final Map<Instantiation<Class>, Class> mapping =
+      <Instantiation<Class>, Class>{};
+
+  final Map<Class, Instantiation<Class>> instantiatedClasses =
+      <Class, Instantiation<Class>>{};
+
+  _createInstantiation(Instantiation<Class> instantiation) {
+    final clone = new Class(
+        name: _className(instantiation),
+        isAbstract: instantiation.klass.isAbstract,
+        fileUri: instantiation.klass.fileUri);
+    instantiation.klass.enclosingLibrary.addClass(clone);
+    mapping[instantiation] = clone;
+    instantiatedClasses[clone] = instantiation;
+  }
+
+  _substitutionFromInstantiation(Instantiation<Class> instantiation) =>
+      Substitution.fromMap(makeSubstitution(
+          instantiation.klass.typeParameters, instantiation.types.kinds));
+
+  Map<Instantiation<Class>, Map<TreeNode, TreeNode>> outlineMappings =
+      <Instantiation<Class>, Map<TreeNode, TreeNode>>{};
+  Map<Instantiation<Class>, Map<TypeParameter, DartType>> typeSubstitutions =
+      <Instantiation<Class>, Map<TypeParameter, DartType>>{};
+
+  static String findNativeName(Member procedure) {
+    // Native procedures are marked as external and have an annotation,
+    // which looks like this:
+    //
+    //    import 'dart:_internal' as internal;
+    //
+    //    @internal.ExternalName("<name-of-native>")
+    //    external Object foo(arg0, ...);
+    //
+    if (procedure.isExternal) {
+      for (final Expression annotation in procedure.annotations) {
+        if (annotation is ConstructorInvocation) {
+          final Class klass = annotation.target.enclosingClass;
+          if (klass.name == 'ExternalName' &&
+              klass.enclosingLibrary.importUri.toString() == 'dart:_internal') {
+            assert(annotation.arguments.positional.length == 1);
+            return (annotation.arguments.positional[0] as StringLiteral).value;
+          }
+        } else if (annotation is ConstantExpression) {
+          final Constant constant = annotation.constant;
+          if (constant is InstanceConstant) {
+            final Class klass = constant.klass;
+            if (klass.name == 'ExternalName' &&
+                klass.enclosingLibrary.importUri.toString() ==
+                    'dart:_internal') {
+              assert(constant.fieldValues.length == 1);
+              return (constant.fieldValues.values.first as StringConstant)
+                  .value;
+            }
+          }
+        }
+      }
+      throw 'External procedure ${procedure} has no @ExternalName("...") annotation!';
+    }
+    return null;
+  }
+
+  TreeNode createNativeName(String name) {
+    return new ConstructorInvocation(
+        coreTypes.getMember('dart:_internal', 'ExternalName', ''),
+        new Arguments([new StringLiteral(name)]));
+  }
+
+  TypeSubstitutor _typeSubstitutor;
+  _createOutline(Instantiation<Class> instantiation) {
+    final outlineMapping =
+        outlineMappings[instantiation] ??= <TreeNode, TreeNode>{};
+
+    final original = instantiation.klass;
+    final cloned = mapping[instantiation];
+
+    final substitutionMapping = typeSubstitutions[instantiation] ??=
+        makeSubstitution(
+            instantiation.klass.typeParameters, instantiation.types.kinds);
+    final substitution = Substitution.fromMap(substitutionMapping);
+
+    DartType substituteType(DartType type) =>
+        _typeSubstitutor.visit(substitution.substituteType(type));
+
+    substituteSupertype(Supertype type) {
+      if (type != null) {
+        type = _typeSubstitutor
+            .visitSupertype(substitution.substituteSupertype(type));
+      }
+      return type;
+    }
+
+    TypeParameter cloneTypeParameter(TypeParameter param) {
+      var newNode = new TypeParameter(param.name);
+      substitutionMapping[param] = new TypeParameterType(newNode);
+      newNode.bound = _typeSubstitutor.visit(param.bound);
+      return newNode;
+    }
+
+    cloned.typeParameters
+        .addAll(original.typeParameters.map((TypeParameter param) {
+      return !substitutionMapping.containsKey(param)
+          ? cloneTypeParameter(param)
+          : null;
+    }).where((p) => p != null));
+    for (var param in cloned.typeParameters) param.parent = cloned;
+
+    cloned.supertype = substituteSupertype(original.supertype);
+    cloned.mixedInType = substituteSupertype(original.mixedInType);
+    cloned.implementedTypes
+        .addAll(original.implementedTypes.map(substituteSupertype));
+
+    cloneConstructor(Constructor node) {
+      return outlineMapping[node] = new Constructor(null,
+          name: node.name,
+          isConst: node.isConst,
+          isExternal: node.isExternal,
+          transformerFlags: node.transformerFlags)
+        ..fileEndOffset = node.fileEndOffset;
+    }
+
+    cloneProcedure(Procedure node) {
+      final Procedure proc = new Procedure(node.name, node.kind, null,
+          isAbstract: node.isAbstract,
+          isStatic: node.isStatic,
+          isExternal: node.isExternal,
+          isConst: node.isConst,
+          transformerFlags: node.transformerFlags,
+          fileUri: node.fileUri)
+        ..fileEndOffset = node.fileEndOffset;
+
+      final FunctionNode func = node.function;
+
+      Map<TypeParameter, DartType> s = substitutionMapping;
+      List<TypeParameter> typeParameters;
+      if (func.typeParameters.isNotEmpty) {
+        s = new Map<TypeParameter, DartType>.from(s);
+        if (node.isFactory) {
+          typeParameters = cloned.typeParameters
+              .map((p) => new TypeParameter(p.name, p.bound))
+              .toList();
+          var j = 0;
+          for (var i = 0; i < func.typeParameters.length; i++) {
+            final type = substitutionMapping[original.typeParameters[i]];
+            s[func.typeParameters[i]] = type is TypeParameterType
+                ? new TypeParameterType(typeParameters[j++])
+                : type;
+          }
+          assert(j == typeParameters.length);
+        } else {
+          typeParameters = func.typeParameters
+              .map((p) =>
+                  new TypeParameter(p.name, _typeSubstitutor.visit(p.bound)))
+              .toList();
+          for (var i = 0; i < func.typeParameters.length; i++) {
+            s[func.typeParameters[i]] =
+                new TypeParameterType(typeParameters[i]);
+          }
+        }
+      }
+
+      final cloner = new CloneVisitor(
+          typeSubstitution: s, typeSubstitutor: _typeSubstitutor.visit);
+
+      var positional = func.positionalParameters.map(cloner.clone).toList();
+      var named = func.namedParameters.map(cloner.clone).toList();
+
+      proc.function = new FunctionNode(null,
+          typeParameters: typeParameters,
+          positionalParameters: positional,
+          namedParameters: named,
+          requiredParameterCount: func.requiredParameterCount,
+          returnType: cloner.visitType(func.returnType),
+          asyncMarker: func.asyncMarker,
+          dartAsyncMarker: func.dartAsyncMarker)
+        ..fileEndOffset = node.fileEndOffset;
+      proc.function.parent = proc;
+
+      return outlineMapping[node] = proc;
+    }
+
+    cloneField(Field node) {
+      return outlineMapping[node] = new Field(node.name,
+          type: substituteType(node.type),
+          initializer: null,
+          isFinal: node.isFinal,
+          isConst: node.isConst,
+          isStatic: node.isStatic,
+          hasImplicitGetter: node.hasImplicitGetter,
+          hasImplicitSetter: node.hasImplicitSetter,
+          transformerFlags: node.transformerFlags,
+          fileUri: node.fileUri)
+        ..fileEndOffset = node.fileEndOffset;
+    }
+
+    cloned.constructors.addAll(original.constructors.map(cloneConstructor));
+    setParents(cloned.constructors, cloned);
+
+    cloned.procedures.addAll(original.procedures.map(cloneProcedure));
+    setParents(cloned.procedures, cloned);
+
+    cloned.fields.addAll(original.fields.map(cloneField));
+    setParents(cloned.fields, cloned);
+  }
+
+  final Map<Instantiation<Procedure>, Procedure> instantiatedMethods =
+      <Instantiation<Procedure>, Procedure>{};
+  final Map<Procedure, Instantiation<Procedure>> methodToInstantiation =
+      new Map<Procedure, Instantiation<Procedure>>();
+
+  Map<TypeParameter, DartType> _createSubstitutionForMethod(
+      Map<TypeParameter, DartType> host,
+      Instantiation<Procedure> instantiation,
+      List<TypeParameter> clone) {
+    final FunctionNode original = instantiation.klass.function;
+    Map<TypeParameter, DartType> s =
+        makeSubstitution(original.typeParameters, instantiation.types.kinds);
+    if (host != null) {
+      s.addAll(host);
+    }
+    var j = 0;
+    for (var i = 0; i < original.typeParameters.length; i++) {
+      s[original.typeParameters[i]] ??= new TypeParameterType(clone[j++]);
+    }
+    assert(j == clone.length);
+    return s;
+  }
+
+  _createProcedureOutline(Instantiation<Procedure> instantiation) {
+    final Procedure proc = instantiation.klass;
+    assert(!proc.isFactory);
+
+    print('${instantiation}');
+
+    cloneProcedure(Procedure node) {
+      final Procedure proc = new Procedure(
+          new Name("${node.name.name}%${_abbreviate(instantiation.types)}",
+              node.name.library),
+          node.kind,
+          null,
+          isAbstract: node.isAbstract,
+          isStatic: node.isStatic,
+          isExternal: node.isExternal,
+          isConst: node.isConst,
+          transformerFlags: node.transformerFlags,
+          fileUri: node.fileUri)
+        ..fileEndOffset = node.fileEndOffset;
+
+      final FunctionNode func = node.function;
+
+      List<TypeParameter> typeParameters = [];
+      for (var i = 0; i < func.typeParameters.length; i++) {
+        if (instantiation.types.kinds[i] == TypeKind.Reference) {
+          typeParameters.add(new TypeParameter(
+              func.typeParameters[i].name, func.typeParameters[i].bound));
+        }
+      }
+      final Map<TypeParameter, DartType> s =
+          _createSubstitutionForMethod(null, instantiation, typeParameters);
+
+      final cloner = new CloneVisitor(
+          typeSubstitution: s, typeSubstitutor: _typeSubstitutor.visit);
+
+      var positional = func.positionalParameters.map(cloner.clone).toList();
+      var named = func.namedParameters.map(cloner.clone).toList();
+
+      proc.function = new FunctionNode(null,
+          typeParameters: typeParameters,
+          positionalParameters: positional,
+          namedParameters: named,
+          requiredParameterCount: func.requiredParameterCount,
+          returnType: cloner.visitType(func.returnType),
+          asyncMarker: func.asyncMarker,
+          dartAsyncMarker: func.dartAsyncMarker)
+        ..fileEndOffset = node.fileEndOffset;
+      proc.function.parent = proc;
+
+      methodToInstantiation[proc] = instantiation;
+      instantiatedMethods[instantiation] = proc;
+      return proc;
+    }
+
+    final clone = cloneProcedure(proc);
+    if (proc.enclosingClass != null) {
+      proc.enclosingClass.addMember(clone);
+    } else {
+      proc.enclosingLibrary.addMember(clone);
+    }
+  }
+
+  _fillProcedureOutline(Instantiation<Procedure> instantiation) {
+    final procClone = instantiatedMethods[instantiation];
+    assert(procClone != null);
+
+    Member redirect(TreeNode receiver, Member m, Arguments arguments) {
+      if (m is Constructor) {} else if (m is Procedure) {
+        if (m.isFactory) {
+          return m;
+        }
+      }
+      return m;
+    }
+
+    fillProcedure(Procedure node) {
+      final FunctionNode funcClone = procClone.function;
+
+      Map<TypeParameter, DartType> s = _createSubstitutionForMethod(
+          null, instantiation, funcClone.typeParameters);
+
+      final cloner = new RedirectingCloneVisitor(this,
+          typeSubstitution: s,
+          memberSubstitutor: redirect,
+          typeSubstitutor: _typeSubstitutor.visit);
+
+      final originalFunction = instantiation.klass.function;
+
+      for (var i = 0; i < originalFunction.positionalParameters.length; i++) {
+        cloner.variables[originalFunction.positionalParameters[i]] =
+            funcClone.positionalParameters[i];
+      }
+
+      for (var i = 0; i < originalFunction.namedParameters.length; i++) {
+        cloner.variables[originalFunction.namedParameters[i]] =
+            funcClone.namedParameters[i];
+      }
+
+      funcClone.body = cloner.cloneFunctionNodeBody(originalFunction);
+      funcClone.body?.parent = funcClone;
+    }
+
+    fillProcedure(instantiation.klass);
+  }
+
+  _fillMembers(Instantiation<Class> instantiation) {
+    final original = instantiation.klass;
+    final cloned = mapping[instantiation];
+
+    final outlineMapping = outlineMappings[instantiation];
+    final Map<TypeParameter, DartType> substitution =
+        typeSubstitutions[instantiation];
+
+    environment.thisType = cloned.thisType;
+
+
+    Member redirect(TreeNode receiver, Member m, Arguments arguments) {
+      if (m is Constructor) {} else if (m is Procedure) {
+        if (m.isFactory) {
+          return m;
+        }
+      }
+      return m;
+    }
+
+    final clone = new RedirectingCloneVisitor(this,
+        typeSubstitution: substitution,
+        memberSubstitutor: redirect,
+        typeSubstitutor: (t) => _typeSubstitutor.visit(t)).clone;
+
+    fillConstructor(Constructor node) {
+      final Constructor nodeClone = outlineMapping[node];
+      nodeClone.function = clone(node.function);
+      nodeClone.function?.parent = nodeClone;
+      nodeClone.initializers = node.initializers.map(clone).toList();
+      setParents(nodeClone.initializers, nodeClone);
+    }
+
+    fillProcedure(Procedure node) {
+      final Procedure procClone = outlineMapping[node];
+      final FunctionNode funcClone = procClone.function;
+
+      final Instantiation<Procedure> methodInstantiation =
+          methodToInstantiation[node];
+
+      Map<TypeParameter, DartType> s = substitution;
+      if (node.isFactory) {
+        assert(node.function.typeParameters.length ==
+            original.typeParameters.length);
+        assert(substitution.length <= original.typeParameters.length);
+        s = new Map<TypeParameter, DartType>();
+        var j = 0;
+        for (var i = 0; i < node.function.typeParameters.length; i++) {
+          final type = substitution[original.typeParameters[i]];
+          assert(type != null);
+          s[node.function.typeParameters[i]] = type is TypeParameterType
+              ? new TypeParameterType(funcClone.typeParameters[j++])
+              : type;
+        }
+        assert(j == funcClone.typeParameters.length);
+      } else if (methodInstantiation != null) {
+        s = _createSubstitutionForMethod(
+            substitution, methodInstantiation, funcClone.typeParameters);
+      } else if (node.function.typeParameters.isNotEmpty) {
+        s = new Map<TypeParameter, DartType>.from(substitution);
+        for (var i = 0; i < node.function.typeParameters.length; i++) {
+          s[node.function.typeParameters[i]] =
+              new TypeParameterType(funcClone.typeParameters[i]);
+        }
+      }
+
+      final originalFunction = methodInstantiation != null
+          ? methodInstantiation.klass.function
+          : node.function;
+
+      final externalName = findNativeName(node);
+      if (externalName != null) {
+        final tp = <TypeParameter>[];
+        if (!node.isFactory) {
+          tp.addAll(original.typeParameters);
+        }
+        tp.addAll(originalFunction.typeParameters);
+        final suffix =
+            _abbreviateList(typesToKinds(tp.map((t) => s[t]).toList()));
+        procClone.addAnnotation(createNativeName('${externalName}%${suffix}'));
+      }
+
+      final cloner = new RedirectingCloneVisitor(this,
+          typeSubstitution: s,
+          memberSubstitutor: redirect,
+          typeSubstitutor: _typeSubstitutor.visit);
+
+      for (var i = 0; i < originalFunction.positionalParameters.length; i++) {
+        cloner.variables[originalFunction.positionalParameters[i]] =
+            funcClone.positionalParameters[i];
+      }
+
+      for (var i = 0; i < originalFunction.namedParameters.length; i++) {
+        cloner.variables[originalFunction.namedParameters[i]] =
+            funcClone.namedParameters[i];
+      }
+
+      funcClone.body = cloner.cloneFunctionNodeBody(originalFunction);
+      funcClone.body?.parent = funcClone;
+    }
+
+    fillField(Field node) {
+      final Field nodeClone = outlineMapping[node];
+      nodeClone.initializer =
+          node.initializer != null ? clone(node.initializer) : null;
+      nodeClone.initializer?.parent = nodeClone;
+    }
+
+    original.constructors.forEach(fillConstructor);
+    original.procedures.forEach(fillProcedure);
+    original.fields.forEach(fillField);
+  }
+
   void processProgram() {
+    collectGenerics();
+
+    processWorklist();
+
+    for (var generic in generics.values) {
+      if (generic is GenericProcedure) {
+        print('${generic}: ${generic.instantiations}');
+      }
+    }
+
+    // Ready to instantiate all classes and methods.
+    for (var generic in generics.values) {
+      if (generic is GenericClass) {
+        final klass = generic.original;
+        for (var v in generic.instantiations) {
+          final instantiation = new Instantiation(klass, v);
+          _createInstantiation(instantiation);
+        }
+      } else if (generic is GenericProcedure &&
+          generic.parent == null &&
+          !generic.original.isFactory) {
+        // Instantiate.
+      }
+    }
+
+    for (var generic in generics.values) {
+      if (generic is GenericClass) {
+        final klass = generic.original;
+
+        // First instantiate all instance method generics.
+        for (var method in generic.nestedGenerics) {
+          for (var v in method.instantiations) {
+            final instantiation = new Instantiation(method.original, v);
+            _createProcedureOutline(instantiation);
+          }
+        }
+
+        for (var v in generic.instantiations) {
+          final instantiation = new Instantiation(klass, v);
+          _createOutline(instantiation);
+        }
+      } else if (generic is GenericProcedure &&
+          generic.parent == null &&
+          !generic.original.isFactory) {
+        // Instantiate.
+        for (var v in generic.instantiations) {
+          _createProcedureOutline(new Instantiation(generic.original, v));
+        }
+      }
+    }
+
+    environment = new TypeEnvironment(
+        coreTypes, new ClosedWorldClassHierarchy(program),
+        strongMode: true);
+
+    for (var generic in generics.values) {
+      if (generic is GenericClass) {
+        final klass = generic.original;
+        for (var v in generic.instantiations) {
+          final instantiation = new Instantiation(klass, v);
+          _fillMembers(instantiation);
+        }
+      } else if (generic is GenericProcedure &&
+          generic.parent == null &&
+          !generic.original.isFactory) {
+        for (var v in generic.instantiations) {
+          _fillProcedureOutline(new Instantiation(generic.original, v));
+        }
+      }
+    }
+
+    // All generics are instantiated. Now fix the code.
+    final visitor = new RedirectingVisitor(this);
+    for (var library in program.libraries) {
+      for (var klass in library.classes) {
+        environment.thisType = klass.thisType;
+        try {
+          klass.accept(visitor);
+        } catch (e) {
+          print('Error while visiting ${klass}');
+          rethrow;
+        }
+      }
+
+      for (var procedure in library.procedures) {
+        procedure.accept(visitor);
+      }
+
+      for (var field in library.fields) {
+        field.accept(visitor);
+      }
+    }
+  }
+
+  void collectGenerics() {
     for (var library in program.libraries) {
       library.classes.toList(growable: false).forEach(processClass);
 
@@ -371,6 +992,7 @@
     if (node.typeParameters.isNotEmpty && node.parent is! Member) {
       throw "Generic closures are currently unsupported";
     }
+    return super.visitFunctionNode(node);
   }
 
   @override
@@ -381,123 +1003,52 @@
       handleInterfaceType(new InterfaceType(klass, types.toList()),
           where: node);
     }
+    return super.visitConstructorInvocation(node);
   }
 
   @override
   visitMethodInvocation(MethodInvocation node) {
     final List<DartType> types = node.arguments.types;
     if (types.isNotEmpty) {
-      handleMethodInstantiation(new MethodInstantiation(node.name.name, types));
-    }
-  }
-
-  List<TypeKind> typesToKinds(List<DartType> types,
-          {ignoreTypeParameters: true}) =>
-      types.map((t) {
-        return (ignoreTypeParameters && t is TypeParameterType)
-            ? TypeKind.Reference
-            : typeKindAnalyzer.toTypeKind(t);
-      }).toList(growable: false);
-
-  static bool containsPrimitives(List<TypeKind> kinds) =>
-      kinds.any((k) => k != TypeKind.Reference);
-}
-
-class GenericInstantiator extends RecursiveVisitor {
-  final Program program;
-  final TypeKindAnalyzer typeKindAnalyzer;
-
-  CoreTypes get coreTypes => typeKindAnalyzer.coreTypes;
-
-  GenericInstantiator(CoreTypes coreTypes, this.program)
-      : typeKindAnalyzer = new TypeKindAnalyzer(program, coreTypes) {
-    processProgram();
-  }
-
-  void processClass(Class class_) {
-    for (var constructor in class_.constructors) {
-      constructor.accept(this);
+      handleMethodInstantiation(
+          new MethodInstantiation<String>(node.name.name, types));
     }
 
-    for (var procedure in class_.procedures) {
-      procedure.accept(this);
-    }
-
-    class_.fields.forEach((f) => f.accept(this));
+    return super.visitMethodInvocation(node);
   }
 
-  void processProgram() {
-    for (var library in program.libraries) {
-      library.classes.toList(growable: false).forEach(processClass);
-
-      for (var procedure in library.procedures) {
-        procedure.accept(this);
-      }
-
-      for (var field in library.fields) {
-        field.accept(this);
-      }
-    }
-
-    //drainWorklist();
-  }
-
-  final Set<MethodInvocation> instantiated = new Set<MethodInvocation>();
-
   @override
   visitStaticInvocation(StaticInvocation node) {
-    final types = node.arguments.types;
+    final List<DartType> types = node.arguments.types;
     if (types.isNotEmpty) {
-      final kinds = typesToKinds(types, ignoreTypeParameters: true);
-      if (containsPrimitives(kinds)) {
-        final suffix = new List.generate(kinds.length, (idx) {
-          switch (kinds[idx]) {
-            case TypeKind.Reference:
-              return 'R';
-            case TypeKind.Double:
-              return 'D';
-            case TypeKind.Integer:
-              return 'I';
-          }
-        }).join();
-
-        print('>>> static call ${node.name.name}%T(${suffix})');
-        //node.name = new Name('${node.name.name}%T(${suffix})', node.name.library);
-
-        // instantiated.add(node);
+      handleMethodInstantiation(
+          new MethodInstantiation<Procedure>(node.target, types));
+      if (node.target.isFactory) {
+        handleInterfaceType(
+            new InterfaceType(node.target.enclosingClass, types));
       }
     }
+
+    return super.visitStaticInvocation(node);
   }
 
   @override
-  visitMethodInvocation(MethodInvocation node) {
-    final types = node.arguments.types;
+  visitDirectMethodInvocation(DirectMethodInvocation node) {
+    final List<DartType> types = node.arguments.types;
     if (types.isNotEmpty) {
-      // Proceed to instantiate.
-      final kinds = typesToKinds(types, ignoreTypeParameters: true);
-      if (containsPrimitives(kinds)) {
-        final suffix = new List.generate(kinds.length, (idx) {
-          switch (kinds[idx]) {
-            case TypeKind.Reference:
-              return 'R';
-            case TypeKind.Double:
-              return 'D';
-            case TypeKind.Integer:
-              return 'I';
-          }
-        }).join();
-
-        print('>>> method call ${node.name.name}%T(${suffix})');
-        node.name =
-            new Name('${node.name.name}%T(${suffix})', node.name.library);
-
-        instantiated.add(node);
-      }
+      print('method invocation -> ${node}');
     }
+    return super.visitDirectMethodInvocation(node);
   }
 
   @override
-  visitDirectMethodInvocation(DirectMethodInvocation node) {}
+  visitSuperMethodInvocation(SuperMethodInvocation node) {
+    final List<DartType> types = node.arguments.types;
+    if (types.isNotEmpty) {
+      print('super method invocation -> ${node}');
+    }
+    return super.visitSuperMethodInvocation(node);
+  }
 
   List<TypeKind> typesToKinds(List<DartType> types,
           {ignoreTypeParameters: true}) =>
@@ -510,143 +1061,189 @@
   static bool containsPrimitives(List<TypeKind> kinds) =>
       kinds.any((k) => k != TypeKind.Reference);
 
+  Class instantiate(Class klass, List<DartType> typeArguments) {
+    assert(!instantiatedClasses.containsKey(klass), "not implemented yet");
+
+    final kinds = typesToKinds(typeArguments);
+    final clone =
+        mapping[new Instantiation(klass, new TypeKindVector(kinds))] ?? klass;
+    if (clone != klass) {
+      var j = 0;
+      for (var i = 0; i < typeArguments.length; i++) {
+        if (kinds[i] == TypeKind.Reference) {
+          typeArguments[j++] = typeArguments[i];
+        }
+      }
+      typeArguments.length = j;
+    }
+    return clone;
+  }
+
+  Name instantiateName(Name name, List<DartType> typeArguments) {
+    assert(!name.name.contains('%'), 'not implemented yet');
+    final kinds = typesToKinds(typeArguments);
+    if (kinds.any((k) => k != TypeKind.Reference)) {
+      name = new Name("${name.name}%${_abbreviateList(kinds)}", name.library);
+      var j = 0;
+      for (var i = 0; i < typeArguments.length; i++) {
+        if (kinds[i] == TypeKind.Reference) {
+          typeArguments[j++] = typeArguments[i];
+        }
+      }
+      typeArguments.length = j;
+    }
+    return name;
+  }
+
+  final lookupCache = new LookupCache();
+}
+
+class TypeSubstitutor extends DartTypeVisitor<DartType> {
+  final FixPointGenericInstantiator owner;
+
+  TypeSubstitutor(this.owner);
+
+  int changed = 0;
+
+  Class instantiate(Class klass, List<DartType> typeArguments) {
+    final clone = owner.instantiate(klass, typeArguments);
+    if (clone != klass) changed++;
+    return clone;
+  }
+
+  DartType visit(DartType node) {
+    changed = 0;
+    return node.accept(this);
+  }
+
   @override
-  visitConstructorInvocation(ConstructorInvocation node) {
-    final List<DartType> types = node.arguments.types;
-    if (types.isNotEmpty) {
-      final Class klass = node.target.enclosingClass;
-      final kinds = typesToKinds(types, ignoreTypeParameters: true);
-      final Class clone = instantiate(klass, kinds);
-      if (!identical(clone, klass)) {
-        final name = node.target.name;
-        final ctor = clone.constructors.firstWhere((ctor) => ctor.name == name);
-        node.target = ctor;
-        var j = 0;
-        for (var i = 0; i < types.length; i++) {
-          if (kinds[i] == TypeKind.Reference) {
-            types[j++] = types[i];
-          }
-        }
-        types.length = j;
-      }
+  InterfaceType visitInterfaceType(InterfaceType node) {
+    if (node.typeArguments.isEmpty) {
+      return node;
     }
-  }
 
-  final Set<Instantiation> insns = new Set<Instantiation>();
-  List<Instantiation> insnsWorklist = new List<Instantiation>();
-
-  static List<DartType> removePrimitives(
-      List<DartType> types, List<TypeKind> kinds) {
-    List<DartType> result = <DartType>[];
-    for (var i = 0; i < types.length; i++) {
-      if (kinds[i] == TypeKind.Reference) result.add(types[i]);
+    final oldChanged = changed;
+    final typeArguments = node.typeArguments.map(visit).toList();
+    final classNode = instantiate(node.classNode, typeArguments);
+    if (changed != oldChanged) {
+      changed++;
+      return new InterfaceType(classNode, typeArguments);
     }
-    return result;
+    return node;
   }
 
-  InterfaceType instantiateInterfaceType(InterfaceType supertype) {
-    final kinds = typesToKinds(supertype.typeArguments);
-    final klass = instantiate(supertype.classNode, kinds);
-    return new InterfaceType(
-        klass, removePrimitives(supertype.typeArguments, kinds));
+  @override
+  FunctionType visitFunctionType(FunctionType node) {
+    final oldChanged = changed;
+    final List<TypeParameter> typeParameters = node.typeParameters;
+    final int requiredParameterCount = node.requiredParameterCount;
+    final List<DartType> positionalParameters =
+        node.positionalParameters.map(visit).toList();
+    final List<NamedType> namedParameters =
+        node.namedParameters.map(visitNamedType).toList();
+    final DartType returnType = visit(node.returnType);
+    if (oldChanged != changed) {
+      return new FunctionType(positionalParameters, returnType,
+          requiredParameterCount: requiredParameterCount,
+          namedParameters: namedParameters,
+          typeParameters: typeParameters);
+    }
+    return node;
   }
 
-  Class instantiate(Class klass, List<TypeKind> types) {
-    final insn = new Instantiation(klass, types);
-    if (insns.add(insn)) {
-      if (!containsPrimitives(insn.types)) {
-        insn.node = insn.klass;
-        return insn.node;
-      }
+  @override
+  TypeParameterType visitTypeParameterType(TypeParameterType node) {
+    // TODO
+    return node;
+  }
 
-      print('Instantiating ${insn.klass} with ${insn.types}');
-      final orig = insn.klass;
-      final suffix = insn.types.map((t) {
-        switch (t) {
-          case TypeKind.Reference:
-            return "R";
-          case TypeKind.Double:
-            return "D";
-          case TypeKind.Integer:
-            return "I";
-        }
-      }).join('');
+  @override
+  TypedefType visitTypedefType(TypedefType node) {
+    // TODO
+    return node;
+  }
 
-      Map<TypeParameter, DartType> typeSubstitution =
-          <TypeParameter, DartType>{};
-      for (var i = 0; i < orig.typeParameters.length; i++) {
-        final param = orig.typeParameters[i];
-        final kind = insn.types[i];
-        switch (kind) {
-          case TypeKind.Reference:
-            break;
-          case TypeKind.Integer:
-            typeSubstitution[param] = coreTypes.intClass.rawType;
-            break;
-          case TypeKind.Double:
-            typeSubstitution[param] = coreTypes.doubleClass.rawType;
-            break;
-        }
-      }
+  @override
+  InvalidType visitInvalidType(InvalidType node) => node;
 
-      var supertype = Substitution
-          .fromMap(typeSubstitution)
-          .substituteSupertype(klass.supertype);
-      if (supertype != klass.supertype) {}
+  @override
+  DynamicType visitDynamicType(DynamicType node) => node;
 
-      final cloner = new CloneVisitor(
-          typeSubstitution: typeSubstitution,
-          classRenamer: (Class node) {
-            if (identical(node, orig)) {
-              return "${orig.name}%${suffix}";
-            }
-            return node.name;
-          });
+  @override
+  VoidType visitVoidType(VoidType node) => node;
 
-      final clone = cloner.visitClass(orig);
-      orig.enclosingLibrary.addClass(clone);
-      insn.node = clone;
-      insnsWorklist.add(insn);
+  @override
+  BottomType visitBottomType(BottomType node) => node;
 
-      return insn.node;
+  @override
+  VectorType visitVectorType(VectorType node) => node;
+
+  NamedType visitNamedType(NamedType type) {
+    final oldChanged = changed;
+    final DartType t = visit(type.type);
+    if (oldChanged != changed) {
+      return new NamedType(type.name, t);
     } else {
-      return insns.lookup(insn).node;
+      return type;
     }
   }
 
-  void drainWorklist() {
-    while (insnsWorklist.isNotEmpty) {
-      final items = insnsWorklist;
-      insnsWorklist = new List<Instantiation>();
-
-      for (var insn in items) {
-        processClass(insn.node);
-      }
+  Supertype visitSupertype(Supertype node) {
+    changed = 0;
+    if (node.typeArguments.isEmpty) {
+      return node;
     }
+
+    final oldChanged = changed;
+    final List<DartType> typeArguments = node.typeArguments.map(visit).toList();
+    final Class classNode = instantiate(node.classNode, typeArguments);
+    if (oldChanged != changed) {
+      return new Supertype(classNode, typeArguments);
+    }
+    return node;
   }
 }
 
+class GenericInstantiator extends Transformer {
+  final FixPointGenericInstantiator owner;
+  GenericInstantiator(this.owner);
+}
+
+class TypeKindVector {
+  int _hashCode;
+  final List<TypeKind> kinds;
+
+  TypeKindVector(this.kinds);
+
+  bool operator ==(other) {
+    return other is TypeKindVector && _listEquals(this.kinds, other.kinds);
+  }
+
+  int get hashCode => _hashCode ??= _listHash(kinds);
+
+  toString() =>
+      '<' + kinds.map(FixPointGenericInstantiator._letter).join(', ') + '>';
+}
+
 class Instantiation<ClassT> {
   final ClassT klass;
-  final List<TypeKind> types;
-
-  ClassT node;
+  final TypeKindVector types;
 
   Instantiation(this.klass, this.types);
 
   bool operator ==(other) {
     return other is Instantiation<ClassT> &&
         this.klass == other.klass &&
-        _listEquals(this.types, other.types);
+        this.types == other.types;
   }
 
-  int get hashCode => klass.hashCode ^ _listHash(types);
+  int get hashCode => klass.hashCode ^ types.hashCode;
 
-  toString() => 'Instantiation(${klass} with ${types})';
+  String toString() => '${klass}<${types}>';
 }
 
-class MethodInstantiation {
-  final String name;
+class MethodInstantiation<T> {
+  final T name;
   final List<DartType> typeArguments;
 
   MethodInstantiation(this.name, this.typeArguments);
@@ -660,6 +1257,8 @@
 
   @override
   int get hashCode => name.hashCode ^ _listHash(typeArguments);
+
+  toString() => '${name}<${typeArguments}>';
 }
 
 bool _listEquals<T>(List<T> a, List<T> b) {
@@ -677,3 +1276,481 @@
   }
   return result;
 }
+
+class Redirector {
+  final FixPointGenericInstantiator owner;
+
+  Redirector(this.owner);
+
+  Class get currentClass => owner.environment.thisType.classNode;
+
+  Field lookupField(Class klass, Name name) {
+    return klass.fields.firstWhere((f) => f.name == name);
+  }
+
+  Constructor lookupConstructor(Class klass, Name name) {
+    return klass.constructors.firstWhere((ctor) => ctor.name == name);
+  }
+
+  Procedure lookupStatic(Class klass, Name name) {
+    return klass.procedures
+        .firstWhere((proc) => proc.isStatic && proc.name == name);
+  }
+
+  ConstructorInvocation redirectConstructorInvocation(
+      ConstructorInvocation node) {
+    final types = node.arguments.types;
+    if (types.isNotEmpty) {
+      final Class klass = node.target.enclosingClass;
+      final instantiated = owner.instantiate(klass, types);
+      if (instantiated != klass) {
+        node.target = lookupConstructor(instantiated, node.target.name);
+      }
+    }
+    return node;
+  }
+
+  FieldInitializer redirectFieldInitializer(FieldInitializer node) {
+    node.field = lookupField(currentClass, node.field.name);
+    return node;
+  }
+
+  SuperInitializer redirectSuperInitializer(SuperInitializer node) {
+    node.target = lookupConstructor(currentClass.superclass, node.target.name);
+    return node;
+  }
+
+  PropertyGet redirectPropertyGet(PropertyGet node) {
+    if (node.interfaceTarget != null) {
+      final type = node.receiver.getStaticType(owner.environment);
+      if (type is InterfaceType) {
+        final target = owner.lookupCache.lookupInterfaceSelector(
+            type.classNode, new Selector(SelectorKind.Getter, node.name));
+        if (target == null) {
+          print('Was ${node.interfaceTarget} on type ${type} in ${node}');
+        }
+        assert(target != null);
+        node.interfaceTarget = target;
+      }
+    }
+    return node;
+  }
+
+  PropertySet redirectPropertySet(PropertySet node) {
+    if (node.interfaceTarget != null) {
+      final type = node.receiver.getStaticType(owner.environment);
+      if (type is InterfaceType) {
+        final target = owner.lookupCache.lookupInterfaceSelector(
+            type.classNode, new Selector(SelectorKind.Setter, node.name));
+        if (target == null) {
+          print('Was ${node.interfaceTarget} on type ${type} in ${node}');
+        }
+        assert(target != null);
+        node.interfaceTarget = target;
+      }
+    }
+    return node;
+  }
+
+  DirectPropertyGet redirectDirectPropertyGet(DirectPropertyGet node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  DirectPropertySet redirectDirectPropertySet(DirectPropertySet node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  SuperPropertyGet redirectSuperPropertyGet(SuperPropertyGet node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  SuperPropertySet redirectSuperPropertySet(SuperPropertySet node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  StaticGet redirectStaticGet(StaticGet node) {
+    return node;
+  }
+
+  StaticSet redirectStaticSet(StaticSet node) {
+    return node;
+  }
+
+  MethodInvocation redirectMethodInvocation(MethodInvocation node) {
+    if (node.arguments.types.isNotEmpty) {
+      node.name = owner.instantiateName(node.name, node.arguments.types);
+    }
+    assert((node.interfaceTarget != null) || node.interfaceTarget == null);
+    if (node.interfaceTarget != null) {
+      final type = node.receiver.getStaticType(owner.environment);
+      if (type is InterfaceType) {
+        final target = owner.lookupCache.lookupInterfaceSelector(
+            type.classNode, new Selector(SelectorKind.Method, node.name));
+        if (target == null) {
+          print(
+              'Was ${node.interfaceTarget} on type ${type} in ${node} [${node.name}]');
+        }
+        assert(target != null);
+        node.interfaceTarget = target;
+      }
+    }
+    return node;
+  }
+
+  DirectMethodInvocation redirectDirectMethodInvocation(
+      DirectMethodInvocation node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  SuperMethodInvocation redirectSuperMethodInvocation(
+      SuperMethodInvocation node) {
+    assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
+    return node;
+  }
+
+  StaticInvocation redirectStaticInvocation(StaticInvocation node) {
+    final types = node.arguments.types;
+
+    if (types.isNotEmpty) {
+      if (node.target.isFactory) {
+        final Class klass = node.target.enclosingClass;
+        final instantiated = owner.instantiate(klass, types);
+        if (instantiated != klass) {
+          node.target = lookupStatic(instantiated, node.target.name);
+        }
+      } else {
+        // TODO(vegorov) redirect static method properly.
+      }
+    } else {
+      // TODO(vegorov) redirect self methods to the node.
+    }
+
+    return node;
+  }
+}
+
+class RedirectingCloneVisitor extends CloneVisitor {
+  final FixPointGenericInstantiator owner;
+  final Redirector redirector;
+
+  RedirectingCloneVisitor(this.owner,
+      {Map<TypeParameter, DartType> typeSubstitution,
+      memberSubstitutor,
+      typeSubstitutor})
+      : redirector = new Redirector(owner),
+        super(
+            typeSubstitution: typeSubstitution,
+            memberSubstitutor: memberSubstitutor,
+            typeSubstitutor: typeSubstitutor);
+
+  @override
+  visitIsExpression(IsExpression node) {
+    final IsExpression clone = super.visitIsExpression(node);
+    if (!owner.environment.isSubtypeOf(
+        clone.operand.getStaticType(owner.environment), clone.type)) {
+      return new BoolLiteral(false);
+    }
+    return clone;
+  }
+
+  @override
+  visitIfStatement(IfStatement node) {
+    final cond = clone(node.condition);
+    if (cond is BoolLiteral) {
+      return cloneOptional(cond.value ? node.then : node.otherwise) ??
+          new EmptyStatement();
+    }
+    return new IfStatement(
+        cond, cloneOptional(node.then), cloneOptional(node.otherwise));
+  }
+
+  @override
+  visitConstructorInvocation(ConstructorInvocation node) => redirector
+      .redirectConstructorInvocation(super.visitConstructorInvocation(node));
+
+  @override
+  visitFieldInitializer(FieldInitializer node) =>
+      redirector.redirectFieldInitializer(super.visitFieldInitializer(node));
+
+  @override
+  visitSuperInitializer(SuperInitializer node) =>
+      redirector.redirectSuperInitializer(super.visitSuperInitializer(node));
+
+  @override
+  visitPropertyGet(PropertyGet node) =>
+      redirector.redirectPropertyGet(super.visitPropertyGet(node));
+
+  @override
+  visitPropertySet(PropertySet node) =>
+      redirector.redirectPropertySet(super.visitPropertySet(node));
+
+  @override
+  visitDirectPropertyGet(DirectPropertyGet node) =>
+      redirector.redirectDirectPropertyGet(super.visitDirectPropertyGet(node));
+
+  @override
+  visitDirectPropertySet(DirectPropertySet node) =>
+      redirector.redirectDirectPropertySet(super.visitDirectPropertySet(node));
+
+  @override
+  visitSuperPropertyGet(SuperPropertyGet node) =>
+      redirector.redirectSuperPropertyGet(super.visitSuperPropertyGet(node));
+
+  @override
+  visitSuperPropertySet(SuperPropertySet node) =>
+      redirector.redirectSuperPropertySet(super.visitSuperPropertySet(node));
+
+  @override
+  visitStaticGet(StaticGet node) =>
+      redirector.redirectStaticGet(super.visitStaticGet(node));
+
+  @override
+  visitStaticSet(StaticSet node) =>
+      redirector.redirectSuperPropertySet(super.visitStaticSet(node));
+
+  @override
+  visitMethodInvocation(MethodInvocation node) =>
+      redirector.redirectMethodInvocation(super.visitMethodInvocation(node));
+
+  @override
+  visitDirectMethodInvocation(DirectMethodInvocation node) =>
+      redirector.redirectDirectMethodInvocation(super.visitDirectMethodInvocation(node));
+
+  @override
+  visitSuperMethodInvocation(SuperMethodInvocation node) =>
+      redirector.redirectSuperMethodInvocation(super.visitSuperMethodInvocation(node));
+
+  @override
+  visitStaticInvocation(StaticInvocation node) =>
+      redirector.redirectStaticInvocation(super.visitStaticInvocation(node));
+}
+
+class RedirectingVisitor extends Transformer {
+  final FixPointGenericInstantiator owner;
+  final Redirector redirector;
+
+  RedirectingVisitor(this.owner) : redirector = new Redirector(owner);
+
+  @override
+  Class visitClass(Class node) {
+    if (owner.instantiatedClasses.containsKey(node)) {
+      return node;
+    }
+    return super.visitClass(node);
+  }
+
+  @override
+  Procedure visitProcedure(Procedure node) {
+    if (owner.methodToInstantiation.containsKey(node)) {
+      return node;
+    }
+    try {
+      return super.visitProcedure(node);
+    } catch (e) {
+      print('Error while visiting ${node}');
+      rethrow;
+    }
+  }
+
+  @override
+  DartType visitDartType(DartType type) {
+    return owner._typeSubstitutor.visit(type);
+  }
+
+  @override
+  visitConstructorInvocation(ConstructorInvocation node) {
+    return redirector
+        .redirectConstructorInvocation(super.visitConstructorInvocation(node));
+  }
+
+  @override
+  visitStaticInvocation(StaticInvocation node) {
+    return redirector
+        .redirectStaticInvocation(super.visitStaticInvocation(node));
+  }
+
+  @override
+  visitPropertyGet(PropertyGet node) {
+    return redirector.redirectPropertyGet(super.visitPropertyGet(node));
+  }
+
+  @override
+  visitPropertySet(PropertySet node) {
+    return redirector.redirectPropertySet(super.visitPropertySet(node));
+  }
+
+  @override
+  visitMethodInvocation(MethodInvocation node) {
+    return redirector
+        .redirectMethodInvocation(super.visitMethodInvocation(node));
+  }
+}
+
+class Selector {
+  final SelectorKind kind;
+  final Name name;
+
+  Selector(this.kind, this.name);
+
+  factory Selector.fromProcedure(Procedure procedure) {
+    SelectorKind kind;
+    switch (procedure.kind) {
+      case ProcedureKind.Getter:
+        kind = SelectorKind.Getter;
+        break;
+      case ProcedureKind.Setter:
+        kind = SelectorKind.Setter;
+        break;
+      case ProcedureKind.Operator:
+      case ProcedureKind.Method:
+      case ProcedureKind.Factory:
+        kind = SelectorKind.Method;
+        break;
+    }
+    return new Selector(kind, procedure.name);
+  }
+
+  Selector get asGetter => new Selector(SelectorKind.Getter, name);
+
+  Selector get asMethod => new Selector(SelectorKind.Method, name);
+
+  Selector modifyKindToAccess(Member target) {
+    if (target is Procedure) {
+      if (kind == SelectorKind.Method && target.kind == ProcedureKind.Getter) {
+        return asGetter; // This is a call-though-getter.
+      } else if (kind == SelectorKind.Getter &&
+          target.kind == ProcedureKind.Method) {
+        return asMethod; // This is a tear-off.
+      }
+    } else if (target is Field) {
+      if (kind == SelectorKind.Method) {
+        return asGetter; // This is a call-though-field.
+      }
+    }
+    return this;
+  }
+
+  int get hashCode => kind.hashCode ^ name.hashCode;
+
+  bool operator ==(other) =>
+      other is Selector && other.kind == kind && other.name == name;
+
+  String toString() {
+    if (kind == SelectorKind.Getter) return 'get:${name}';
+    if (kind == SelectorKind.Setter) return 'set:${name}';
+    assert(kind == SelectorKind.Method);
+    return '$name';
+  }
+}
+
+enum SelectorKind {
+  Method,
+  Getter,
+  Setter,
+}
+
+class LookupCache {
+  final Map<Class, Map<Selector, Member>> lookupCache =
+      <Class, Map<Selector, Member>>{};
+
+  final Map<Class, Map<Selector, Member>> interfaceLookupCache =
+      <Class, Map<Selector, Member>>{};
+
+  Member lookupSelector(Class klass, Selector selector) {
+    return lookupCache
+        .putIfAbsent(klass, () => <Selector, Member>{})
+        .putIfAbsent(selector, () {
+      while (klass != null) {
+        for (final Member member in klass.members) {
+          if (member.name == selector.name) {
+            if (member is Field) {
+              // A field shadows getter/call and setter iff non-final/non-const.
+              if (selector.kind == SelectorKind.Method) {
+                ensureIsFunctionType(
+                    member.type, 'call-through-field usage $selector');
+                return member;
+              }
+              if (selector.kind == SelectorKind.Getter) {
+                return member;
+              }
+              if (selector.kind == SelectorKind.Setter &&
+                  (!member.isFinal && !member.isConst)) {
+                return member;
+              }
+            } else if (member is Procedure) {
+              // A procedure shadows getter/call but not setter.
+              if (selector.kind == SelectorKind.Getter &&
+                  member.kind == ProcedureKind.Method) {
+                ensureProcedureCanBeTornOff(member);
+                return member;
+              }
+              if (selector.kind == SelectorKind.Getter &&
+                  member.kind == ProcedureKind.Getter) {
+                return member;
+              }
+              if (selector.kind == SelectorKind.Setter &&
+                  member.kind == ProcedureKind.Setter) {
+                return member;
+              }
+              if (selector.kind == SelectorKind.Method &&
+                  (member.kind == ProcedureKind.Method ||
+                      member.kind == ProcedureKind.Operator ||
+                      member.kind == ProcedureKind.Factory)) {
+                return member;
+              }
+              if (selector.kind == SelectorKind.Method &&
+                  (member.kind == ProcedureKind.Getter)) {
+                ensureIsFunctionType(member.function.returnType,
+                    'call-through-field usage $selector');
+                return member;
+              }
+            } else {
+              throw "unreachable";
+            }
+          }
+        }
+        klass = klass.superclass;
+      }
+      return null;
+    });
+  }
+
+  Member lookupInterfaceSelector(Class klass, Selector selector) {
+    return interfaceLookupCache
+        .putIfAbsent(klass, () => <Selector, Member>{})
+        .putIfAbsent(selector, () {
+      Member member = lookupSelector(klass, selector);
+      if (member != null) return member;
+
+      if (klass.superclass != null) {
+        member = lookupInterfaceSelector(klass.superclass, selector);
+        if (member != null) return member;
+      }
+
+      for (final Supertype superType in klass.implementedTypes) {
+        member = lookupInterfaceSelector(superType.classNode, selector);
+        if (member != null) return member;
+      }
+      return null;
+    });
+  }
+}
+
+void ensureProcedureCanBeTornOff(Procedure procedure) {
+  // final FunctionNode function = procedure.function;
+  // if (function.requiredParameterCount != function.positionalParameters.length ||
+  //    function.namedParameters.isNotEmpty) {
+  //  throw 'No tear-off support for functions with optional parameters!';
+  // }
+}
+
+void ensureIsFunctionType(DartType type, String msg) {
+  // if (type is! FunctionType) {
+  //   throw 'Expected a [FunctionType] for $msg but got "$type".';
+  // }
+}
diff --git a/pkg/kernel/lib/verifier.dart b/pkg/kernel/lib/verifier.dart
index 4ebbb3c..95aa7fc 100644
--- a/pkg/kernel/lib/verifier.dart
+++ b/pkg/kernel/lib/verifier.dart
@@ -85,8 +85,8 @@
       problem(
           node,
           "Incorrect parent pointer on ${node.runtimeType}:"
-          " expected '${currentParent.runtimeType}',"
-          " but found: '${node.parent.runtimeType}'.");
+          " expected '${currentParent is FunctionNode ? currentParent.parent : currentParent}',"
+          " but found: '${node.parent is FunctionNode ? node.parent.parent : node.parent}'.");
     }
     var oldParent = currentParent;
     currentParent = node;
diff --git a/pkg/kernel/lib/vm/native_effects.dart b/pkg/kernel/lib/vm/native_effects.dart
index ef326a3..cb7b07c 100644
--- a/pkg/kernel/lib/vm/native_effects.dart
+++ b/pkg/kernel/lib/vm/native_effects.dart
@@ -27,11 +27,27 @@
   effects.markAllocated('dart:core', '_List');
 }
 
+_EffectsHandler AllocatesListEffectX(String suffix) {
+  return (NativeEffectsHandler effects) {
+    effects.markAllocated('dart:core', '_GrowableList%${suffix}');
+    effects.markAllocated('dart:core', '_List%${suffix}');
+  };
+}
+
+
 class VmNativeEffects implements NativeEffects {
   final Map<String, _EffectsHandler> nativeEffects = <String, _EffectsHandler>{
     'List_allocate': AllocatesClassEffect('dart:core', '_List'),
     'List_getIndexed': NoEffect,
     'List_setIndexed': NoEffect,
+    'List_getLength': NoEffect,
+    'List_slice': AllocatesClassEffect('dart:core', '_List'),
+    'List_allocate%I': AllocatesClassEffect('dart:core', '_List%I'),
+    'List_getIndexed%I': NoEffect,
+    'List_setIndexed%I': NoEffect,
+    'List_getLength%I': NoEffect,
+    'List_slice%I': AllocatesClassEffect('dart:core', '_List%I'),
+    'GrowableList_allocate%I': AllocatesListEffectX('I'),
     'GrowableList_allocate': AllocatesListEffect,
     'GrowableList_setLength': NoEffect,
     'GrowableList_getIndexed': NoEffect,
@@ -40,6 +56,13 @@
     'GrowableList_getCapacity': NoEffect,
     'GrowableList_setData': NoEffect,
     'GrowableList_getData': NoEffect,
+    'GrowableList_setLength%I': NoEffect,
+    'GrowableList_getIndexed%I': NoEffect,
+    'GrowableList_setIndexed%I': NoEffect,
+    'GrowableList_getLength%I': NoEffect,
+    'GrowableList_getCapacity%I': NoEffect,
+    'GrowableList_setData%I': NoEffect,
+    'GrowableList_getData%I': NoEffect,
     'DateTime_currentTimeMicros': NoEffect,
     'LinkedHashMap_getIndex': NoEffect,
     'LinkedHashMap_setIndex': NoEffect,
@@ -85,8 +108,6 @@
     'Mint_bitNegate': NoEffect,
     'Mint_bitLength': NoEffect,
     'Mint_shlFromInt': NoEffect,
-    'List_getLength': NoEffect,
-    'List_slice': NoEffect,
     'ClassID_getID': NoEffect,
     'Internal_makeListFixedLength': AllocatesClassEffect('dart:core', '_List'),
     'Random_setupSeed': NoEffect,