[package:js] Add/modify tests for JS interop subtyping

Adds some basic tests to check for runtime subtyping with JS
interop types. Uses function types only to check for subtyping
relationships. Extends some tests on is and as checks as well.

Change-Id: I9cbc91d0c475fba3213dd9a3443921232c286804
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170660
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Srujan Gaddam 2020-11-11 19:28:13 +00:00 committed by commit-bot@chromium.org
parent 5f6d30da26
commit 4cd0926703
7 changed files with 251 additions and 93 deletions

View file

@ -0,0 +1,10 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'extends_test_util.dart';
void main() {
setUpWithoutES6Syntax();
testSubtyping();
}

View file

@ -5,53 +5,6 @@
import 'extends_test_util.dart';
void main() {
// Use the old way to define inheritance between JS objects.
eval(r"""
function inherits(child, parent) {
if (child.prototype.__proto__) {
child.prototype.__proto__ = parent.prototype;
} else {
function tmp() {};
tmp.prototype = parent.prototype;
child.prototype = new tmp();
child.prototype.constructor = child;
}
}
function JSClass(a) {
this.a = a;
this.getA = function() {
return this.a;
}
this.getAOrB = function() {
return this.getA();
}
}
function JSExtendJSClass(a, b) {
JSClass.call(this, a);
this.b = b;
this.getB = function() {
return this.b;
}
this.getAOrB = function() {
return this.getB();
}
}
inherits(JSExtendJSClass, JSClass);
function JSExtendAnonymousClass(a, b) {
this.a = a;
this.b = b;
this.getA = function() {
return this.a;
}
this.getB = function() {
return this.b;
}
this.getAOrB = function() {
return this.getB();
}
}
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
setUpWithoutES6Syntax();
testInheritance();
}

View file

@ -60,6 +60,108 @@ class AnonymousExtendJSClass extends JSClass {
external AnonymousExtendAnonymousClass get anonExtendAnon;
external AnonymousExtendJSClass get anonExtendJS;
void useJSClass(JSClass js) {}
void useAnonymousClass(AnonymousClass a) {}
void setUpWithoutES6Syntax() {
// Use the old way to define inheritance between JS objects.
eval(r"""
function inherits(child, parent) {
if (child.prototype.__proto__) {
child.prototype.__proto__ = parent.prototype;
} else {
function tmp() {};
tmp.prototype = parent.prototype;
child.prototype = new tmp();
child.prototype.constructor = child;
}
}
function JSClass(a) {
this.a = a;
this.getA = function() {
return this.a;
}
this.getAOrB = function() {
return this.getA();
}
}
function JSExtendJSClass(a, b) {
JSClass.call(this, a);
this.b = b;
this.getB = function() {
return this.b;
}
this.getAOrB = function() {
return this.getB();
}
}
inherits(JSExtendJSClass, JSClass);
function JSExtendAnonymousClass(a, b) {
this.a = a;
this.b = b;
this.getA = function() {
return this.a;
}
this.getB = function() {
return this.b;
}
this.getAOrB = function() {
return this.getB();
}
}
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
}
void setUpWithES6Syntax() {
// Use the ES6 syntax for classes to make inheritance easier.
eval(r"""
class JSClass {
constructor(a) {
this.a = a;
}
getA() {
return this.a;
}
getAOrB() {
return this.getA();
}
}
class JSExtendJSClass extends JSClass {
constructor(a, b) {
super(a);
this.b = b;
}
getB() {
return this.b;
}
getAOrB() {
return this.getB();
}
}
self.JSExtendJSClass = JSExtendJSClass;
class JSExtendAnonymousClass {
constructor(a, b) {
this.a = a;
this.b = b;
}
getA() {
return this.a;
}
getB() {
return this.b;
}
getAOrB() {
return this.getB();
}
}
self.JSExtendAnonymousClass = JSExtendAnonymousClass;
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
}
void testInheritance() {
// Note that for the following, there are no meaningful tests for is checks or
// as casts, since the web compilers should return true and succeed for all JS
@ -92,3 +194,17 @@ void testInheritance() {
expect(anonExtendJS.getAOrB(), 2);
expect((anonExtendJS as JSClass).getAOrB(), 2);
}
void testSubtyping() {
// Test subtyping for inheritance between JS and anonymous classes.
expect(useJSClass is void Function(JSExtendJSClass js), true);
expect(useAnonymousClass is void Function(AnonymousExtendAnonymousClass a),
true);
expect(useJSClass is void Function(AnonymousExtendJSClass a), true);
expect(useAnonymousClass is void Function(JSExtendAnonymousClass js), true);
expect(useJSClass is void Function(AnonymousExtendAnonymousClass a), false);
expect(useAnonymousClass is void Function(JSExtendJSClass js), false);
expect(useJSClass is void Function(JSExtendAnonymousClass js), false);
expect(useAnonymousClass is void Function(AnonymousExtendJSClass a), false);
}

View file

@ -0,0 +1,10 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'extends_test_util.dart';
void main() {
setUpWithES6Syntax();
testSubtyping();
}

View file

@ -5,50 +5,6 @@
import 'extends_test_util.dart';
void main() {
// Use the ES6 syntax for classes to make inheritance easier.
eval(r"""
class JSClass {
constructor(a) {
this.a = a;
}
getA() {
return this.a;
}
getAOrB() {
return this.getA();
}
}
class JSExtendJSClass extends JSClass {
constructor(a, b) {
super(a);
this.b = b;
}
getB() {
return this.b;
}
getAOrB() {
return this.getB();
}
}
self.JSExtendJSClass = JSExtendJSClass;
class JSExtendAnonymousClass {
constructor(a, b) {
this.a = a;
this.b = b;
}
getA() {
return this.a;
}
getB() {
return this.b;
}
getAOrB() {
return this.getB();
}
}
self.JSExtendAnonymousClass = JSExtendAnonymousClass;
self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
self.anonExtendJS = new JSExtendJSClass(1, 2);
""");
setUpWithES6Syntax();
testInheritance();
}

View file

@ -9,6 +9,7 @@
library is_check_and_as_cast_test;
import 'package:js/js.dart';
import 'package:expect/expect.dart' show hasUnsoundNullSafety;
import 'package:expect/minitest.dart';
@JS()
@ -57,6 +58,8 @@ class LiteralB {
external LiteralA get a;
external LiteralB get b;
class DartClass {}
void main() {
eval(r"""
function Foo(a) {
@ -119,4 +122,26 @@ void main() {
expect(() => (foo as LiteralB), returnsNormally);
expect(a is Foo, isTrue);
expect(() => (a as Foo), returnsNormally);
// You cannot cast between JS interop objects and Dart objects, however.
var dartClass = DartClass();
expect(dartClass is Foo, isFalse);
expect(() => (dartClass as Foo), throws);
expect(dartClass is LiteralA, isFalse);
expect(() => (dartClass as LiteralA), throws);
expect(foo is DartClass, isFalse);
expect(() => (foo as DartClass), throws);
expect(a is DartClass, isFalse);
expect(() => (a as DartClass), throws);
// Test that nullability is still respected with JS types.
Foo? nullableFoo = null;
expect(nullableFoo is Foo, false);
expect(() => (nullableFoo as Foo),
hasUnsoundNullSafety ? returnsNormally : throws);
LiteralA? nullableA = null;
expect(nullableA is LiteralA, false);
expect(() => (nullableA as LiteralA),
hasUnsoundNullSafety ? returnsNormally : throws);
}

View file

@ -0,0 +1,88 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests subtyping relationships between JS and anonymous classes.
@JS()
library subtyping_test;
import 'package:js/js.dart';
import 'package:expect/expect.dart' show hasUnsoundNullSafety;
import 'package:expect/minitest.dart';
@JS()
class JSClassA {}
@JS()
class JSClassB {}
@JS()
@anonymous
class AnonymousClassA {}
@JS()
@anonymous
class AnonymousClassB {}
class DartClass {}
void useJSClassA(JSClassA _) {}
void useAnonymousClassA(AnonymousClassA _) {}
void useDartClass(DartClass _) {}
void useNullableJSClassA(JSClassA? _) {}
void useNullableAnonymousClassA(AnonymousClassA? _) {}
// Avoid static type optimization by running all tests using this.
@pragma('dart2js:noInline')
@pragma('dart2js:assumeDynamic')
confuse(x) => x;
void main() {
// Checks subtyping with the same type and nullability subtyping.
expect(useJSClassA is void Function(JSClassA), true);
expect(useAnonymousClassA is void Function(AnonymousClassA), true);
expect(useJSClassA is void Function(JSClassA?), hasUnsoundNullSafety);
expect(useAnonymousClassA is void Function(AnonymousClassA?),
hasUnsoundNullSafety);
expect(useNullableJSClassA is void Function(JSClassA?), true);
expect(useNullableAnonymousClassA is void Function(AnonymousClassA?), true);
expect(useNullableJSClassA is void Function(JSClassA), true);
expect(useNullableAnonymousClassA is void Function(AnonymousClassA), true);
expect(confuse(useJSClassA) is void Function(JSClassA), true);
expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA), true);
expect(
confuse(useJSClassA) is void Function(JSClassA?), hasUnsoundNullSafety);
expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA?),
hasUnsoundNullSafety);
expect(confuse(useNullableJSClassA) is void Function(JSClassA?), true);
expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA?),
true);
expect(confuse(useNullableJSClassA) is void Function(JSClassA), true);
expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA),
true);
// No subtyping between JS and anonymous classes.
expect(useJSClassA is void Function(AnonymousClassA), false);
expect(useAnonymousClassA is void Function(JSClassA), false);
expect(confuse(useJSClassA) is void Function(AnonymousClassA), false);
expect(confuse(useAnonymousClassA) is void Function(JSClassA), false);
// No subtyping between separate classes even if they're both JS classes or
// anonymous classes.
expect(useJSClassA is void Function(JSClassB), false);
expect(useAnonymousClassA is void Function(AnonymousClassB), false);
expect(confuse(useJSClassA) is void Function(JSClassB), false);
expect(confuse(useAnonymousClassA) is void Function(AnonymousClassB), false);
// No subtyping between JS/anonymous classes and Dart classes.
expect(useJSClassA is void Function(DartClass), false);
expect(useAnonymousClassA is void Function(DartClass), false);
expect(confuse(useJSClassA) is void Function(DartClass), false);
expect(confuse(useAnonymousClassA) is void Function(DartClass), false);
}