fix #32980, better error messages for type errors at runtime

Change-Id: I4666c82b706dcaa963f4acf1c1c5d770e0ab1c6a
Reviewed-on: https://dart-review.googlesource.com/54101
Commit-Queue: Jenny Messerly <jmesserly@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index c7c523d..c2acf4f 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -3642,10 +3642,25 @@
     var result = _emitForeignJS(node, e);
     if (result != null) return result;
 
-    if (e?.name == 'extensionSymbol' && isSdkInternalRuntime(e.library)) {
-      var args = node.argumentList.arguments;
-      var firstArg = args.length == 1 ? args[0] : null;
-      if (firstArg is StringLiteral) {
+    // Optimize some internal SDK calls.
+    if (e != null &&
+        isSdkInternalRuntime(e.library) &&
+        node.argumentList.arguments.length == 1) {
+      var firstArg = node.argumentList.arguments[0];
+      if (e.name == 'getGenericClass' && firstArg is SimpleIdentifier) {
+        var typeElem = firstArg.staticElement;
+        if (typeElem is TypeDefiningElement &&
+            typeElem.type is ParameterizedType) {
+          return _emitTopLevelNameNoInterop(typeElem, suffix: '\$');
+        }
+      }
+      if (e.name == 'unwrapType' && firstArg is SimpleIdentifier) {
+        var typeElem = firstArg.staticElement;
+        if (typeElem is TypeDefiningElement) {
+          return _emitType(fillDynamicTypeArgs(typeElem.type));
+        }
+      }
+      if (e.name == 'extensionSymbol' && firstArg is StringLiteral) {
         return _getExtensionSymbolInternal(firstArg.stringValue);
       }
     }
@@ -4059,27 +4074,6 @@
       }
     }
 
-    JS.Expression visitTemplateArg(Expression arg) {
-      if (arg is InvocationExpression) {
-        var e = arg is MethodInvocation
-            ? arg.methodName.staticElement
-            : (arg as FunctionExpressionInvocation).staticElement;
-        if (e?.name == 'getGenericClass' &&
-            e.library.name == 'dart._runtime' &&
-            arg.argumentList.arguments.length == 1) {
-          var typeArg = arg.argumentList.arguments[0];
-          if (typeArg is SimpleIdentifier) {
-            var typeElem = typeArg.staticElement;
-            if (typeElem is TypeDefiningElement &&
-                typeElem.type is ParameterizedType) {
-              return _emitTopLevelNameNoInterop(typeElem, suffix: '\$');
-            }
-          }
-        }
-      }
-      return _visitExpression(arg);
-    }
-
     // TODO(rnystrom): The JS() calls are almost never nested, and probably
     // really shouldn't be, but there are at least a couple of calls in the
     // HTML library where an argument to JS() is itself a JS() call. If those
@@ -4088,7 +4082,7 @@
     // wrapped Type object.
     var wasInForeignJS = _isInForeignJS;
     _isInForeignJS = true;
-    var jsArgs = templateArgs.map(visitTemplateArg).toList();
+    var jsArgs = templateArgs.map(_visitExpression).toList();
     _isInForeignJS = wasInForeignJS;
 
     var result = js.parseForeignJS(source).instantiate(jsArgs);
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index e18a3be..819227d 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4378,11 +4378,21 @@
     if (isInlineJS(target)) return _emitInlineJSCode(node) as JS.Expression;
     if (target.isFactory) return _emitFactoryInvocation(node);
 
-    if (target.name.name == 'extensionSymbol' &&
-        isSdkInternalRuntime(target.enclosingLibrary)) {
-      var args = node.arguments;
-      var firstArg = args.positional.length == 1 ? args.positional[0] : null;
-      if (firstArg is StringLiteral) {
+    // Optimize some internal SDK calls.
+    if (isSdkInternalRuntime(target.enclosingLibrary) &&
+        node.arguments.positional.length == 1) {
+      var name = target.name.name;
+      var firstArg = node.arguments.positional[0];
+      if (name == 'getGenericClass' && firstArg is TypeLiteral) {
+        var type = firstArg.type;
+        if (type is InterfaceType) {
+          return _emitTopLevelNameNoInterop(type.classNode, suffix: '\$');
+        }
+      }
+      if (name == 'unwrapType' && firstArg is TypeLiteral) {
+        return _emitType(firstArg.type);
+      }
+      if (name == 'extensionSymbol' && firstArg is StringLiteral) {
         return _getExtensionSymbolInternal(firstArg.value);
       }
     }
@@ -4531,25 +4541,6 @@
       }
     }
 
-    JS.Expression visitTemplateArg(Expression arg) {
-      if (arg is StaticInvocation) {
-        var target = arg.target;
-        var positional = arg.arguments.positional;
-        if (target.name.name == 'getGenericClass' &&
-            isSdkInternalRuntime(target.enclosingLibrary) &&
-            positional.length == 1) {
-          var typeArg = positional[0];
-          if (typeArg is TypeLiteral) {
-            var type = typeArg.type;
-            if (type is InterfaceType) {
-              return _emitTopLevelNameNoInterop(type.classNode, suffix: '\$');
-            }
-          }
-        }
-      }
-      return _visitExpression(arg);
-    }
-
     // TODO(rnystrom): The JS() calls are almost never nested, and probably
     // really shouldn't be, but there are at least a couple of calls in the
     // HTML library where an argument to JS() is itself a JS() call. If those
@@ -4558,7 +4549,7 @@
     // wrapped Type object.
     var wasInForeignJS = _isInForeignJS;
     _isInForeignJS = true;
-    var jsArgs = templateArgs.map(visitTemplateArg).toList();
+    var jsArgs = templateArgs.map(_visitExpression).toList();
     _isInForeignJS = wasInForeignJS;
 
     var result = js.parseForeignJS(source).instantiate(jsArgs);
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart
index 0f0c078..163e26c 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart
@@ -100,8 +100,13 @@
 @JSExportName('implements')
 final _implements = JS('', 'Symbol("implements")');
 
-getImplements(clazz) => JS('', 'Object.hasOwnProperty.call(#, #) ? #[#] : null',
-    clazz, _implements, clazz, _implements);
+List Function() getImplements(clazz) => JS(
+    '',
+    'Object.hasOwnProperty.call(#, #) ? #[#] : null',
+    clazz,
+    _implements,
+    clazz,
+    _implements);
 
 /// The Symbol for storing type arguments on a specialized generic type.
 final _typeArguments = JS('', 'Symbol("typeArguments")');
@@ -159,6 +164,14 @@
 List getGenericArgs(type) =>
     JS('List', '#', safeGetOwnProperty(type, _typeArguments));
 
+List<TypeVariable> getGenericTypeFormals(genericClass) {
+  return _typeFormalsFromFunction(getGenericTypeCtor(genericClass));
+}
+
+Object instantiateClass(Object genericClass, List<Object> typeArgs) {
+  return JS('', '#.apply(null, #)', genericClass, typeArgs);
+}
+
 final _constructorSig = JS('', 'Symbol("sigCtor")');
 final _methodSig = JS('', 'Symbol("sigMethod")');
 final _fieldSig = JS('', 'Symbol("sigField")');
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/errors.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/errors.dart
index 4d2f78f..a6b3b01 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/errors.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/errors.dart
@@ -17,6 +17,7 @@
   JS('', 'dart.__ignoreWhitelistedErrors = #', flag);
 }
 
+// TODO(jmesserly): remove this?
 void ignoreAllErrors(bool flag) {
   JS('', 'dart.__ignoreAllErrors = #', flag);
 }
@@ -49,3 +50,38 @@
   throw new NoSuchMethodError(
       null, new Symbol('<Unexpected Null Value>'), null, null, null);
 }
+
+castError(obj, expectedType, [bool typeError = undefined]) {
+  var actualType = getReifiedType(obj);
+  var message = _castErrorMessage(actualType, expectedType);
+  if (JS('!', 'dart.__ignoreAllErrors')) {
+    JS('', 'console.error(#)', message);
+    return obj;
+  }
+  if (JS('!', 'dart.__trapRuntimeErrors')) JS('', 'debugger');
+  var error = JS<bool>('!', '#', typeError)
+      ? new TypeErrorImpl(message)
+      : new CastErrorImpl(message);
+  throw error;
+}
+
+String _castErrorMessage(from, to) {
+  // If both types are generic classes, see if we can infer generic type
+  // arguments for `from` that would allow the subtype relation to work.
+  var fromClass = getGenericClass(from);
+  if (fromClass != null) {
+    var fromTypeFormals = getGenericTypeFormals(fromClass);
+    var fromType = instantiateClass(fromClass, fromTypeFormals);
+    var inferrer = new _TypeInferrer(fromTypeFormals);
+    if (inferrer.trySubtypeMatch(fromType, to)) {
+      var inferredTypes = inferrer.getInferredTypes();
+      if (inferredTypes != null) {
+        var inferred = instantiateClass(fromClass, inferredTypes);
+        return "Type '${typeName(from)}' should be '${typeName(inferred)}' "
+            "to implement expected type '${typeName(to)}'.";
+      }
+    }
+  }
+  return "Type '${typeName(from)}' is not a subtype of "
+      "expected type '${typeName(to)}'.";
+}
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
index b28594e..ca353ba 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
@@ -476,33 +476,9 @@
     throw new BooleanConversionAssertionError();
 
 void booleanConversionFailed(obj) {
-  if (obj == null) {
-    _throwBooleanConversionError();
-  }
-  var actual = getReifiedType(obj);
-  var expected = JS('', '#', bool);
-  throw new TypeErrorImplementation.fromMessage(
-      "type '${typeName(actual)}' is not a subtype of "
-      "type '${typeName(expected)}' in boolean expression");
-}
-
-castError(obj, type, bool isExplicit) {
-  var objType = getReifiedType(obj);
-  if (JS('bool', '!dart.__ignoreAllErrors')) {
-    var errorInStrongMode = isSubtype(objType, type) == null;
-
-    var actual = typeName(objType);
-    var expected = typeName(type);
-    if (JS('bool', 'dart.__trapRuntimeErrors')) JS('', 'debugger');
-
-    var error = JS('bool', '#', isExplicit)
-        ? new TypeErrorImplementation(obj, actual, expected, errorInStrongMode)
-        : new CastErrorImplementation(obj, actual, expected, errorInStrongMode);
-    throw error;
-  }
-  JS('', 'console.error(#)',
-      'Actual: ${typeName(objType)} Expected: ${typeName(type)}');
-  return obj;
+  var actual = typeName(getReifiedType(test(obj)));
+  throw new TypeErrorImpl(
+      "type '$actual' is not a 'bool' in boolean expression");
 }
 
 asInt(obj) {
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
index ea298ee..fd62cd8 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
@@ -14,14 +14,10 @@
     show
         AssertionErrorImpl,
         BooleanConversionAssertionError,
-        CastErrorImplementation,
+        CastErrorImpl,
         DartIterator,
         getTraceFromException,
-        Primitives,
-        TypeErrorImplementation,
-        StrongModeCastError,
-        StrongModeErrorImplementation,
-        StrongModeTypeError,
+        TypeErrorImpl,
         JsLinkedHashMap,
         ImmutableMap,
         PrivateSymbol,
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
index 48b35b0..c7a40c5 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
@@ -66,7 +66,7 @@
   check_T(object) => cast(object, this, true);
 }
 
-class Dynamic extends TypeRep {
+class DynamicType extends TypeRep {
   toString() => 'dynamic';
 
   @JSExportName('is')
@@ -165,26 +165,26 @@
 }
 
 @JSExportName('dynamic')
-final _dynamic = new Dynamic();
+final _dynamic = new DynamicType();
 
-class Void extends TypeRep {
+class VoidType extends TypeRep {
   toString() => 'void';
 }
 
 @JSExportName('void')
-final void_ = new Void();
+final void_ = new VoidType();
 
-class Bottom extends TypeRep {
+class BottomType extends TypeRep {
   toString() => 'bottom';
 }
 
-final bottom = new Bottom();
+final bottom = new BottomType();
 
-class JSObject extends TypeRep {
+class JSObjectType extends TypeRep {
   toString() => 'NativeJavaScriptObject';
 }
 
-final jsobject = new JSObject();
+final jsobject = new JSObjectType();
 
 class WrappedType extends Type {
   final _wrappedType;
@@ -348,6 +348,25 @@
 
   toString() => name;
 
+  int get requiredParameterCount => args.length;
+  int get positionalParameterCount => args.length + optionals.length;
+
+  getPositionalParameter(int i) {
+    int n = args.length;
+    return i < n ? args[i] : optionals[i + n];
+  }
+
+  Map<String, Object> getNamedParameters() {
+    var result = <String, Object>{};
+    var names = getOwnPropertyNames(named);
+    JS('', '#.sort()', names);
+    for (var i = 0; JS('bool', '# < #.length', i, names); ++i) {
+      String name = JS('!', '#[#]', names, i);
+      result[name] = JS('', '#[#]', named, name);
+    }
+    return result;
+  }
+
   get name {
     if (_stringValue != null) return _stringValue;
 
@@ -490,29 +509,7 @@
 
   List<TypeVariable> get typeFormals {
     if (_typeFormals != null) return _typeFormals;
-
-    // Extract parameter names from the function parameters.
-    //
-    // This is not robust in general for user-defined JS functions, but it
-    // should handle the functions generated by our compiler.
-    //
-    // TODO(jmesserly): names of TypeVariables are only used for display
-    // purposes, such as when an error happens or if someone calls
-    // `Type.toString()`. So we could recover them lazily rather than eagerly.
-    // Alternatively we could synthesize new names.
-    String str = JS('!', '#.toString()', _instantiateTypeParts);
-    var hasParens = str[0] == '(';
-    var end = str.indexOf(hasParens ? ')' : '=>');
-    if (hasParens) {
-      _typeFormals = str
-          .substring(1, end)
-          .split(',')
-          .map((n) => new TypeVariable(n.trim()))
-          .toList();
-    } else {
-      _typeFormals = [new TypeVariable(str.substring(0, end).trim())];
-    }
-    return _typeFormals;
+    return _typeFormals = _typeFormalsFromFunction(_instantiateTypeParts);
   }
 
   checkBounds(List typeArgs) {
@@ -658,6 +655,30 @@
   }
 }
 
+List<TypeVariable> _typeFormalsFromFunction(Object typeConstructor) {
+  // Extract parameter names from the function parameters.
+  //
+  // This is not robust in general for user-defined JS functions, but it
+  // should handle the functions generated by our compiler.
+  //
+  // TODO(jmesserly): names of TypeVariables are only used for display
+  // purposes, such as when an error happens or if someone calls
+  // `Type.toString()`. So we could recover them lazily rather than eagerly.
+  // Alternatively we could synthesize new names.
+  String str = JS('!', '#.toString()', typeConstructor);
+  var hasParens = str[0] == '(';
+  var end = str.indexOf(hasParens ? ')' : '=>');
+  if (hasParens) {
+    return str
+        .substring(1, end)
+        .split(',')
+        .map((n) => new TypeVariable(n.trim()))
+        .toList();
+  } else {
+    return [new TypeVariable(str.substring(0, end).trim())];
+  }
+}
+
 Typedef typedef(name, AbstractFunctionType Function() closure) =>
     new Typedef(name, closure);
 
@@ -735,7 +756,7 @@
 /// If [isCovariant] is true, then we are checking subtyping in a covariant
 /// position, and hence the direction of the check for function types
 /// corresponds to the direction of the check according to the Dart spec.
-isFunctionSubtype(ft1, ft2, isCovariant) => JS('', '''(() => {
+_isFunctionSubtype(ft1, ft2, isCovariant) => JS('', '''(() => {
   let ret1 = $ft1.returnType;
   let ret2 = $ft2.returnType;
 
@@ -838,28 +859,28 @@
 }
 
 bool _isFutureOr(type) =>
-    JS('bool', '# === #', getGenericClass(type), getGenericClass(FutureOr));
+    identical(getGenericClass(type), getGenericClass(FutureOr));
 
 bool _isSubtype(t1, t2, isCovariant) => JS('', '''(() => {
   if ($t1 === $t2) return true;
 
   // Trivially true.
-  if ($_isTop($t2) || $_isBottom($t1)) {
+  if (${_isTop(t2)} || ${_isBottom(t1)}) {
     return true;
   }
 
   // Trivially false.
-  if ($_isBottom($t2)) return null;
-  if ($_isTop($t1)) {
+  if (${_isBottom(t2)}) return null;
+  if (${_isTop(t1)}) {
     if ($t1 === $dynamic) return null;
     return false;
   }
 
   // Handle FutureOr<T> union type.
-  if ($_isFutureOr($t1)) {
-    let t1TypeArg = $getGenericArgs($t1)[0];
-    if ($_isFutureOr($t2)) {
-      let t2TypeArg = $getGenericArgs($t2)[0];
+  if (${_isFutureOr(t1)}) {
+    let t1TypeArg = ${getGenericArgs(t1)}[0];
+    if (${_isFutureOr(t2)}) {
+      let t2TypeArg = ${getGenericArgs(t2)}[0];
       // FutureOr<A> <: FutureOr<B> iff A <: B
       return $_isSubtype(t1TypeArg, t2TypeArg, $isCovariant);
     }
@@ -874,8 +895,8 @@
   if ($_isFutureOr($t2)) {
     // given t2 is Future<A> | A, then:
     // t1 <: (Future<A> | A) iff t1 <: Future<A> or t1 <: A
-    let t2TypeArg = $getGenericArgs($t2)[0];
-    var t2Future = ${getGenericClass(Future)}(t2TypeArg);
+    let t2TypeArg = ${getGenericArgs(t2)}[0];
+    let t2Future = ${getGenericClass(Future)}(t2TypeArg);
     let s1 = $_isSubtype($t1, t2Future, $isCovariant);
     let s2 = $_isSubtype($t1, t2TypeArg, $isCovariant);
     if (s1 === true || s2 === true) return true;
@@ -899,7 +920,7 @@
     if ($t1 === $jsobject && $t2 instanceof $AnonymousJSType) return true;
 
     // Compare two interface types:
-    return $isClassSubType($t1, $t2, $isCovariant);
+    return ${_isInterfaceSubtype(t1, t2, isCovariant)};
   }
 
   // Function subtyping.
@@ -953,10 +974,10 @@
   }
 
   // Handle non-generic functions.
-  return $isFunctionSubtype($t1, $t2, $isCovariant);
+  return ${_isFunctionSubtype(t1, t2, isCovariant)};
 })()''');
 
-isClassSubType(t1, t2, isCovariant) => JS('', '''(() => {
+_isInterfaceSubtype(t1, t2, isCovariant) => JS('', '''(() => {
   // We support Dart's covariant generics with the caveat that we do not
   // substitute bottom for dynamic in subtyping rules.
   // I.e., given T1, ..., Tn where at least one Ti != dynamic we disallow:
@@ -1006,7 +1027,7 @@
 
   let indefinite = false;
   function definitive(t1, t2) {
-    let result = $isClassSubType(t1, t2, $isCovariant);
+    let result = $_isInterfaceSubtype(t1, t2, $isCovariant);
     if (result == null) {
       indefinite = true;
       return false;
@@ -1050,73 +1071,373 @@
   if (typeArguments.isEmpty) {
     throw new ArgumentError('Cannot extract from non-generic type ($type).');
   }
-  List typeArgs = _extractTypes(getReifiedType(instance), type, typeArguments);
+  var supertype = _getMatchingSupertype(getReifiedType(instance), type);
   // The signature of this method guarantees that instance is a T, so we
   // should have a valid non-empty list at this point.
+  assert(supertype != null);
+  var typeArgs = getGenericArgs(supertype);
   assert(typeArgs != null && typeArgs.isNotEmpty);
   return dgcall(f, typeArgs, []);
 }
 
-// Let t2 = T<T1, ..., Tn>
-// If t1 </: T<T1, ..., Tn>
-// - return null
-// If t1 <: T<T1, ..., Tn>
-// - return [S1, ..., Sn] such that there exists no Ri where
-//   Ri != Si && Ri <: Si && t1 <: T<S1, ..., Ri, ..., Sn>
-//
-// Note: In Dart 1, there isn't necessarily a unique solution to the above -
-// t1 <: Foo<int> and t1 <: Foo<String> could both be true.  Dart 2 will
-// statically disallow.  Until then, this could return either [int] or
-// [String] depending on which it hits first.
-//
-// TODO(vsm): Consider merging with similar isClassSubType logic.
-List _extractTypes(Type t1, Type t2, List typeArguments2) => JS('', '''(() => {
-  if ($t1 == $t2) return typeArguments2;
+/// Infers type variables based on a series of [trySubtypeMatch] calls, followed
+/// by [getInferredTypes] to return the type.
+class _TypeInferrer {
+  final Map<TypeVariable, TypeConstraint> _typeVariables;
 
-  if ($t1 == $Object) return null;
+  /// Creates a [TypeConstraintGatherer] which is prepared to gather type
+  /// constraints for the given type parameters.
+  _TypeInferrer(Iterable<TypeVariable> typeVariables)
+      : _typeVariables = new Map.fromIterables(
+            typeVariables, typeVariables.map((_) => new TypeConstraint()));
 
-  // If t1 is a JS Object, we may not hit core.Object.
-  if ($t1 == null) return null;
-
-  // Check if t1 and t2 have the same raw type.  If so, check covariance on
-  // type parameters.
-  let raw1 = $getGenericClass($t1);
-  let raw2 = $getGenericClass($t2);
-  if (raw1 != null && raw1 == raw2) {
-    let typeArguments1 = $getGenericArgs($t1);
-    let length = typeArguments1.length;
-    if (length == 0 || length != typeArguments2.length) $assertFailed();
-    // TODO(vsm): Remove this subtyping check if/when we eliminate the ability
-    // to implement multiple versions of the same interface
-    // (e.g., Foo<int>, Foo<String>).
-    for (let i = 0; i < length; ++i) {
-      let result =
-          $_isSubtype(typeArguments1[i], typeArguments2[i], true);
-      if (!result) {
+  /// Returns the inferred types based on the current constraints.
+  List<Object> getInferredTypes() {
+    var result = new List<Object>();
+    for (var constraint in _typeVariables.values) {
+      // Prefer the known bound, if any.
+      if (constraint.lower != null) {
+        result.add(constraint.lower);
+      } else if (constraint.upper != null) {
+        result.add(constraint.upper);
+      } else {
         return null;
       }
     }
-    return typeArguments1;
+    return result;
   }
 
-  var result = $_extractTypes($t1.__proto__, $t2, $typeArguments2);
-  if (result) return result;
+  /// Tries to match [subtype] against [supertype].
+  ///
+  /// If the match succeeds, the resulting type constraints are recorded for
+  /// later use by [computeConstraints].  If the match fails, the set of type
+  /// constraints is unchanged.
+  bool trySubtypeMatch(Object subtype, Object supertype) =>
+      _isSubtypeMatch(subtype, supertype);
+
+  void _constrainLower(TypeVariable parameter, Object lower) {
+    _typeVariables[parameter]._constrainLower(lower);
+  }
+
+  void _constrainUpper(TypeVariable parameter, Object upper) {
+    _typeVariables[parameter]._constrainUpper(upper);
+  }
+
+  bool _isFunctionSubtypeMatch(FunctionType subtype, FunctionType supertype) {
+    // A function type `(M0,..., Mn, [M{n+1}, ..., Mm]) -> R0` is a subtype
+    // match for a function type `(N0,..., Nk, [N{k+1}, ..., Nr]) -> R1` with
+    // respect to `L` under constraints `C0 + ... + Cr + C`
+    // - If `R0` is a subtype match for a type `R1` with respect to `L` under
+    //   constraints `C`:
+    // - If `n <= k` and `r <= m`.
+    // - And for `i` in `0...r`, `Ni` is a subtype match for `Mi` with respect
+    //   to `L` under constraints `Ci`.
+    // Function types with named parameters are treated analogously to the
+    // positional parameter case above.
+    // A generic function type `<T0 extends B0, ..., Tn extends Bn>F0` is a
+    // subtype match for a generic function type `<S0 extends B0, ..., Sn
+    // extends Bn>F1` with respect to `L` under constraints `Cl`:
+    // - If `F0[Z0/T0, ..., Zn/Tn]` is a subtype match for `F0[Z0/S0, ...,
+    //   Zn/Sn]` with respect to `L` under constraints `C`, where each `Zi` is a
+    //   fresh type variable with bound `Bi`.
+    // - And `Cl` is `C` with each constraint replaced with its closure with
+    //   respect to `[Z0, ..., Zn]`.
+    if (subtype.requiredParameterCount > supertype.requiredParameterCount) {
+      return false;
+    }
+    if (subtype.positionalParameterCount < supertype.positionalParameterCount) {
+      return false;
+    }
+    // Test the return types.
+    if (supertype.returnType is! VoidType &&
+        !_isSubtypeMatch(subtype.returnType, supertype.returnType)) {
+      return false;
+    }
+
+    // Test the parameter types.
+    for (int i = 0, n = supertype.positionalParameterCount; i < n; ++i) {
+      if (!_isSubtypeMatch(supertype.getPositionalParameter(i),
+          subtype.getPositionalParameter(i))) {
+        return false;
+      }
+    }
+    var supertypeNamed = supertype.getNamedParameters();
+    var subtypeNamed = supertype.getNamedParameters();
+    for (var name in supertypeNamed.keys) {
+      var subtypeParamType = subtypeNamed[name];
+      if (subtypeParamType == null) return false;
+      if (!_isSubtypeMatch(supertypeNamed[name], subtypeParamType)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool _isInterfaceSubtypeMatch(Object subtype, Object supertype) {
+    // A type `P<M0, ..., Mk>` is a subtype match for `P<N0, ..., Nk>` with
+    // respect to `L` under constraints `C0 + ... + Ck`:
+    // - If `Mi` is a subtype match for `Ni` with respect to `L` under
+    //   constraints `Ci`.
+    // A type `P<M0, ..., Mk>` is a subtype match for `Q<N0, ..., Nj>` with
+    // respect to `L` under constraints `C`:
+    // - If `R<B0, ..., Bj>` is the superclass of `P<M0, ..., Mk>` and `R<B0,
+    //   ..., Bj>` is a subtype match for `Q<N0, ..., Nj>` with respect to `L`
+    //   under constraints `C`.
+    // - Or `R<B0, ..., Bj>` is one of the interfaces implemented by `P<M0, ...,
+    //   Mk>` (considered in lexical order) and `R<B0, ..., Bj>` is a subtype
+    //   match for `Q<N0, ..., Nj>` with respect to `L` under constraints `C`.
+    // - Or `R<B0, ..., Bj>` is a mixin into `P<M0, ..., Mk>` (considered in
+    //   lexical order) and `R<B0, ..., Bj>` is a subtype match for `Q<N0, ...,
+    //   Nj>` with respect to `L` under constraints `C`.
+
+    // Note that since kernel requires that no class may only appear in the set
+    // of supertypes of a given type more than once, the order of the checks
+    // above is irrelevant; we just need to find the matched superclass,
+    // substitute, and then iterate through type variables.
+    var matchingSupertype = _getMatchingSupertype(subtype, supertype);
+    if (matchingSupertype == null) return false;
+
+    var matchingTypeArgs = getGenericArgs(matchingSupertype);
+    var supertypeTypeArgs = getGenericArgs(supertype);
+    for (int i = 0; i < supertypeTypeArgs.length; i++) {
+      if (!_isSubtypeMatch(matchingTypeArgs[i], supertypeTypeArgs[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool _isNull(Object type) => identical(type, unwrapType(Null));
+
+  /// Attempts to match [subtype] as a subtype of [supertype], gathering any
+  /// constraints discovered in the process.
+  ///
+  /// If a set of constraints was found, `true` is returned and the caller
+  /// may proceed to call [computeConstraints].  Otherwise, `false` is returned.
+  ///
+  /// In the case where `false` is returned, some bogus constraints may have
+  /// been added to [_protoConstraints].  It is the caller's responsibility to
+  /// discard them if necessary.
+  bool _isSubtypeMatch(Object subtype, Object supertype) {
+    // A type variable `T` in `L` is a subtype match for any type schema `Q`:
+    // - Under constraint `T <: Q`.
+    if (subtype is TypeVariable && _typeVariables.containsKey(subtype)) {
+      _constrainUpper(subtype, supertype);
+      return true;
+    }
+    // A type schema `Q` is a subtype match for a type variable `T` in `L`:
+    // - Under constraint `Q <: T`.
+    if (supertype is TypeVariable && _typeVariables.containsKey(supertype)) {
+      _constrainLower(supertype, subtype);
+      return true;
+    }
+    // Any two equal types `P` and `Q` are subtype matches under no constraints.
+    // Note: to avoid making the algorithm quadratic, we just check for
+    // identical().  If P and Q are equal but not identical, recursing through
+    // the types will give the proper result.
+    if (identical(subtype, supertype)) return true;
+    // Any type `P` is a subtype match for `dynamic`, `Object`, or `void` under
+    // no constraints.
+    if (_isTop(supertype)) return true;
+    // `Null` is a subtype match for any type `Q` under no constraints.
+    // Note that nullable types will change this.
+    if (_isNull(subtype)) return true;
+
+    // Handle FutureOr<T> union type.
+    if (_isFutureOr(subtype)) {
+      var subtypeArg = getGenericArgs(subtype)[0];
+      if (_isFutureOr(supertype)) {
+        // `FutureOr<P>` is a subtype match for `FutureOr<Q>` with respect to `L`
+        // under constraints `C`:
+        // - If `P` is a subtype match for `Q` with respect to `L` under constraints
+        //   `C`.
+        var supertypeArg = getGenericArgs(supertype)[0];
+        return _isSubtypeMatch(subtypeArg, supertypeArg);
+      }
+
+      // `FutureOr<P>` is a subtype match for `Q` with respect to `L` under
+      // constraints `C0 + C1`:
+      // - If `Future<P>` is a subtype match for `Q` with respect to `L` under
+      //   constraints `C0`.
+      // - And `P` is a subtype match for `Q` with respect to `L` under
+      //   constraints `C1`.
+      var subtypeFuture = JS('!', '#(#)', getGenericClass(Future), subtypeArg);
+      return _isSubtypeMatch(subtypeFuture, supertype) &&
+          _isSubtypeMatch(subtypeArg, supertype);
+    }
+
+    if (_isFutureOr(supertype)) {
+      // `P` is a subtype match for `FutureOr<Q>` with respect to `L` under
+      // constraints `C`:
+      // - If `P` is a subtype match for `Future<Q>` with respect to `L` under
+      //   constraints `C`.
+      // - Or `P` is not a subtype match for `Future<Q>` with respect to `L` under
+      //   constraints `C`
+      //   - And `P` is a subtype match for `Q` with respect to `L` under
+      //     constraints `C`
+      var supertypeArg = getGenericArgs(supertype)[0];
+      var supertypeFuture =
+          JS('!', '#(#)', getGenericClass(Future), supertypeArg);
+      return _isSubtypeMatch(subtype, supertypeFuture) ||
+          _isSubtypeMatch(subtype, supertypeArg);
+    }
+
+    // A type variable `T` not in `L` with bound `P` is a subtype match for the
+    // same type variable `T` with bound `Q` with respect to `L` under
+    // constraints `C`:
+    // - If `P` is a subtype match for `Q` with respect to `L` under constraints
+    //   `C`.
+    if (subtype is TypeVariable) {
+      return supertype is TypeVariable && identical(subtype, supertype);
+    }
+    if (subtype is GenericFunctionType) {
+      if (supertype is GenericFunctionType) {
+        // Given generic functions g1 and g2, g1 <: g2 iff:
+        //
+        //     g1<TFresh> <: g2<TFresh>
+        //
+        // where TFresh is a list of fresh type variables that both g1 and g2 will
+        // be instantiated with.
+        var formalCount = subtype.formalCount;
+        if (formalCount != supertype.formalCount) return false;
+
+        // Using either function's type formals will work as long as they're
+        // both instantiated with the same ones. The instantiate operation is
+        // guaranteed to avoid capture because it does not depend on its
+        // TypeVariable objects, rather it uses JS function parameters to ensure
+        // correct binding.
+        var fresh = supertype.typeFormals;
+
+        // Check the bounds of the type parameters of g1 and g2.
+        // given a type parameter `T1 extends U1` from g1, and a type parameter
+        // `T2 extends U2` from g2, we must ensure that:
+        //
+        //      U2 <: U1
+        //
+        // (Note the reversal of direction -- type formal bounds are
+        // contravariant, similar to the function's formal parameter types).
+        //
+        var t1Bounds = subtype.instantiateTypeBounds(fresh);
+        var t2Bounds = supertype.instantiateTypeBounds(fresh);
+        // TODO(jmesserly): we could optimize for the common case of no bounds.
+        for (var i = 0; i < formalCount; i++) {
+          if (!_isSubtypeMatch(t2Bounds[i], t1Bounds[i])) {
+            return false;
+          }
+        }
+        return _isFunctionSubtypeMatch(
+            subtype.instantiate(fresh), supertype.instantiate(fresh));
+      } else {
+        return false;
+      }
+    } else if (supertype is GenericFunctionType) {
+      return false;
+    }
+
+    // A type `P` is a subtype match for `Function` with respect to `L` under no
+    // constraints:
+    // - If `P` implements a call method.
+    // - Or if `P` is a function type.
+    // TODO(paulberry): implement this case.
+    // A type `P` is a subtype match for a type `Q` with respect to `L` under
+    // constraints `C`:
+    // - If `P` is an interface type which implements a call method of type `F`,
+    //   and `F` is a subtype match for a type `Q` with respect to `L` under
+    //   constraints `C`.
+    // TODO(paulberry): implement this case.
+    if (subtype is FunctionType) {
+      if (supertype is! FunctionType) {
+        if (identical(supertype, unwrapType(Function)) ||
+            identical(supertype, unwrapType(Object))) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+      if (supertype is FunctionType) {
+        return _isFunctionSubtypeMatch(subtype, supertype);
+      }
+    }
+    return _isInterfaceSubtypeMatch(subtype, supertype);
+  }
+
+  bool _isTop(Object type) =>
+      identical(type, _dynamic) ||
+      identical(type, void_) ||
+      identical(type, unwrapType(Object));
+}
+
+/// A constraint on a type parameter that we're inferring.
+class TypeConstraint {
+  /// The lower bound of the type being constrained.  This bound must be a
+  /// subtype of the type being constrained.
+  Object lower;
+
+  /// The upper bound of the type being constrained.  The type being constrained
+  /// must be a subtype of this bound.
+  Object upper;
+
+  void _constrainLower(Object type) {
+    if (lower != null) {
+      if (isSubtype(lower, type)) {
+        // nothing to do, existing lower bound is lower than the new one.
+        return;
+      }
+      if (!isSubtype(type, lower)) {
+        // Neither bound is lower and we don't have GLB, so use bottom type.
+        type = unwrapType(Null);
+      }
+    }
+    lower = type;
+  }
+
+  void _constrainUpper(Object type) {
+    if (upper != null) {
+      if (isSubtype(type, upper)) {
+        // nothing to do, existing upper bound is higher than the new one.
+        return;
+      }
+      if (!isSubtype(upper, type)) {
+        // Neither bound is higher and we don't have LUB, so use top type.
+        type = unwrapType(Object);
+      }
+    }
+    upper = type;
+  }
+
+  String toString() => '${typeName(lower)} <: <type> <: ${typeName(upper)}';
+}
+
+/// Finds a supertype of [subtype] that matches the class [supertype], but may
+/// contain different generic type arguments.
+Object _getMatchingSupertype(Object subtype, Object supertype) {
+  if (identical(subtype, supertype)) return supertype;
+  if (subtype == null || subtype == unwrapType(Object)) return null;
+
+  var subclass = getGenericClass(subtype);
+  var superclass = getGenericClass(supertype);
+  if (subclass != null && identical(subclass, superclass)) {
+    return subtype; // matching supertype found!
+  }
+
+  var result = _getMatchingSupertype(JS('', '#.__proto__', subtype), supertype);
+  if (result != null) return result;
 
   // Check mixin.
-  let m1 = $getMixin($t1);
-  if (m1 != null) {
-    result = $_extractTypes(m1, $t2, $typeArguments2);
-    if (result) return result;
+  var mixin = getMixin(subtype);
+  if (mixin != null) {
+    result = _getMatchingSupertype(mixin, supertype);
+    if (result != null) return result;
   }
 
   // Check interfaces.
-  let getInterfaces = $getImplements($t1);
-  if (getInterfaces) {
-    for (let i1 of getInterfaces()) {
-      result = $_extractTypes(i1, $t2, $typeArguments2);
-      if (result) return result;
+  var getInterfaces = getImplements(subtype);
+  if (getInterfaces != null) {
+    for (var iface in getInterfaces()) {
+      result = _getMatchingSupertype(iface, supertype);
+      if (result != null) return result;
     }
   }
 
   return null;
-})()''');
+}
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/utils.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/utils.dart
index d913dfa8..87b3cbb 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/utils.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/utils.dart
@@ -40,7 +40,7 @@
 /// assertion failure (TypeError) or CastError.
 void throwTypeError(String message) {
   if (JS('bool', 'dart.__trapRuntimeErrors')) JS('', 'debugger');
-  throw new TypeErrorImplementation.fromMessage(message);
+  throw new TypeErrorImpl(message);
 }
 
 /// This error indicates a bug in the runtime or the compiler.
diff --git a/pkg/dev_compiler/tool/input_sdk/private/js_helper.dart b/pkg/dev_compiler/tool/input_sdk/private/js_helper.dart
index e7e7bd6..738db86 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/js_helper.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/js_helper.dart
@@ -737,46 +737,25 @@
 // TODO(lrn): These exceptions should be implemented in core.
 // When they are, remove the 'Implementation' here.
 
-/** Thrown by type assertions that fail. */
-class TypeErrorImplementation extends Error implements TypeError {
+/// Thrown by type assertions that fail.
+class TypeErrorImpl extends Error implements TypeError {
   final String message;
 
-  /**
-   * Normal type error caused by a failed subtype test.
-   */
-  // TODO(sra): Include [value] in message.
-  TypeErrorImplementation(Object value, Object actualType, Object expectedType,
-      bool strongModeError)
-      : message = "Type '${actualType}' is not a subtype "
-            "of type '${expectedType}'" +
-            (strongModeError ? " in strong mode" : "");
-
-  TypeErrorImplementation.fromMessage(String this.message);
+  TypeErrorImpl(this.message);
 
   String toString() => message;
 }
 
-/** Thrown by the 'as' operator if the cast isn't valid. */
-class CastErrorImplementation extends Error implements CastError {
-  // TODO(lrn): Rename to CastError (and move implementation into core).
+/// Thrown by the 'as' operator if the cast isn't valid.
+class CastErrorImpl extends Error implements CastError {
   final String message;
 
-  /**
-   * Normal cast error caused by a failed type cast.
-   */
-  // TODO(sra): Include [value] in message.
-  CastErrorImplementation(Object value, Object actualType, Object expectedType,
-      bool strongModeError)
-      : message = "CastError: Casting value of type '$actualType' to"
-            " type '$expectedType' which is incompatible" +
-            (strongModeError ? " in strong mode": "");
+  CastErrorImpl(this.message);
 
   String toString() => message;
 }
 
-
 class FallThroughErrorImplementation extends FallThroughError {
-  FallThroughErrorImplementation();
   String toString() => "Switch case fall-through.";
 }
 
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
index d278b10..a6f0aee 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_constraint_gatherer.dart
@@ -42,7 +42,7 @@
 
   /// Tries to match [subtype] against [supertype].
   ///
-  /// If the match suceeds, the resulting type constraints are recorded for
+  /// If the match succeeds, the resulting type constraints are recorded for
   /// later use by [computeConstraints].  If the match fails, the set of type
   /// constraints is unchanged.
   bool trySubtypeMatch(DartType subtype, DartType supertype) {
diff --git a/tests/language_2/checked_method_error_order_test.dart b/tests/language_2/checked_method_error_order_test.dart
index 508a957..c5370cf 100644
--- a/tests/language_2/checked_method_error_order_test.dart
+++ b/tests/language_2/checked_method_error_order_test.dart
@@ -18,10 +18,13 @@
   if (checkedMode) {
     dynamic x = 'e';
     dynamic y = 3;
-    Expect.throws(
-        () => new Bar().foo(i: x, a: y),
-        (e) =>
-            e is TypeError &&
-            e.message.toString().contains("is not a subtype of type 'int'"));
+    Expect.throws(() => new Bar().foo(i: x, a: y), (e) {
+      if (e is TypeError) {
+        var m = e.message.toString();
+        return m.contains("is not a subtype of type 'int'") ||
+            m.contains("is not a subtype of expected type 'int'");
+      }
+      return false;
+    });
   }
 }