fix #28642, handling of top and bottom types in DDC
in particular: void, Null, and combinations with FutureOr<T>
R=leafp@google.com
Review-Url: https://codereview.chromium.org/2671113002 .
diff --git a/pkg/dev_compiler/lib/js/amd/dart_sdk.js b/pkg/dev_compiler/lib/js/amd/dart_sdk.js
index 21e2524..b612e9f 100644
--- a/pkg/dev_compiler/lib/js/amd/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/amd/dart_sdk.js
@@ -1709,10 +1709,7 @@
return null;
}
}
- if (ret2 === dart.void) return true;
- if (ret1 === dart.void) {
- return ret2 === dart.dynamic || ret2 === async.FutureOr;
- }
+ if (ret1 === dart.void) return dart._isTop(ret2);
if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
return true;
};
@@ -1733,13 +1730,13 @@
};
};
dart._isBottom = function(type) {
- return type == dart.bottom;
+ return type == dart.bottom || type == core.Null;
};
dart._isTop = function(type) {
if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
return dart._isTop(dart.getGenericArgs(type)[0]);
}
- return type == core.Object || type == dart.dynamic;
+ return type == core.Object || type == dart.dynamic || type == dart.void;
};
dart._isSubtype = function(t1, t2, isCovariant) {
if (t1 === t2) return true;
@@ -2186,6 +2183,9 @@
return false;
};
dart.is = function(obj, type) {
+ if (obj == null) {
+ return type == core.Null || dart._isTop(type);
+ }
let result = dart.strongInstanceOf(obj, type);
if (result !== null) return result;
let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/common/dart_sdk.js b/pkg/dev_compiler/lib/js/common/dart_sdk.js
index 66320d1..a35267b 100644
--- a/pkg/dev_compiler/lib/js/common/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/common/dart_sdk.js
@@ -1709,10 +1709,7 @@
return null;
}
}
- if (ret2 === dart.void) return true;
- if (ret1 === dart.void) {
- return ret2 === dart.dynamic || ret2 === async.FutureOr;
- }
+ if (ret1 === dart.void) return dart._isTop(ret2);
if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
return true;
};
@@ -1733,13 +1730,13 @@
};
};
dart._isBottom = function(type) {
- return type == dart.bottom;
+ return type == dart.bottom || type == core.Null;
};
dart._isTop = function(type) {
if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
return dart._isTop(dart.getGenericArgs(type)[0]);
}
- return type == core.Object || type == dart.dynamic;
+ return type == core.Object || type == dart.dynamic || type == dart.void;
};
dart._isSubtype = function(t1, t2, isCovariant) {
if (t1 === t2) return true;
@@ -2186,6 +2183,9 @@
return false;
};
dart.is = function(obj, type) {
+ if (obj == null) {
+ return type == core.Null || dart._isTop(type);
+ }
let result = dart.strongInstanceOf(obj, type);
if (result !== null) return result;
let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/es6/dart_sdk.js b/pkg/dev_compiler/lib/js/es6/dart_sdk.js
index 83266e7..a7fbad0 100644
--- a/pkg/dev_compiler/lib/js/es6/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/es6/dart_sdk.js
@@ -1707,10 +1707,7 @@
return null;
}
}
- if (ret2 === dart.void) return true;
- if (ret1 === dart.void) {
- return ret2 === dart.dynamic || ret2 === async.FutureOr;
- }
+ if (ret1 === dart.void) return dart._isTop(ret2);
if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
return true;
};
@@ -1731,13 +1728,13 @@
};
};
dart._isBottom = function(type) {
- return type == dart.bottom;
+ return type == dart.bottom || type == core.Null;
};
dart._isTop = function(type) {
if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
return dart._isTop(dart.getGenericArgs(type)[0]);
}
- return type == core.Object || type == dart.dynamic;
+ return type == core.Object || type == dart.dynamic || type == dart.void;
};
dart._isSubtype = function(t1, t2, isCovariant) {
if (t1 === t2) return true;
@@ -2184,6 +2181,9 @@
return false;
};
dart.is = function(obj, type) {
+ if (obj == null) {
+ return type == core.Null || dart._isTop(type);
+ }
let result = dart.strongInstanceOf(obj, type);
if (result !== null) return result;
let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/lib/js/legacy/dart_sdk.js b/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
index 5ed7333..b313c6a 100644
--- a/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
+++ b/pkg/dev_compiler/lib/js/legacy/dart_sdk.js
@@ -1710,10 +1710,7 @@
return null;
}
}
- if (ret2 === dart.void) return true;
- if (ret1 === dart.void) {
- return ret2 === dart.dynamic || ret2 === async.FutureOr;
- }
+ if (ret1 === dart.void) return dart._isTop(ret2);
if (!dart._isSubtype(ret1, ret2, isCovariant)) return null;
return true;
};
@@ -1734,13 +1731,13 @@
};
};
dart._isBottom = function(type) {
- return type == dart.bottom;
+ return type == dart.bottom || type == core.Null;
};
dart._isTop = function(type) {
if (dart.getGenericClass(type) === dart.getGenericClass(async.FutureOr)) {
return dart._isTop(dart.getGenericArgs(type)[0]);
}
- return type == core.Object || type == dart.dynamic;
+ return type == core.Object || type == dart.dynamic || type == dart.void;
};
dart._isSubtype = function(t1, t2, isCovariant) {
if (t1 === t2) return true;
@@ -2187,6 +2184,9 @@
return false;
};
dart.is = function(obj, type) {
+ if (obj == null) {
+ return type == core.Null || dart._isTop(type);
+ }
let result = dart.strongInstanceOf(obj, type);
if (result !== null) return result;
let actual = dart.getReifiedType(obj);
diff --git a/pkg/dev_compiler/test/browser/runtime_tests.js b/pkg/dev_compiler/test/browser/runtime_tests.js
index 39d9a53..3cb562a 100644
--- a/pkg/dev_compiler/test/browser/runtime_tests.js
+++ b/pkg/dev_compiler/test/browser/runtime_tests.js
@@ -4,6 +4,7 @@
define(['dart_sdk'], function(dart_sdk) {
const assert = chai.assert;
+ const async = dart_sdk.async;
const core = dart_sdk.core;
const collection = dart_sdk.collection;
const dart = dart_sdk.dart;
@@ -211,10 +212,13 @@
function checkType(x, type, expectedTrue, strongOnly) {
if (expectedTrue === undefined) expectedTrue = true;
- if (strongOnly == undefined) strongOnly = false;
+ if (strongOnly === undefined) strongOnly = false;
if (!strongOnly) {
assert.doesNotThrow(() => instanceOf(x, type));
- expect(instanceOf(x, type), expectedTrue);
+ expect(instanceOf(x, type), expectedTrue,
+ '"' + x + '" ' +
+ (expectedTrue ? 'should' : 'should not') +
+ ' be an instance of "' + dart.typeName(type) + '"');
} else {
assert.throws(() => instanceOf(x, type), dart.StrongModeError);
expect(expectedTrue, false);
@@ -801,25 +805,34 @@
let dyn = dart.dynamic;
function always(t1, t2) {
- assert.equal(isSubtype(t1, t2), true);
+ assert.equal(isSubtype(t1, t2), true,
+ dart.toString(t1) +
+ " should always be a subtype of " +
+ dart.toString(t2));
}
function never(t1, t2) {
- assert.equal(isSubtype(t1, t2), false);
+ assert.equal(isSubtype(t1, t2), false,
+ dart.toString(t1) +
+ " should never be a subtype of " +
+ dart.toString(t2));
}
function maybe(t1, t2) {
- assert.equal(isSubtype(t1, t2), null);
+ assert.equal(isSubtype(t1, t2), null,
+ dart.toString(t1) +
+ " should maybe be a subtype of " +
+ dart.toString(t2));
}
function always2(t1, t2) {
- assert.equal(isSubtype(t1, t2), true);
+ always(t1, t2);
always(functionType(t1, [t2]), functionType(t2, [t1]));
}
function never2(t1, t2) {
- assert.equal(isSubtype(t1, t2), false);
+ never(t1, t2);
maybe(functionType(t1, [t2]), functionType(t2, [t1]));
}
function maybe2(t1, t2) {
- assert.equal(isSubtype(t1, t2), null);
+ maybe(t1, t2);
maybe(functionType(t1, [t2]), functionType(t2, [t1]));
}
@@ -919,6 +932,44 @@
run_test(func1, func2, func2opt, func1extra, func2extra);
});
+ test('top and bottom types', () => {
+ let FutureOr = async.FutureOr$;
+ let tops = [
+ dart.dynamic,
+ core.Object,
+ dart.void,
+ FutureOr(dart.dynamic),
+ FutureOr(core.Object),
+ FutureOr(dart.void),
+ FutureOr(FutureOr(core.Object)),
+ // ... skip the (infinite) rest of the top types :D
+ ];
+ let bottoms = [dart.bottom, core.Null];
+
+ for (let top of tops) {
+ for (let bottom of bottoms) {
+ always(bottom, top);
+ always(
+ definiteFunctionType(bottom, [top]),
+ definiteFunctionType(top, [bottom]));
+ }
+ }
+
+ for (let equalTypes of [tops, bottoms]) {
+ for (let t1 of equalTypes) {
+ for (let t2 of equalTypes) {
+ always(t1, t2);
+ always(t2, t1);
+
+ let t11 = definiteFunctionType(t1, [t1]);
+ let t22 = definiteFunctionType(t2, [t2]);
+ always(t11, t22);
+ always(t22, t11);
+ }
+ }
+ }
+ });
+
test('basic typedefs', () => {
function func1(S) {
return dart.typedef('Func1', () => functionType(S, []))
@@ -975,7 +1026,6 @@
always(functionType(dyn, [], [dyn]), functionType(dyn, [dyn]));
always(functionType(dyn, [], [dyn]), functionType(dyn, []));
always(functionType(dyn, [dyn], {extra: dyn}), functionType(dyn, [dyn]));
-
});
test('void function types', () => {
@@ -1020,7 +1070,6 @@
never(functionType(dart.void, [], [int]), functionType(int, [int]));
never(functionType(dart.void, [], [int]), functionType(int, []));
never(functionType(dart.void, [int], {extra: int}), functionType(int, [int]));
-
});
test('higher-order typedef', () => {
@@ -1060,10 +1109,7 @@
maybe(AA$(functionType(dyn, [dyn])), AA$(functionType(int, [int])));
maybe(AA$(functionType(core.Object, [core.Object])),
AA$(functionType(int, [int])));
-
-
});
-
});
suite('canonicalization', function() {
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 5975ddc..69b01a5 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
@@ -456,6 +456,9 @@
instanceOf(obj, type) => JS(
'',
'''(() => {
+ if ($obj == null) {
+ return $type == $Null || $_isTop($type);
+ }
let result = $strongInstanceOf($obj, $type);
if (result !== null) return result;
let actual = $getReifiedType($obj);
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 abc476d..e64daf7 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
@@ -656,15 +656,9 @@
// Check return type last, so that arity mismatched functions can be
// definitively rejected.
- // We allow any type to subtype a void return type, but not vice versa
- if (ret2 === $_void) return true;
- // Dart allows void functions to subtype dynamic functions, but not
- // other functions.
- // TODO(jmesserly): this check does not match our compile time subtype
- // implementation. Reconcile.
- if (ret1 === $_void) {
- return ret2 === $dynamic || ret2 === $FutureOr;
- }
+ // For `void` we will give the same answer as the VM, so don't return null.
+ if (ret1 === $_void) return $_isTop(ret2);
+
if (!$_isSubtype(ret1, ret2, $isCovariant)) return null;
return true;
})()''');
@@ -698,13 +692,14 @@
final isSubtype = JS(
'', '$_subtypeMemo((t1, t2) => (t1 === t2) || $_isSubtype(t1, t2, true))');
-_isBottom(type) => JS('bool', '# == #', type, bottom);
+_isBottom(type) => JS('bool', '# == # || # == #', type, bottom, type, Null);
_isTop(type) {
if (JS('bool', '# === #', getGenericClass(type), getGenericClass(FutureOr))) {
return _isTop(JS('', '#[0]', getGenericArgs(type)));
}
- return JS('bool', '# == # || # == #', type, Object, type, dynamic);
+ return JS('bool', '# == # || # == # || # == #',
+ type, Object, type, dynamic, type, _void);
}
_isSubtype(t1, t2, isCovariant) => JS(