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;
+ });
}
}