Index: tool/input_sdk/private/ddc_runtime/types.dart |
diff --git a/tool/input_sdk/private/ddc_runtime/types.dart b/tool/input_sdk/private/ddc_runtime/types.dart |
index 0b0098731c29271cb36134dc049a9e57e183bb27..c3dabfd1ec02bd7edad5de3173e65dd62756ab8c 100644 |
--- a/tool/input_sdk/private/ddc_runtime/types.dart |
+++ b/tool/input_sdk/private/ddc_runtime/types.dart |
@@ -313,7 +313,13 @@ bool isDartType(type) => JS('bool', '#(#) === #', _getRuntimeType, type, Type); |
typeName(type) => JS('', '''(() => { |
// Non-instance types |
- if ($type instanceof $TypeRep) return $type.toString(); |
+ if ($type instanceof $TypeRep) { |
+ if ($type instanceof $Typedef) { |
+ return $type.name + "(" + $type.functionType.toString() + ")"; |
+ } |
+ return $type.toString(); |
+ } |
+ |
// Instance types |
let tag = $_getRuntimeType($type); |
if (tag === $Type) { |
@@ -355,36 +361,35 @@ isFunctionType(type) => |
JS('bool', '# instanceof # || # === #', |
type, AbstractFunctionType, type, Function); |
-isFunctionSubType(ft1, ft2) => JS('', '''(() => { |
- if ($ft2 == $Function) { |
+/// Returns true if [ft1] <: [ft2]. |
+/// Returns false if [ft1] </: [ft2] in both spec and strong mode |
+/// Returns null if [ft1] </: [ft2] in strong mode, but spec mode |
+/// may differ |
+/// If [covariant] 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, covariant) => JS('', '''(() => { |
+ if ($ft2 === $Function) { |
return true; |
} |
let ret1 = $ft1.returnType; |
let ret2 = $ft2.returnType; |
- if (!$isSubtype_(ret1, ret2)) { |
- // Covariant return types |
- // Note, void (which can only appear as a return type) is effectively |
- // treated as dynamic. If the base return type is void, we allow any |
- // subtype return type. |
- // E.g., we allow: |
- // () -> int <: () -> void |
- if (ret2 != $voidR) { |
- return false; |
- } |
- } |
- |
let args1 = $ft1.args; |
let args2 = $ft2.args; |
if (args1.length > args2.length) { |
- return false; |
+ // If we're in a covariant position, then Dart's arity rules |
+ // agree with strong mode, otherwise we can't be sure. |
+ return ($covariant) ? false : null; |
} |
for (let i = 0; i < args1.length; ++i) { |
- if (!$isSubtype_(args2[i], args1[i])) { |
- return false; |
+ if (!$isSubtype_(args2[i], args1[i], !$covariant)) { |
+ // Even if isSubtype returns false, assignability |
+ // means that we can't be definitive |
+ return null; |
} |
} |
@@ -392,19 +397,19 @@ isFunctionSubType(ft1, ft2) => JS('', '''(() => { |
let optionals2 = $ft2.optionals; |
if (args1.length + optionals1.length < args2.length + optionals2.length) { |
- return false; |
+ return ($covariant) ? false : null; |
} |
let j = 0; |
for (let i = args1.length; i < args2.length; ++i, ++j) { |
- if (!$isSubtype_(args2[i], optionals1[j])) { |
- return false; |
+ if (!$isSubtype_(args2[i], optionals1[j], !$covariant)) { |
+ return null; |
} |
} |
for (let i = 0; i < optionals2.length; ++i, ++j) { |
- if (!$isSubtype_(optionals2[i], optionals1[j])) { |
- return false; |
+ if (!$isSubtype_(optionals2[i], optionals1[j], !$covariant)) { |
+ return null; |
} |
} |
@@ -417,10 +422,28 @@ isFunctionSubType(ft1, ft2) => JS('', '''(() => { |
let n1 = named1[name]; |
let n2 = named2[name]; |
if (n1 === void 0) { |
- return false; |
+ return ($covariant) ? false : null; |
} |
- if (!$isSubtype_(n2, n1)) { |
- return false; |
+ if (!$isSubtype_(n2, n1, !$covariant)) { |
+ return null; |
+ } |
+ } |
+ |
+ // Check return type last, so that arity mismatched functions can be |
+ // definitively rejected. |
+ { |
+ let result = $isSubtype_(ret1, ret2, $covariant); |
+ if (result === null) return result; |
+ if (!result) { |
+ // Covariant return types |
+ // Note, void (which can only appear as a return type) is effectively |
+ // treated as dynamic. If the base return type is void, we allow any |
+ // subtype return type. |
+ // E.g., we allow: |
+ // () -> int <: () -> void |
+ if (ret2 !== $voidR) { |
+ return null; |
+ } |
} |
} |
@@ -446,6 +469,10 @@ canonicalType(t) => JS('', '''(() => { |
})()'''); |
final subtypeMap = JS('', 'new Map()'); |
+/// Returns true if [t1] <: [t2]. |
+/// Returns false if [t1] </: [t2] in both spec and strong mode |
+/// Returns undefined if [t1] </: [t2] in strong mode, but spec |
+/// mode may differ |
isSubtype(t1, t2) => JS('', '''(() => { |
// See if we already know the answer |
// TODO(jmesserly): general purpose memoize function? |
@@ -457,7 +484,7 @@ isSubtype(t1, t2) => JS('', '''(() => { |
} else { |
$subtypeMap.set($t1, map = new Map()); |
} |
- result = $isSubtype_($t1, $t2); |
+ result = $isSubtype_($t1, $t2, true); |
map.set($t2, result); |
return result; |
})()'''); |
@@ -466,10 +493,10 @@ _isBottom(type) => JS('bool', '# == #', type, bottom); |
_isTop(type) => JS('bool', '# == # || # == #', type, Object, type, dynamicR); |
-isSubtype_(t1, t2) => JS('', '''(() => { |
+isSubtype_(t1, t2, covariant) => JS('', '''(() => { |
$t1 = $canonicalType($t1); |
$t2 = $canonicalType($t2); |
- if ($t1 == $t2) return true; |
+ if ($t1 === $t2) return true; |
// Trivially true. |
if ($_isTop($t2) || $_isBottom($t1)) { |
@@ -477,13 +504,16 @@ isSubtype_(t1, t2) => JS('', '''(() => { |
} |
// Trivially false. |
- if ($_isTop($t1) || $_isBottom($t2)) { |
+ if ($_isBottom($t2)) return null; |
+ if ($_isTop($t1)) { |
+ if ($t1 === $dynamicR) return null; |
return false; |
} |
// "Traditional" name-based subtype check. |
- if ($isClassSubType($t1, $t2)) { |
- return true; |
+ { |
+ let result = $isClassSubType($t1, $t2, $covariant); |
+ if (result === true || result === null) return result; |
} |
// Function subtyping. |
@@ -495,12 +525,12 @@ isSubtype_(t1, t2) => JS('', '''(() => { |
if ($isFunctionType($t1) && |
$isFunctionType($t2)) { |
- return $isFunctionSubType($t1, $t2); |
+ return $isFunctionSubtype($t1, $t2, $covariant); |
} |
return false; |
})()'''); |
-isClassSubType(t1, t2) => JS('', '''(() => { |
+isClassSubType(t1, t2, covariant) => 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: |
@@ -528,26 +558,38 @@ isClassSubType(t1, t2) => JS('', '''(() => { |
return true; |
} else if (length == 0) { |
// t1 is raw, but t2 is not |
- return false; |
+ if (typeArguments2.every($_isTop)) return true; |
+ return null; |
} |
$assert_(length == typeArguments2.length); |
for (let i = 0; i < length; ++i) { |
- if (!$isSubtype(typeArguments1[i], typeArguments2[i])) { |
- return false; |
+ let result = |
+ $isSubtype_(typeArguments1[i], typeArguments2[i], $covariant); |
+ if (!result) { |
+ return result; |
} |
} |
return true; |
} |
- // Check superclass. |
- if ($isClassSubType($t1.__proto__, $t2)) return true; |
+ let indefinite = false; |
+ function definitive(t1, t2) { |
+ let result = $isClassSubType(t1, t2, $covariant); |
+ if (result == null) { |
+ indefinite = true; |
+ return false; |
+ } |
+ return result; |
+ } |
+ |
+ if (definitive($t1.__proto__, $t2)) return true; |
// Check mixins. |
let mixins = $getMixins($t1); |
if (mixins) { |
for (let m1 of mixins) { |
// TODO(jmesserly): remove the != null check once we can load core libs. |
- if (m1 != null && $isClassSubType(m1, $t2)) return true; |
+ if (m1 != null && definitive(m1, $t2)) return true; |
} |
} |
@@ -556,10 +598,15 @@ isClassSubType(t1, t2) => JS('', '''(() => { |
if (getInterfaces) { |
for (let i1 of getInterfaces()) { |
// TODO(jmesserly): remove the != null check once we can load core libs. |
- if (i1 != null && $isClassSubType(i1, $t2)) return true; |
+ if (i1 != null && definitive(i1, $t2)) return true; |
} |
} |
+ // We found no definite supertypes, and at least one indefinite supertype |
+ // so the answer is indefinite. |
+ if (indefinite) return null; |
+ // We found no definite supertypes and no indefinite supertypes, so we |
+ // can return false. |
return false; |
})()'''); |