Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f5a98e7

Browse files
srujzscommit-bot@chromium.org
authored and
commit-bot@chromium.org
committed
[ddc] Unify pkg:js types and allow subtyping between them
Removes the lazy loading of the underlying type in LazyJSTypes. As such, this removes the need to keep AnonymousJSType and LazyJSType separate, and is therefore refactored to PackageJSType. Similarly, subtyping is fixed such that PackageJSTypes are all subtypes of each other. Change-Id: If489defdbeb5cb932db802a7d146ad2fc393b12c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/207982 Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
1 parent be2e01e commit f5a98e7

File tree

8 files changed

+75
-145
lines changed

8 files changed

+75
-145
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,16 @@ Updated the Linter to `1.8.0`, which includes changes that
264264
[#46545]: https://github.com/dart-lang/sdk/issues/46545
265265
[1]: https://dart.dev/faq#q-what-browsers-do-you-support-as-javascript-compilation-targets
266266

267+
#### Dart Dev Compiler (DDC)
268+
269+
- **Breaking Change** [#44154][]: Subtyping relations of `package:js` classes
270+
have been changed to be more correct and consistent with Dart2JS.
271+
Like `anonymous` classes, non-`anonymous` classes will no longer check the
272+
underlying type in DDC. The internal type representation of these objects have
273+
changed as well, which will affect the `toString` value of these types.
274+
275+
[#44154]: https://github.com/dart-lang/sdk/issues/44154
276+
267277
## 2.13.4 - 2021-06-28
268278

269279
This is a patch release that fixes:

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
16771677
ctorFields = ctor.initializers
16781678
.map((c) => c is FieldInitializer ? c.field : null)
16791679
.toSet()
1680-
..remove(null);
1680+
..remove(null);
16811681
}
16821682

16831683
var body = <js_ast.Statement>[];
@@ -2884,27 +2884,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
28842884
js_ast.Expression typeRep;
28852885

28862886
// Type parameters don't matter as JS interop types cannot be reified.
2887-
// We have to use lazy JS types because until we have proper module
2888-
// loading for JS libraries bundled with Dart libraries, we will sometimes
2889-
// need to load Dart libraries before the corresponding JS libraries are
2890-
// actually loaded.
2891-
// Given a JS type such as:
2892-
// @JS('google.maps.Location')
2893-
// class Location { ... }
2894-
// We can't emit a reference to MyType because the JS library that defines
2895-
// it may be loaded after our code. So for now, we use a special lazy type
2896-
// object to represent MyType.
2897-
// Anonymous JS types do not have a corresponding concrete JS type so we
2898-
// have to use a helper to define them.
2899-
if (isJSAnonymousType(c)) {
2900-
typeRep = runtimeCall(
2901-
'anonymousJSType(#)', [js.escapedString(getLocalClassName(c))]);
2902-
} else {
2903-
var jsName = _emitJsNameWithoutGlobal(c);
2904-
if (jsName != null) {
2905-
typeRep = runtimeCall('lazyJSType(() => #, #)',
2906-
[_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]);
2907-
}
2887+
// package:js types fall under either named or anonymous types. Named types
2888+
// are used to correspond to JS types that exist, but we do not use the
2889+
// underlying type for type checks, so they operate virtually the same as
2890+
// anonymous types. We represent package:js types with a corresponding type
2891+
// object.
2892+
var jsName = isJSAnonymousType(c) ?
2893+
getLocalClassName(c) : _emitJsNameWithoutGlobal(c);
2894+
if (jsName != null) {
2895+
typeRep = runtimeCall('packageJSType(#)', [js.escapedString(jsName)]);
29082896
}
29092897

29102898
if (typeRep != null) {

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ final List<Object> _cacheMaps = JS('!', '[]');
181181
/// A list of functions to reset static fields back to their uninitialized
182182
/// state.
183183
///
184-
/// This is populated by [defineLazyField] and [LazyJSType] and only contains
185-
/// fields that have been initialized.
184+
/// This is populated by [defineLazyField] and only contains fields that have
185+
/// been initialized.
186186
@notNull
187187
final List<void Function()> _resetFields = JS('', '[]');
188188

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart

Lines changed: 41 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ void nativeNonNullAsserts(bool enable) {
8181

8282
final metadata = JS('', 'Symbol("metadata")');
8383

84+
/// A javascript Symbol used to store a canonical version of T? on T.
85+
final _cachedNullable = JS('', 'Symbol("cachedNullable")');
86+
87+
/// A javascript Symbol used to store a canonical version of T* on T.
88+
final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
89+
90+
/// A javascript Symbol used to store prior subtype checks and their results.
91+
final _subtypeCache = JS('', 'Symbol("_subtypeCache")');
92+
8493
/// Types in dart are represented internally at runtime as follows.
8594
///
8695
/// - Normal nominal types, produced from classes, are represented
@@ -197,48 +206,15 @@ F tearoffInterop<F extends Function?>(F f) {
197206
return JS('', '#', ret);
198207
}
199208

200-
/// The Dart type that represents a JavaScript class(/constructor) type.
209+
/// Dart type that represents a package:js class type (either anonymous or not).
201210
///
202-
/// The JavaScript type may not exist, either because it's not loaded yet, or
203-
/// because it's not available (such as with mocks). To handle this gracefully,
204-
/// we disable type checks for in these cases, and allow any JS object to work
205-
/// as if it were an instance of this JS type.
206-
class LazyJSType extends DartType {
207-
Function() _getRawJSTypeFn;
208-
@notNull
211+
/// For the purposes of subtype checks, these match any JS type.
212+
class PackageJSType extends DartType {
209213
final String _dartName;
210-
Object? _rawJSType;
211-
212-
LazyJSType(this._getRawJSTypeFn, this._dartName);
213-
214-
toString() {
215-
var raw = _getRawJSType();
216-
return raw != null ? typeName(raw) : "JSObject<$_dartName>";
217-
}
218-
219-
Object? _getRawJSType() {
220-
var raw = _rawJSType;
221-
if (raw != null) return raw;
222-
223-
// Try to evaluate the JS type. If this fails for any reason, we'll try
224-
// again next time.
225-
// TODO(jmesserly): is it worth trying again? It may create unnecessary
226-
// overhead, especially if exceptions are being thrown. Also it means the
227-
// behavior of a given type check can change later on.
228-
try {
229-
raw = _getRawJSTypeFn();
230-
} catch (e) {}
214+
PackageJSType(this._dartName);
231215

232-
if (raw == null) {
233-
_warn('Cannot find native JavaScript type ($_dartName) for type check');
234-
} else {
235-
_rawJSType = raw;
236-
JS('', '#.push(() => # = null)', _resetFields, _rawJSType);
237-
}
238-
return raw;
239-
}
240-
241-
Object rawJSTypeForCheck() => _getRawJSType() ?? typeRep<JavaScriptObject>();
216+
@override
217+
String toString() => _dartName;
242218

243219
@notNull
244220
@JSExportName('is')
@@ -250,23 +226,6 @@ class LazyJSType extends DartType {
250226
as_T(obj) => is_T(obj) ? obj : castError(obj, this);
251227
}
252228

253-
/// An anonymous JS type
254-
///
255-
/// For the purposes of subtype checks, these match any JS type.
256-
class AnonymousJSType extends DartType {
257-
final String _dartName;
258-
AnonymousJSType(this._dartName);
259-
toString() => _dartName;
260-
261-
@JSExportName('is')
262-
bool is_T(obj) =>
263-
obj != null &&
264-
(_isJsObject(obj) || isSubtypeOf(getReifiedType(obj), this));
265-
266-
@JSExportName('as')
267-
as_T(obj) => is_T(obj) ? obj : castError(obj, this);
268-
}
269-
270229
void _warn(arg) {
271230
JS('void', 'console.warn(#)', arg);
272231
}
@@ -299,35 +258,25 @@ void _nullWarnOnType(type) {
299258
}
300259
}
301260

302-
var _lazyJSTypes = JS<Object>('', 'new Map()');
303-
var _anonymousJSTypes = JS<Object>('', 'new Map()');
304-
305-
lazyJSType(Function() getJSTypeCallback, String name) {
306-
var ret = JS('', '#.get(#)', _lazyJSTypes, name);
307-
if (ret == null) {
308-
ret = LazyJSType(getJSTypeCallback, name);
309-
JS('', '#.set(#, #)', _lazyJSTypes, name, ret);
310-
}
311-
return ret;
312-
}
261+
var _packageJSTypes = JS<Object>('', 'new Map()');
313262

314-
anonymousJSType(String name) {
315-
var ret = JS('', '#.get(#)', _anonymousJSTypes, name);
263+
packageJSType(String name) {
264+
var ret = JS('', '#.get(#)', _packageJSTypes, name);
316265
if (ret == null) {
317-
ret = AnonymousJSType(name);
318-
JS('', '#.set(#, #)', _anonymousJSTypes, name, ret);
266+
ret = PackageJSType(name);
267+
JS('', '#.set(#, #)', _packageJSTypes, name, ret);
319268
}
320269
return ret;
321270
}
322271

323-
/// A javascript Symbol used to store a canonical version of T? on T.
324-
final _cachedNullable = JS('', 'Symbol("cachedNullable")');
325-
326-
/// A javascript Symbol used to store a canonical version of T* on T.
327-
final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
328-
329-
/// A javascript Symbol used to store prior subtype checks and their results.
330-
final _subtypeCache = JS('', 'Symbol("_subtypeCache")');
272+
/// Since package:js types are all subtypes of each other, we use this var to
273+
/// denote *some* package:js type in our subtyping logic.
274+
///
275+
/// Used only when a concrete PackageJSType is not available i.e. when neither
276+
/// the object nor the target type is a PackageJSType. Avoids initializating a
277+
/// new PackageJSType every time. Note that we don't add it to the set of JS
278+
/// types, since it's not an actual JS class.
279+
final _pkgJSTypeForSubtyping = PackageJSType('');
331280

332281
/// Returns a nullable (question, ?) version of [type].
333282
///
@@ -1522,34 +1471,20 @@ bool _isSubtype(t1, t2, @notNull bool strictMode) => JS<bool>('!', '''(() => {
15221471
//
15231472
// JavaScriptObject <: package:js types
15241473
// package:js types <: JavaScriptObject
1525-
//
1526-
// TODO: This doesn't allow package:js type <: another package:js type yet.
15271474
15281475
if (${_isInterfaceSubtype(t1, JavaScriptObject, strictMode)}
1529-
&& (${_jsInstanceOf(t2, LazyJSType)}
1530-
|| ${_jsInstanceOf(t2, AnonymousJSType)}
1531-
// TODO: Since package:js types are instances of LazyJSType and
1532-
// AnonymousJSType, we don't have a mechanism to determine if *some*
1533-
// package:js type implements t2. This will possibly require keeping
1534-
// a map of these relationships for this subtyping check. For now,
1535-
// don't execute the following checks.
1536-
// || _isInterfaceSubtype(LazyJSType, t2, strictMode)
1537-
// || _isInterfaceSubtype(AnonymousJSType, t2, strictMode)
1538-
)) {
1476+
&&
1477+
// TODO: Since package:js types are instances of PackageJSType and
1478+
// we don't have a mechanism to determine if *some* package:js type
1479+
// implements t2. This will possibly require keeping a map of these
1480+
// relationships for this subtyping check. For now, this will only
1481+
// work if t2 is also a PackageJSType.
1482+
${_isInterfaceSubtype(_pkgJSTypeForSubtyping, t2, strictMode)}) {
15391483
return true;
15401484
}
15411485
15421486
if (${_isInterfaceSubtype(JavaScriptObject, t2, strictMode)}
1543-
&& (${_jsInstanceOf(t1, LazyJSType)}
1544-
|| ${_jsInstanceOf(t1, AnonymousJSType)}
1545-
// TODO: We don't have a check in `isInterfaceSubtype` to check that
1546-
// a class is a subtype of *some* package:js class. This will likely
1547-
// require modifying `_isInterfaceSubtype` to check if the
1548-
// interfaces of t1 include a package:js type. For now, don't
1549-
// execute the following checks.
1550-
// || _isInterfaceSubtype(t1, LazyJSType, strictMode)
1551-
// || _isInterfaceSubtype(t1, AnonymousJSType, strictMode)
1552-
)) {
1487+
&& ${_isInterfaceSubtype(t1, _pkgJSTypeForSubtyping, strictMode)}) {
15531488
return true;
15541489
}
15551490
@@ -1617,10 +1552,11 @@ bool _isSubtype(t1, t2, @notNull bool strictMode) => JS<bool>('!', '''(() => {
16171552
})()''');
16181553

16191554
bool _isInterfaceSubtype(t1, t2, @notNull bool strictMode) => JS('', '''(() => {
1620-
// If we have lazy JS types, unwrap them. This will effectively
1621-
// reduce to a prototype check below.
1622-
if (${_jsInstanceOf(t1, LazyJSType)}) $t1 = $t1.rawJSTypeForCheck();
1623-
if (${_jsInstanceOf(t2, LazyJSType)}) $t2 = $t2.rawJSTypeForCheck();
1555+
// Instances of PackageJSType are all subtypes of each other.
1556+
if (${_jsInstanceOf(t1, PackageJSType)}
1557+
&& ${_jsInstanceOf(t2, PackageJSType)}) {
1558+
return true;
1559+
}
16241560
16251561
if ($t1 === $t2) {
16261562
return true;

tests/dartdevc/debugger/debugger_test_golden.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6519,7 +6519,7 @@ Value:
65196519
{
65206520
"style": "background-color: #d9edf7;color: black"
65216521
},
6522-
"Instance of 'TestGenericClass<JSObject<ExampleJSClass>, int>'"
6522+
"Instance of 'TestGenericClass<ExampleJSClass, int>'"
65236523
]
65246524
-----------------------------------
65256525
Test: TestGenericClassJSInterop instance body
@@ -6616,7 +6616,7 @@ Value:
66166616
{
66176617
"style": "background-color: #d9edf7;color: black"
66186618
},
6619-
"TestGenericClass<JSObject<ExampleJSClass>, int>"
6619+
"TestGenericClass<ExampleJSClass, int>"
66206620
]
66216621
-----------------------------------
66226622
Test: TestGenericClassJSInterop definition formatting body

tests/dartdevc_2/debugger/debugger_test_golden.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6519,7 +6519,7 @@ Value:
65196519
{
65206520
"style": "background-color: #d9edf7;color: black"
65216521
},
6522-
"Instance of 'TestGenericClass<JSObject<ExampleJSClass>, int>'"
6522+
"Instance of 'TestGenericClass<ExampleJSClass, int>'"
65236523
]
65246524
-----------------------------------
65256525
Test: TestGenericClassJSInterop instance body
@@ -6616,7 +6616,7 @@ Value:
66166616
{
66176617
"style": "background-color: #d9edf7;color: black"
66186618
},
6619-
"TestGenericClass<JSObject<ExampleJSClass>, int>"
6619+
"TestGenericClass<ExampleJSClass, int>"
66206620
]
66216621
-----------------------------------
66226622
Test: TestGenericClassJSInterop definition formatting body

tests/lib/html/js_typed_interop_lazy_test.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,9 @@ baz.LazyClass = function LazyClass(a) {
142142
expect(new Object() is! AnonClass2, isTrue);
143143

144144
expect(<AnonClass>[] is List<AnonClass>, isTrue);
145-
// TODO(jacobr): why doesn't this test pass?
146-
// expect(<AnonClass>[] is List<AnonClass2>, isTrue);
145+
expect(<AnonClass>[] is List<AnonClass2>, isTrue);
147146
expect(<int>[] is! List<AnonClass>, isTrue);
148-
expect(<AnonClass>[] is! List<LazyClass>, isTrue); //# 01: ok
147+
expect(<AnonClass>[] is List<LazyClass>, isTrue);
149148
expect(<int>[] is! List<LazyClass>, isTrue);
150149
expect(<LazyClass>[] is List<LazyClass>, isTrue);
151150

@@ -234,10 +233,9 @@ baz.foo.NestedLazyClass = function NestedLazyClass(a) {
234233
expect(new Object() is! AnonClass2, isTrue);
235234

236235
expect(<AnonClass>[] is List<AnonClass>, isTrue);
237-
// TODO(jacobr): why doesn't this test pass?
238-
// expect(<AnonClass>[] is List<AnonClass2>, isTrue);
236+
expect(<AnonClass>[] is List<AnonClass2>, isTrue);
239237
expect(<int>[] is! List<AnonClass>, isTrue);
240-
expect(<AnonClass>[] is! List<NestedLazyClass>, isTrue); //# 01: ok
238+
expect(<AnonClass>[] is List<NestedLazyClass>, isTrue);
241239
expect(<int>[] is! List<NestedLazyClass>, isTrue);
242240
expect(<NestedLazyClass>[] is List<NestedLazyClass>, isTrue);
243241

tests/lib_2/html/js_typed_interop_lazy_test.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,9 @@ baz.LazyClass = function LazyClass(a) {
144144
expect(new Object() is! AnonClass2, isTrue);
145145

146146
expect(<AnonClass>[] is List<AnonClass>, isTrue);
147-
// TODO(jacobr): why doesn't this test pass?
148-
// expect(<AnonClass>[] is List<AnonClass2>, isTrue);
147+
expect(<AnonClass>[] is List<AnonClass2>, isTrue);
149148
expect(<int>[] is! List<AnonClass>, isTrue);
150-
expect(<AnonClass>[] is! List<LazyClass>, isTrue); //# 01: ok
149+
expect(<AnonClass>[] is List<LazyClass>, isTrue);
151150
expect(<int>[] is! List<LazyClass>, isTrue);
152151
expect(<LazyClass>[] is List<LazyClass>, isTrue);
153152

@@ -236,10 +235,9 @@ baz.foo.NestedLazyClass = function NestedLazyClass(a) {
236235
expect(new Object() is! AnonClass2, isTrue);
237236

238237
expect(<AnonClass>[] is List<AnonClass>, isTrue);
239-
// TODO(jacobr): why doesn't this test pass?
240-
// expect(<AnonClass>[] is List<AnonClass2>, isTrue);
238+
expect(<AnonClass>[] is List<AnonClass2>, isTrue);
241239
expect(<int>[] is! List<AnonClass>, isTrue);
242-
expect(<AnonClass>[] is! List<NestedLazyClass>, isTrue); //# 01: ok
240+
expect(<AnonClass>[] is List<NestedLazyClass>, isTrue);
243241
expect(<int>[] is! List<NestedLazyClass>, isTrue);
244242
expect(<NestedLazyClass>[] is List<NestedLazyClass>, isTrue);
245243

0 commit comments

Comments
 (0)