Reapply "[dart2js,rti] Add direct methods for `as JSObject`"
This reverts commit 43b5c1432e14bdca6bfd80ddd1bfa5539ee73906.
This time, fall back on code that works for mocks and fakes.
Issue: #60746
Change-Id: Iecb822486729f8b10377c94bf87d06406bb0f5bc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434920
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/common/elements.dart b/pkg/compiler/lib/src/common/elements.dart
index 302cde2..1dc11ad 100644
--- a/pkg/compiler/lib/src/common/elements.dart
+++ b/pkg/compiler/lib/src/common/elements.dart
@@ -700,6 +700,9 @@
late final ClassEntity jsUInt31Class = _findInterceptorsClass('JSUInt31');
+ // Static interop JavaScript object interface class.
+ late final ClassEntity jsObjectClass = _findInterceptorsClass('JSObject');
+
/// Returns `true` member is the 'findIndexForNativeSubclassType' method
/// declared in `dart:_interceptors`.
bool isFindIndexForNativeSubclassType(MemberEntity member) {
@@ -1100,6 +1103,11 @@
FunctionEntity get specializedAsStringNullable =>
_findRtiFunction('_asStringQ');
+ FunctionEntity get specializedAsJSObject => _findRtiFunction('_asJSObject');
+
+ FunctionEntity get specializedAsJSObjectNullable =>
+ _findRtiFunction('_asJSObjectQ');
+
FunctionEntity get instantiatedGenericFunctionType =>
_findRtiFunction('instantiatedGenericFunctionType');
diff --git a/pkg/compiler/lib/src/js_backend/specialized_checks.dart b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
index 7d7d4eb..47b4555 100644
--- a/pkg/compiler/lib/src/js_backend/specialized_checks.dart
+++ b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
@@ -192,6 +192,15 @@
return commonElements.specializedAsInt;
}
+ if (element == commonElements.objectClass) {
+ if (!nullable) return commonElements.specializedAsObject;
+ }
+
+ if (element == commonElements.jsObjectClass) {
+ if (nullable) return commonElements.specializedAsJSObjectNullable;
+ return commonElements.specializedAsJSObject;
+ }
+
return null;
}
}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 12c0ade..ba9198b 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -1973,6 +1973,7 @@
commonElements.listClass,
commonElements.objectClass,
commonElements.mapClass,
+ commonElements.jsObjectClass,
];
// TODO(floitsch): this should probably be on a per-fragment basis.
diff --git a/sdk/lib/_internal/js_shared/lib/rti.dart b/sdk/lib/_internal/js_shared/lib/rti.dart
index 5df35b6..db73e91 100644
--- a/sdk/lib/_internal/js_shared/lib/rti.dart
+++ b/sdk/lib/_internal/js_shared/lib/rti.dart
@@ -1337,6 +1337,8 @@
asFn = RAW_DART_FUNCTION_REF(_asNumQ);
} else if (_Utils.isIdentical(testRti, TYPE_REF<double?>())) {
asFn = RAW_DART_FUNCTION_REF(_asDoubleQ);
+ } else if (_Utils.isIdentical(testRti, TYPE_REF<JSObject?>())) {
+ asFn = RAW_DART_FUNCTION_REF(_asJSObjectQ);
}
} else {
if (_Utils.isIdentical(testRti, TYPE_REF<int>())) {
@@ -1349,6 +1351,8 @@
asFn = RAW_DART_FUNCTION_REF(_asNum);
} else if (_Utils.isIdentical(testRti, TYPE_REF<double>())) {
asFn = RAW_DART_FUNCTION_REF(_asDouble);
+ } else if (_Utils.isIdentical(testRti, TYPE_REF<JSObject>())) {
+ asFn = RAW_DART_FUNCTION_REF(_asJSObject);
}
}
return asFn;
@@ -1460,6 +1464,27 @@
return false;
}
+/// A version of _isJSObject that does not need to be attached to an Rti `_is`
+/// method. The Rti is needed on the uncommon slow path (Dart mocks and fakes of
+/// interp objects), so this method falls back to using a full `is JSObject`
+/// expression, with some duplicated work on the uncommon paths.
+bool _isJSObjectStandalone(Object? object) {
+ // Keep the control flow here matching `_isJSObject`.
+ if (JS('bool', 'typeof # == "object"', object)) {
+ if (_isDartObject(object)) {
+ return object is JSObject;
+ }
+ return true;
+ }
+ if (JS('bool', 'typeof # == "function"', object)) {
+ if (JS_GET_FLAG('DEV_COMPILER')) {
+ return object is JSObject;
+ }
+ return true;
+ }
+ return false;
+}
+
/// General unspecialized 'as' check that works for any type.
/// Called from generated code.
@pragma('dart2js:stack-starts-at-throw')
@@ -1701,6 +1726,23 @@
throw _TypeError.forType(object, 'String?');
}
+/// Specialization for 'as JSObject'.
+/// Called from generated code.
+@pragma('dart2js:stack-starts-at-throw')
+JSObject _asJSObject(Object? object) {
+ if (_isJSObjectStandalone(object)) return _Utils.asJSObject(object);
+ throw _TypeError.forType(object, 'JSObject');
+}
+
+/// Specialization for 'as JSObject?'.
+/// Called from generated code.
+@pragma('dart2js:stack-starts-at-throw')
+JSObject? _asJSObjectQ(Object? object) {
+ if (object == null) return _Utils.asNull(object);
+ if (_isJSObjectStandalone(object)) return _Utils.asJSObject(object);
+ throw _TypeError.forType(object, 'JSObject?');
+}
+
String _rtiArrayToString(Object? array, List<String>? genericContext) {
String s = '', sep = '';
for (int i = 0; i < _Utils.arrayLength(array); i++) {
@@ -3920,6 +3962,7 @@
static Rti asRti(Object? s) => JS('Rti', '#', s);
static Rti? asRtiOrNull(Object? s) => JS('Rti|Null', '#', s);
static _Type as_Type(Object? o) => JS('_Type', '#', o);
+ static JSObject asJSObject(Object? o) => JS('', '#', o);
static bool isString(Object? o) => JS('bool', 'typeof # == "string"', o);
static bool isNum(Object? o) => JS('bool', 'typeof # == "number"', o);
diff --git a/tests/web/internal/html_mocks_with_static_interop_test.dart b/tests/web/internal/html_mocks_with_static_interop_test.dart
index 3ad5704..fe51cb7 100644
--- a/tests/web/internal/html_mocks_with_static_interop_test.dart
+++ b/tests/web/internal/html_mocks_with_static_interop_test.dart
@@ -23,11 +23,14 @@
}
void main() {
- test(MockWindow(), true);
- test(window, false);
+ testIs(MockWindow(), true);
+ testIs(window, false);
+
+ testAs(MockWindow(), true);
+ testAs(window, false);
}
-void test(Window w, bool isMock) {
+void testIs(Window w, bool isMock) {
Expect.type<Window>(w);
Expect.type<dartJsInterop.JSObject>(w);
@@ -46,3 +49,31 @@
Expect.isFalse(doc is MockDocument);
}
}
+
+void testAs(Window w, bool isMock) {
+ asCheck<Window>(w);
+ asCheck<dartJsInterop.JSObject>(w);
+
+ final doc = w.document;
+
+ asCheck<HtmlDocument>(doc);
+ asCheck<dartJsInterop.JSObject>(doc);
+
+ if (isMock) {
+ asCheck<MockWindow>(w);
+ asCheck<MockDocument>(doc);
+ w as MockWindow;
+ doc as MockDocument;
+ } else {
+ Expect.throws(() => asCheck<MockWindow>(w));
+ Expect.throws(() => asCheck<MockDocument>(doc));
+ Expect.throws(() => w as MockWindow);
+ Expect.throws(() => doc as MockDocument);
+ }
+
+ asCheck<String>('hello');
+ Expect.throws(() => asCheck<String>(w));
+}
+
+@pragma('dart2js:never-inline')
+void asCheck<T>(Object? o) => o as T;