blob: 8e700e5fe2f5eab8cea62e6ae5a5224e0fb2ccc7 [file] [log] [blame]
// Copyright (c) 2012, 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.
library dart._interceptors;
import 'dart:collection';
import 'dart:_internal' hide Symbol;
import 'dart:_js_helper';
import 'dart:_foreign_helper' show JS, JSExportName;
import 'dart:math' show Random, ln2;
import 'dart:_runtime' as dart;
part 'js_array.dart';
part 'js_number.dart';
part 'js_string.dart';
// TODO(jmesserly): remove, this doesn't do anything for us.
abstract class Interceptor {
const Interceptor();
// Use native JS toString method instead of standard Dart Object.toString.
String toString() => JS<String>('!', '#.toString()', this);
}
// TODO(jmesserly): remove
getInterceptor(obj) => obj;
/**
* The interceptor class for [bool].
*/
@JsPeerInterface(name: 'Boolean')
class JSBool extends Interceptor implements bool {
const JSBool();
// Note: if you change this, also change the function [S].
@notNull
String toString() => JS<String>('!', r'String(#)', this);
// The values here are SMIs, co-prime and differ about half of the bit
// positions, including the low bit, so they are different mod 2^k.
@notNull
int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811);
@notNull
bool operator &(@nullCheck bool other) =>
JS<bool>('!', "# && #", other, this);
@notNull
bool operator |(@nullCheck bool other) =>
JS<bool>('!', "# || #", other, this);
@notNull
bool operator ^(@nullCheck bool other) => !identical(this, other);
Type get runtimeType => bool;
}
/**
* The supertype for JSString and JSArray. Used by the backend as to
* have a type mask that contains the objects that we can use the
* native JS [] operator and length on.
*/
abstract class JSIndexable<E> {
int get length;
E operator [](int index);
}
/// The supertype for JSMutableArray and JavaScriptIndexingBehavior.
///
// TODO(nshahan) Use as a type mask that contains the objects we can use the JS
// []= operator on.
abstract class JSMutableIndexable<E> extends JSIndexable<E> {
operator []=(int index, E value);
}
/// The interface implemented by JavaScript objects.
///
/// These are methods in addition to the regular Dart Object methods like
/// [Object.hashCode]. This is the type that should be exported by a JavaScript
/// interop library.
abstract class JSObject {}
/// Superclass of all interop objects and native types defined in the web
/// libraries.
///
/// This is the class static interop classes erase to and the class interop
/// extension types should use as the on-type.
class JavaScriptObject extends Interceptor {
const JavaScriptObject();
}
/// Interceptor base class for JavaScript objects not recognized as some more
/// specific native type.
///
/// Note that this used to be `JavaScriptObject`.
class LegacyJavaScriptObject extends JavaScriptObject implements JSObject {
const LegacyJavaScriptObject();
}
/// Interceptor for plain JavaScript objects created as JavaScript object
/// literals or `new Object()`.
///
/// Note that this isn't being used today in ddc. Instead of using interceptors,
/// we have other type logic to distinguish JS types.
class PlainJavaScriptObject extends LegacyJavaScriptObject {
const PlainJavaScriptObject();
}
/// Interceptor for unclassified JavaScript objects, typically objects with a
/// non-trivial prototype chain.
///
/// This class also serves as a fallback for unknown JavaScript exceptions.
/// Note that this isn't being used today in ddc. Instead of using interceptors,
/// we have other type logic to distinguish JS types.
class UnknownJavaScriptObject extends LegacyJavaScriptObject {
const UnknownJavaScriptObject();
}
class NativeError extends Interceptor {
String dartStack() => JS<String>('!', '#.stack', this);
}
// Note that this needs to be in interceptors.dart in order for
// it to be picked up as an extension type.
@JsPeerInterface(name: 'TypeError')
class JSNoSuchMethodError extends NativeError implements NoSuchMethodError {
static final _nullError = RegExp(r"^Cannot read property '(.+)' of null$");
static final _notAFunction = RegExp(r"^(.+) is not a function$");
static final _extensionName = RegExp(r"^Symbol\(dartx\.(.+)\)$");
static final _privateName = RegExp(r"^Symbol\((_.+)\)$");
String? _fieldName(String message) {
RegExpMatch? match = _nullError.firstMatch(message);
if (match == null) return null;
String name = match[1]!;
match = _extensionName.firstMatch(name) ?? _privateName.firstMatch(name);
return match != null ? match[1] : name;
}
String? _functionCallTarget(String message) {
var match = _notAFunction.firstMatch(message);
return match != null ? match[1] : null;
}
String dartStack() {
var stack = super.dartStack();
// Strip TypeError from first line.
stack = toString() + '\n' + stack.split('\n').sublist(1).join('\n');
return stack;
}
StackTrace get stackTrace => dart.stackTrace(this);
String toString() {
String message = JS('!', '#.message', this);
var callTarget = _functionCallTarget(message);
if (callTarget != null) {
return "NoSuchMethodError: tried to call a non-function, such as null: "
"'$callTarget'";
}
// TODO(vsm): Distinguish between null reference errors and other
// TypeErrors. We should not get non-null TypeErrors from DDC code,
// but we may from native JavaScript.
var name = _fieldName(message);
if (name == null) {
// Not a Null NSM error: fallback to JS.
return JS<String>('!', '#.toString()', this);
}
return "NoSuchMethodError: invalid member on null: '$name'";
}
}
@JsPeerInterface(name: 'Function')
class JSFunction extends Interceptor {
toString() {
// If the function is a Type object, we should just display the type name.
//
// Regular Dart code should typically get wrapped type objects instead of
// raw type (aka JS constructor) objects, however raw type objects can be
// exposed to Dart code via JS interop or debugging tools.
if (dart.isType(this)) return dart.typeName(this);
return JS<String>('!', r'"Closure: " + # + " from: " + #',
dart.typeName(dart.getReifiedType(this)), this);
}
// TODO(nshahan): We can remove these if we canonicalize all tearoffs and no
// longer support weak null safety "same type" equality.
operator ==(other) {
// Basic function values (no generics, no bound instances) are represented
// as the original functions so reference equality is sufficient.
if (JS<bool>('!', '# === #', this, other)) return true;
dynamic boundObj;
dynamic otherFn;
var originalFn = JS('', '#._originalFn', this);
if (originalFn == null) {
// No generic instantiation present.
boundObj = JS('', '#._boundObject', this);
if (boundObj == null) return false;
originalFn = this;
otherFn = other;
} else {
// Generic instantiation was present.
var typeArgs = JS('!', '#._typeArgs', this);
var otherTypeArgs = JS('', '#._typeArgs', other);
// Test if all instantiated type arguments are equal.
if (dart.compileTimeFlag('soundNullSafety')) {
// The list has been canonicalized on creation so reference equality
// is sufficient.
if (JS<bool>('!', '# !== #', typeArgs, otherTypeArgs)) return false;
} else {
// In weak null safety all types arguments must be compared in a way
// that is agnostic to legacy.
var typeArgCount = JS<int>('!', '#.length', typeArgs);
if (JS<bool>('!', '!#', otherTypeArgs) ||
typeArgCount != JS('', '#.length', otherTypeArgs)) {
return false;
}
for (var i = 0; i < typeArgCount; i++) {
var typeArg = JS('!', '#[#]', typeArgs, i);
var otherTypeArg = JS('!', '#[#]', otherTypeArgs, i);
// TODO(nshahan) Replace wrapType() with a lighter weight legacy
// erasure.
if (JS<bool>('!', '# !== #', dart.wrapType(typeArg),
dart.wrapType(otherTypeArg))) {
return false;
}
}
}
boundObj = JS('', '#._boundObject', originalFn);
otherFn = JS('', '#._originalFn', other);
if (boundObj == null) {
// This isn't an instance tearoff, test if the original uninstantiated
// methods are equal.
return JS<bool>('!', '# === #', originalFn, otherFn);
}
}
// This is an instance tearoff, test if the bound instances and methods
// are equal.
return JS<bool>(
'!',
'# === #._boundObject && #._boundMethod === #._boundMethod',
boundObj,
otherFn,
originalFn,
otherFn);
}
get hashCode {
var boundObj = JS<Object?>('', '#._boundObject', this);
if (boundObj == null) return identityHashCode(this);
var boundMethod = JS<Object>('!', '#._boundMethod', this);
int hash = (17 * 31 + boundObj.hashCode) & 0x1fffffff;
return (hash * 31 + identityHashCode(boundMethod)) & 0x1fffffff;
}
get runtimeType => dart.wrapType(dart.getReifiedType(this));
}
/// A class used for implementing `null` tear-offs.
class JSNull {
toString() => 'null';
noSuchMethod(Invocation i) => dart.defaultNoSuchMethod(null, i);
}
final Object jsNull = JSNull();
// Note that this needs to be in interceptors.dart in order for
// it to be picked up as an extension type.
@JsPeerInterface(name: 'RangeError')
class JSRangeError extends Interceptor implements ArgumentError {
StackTrace get stackTrace => dart.stackTrace(this);
get invalidValue => null;
get name => null;
get message => JS<String>('!', '#.message', this);
String toString() => "Invalid argument: $message";
}
// Obsolete in dart dev compiler. Added only so that the same version of
// dart:html can be used in dart2js an dev compiler.
// Warning: calls to these methods need to be removed before custom elements
// and cross-frame dom objects behave correctly in ddc.
// See https://github.com/dart-lang/sdk/issues/28326
findInterceptorConstructorForType(Type? type) {}
findConstructorForNativeSubclassType(Type? type, String name) {}
getNativeInterceptor(object) {}
setDispatchProperty(object, value) {}
// Added to allow dart2js and dartdevc to share tests
// TODO(sigmund): revisit whether this method is still needed after reoganizing
// all web tests.
findInterceptorForType(Type? type) {}