mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
this includes various cleanups found along the way: * various issues with JS annotations, see #30043 * getGenericClass is now optimized when used in a JS builtin with a Type literal * typed list literals should be slightly faster to create * memoized getters used by dart:_runtime should be faster * registerExtension should be a tad faster, and now understands JSArray * dart.bind "safeName" hack gone in codegenerator * JSArray<E>.runtimeType returns List<E> * JSArray<E>.noSuchMethod works * cleans up code that generates `setExtensionBaseClass` (used only by JSArray) * _throwUnsafe helper for generating a `throw` in --unsafe compile mode * fix tearoff of JS interop types (_emitSimpleIdentifier) * fix TODO about splitting LazyJSType and AnonymousJSType * fix is/as/cast methods on TypeRep and related classes * fix codegenerator to not tearoff "hashCode" and "runtimeType" (they are not methods) * fix NoSuchMethodError.toString() (update via dart2js sdk impl) R=vsm@google.com Review-Url: https://codereview.chromium.org/2962263002 .
This commit is contained in:
parent
be4df24c5f
commit
5533a256cb
25 changed files with 1319 additions and 1279 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -391,81 +391,61 @@ class CodeGenerator extends Object
|
|||
return new JS.ObjectInitializer(properties, multiline: true);
|
||||
}
|
||||
|
||||
List<String> _getJSName(Element e) {
|
||||
if (e.library == null ||
|
||||
findAnnotation(e.library, isPublicJSAnnotation) == null) {
|
||||
return null;
|
||||
}
|
||||
/// If [e] is a property accessor element, this returns the
|
||||
/// (possibly synthetic) field that corresponds to it, otherwise returns [e].
|
||||
Element _getNonAccessorElement(Element e) =>
|
||||
e is PropertyAccessorElement ? e.variable : e;
|
||||
|
||||
/// Returns the name of [e] but removes trailing `=` from setter names.
|
||||
// TODO(jmesserly): it would be nice if Analyzer had something like this.
|
||||
// `Element.displayName` is close, but it also normalizes operator names in
|
||||
// a way we don't want.
|
||||
String _getElementName(Element e) => _getNonAccessorElement(e).name;
|
||||
|
||||
bool _isExternal(Element e) =>
|
||||
e is ExecutableElement && e.isExternal ||
|
||||
e is PropertyInducingElement &&
|
||||
(e.getter.isExternal || e.setter.isExternal);
|
||||
|
||||
bool _isJSElement(Element e) =>
|
||||
e?.library != null &&
|
||||
_isJSNative(e.library) &&
|
||||
(_isExternal(e) || e is ClassElement && _isJSNative(e));
|
||||
|
||||
String _getJSNameWithoutGlobal(Element e) {
|
||||
if (!_isJSElement(e)) return null;
|
||||
var libraryJSName = getAnnotationName(e.library, isPublicJSAnnotation);
|
||||
var libraryPrefix = <String>[];
|
||||
if (libraryJSName != null && libraryJSName.isNotEmpty) {
|
||||
libraryPrefix.addAll(libraryJSName.split('.'));
|
||||
}
|
||||
|
||||
var original = e;
|
||||
var variableElement = e;
|
||||
if (original is PropertyAccessorElement) {
|
||||
variableElement = original.variable;
|
||||
if (original.isSynthetic) e = variableElement;
|
||||
}
|
||||
|
||||
String elementJSName;
|
||||
if (findAnnotation(e, isPublicJSAnnotation) != null) {
|
||||
elementJSName = getAnnotationName(e, isPublicJSAnnotation) ?? '';
|
||||
}
|
||||
|
||||
if (variableElement is TopLevelVariableElement) {
|
||||
elementJSName = _jsInteropStaticMemberName(original);
|
||||
}
|
||||
if (elementJSName == null) return null;
|
||||
|
||||
var elementJSParts = <String>[];
|
||||
if (elementJSName.isNotEmpty) {
|
||||
elementJSParts.addAll(elementJSName.split('.'));
|
||||
} else {
|
||||
elementJSParts.add(e.name);
|
||||
}
|
||||
|
||||
return libraryPrefix..addAll(elementJSParts);
|
||||
var jsName =
|
||||
getAnnotationName(e, isPublicJSAnnotation) ?? _getElementName(e);
|
||||
return libraryJSName != null ? '$libraryJSName.$jsName' : jsName;
|
||||
}
|
||||
|
||||
JS.Expression _emitJSInterop(Element e) {
|
||||
var jsName = _getJSName(e);
|
||||
if (jsName == null) return null;
|
||||
var fullName = ['global']..addAll(jsName);
|
||||
JS.Expression access = _runtimeModule;
|
||||
for (var part in fullName) {
|
||||
access = new JS.PropertyAccess(access, js.string(part));
|
||||
var jsName = _getJSNameWithoutGlobal(e);
|
||||
return jsName != null ? _emitJSInteropForGlobal(jsName) : null;
|
||||
}
|
||||
|
||||
JS.Expression _emitJSInteropForGlobal(String name) {
|
||||
var access = _callHelper('global');
|
||||
for (var part in name.split('.')) {
|
||||
access = new JS.PropertyAccess(access, js.escapedString(part, "'"));
|
||||
}
|
||||
return access;
|
||||
}
|
||||
|
||||
String _jsInteropStaticMemberName(Element e) {
|
||||
if (e?.library == null ||
|
||||
findAnnotation(e.library, isPublicJSAnnotation) == null) {
|
||||
return null;
|
||||
JS.Expression _emitJSInteropStaticMemberName(Element e) {
|
||||
if (!_isJSElement(e)) return null;
|
||||
var name = getAnnotationName(e, isPublicJSAnnotation);
|
||||
if (name != null) {
|
||||
if (name.contains('.')) {
|
||||
throw new UnsupportedError(
|
||||
'static members do not support "." in their names. '
|
||||
'See https://github.com/dart-lang/sdk/issues/27926');
|
||||
}
|
||||
} else {
|
||||
name = _getElementName(e);
|
||||
}
|
||||
if (e is ExecutableElement && e.isExternal) {
|
||||
return getAnnotationName(e, isPublicJSAnnotation) ??
|
||||
(e is PropertyAccessorElement ? e.variable : e).name;
|
||||
}
|
||||
if (e is PropertyInducingElement && e.getter.isExternal) {
|
||||
return getAnnotationName(e, isPublicJSAnnotation) ?? e.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _emitJSInteropStaticMemberName(Element e) {
|
||||
var name = _jsInteropStaticMemberName(e);
|
||||
// We do not support statics names with JS annotations containing dots.
|
||||
// See https://github.com/dart-lang/sdk/issues/27926
|
||||
if (name != null && name.contains('.')) {
|
||||
throw new UnsupportedError(
|
||||
'We do not support JS annotations containing dots on static members. '
|
||||
'See https://github.com/dart-lang/sdk/issues/27926');
|
||||
}
|
||||
return name;
|
||||
return js.escapedString(name, "'");
|
||||
}
|
||||
|
||||
/// Flattens blocks in [items] to a single list.
|
||||
|
@ -857,7 +837,7 @@ class CodeGenerator extends Object
|
|||
return _statement(block);
|
||||
}
|
||||
|
||||
JS.Statement _emitJsType(Element e) {
|
||||
JS.Statement _emitJSType(Element e) {
|
||||
var jsTypeName = getAnnotationName(e, isJSAnnotation);
|
||||
if (jsTypeName == null || jsTypeName == e.name) return null;
|
||||
|
||||
|
@ -876,7 +856,7 @@ class CodeGenerator extends Object
|
|||
if (findAnnotation(classElem, isPublicJSAnnotation) != null) return null;
|
||||
|
||||
// If this is a JavaScript type, emit it now and then exit.
|
||||
var jsTypeDef = _emitJsType(classElem);
|
||||
var jsTypeDef = _emitJSType(classElem);
|
||||
if (jsTypeDef != null) return jsTypeDef;
|
||||
|
||||
var ctors = <ConstructorDeclaration>[];
|
||||
|
@ -1699,13 +1679,10 @@ class CodeGenerator extends Object
|
|||
JS.Statement _setBaseClass(ClassElement classElem, JS.Expression className,
|
||||
List<String> jsPeerNames, List<JS.Statement> body) {
|
||||
var typeFormals = classElem.typeParameters;
|
||||
if (jsPeerNames.isNotEmpty && typeFormals.isNotEmpty) {
|
||||
for (var peer in jsPeerNames) {
|
||||
// TODO(jmesserly): we should just extend Array in the first place
|
||||
var newBaseClass = _callHelper('global.#', [peer]);
|
||||
body.add(_callHelperStatement(
|
||||
'setExtensionBaseClass(#, #);', [className, newBaseClass]));
|
||||
}
|
||||
if (jsPeerNames.length == 1 && typeFormals.isNotEmpty) {
|
||||
var newBaseClass = _callHelper('global.#', jsPeerNames[0]);
|
||||
body.add(_callHelperStatement(
|
||||
'setExtensionBaseClass(#, #);', [className, newBaseClass]));
|
||||
} else if (_hasDeferredSupertype.contains(classElem)) {
|
||||
// TODO(vsm): consider just threading the deferred supertype through
|
||||
// instead of recording classElem in a set on the class and recomputing
|
||||
|
@ -2760,9 +2737,7 @@ class CodeGenerator extends Object
|
|||
JS.Expression _emitSimpleIdentifier(SimpleIdentifier node) {
|
||||
var accessor = resolutionMap.staticElementForIdentifier(node);
|
||||
if (accessor == null) {
|
||||
return _callHelper(
|
||||
'throw(Error("compile error: unresolved identifier: " + #))',
|
||||
js.escapedString(node.name ?? '<null>'));
|
||||
return _throwUnsafe('unresolved identifier: ' + (node.name ?? '<null>'));
|
||||
}
|
||||
|
||||
// Get the original declaring element. If we had a property accessor, this
|
||||
|
@ -2807,17 +2782,10 @@ class CodeGenerator extends Object
|
|||
|
||||
// For instance members, we add implicit-this.
|
||||
// For method tear-offs, we ensure it's a bound method.
|
||||
var tearOff = element is MethodElement && !inInvocationContext(node);
|
||||
if (tearOff) {
|
||||
// To be safe always use the symbolized name when binding on a native
|
||||
// class as bind assumes the name will match the name class sigatures
|
||||
// which is symbolized for native classes.
|
||||
var safeName = _emitMemberName(name,
|
||||
isStatic: isStatic,
|
||||
type: type,
|
||||
element: accessor,
|
||||
alwaysSymbolizeNative: true);
|
||||
return _callHelper('bind(this, #)', safeName);
|
||||
if (element is MethodElement &&
|
||||
!inInvocationContext(node) &&
|
||||
!_isJSNative(element.enclosingElement)) {
|
||||
return _callHelper('bind(this, #)', member);
|
||||
}
|
||||
return js.call('this.#', member);
|
||||
}
|
||||
|
@ -3080,7 +3048,6 @@ class CodeGenerator extends Object
|
|||
_declareBeforeUse(element);
|
||||
}
|
||||
|
||||
var interop = _emitJSInterop(element);
|
||||
// Type parameters don't matter as JS interop types cannot be reified.
|
||||
// We have to use lazy JS types because until we have proper module
|
||||
// loading for JS libraries bundled with Dart libraries, we will sometimes
|
||||
|
@ -3094,14 +3061,13 @@ class CodeGenerator extends Object
|
|||
// object to represent MyType.
|
||||
// Anonymous JS types do not have a corresponding concrete JS type so we
|
||||
// have to use a helper to define them.
|
||||
if (interop != null) {
|
||||
if (_isObjectLiteral(element)) {
|
||||
return _callHelper(
|
||||
'lazyAnonymousJSType(#)', js.string(element.displayName));
|
||||
} else {
|
||||
return _callHelper('lazyJSType(() => #, #)',
|
||||
[interop, js.string(_getJSName(element).join('.'))]);
|
||||
}
|
||||
if (_isObjectLiteral(element)) {
|
||||
return _callHelper('anonymousJSType(#)', js.escapedString(element.name));
|
||||
}
|
||||
var jsName = _getJSNameWithoutGlobal(element);
|
||||
if (jsName != null) {
|
||||
return _callHelper('lazyJSType(() => #, #)',
|
||||
[_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]);
|
||||
}
|
||||
|
||||
// TODO(jmesserly): like constants, should we hoist function types out of
|
||||
|
@ -3136,7 +3102,7 @@ class CodeGenerator extends Object
|
|||
jsArgs = [];
|
||||
}
|
||||
if (jsArgs != null) {
|
||||
var genericName = _emitTopLevelName(element, suffix: '\$');
|
||||
var genericName = _emitTopLevelNameNoInterop(element, suffix: '\$');
|
||||
var typeRep = js.call('#(#)', [genericName, jsArgs]);
|
||||
return nameType
|
||||
? _typeTable.nameType(type, typeRep, hoistType: hoistType)
|
||||
|
@ -3148,15 +3114,13 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
JS.PropertyAccess _emitTopLevelName(Element e, {String suffix: ''}) {
|
||||
var interop = _emitJSInterop(e);
|
||||
if (interop != null) return interop;
|
||||
return _emitTopLevelNameNoInterop(e, suffix: suffix);
|
||||
return _emitJSInterop(e) ?? _emitTopLevelNameNoInterop(e, suffix: suffix);
|
||||
}
|
||||
|
||||
JS.PropertyAccess _emitTopLevelNameNoInterop(Element e, {String suffix: ''}) {
|
||||
String name = getJSExportName(e) + suffix;
|
||||
var name = getJSExportName(e) ?? _getElementName(e);
|
||||
return new JS.PropertyAccess(
|
||||
emitLibraryName(e.library), _propertyName(name));
|
||||
emitLibraryName(e.library), _propertyName(name + suffix));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3454,15 +3418,13 @@ class CodeGenerator extends Object
|
|||
JS.Expression _getSuperHelper(Element member, JS.Expression jsName) {
|
||||
var jsMethod = _superHelpers.putIfAbsent(member.name, () {
|
||||
if (member is PropertyAccessorElement) {
|
||||
var field = member.variable as FieldElement;
|
||||
var name = field.name;
|
||||
var isSetter = member.isSetter;
|
||||
var fn = js.call(
|
||||
isSetter
|
||||
? 'function(x) { super[#] = x; }'
|
||||
: 'function() { return super[#]; }',
|
||||
[jsName]);
|
||||
return new JS.Method(new JS.TemporaryId(name), fn,
|
||||
return new JS.Method(new JS.TemporaryId(member.variable.name), fn,
|
||||
isGetter: !isSetter, isSetter: isSetter);
|
||||
} else {
|
||||
var method = member as MethodElement;
|
||||
|
@ -3498,7 +3460,7 @@ class CodeGenerator extends Object
|
|||
var element = node.methodName.staticElement;
|
||||
bool isStatic = element is ExecutableElement && element.isStatic;
|
||||
var name = node.methodName.name;
|
||||
var memberName =
|
||||
var jsName =
|
||||
_emitMemberName(name, type: type, isStatic: isStatic, element: element);
|
||||
|
||||
JS.Expression jsTarget = _emitTarget(target, element, isStatic);
|
||||
|
@ -3508,19 +3470,19 @@ class CodeGenerator extends Object
|
|||
_emitDynamicOperationName('dgsend'),
|
||||
jsTarget,
|
||||
new JS.ArrayInitializer(typeArgs),
|
||||
memberName,
|
||||
jsName,
|
||||
args
|
||||
]);
|
||||
} else {
|
||||
return _callHelper('#(#, #, #)',
|
||||
[_emitDynamicOperationName('dsend'), jsTarget, memberName, args]);
|
||||
[_emitDynamicOperationName('dsend'), jsTarget, jsName, args]);
|
||||
}
|
||||
}
|
||||
if (_isObjectMemberCall(target, name)) {
|
||||
assert(typeArgs == null); // Object methods don't take type args.
|
||||
return _callHelper('#(#, #)', [name, jsTarget, args]);
|
||||
}
|
||||
jsTarget = _emitTargetAccess(jsTarget, memberName, element);
|
||||
jsTarget = _emitTargetAccess(jsTarget, jsName, element);
|
||||
if (typeArgs != null) jsTarget = new JS.Call(jsTarget, typeArgs);
|
||||
|
||||
return new JS.Call(jsTarget, args);
|
||||
|
@ -3677,7 +3639,7 @@ class CodeGenerator extends Object
|
|||
var args = node.argumentList.arguments;
|
||||
// arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer`
|
||||
var code = args[1];
|
||||
List<AstNode> templateArgs;
|
||||
List<Expression> templateArgs;
|
||||
String source;
|
||||
if (code is StringInterpolation) {
|
||||
if (args.length > 2) {
|
||||
|
@ -3720,6 +3682,27 @@ class CodeGenerator extends Object
|
|||
}
|
||||
}
|
||||
|
||||
JS.Expression visitTemplateArg(Expression arg) {
|
||||
if (arg is InvocationExpression) {
|
||||
var e = arg is MethodInvocation
|
||||
? arg.methodName.staticElement
|
||||
: (arg as FunctionExpressionInvocation).staticElement;
|
||||
if (e?.name == 'getGenericClass' &&
|
||||
e.library.name == 'dart._runtime' &&
|
||||
arg.argumentList.arguments.length == 1) {
|
||||
var typeArg = arg.argumentList.arguments[0];
|
||||
if (typeArg is SimpleIdentifier) {
|
||||
var typeElem = typeArg.staticElement;
|
||||
if (typeElem is TypeDefiningElement &&
|
||||
typeElem.type is ParameterizedType) {
|
||||
return _emitTopLevelNameNoInterop(typeElem, suffix: '\$');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _visit(arg);
|
||||
}
|
||||
|
||||
// TODO(rnystrom): The JS() calls are almost never nested, and probably
|
||||
// really shouldn't be, but there are at least a couple of calls in the
|
||||
// HTML library where an argument to JS() is itself a JS() call. If those
|
||||
|
@ -3728,12 +3711,11 @@ class CodeGenerator extends Object
|
|||
// wrapped Type object.
|
||||
var wasInForeignJS = _isInForeignJS;
|
||||
_isInForeignJS = true;
|
||||
|
||||
var template = js.parseForeignJS(source);
|
||||
var result = template.instantiate(_visitList(templateArgs));
|
||||
|
||||
var jsArgs = templateArgs.map(visitTemplateArg).toList();
|
||||
_isInForeignJS = wasInForeignJS;
|
||||
|
||||
var result = js.parseForeignJS(source).instantiate(jsArgs);
|
||||
|
||||
// `throw` is emitted as a statement by `parseForeignJS`.
|
||||
assert(result is JS.Expression || node.parent is ExpressionStatement);
|
||||
return result;
|
||||
|
@ -4060,13 +4042,8 @@ class CodeGenerator extends Object
|
|||
bool isFactory = false;
|
||||
bool isNative = false;
|
||||
if (element == null) {
|
||||
ctor = _callHelper(
|
||||
'throw(Error("compile error: unresolved constructor: " '
|
||||
'+ # + "." + #))',
|
||||
[
|
||||
js.escapedString(type?.name ?? '<null>'),
|
||||
js.escapedString(name?.name ?? '<unnamed>')
|
||||
]);
|
||||
ctor = _throwUnsafe('unresolved constructor: ${type?.name ?? '<null>'}'
|
||||
'.${name?.name ?? '<unnamed>'}');
|
||||
} else {
|
||||
ctor = _emitConstructorName(element, type, name);
|
||||
isFactory = element.isFactory;
|
||||
|
@ -4087,13 +4064,13 @@ class CodeGenerator extends Object
|
|||
return emitNew();
|
||||
}
|
||||
|
||||
bool _isObjectLiteral(ClassElement classElem) {
|
||||
return findAnnotation(classElem, isPublicJSAnnotation) != null &&
|
||||
bool _isObjectLiteral(Element classElem) {
|
||||
return _isJSNative(classElem) &&
|
||||
findAnnotation(classElem, isJSAnonymousAnnotation) != null;
|
||||
}
|
||||
|
||||
bool _isJSNative(ClassElement classElem) =>
|
||||
findAnnotation(classElem, isPublicJSAnnotation) != null;
|
||||
bool _isJSNative(Element e) =>
|
||||
findAnnotation(e, isPublicJSAnnotation) != null;
|
||||
|
||||
JS.Expression _emitObjectLiteral(ArgumentList argumentList) {
|
||||
var args = _emitArgumentList(argumentList);
|
||||
|
@ -4380,20 +4357,20 @@ class CodeGenerator extends Object
|
|||
if (expr.value >= low && expr.value <= high) return expr.value;
|
||||
return null;
|
||||
}
|
||||
int finishIdentifier(SimpleIdentifier identifier) {
|
||||
Element staticElement = identifier.staticElement;
|
||||
if (staticElement is PropertyAccessorElement && staticElement.isGetter) {
|
||||
PropertyInducingElement variable = staticElement.variable;
|
||||
int value = variable?.computeConstantValue()?.toIntValue();
|
||||
if (value != null && value >= low && value <= high) return value;
|
||||
}
|
||||
|
||||
Identifier id;
|
||||
if (expr is SimpleIdentifier) {
|
||||
id = expr;
|
||||
} else if (expr is PrefixedIdentifier && !expr.isDeferred) {
|
||||
id = expr.identifier;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (expr is SimpleIdentifier) {
|
||||
return finishIdentifier(expr);
|
||||
} else if (expr is PrefixedIdentifier && !expr.isDeferred) {
|
||||
return finishIdentifier(expr.identifier);
|
||||
var element = id.staticElement;
|
||||
if (element is PropertyAccessorElement && element.isGetter) {
|
||||
var variable = element.variable;
|
||||
int value = variable?.computeConstantValue()?.toIntValue();
|
||||
if (value != null && value >= low && value <= high) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -4839,17 +4816,16 @@ class CodeGenerator extends Object
|
|||
Expression target, SimpleIdentifier memberId, DartType resultType) {
|
||||
var accessor = memberId.staticElement;
|
||||
// If `member` is a getter/setter, get the corresponding
|
||||
var field =
|
||||
accessor is PropertyAccessorElement ? accessor.variable : accessor;
|
||||
var field = _getNonAccessorElement(accessor);
|
||||
String memberName = memberId.name;
|
||||
var typeArgs = _getTypeArgs(accessor, resultType);
|
||||
|
||||
bool isStatic = field is ClassMemberElement && field.isStatic;
|
||||
var name = _emitMemberName(memberName,
|
||||
var jsName = _emitMemberName(memberName,
|
||||
type: getStaticType(target), isStatic: isStatic, element: accessor);
|
||||
if (isDynamicInvoke(target)) {
|
||||
return _callHelper('#(#, #)',
|
||||
[_emitDynamicOperationName('dload'), _visit(target), name]);
|
||||
[_emitDynamicOperationName('dload'), _visit(target), jsName]);
|
||||
}
|
||||
|
||||
var jsTarget = _emitTarget(target, accessor, isStatic);
|
||||
|
@ -4865,39 +4841,27 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
JS.Expression result;
|
||||
if (accessor is MethodElement && !isStatic) {
|
||||
// Tear-off methods: explicitly bind it.
|
||||
// To be safe always use the symbolized name when binding on a native
|
||||
// class as bind assumes the name will match the name class signatures
|
||||
// which is symbolized for native classes.
|
||||
var safeName = _emitMemberName(memberName,
|
||||
type: getStaticType(target),
|
||||
isStatic: isStatic,
|
||||
element: accessor,
|
||||
alwaysSymbolizeNative: true);
|
||||
if (_isObjectMemberCall(target, memberName)) {
|
||||
if (_isObjectMethod(memberName)) {
|
||||
result = _callHelper('bind(#, #)', [jsTarget, jsName]);
|
||||
} else {
|
||||
result = _callHelper('#(#)', [memberName, jsTarget]);
|
||||
}
|
||||
} else if (accessor is MethodElement &&
|
||||
!isStatic &&
|
||||
!_isJSNative(accessor.enclosingElement)) {
|
||||
if (isSuper) {
|
||||
result = _callHelper('bind(this, #, #)',
|
||||
[safeName, _emitTargetAccess(jsTarget, name, accessor)]);
|
||||
} else if (_isObjectMemberCall(target, memberName)) {
|
||||
var fn = js.call(
|
||||
memberName == 'noSuchMethod'
|
||||
? 'function(i) { return #.#(this, i); }'
|
||||
: 'function() { return #.#(this); }',
|
||||
[_runtimeModule, memberName]);
|
||||
result = _callHelper(
|
||||
'bind(#, #, #)', [jsTarget, _propertyName(memberName), fn]);
|
||||
[jsName, _emitTargetAccess(jsTarget, jsName, accessor)]);
|
||||
} else {
|
||||
result = _callHelper('bind(#, #)', [jsTarget, safeName]);
|
||||
result = _callHelper('bind(#, #)', [jsTarget, jsName]);
|
||||
}
|
||||
} else if (_isObjectMemberCall(target, memberName)) {
|
||||
result = _callHelper('#(#)', [memberName, jsTarget]);
|
||||
} else {
|
||||
result = _emitTargetAccess(jsTarget, name, accessor);
|
||||
result = _emitTargetAccess(jsTarget, jsName, accessor);
|
||||
}
|
||||
if (typeArgs == null) {
|
||||
return result;
|
||||
}
|
||||
return _callHelper('gbind(#, #)', [result, typeArgs]);
|
||||
return typeArgs == null
|
||||
? result
|
||||
: _callHelper('gbind(#, #)', [result, typeArgs]);
|
||||
}
|
||||
|
||||
JS.LiteralString _emitDynamicOperationName(String name) =>
|
||||
|
@ -5410,11 +5374,11 @@ class CodeGenerator extends Object
|
|||
/// Unlike call sites, we always have an element available, so we can use it
|
||||
/// directly rather than computing the relevant options for [_emitMemberName].
|
||||
JS.Expression _declareMemberName(ExecutableElement e, {bool useExtension}) {
|
||||
var name = (e is PropertyAccessorElement) ? e.variable.name : e.name;
|
||||
return _emitMemberName(name,
|
||||
return _emitMemberName(_getElementName(e),
|
||||
isStatic: e.isStatic,
|
||||
useExtension:
|
||||
useExtension ?? _extensionTypes.isNativeClass(e.enclosingElement));
|
||||
useExtension ?? _extensionTypes.isNativeClass(e.enclosingElement),
|
||||
element: e);
|
||||
}
|
||||
|
||||
/// This handles member renaming for private names and operators.
|
||||
|
@ -5461,11 +5425,18 @@ class CodeGenerator extends Object
|
|||
{DartType type,
|
||||
bool isStatic: false,
|
||||
bool useExtension,
|
||||
bool alwaysSymbolizeNative: false,
|
||||
Element element}) {
|
||||
// Static members skip the rename steps and may require JS interop renames.
|
||||
if (isStatic) {
|
||||
return _propertyName(_emitJSInteropStaticMemberName(element) ?? name);
|
||||
return _emitJSInteropStaticMemberName(element) ?? _propertyName(name);
|
||||
}
|
||||
|
||||
// We allow some (illegal in Dart) member names to be used in our private
|
||||
// SDK code. These renames need to be included at every declaration,
|
||||
// including overrides in subclasses.
|
||||
if (element != null) {
|
||||
var runtimeName = getJSExportName(element);
|
||||
if (runtimeName != null) return _propertyName(runtimeName);
|
||||
}
|
||||
|
||||
if (name.startsWith('_')) {
|
||||
|
@ -5492,15 +5463,7 @@ class CodeGenerator extends Object
|
|||
|
||||
var result = _propertyName(name);
|
||||
|
||||
if (useExtension == null) {
|
||||
// Dart "extension" methods. Used for JS Array, Boolean, Number, String.
|
||||
var baseType = type;
|
||||
while (baseType is TypeParameterType) {
|
||||
baseType = (baseType.element as TypeParameterElement).bound;
|
||||
}
|
||||
useExtension = baseType is InterfaceType &&
|
||||
_isSymbolizedMember(baseType, name, alwaysSymbolizeNative);
|
||||
}
|
||||
useExtension ??= _isSymbolizedMember(type, name);
|
||||
|
||||
return useExtension
|
||||
? js.call('#.#', [_extensionSymbolsModule, result])
|
||||
|
@ -5537,33 +5500,38 @@ class CodeGenerator extends Object
|
|||
/// Note, this is an underlying assumption here that, if another native type
|
||||
/// subtypes this one, it also forwards this member to its underlying native
|
||||
/// one without renaming.
|
||||
bool _isSymbolizedMember(
|
||||
InterfaceType type, String name, bool alwaysSymbolizeNative) {
|
||||
bool _isSymbolizedMember(DartType type, String name) {
|
||||
// Object members are handled separately.
|
||||
if (isObjectMember(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var element = type.element;
|
||||
if (_extensionTypes.isNativeClass(element)) {
|
||||
var member = _lookupForwardedMember(element, name);
|
||||
|
||||
// Fields on a native class are implicitly native.
|
||||
// Methods/getters/setters are marked external/native.
|
||||
if (member is FieldElement ||
|
||||
member is ExecutableElement && member.isExternal) {
|
||||
var jsName = getAnnotationName(member, isJsName);
|
||||
return alwaysSymbolizeNative || (jsName != null && jsName != name);
|
||||
} else {
|
||||
// Non-external members must be symbolized.
|
||||
return true;
|
||||
}
|
||||
while (type is TypeParameterType) {
|
||||
type = (type as TypeParameterType).bound;
|
||||
}
|
||||
// If the receiver *may* be a native type (i.e., an interface allowed to
|
||||
// be implemented by a native class), conservatively symbolize - we don't
|
||||
// whether it'll be implemented via forwarding.
|
||||
// TODO(vsm): Consider CHA here to be less conservative.
|
||||
return _extensionTypes.isNativeInterface(element);
|
||||
if (type is InterfaceType) {
|
||||
var element = type.element;
|
||||
if (_extensionTypes.isNativeClass(element)) {
|
||||
var member = _lookupForwardedMember(element, name);
|
||||
|
||||
// Fields on a native class are implicitly native.
|
||||
// Methods/getters/setters are marked external/native.
|
||||
if (member is FieldElement ||
|
||||
member is ExecutableElement && member.isExternal) {
|
||||
var jsName = getAnnotationName(member, isJsName);
|
||||
return jsName != null && jsName != name;
|
||||
} else {
|
||||
// Non-external members must be symbolized.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If the receiver *may* be a native type (i.e., an interface allowed to
|
||||
// be implemented by a native class), conservatively symbolize - we don't
|
||||
// know whether it'll be implemented via forwarding.
|
||||
// TODO(vsm): Consider CHA here to be less conservative.
|
||||
return _extensionTypes.isNativeInterface(element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JS.TemporaryId _emitPrivateNameSymbol(LibraryElement library, String name) {
|
||||
|
@ -5617,6 +5585,9 @@ class CodeGenerator extends Object
|
|||
return false;
|
||||
}
|
||||
|
||||
bool _isObjectMethod(String name) =>
|
||||
name == 'toString' || name == 'noSuchMethod';
|
||||
|
||||
// TODO(leafp): Various analyzer pieces computed similar things.
|
||||
// Share this logic somewhere?
|
||||
DartType _getExpectedReturnType(ExecutableElement element) {
|
||||
|
@ -5675,6 +5646,9 @@ class CodeGenerator extends Object
|
|||
return js.statement('#.$code', args);
|
||||
}
|
||||
|
||||
JS.Expression _throwUnsafe(String message) => _callHelper(
|
||||
'throw(Error(#))', js.escapedString("compile error: $message"));
|
||||
|
||||
_unreachable(AstNode node) {
|
||||
throw new UnsupportedError(
|
||||
'tried to generate an unreachable node: `$node`');
|
||||
|
|
|
@ -74,11 +74,8 @@ bool isNativeAnnotation(DartObjectImpl value) =>
|
|||
/// the SDK), or `null` if there's none. This is used to control the name
|
||||
/// under which functions are compiled and exported.
|
||||
String getJSExportName(Element e) {
|
||||
var original = e;
|
||||
if (original is PropertyAccessorElement) e = original.variable;
|
||||
if (e.source.isInSystemLibrary) {
|
||||
var real = original.isSynthetic ? e : original;
|
||||
return getAnnotationName(real, isJSExportNameAnnotation) ?? e.name;
|
||||
}
|
||||
return e.name;
|
||||
if (!e.source.isInSystemLibrary) return null;
|
||||
|
||||
e = e is PropertyAccessorElement && e.isSynthetic ? e.variable : e;
|
||||
return getAnnotationName(e, isJSExportNameAnnotation);
|
||||
}
|
||||
|
|
|
@ -25,8 +25,10 @@ abstract class JsTypeRefCodegen {
|
|||
/// Finds the qualified path to the type.
|
||||
JS.TypeRef _emitTopLevelTypeRef(DartType type) {
|
||||
var e = type.element;
|
||||
return new JS.TypeRef.qualified(
|
||||
[emitLibraryName(e.library), new JS.Identifier(getJSExportName(e))]);
|
||||
return new JS.TypeRef.qualified([
|
||||
emitLibraryName(e.library),
|
||||
new JS.Identifier(getJSExportName(e) ?? e.name)
|
||||
]);
|
||||
}
|
||||
|
||||
JS.TypeRef emitTypeRef(DartType type) {
|
||||
|
|
|
@ -277,7 +277,6 @@ define(['dart_sdk', 'async_helper', 'expect', 'unittest', 'is', 'require'],
|
|||
'map_from_iterable_test': is.firefox('<=50') ? fail : pass,
|
||||
'nan_infinity_test_01_multi': fail,
|
||||
'null_nosuchmethod_test': fail,
|
||||
'null_test': fail,
|
||||
'reg_exp_all_matches_test': whitelist,
|
||||
'reg_exp_start_end_test': whitelist,
|
||||
'regress_r21715_test': fail,
|
||||
|
|
|
@ -9,9 +9,9 @@ define(['dart_sdk'], function(dart_sdk) {
|
|||
(unresolved_names.C.new = function() {
|
||||
}).prototype = unresolved_names.C.prototype;
|
||||
unresolved_names.main = function() {
|
||||
new (dart.throw(Error("compile error: unresolved constructor: " + "dynamic" + "." + "<unnamed>")))();
|
||||
new (dart.throw(Error("compile error: unresolved constructor: " + "C" + "." + "bar")))();
|
||||
core.print(dart.throw(Error("compile error: unresolved identifier: " + "baz")));
|
||||
new (dart.throw(Error("compile error: unresolved constructor: dynamic.<unnamed>")))();
|
||||
new (dart.throw(Error("compile error: unresolved constructor: C.bar")))();
|
||||
core.print(dart.throw(Error("compile error: unresolved identifier: baz")));
|
||||
core.print(dart.dload(unresolved_names.C, 'quux'));
|
||||
};
|
||||
dart.fn(unresolved_names.main, VoidTodynamic());
|
||||
|
|
|
@ -553,46 +553,38 @@ class NoSuchMethodError {
|
|||
|
||||
@patch
|
||||
String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int i = 0;
|
||||
StringBuffer sb = new StringBuffer('');
|
||||
String comma = '';
|
||||
if (_arguments != null) {
|
||||
for (; i < _arguments.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.write(", ");
|
||||
}
|
||||
sb.write(Error.safeToString(_arguments[i]));
|
||||
for (var argument in _arguments) {
|
||||
sb.write(comma);
|
||||
sb.write(Error.safeToString(argument));
|
||||
comma = ', ';
|
||||
}
|
||||
}
|
||||
if (_namedArguments != null) {
|
||||
_namedArguments.forEach((Symbol key, var value) {
|
||||
if (i > 0) {
|
||||
sb.write(", ");
|
||||
}
|
||||
sb.write(comma);
|
||||
sb.write(_symbolToString(key));
|
||||
sb.write(": ");
|
||||
sb.write(Error.safeToString(value));
|
||||
i++;
|
||||
comma = ', ';
|
||||
});
|
||||
}
|
||||
String memberName = _symbolToString(_memberName);
|
||||
String receiverText = Error.safeToString(_receiver);
|
||||
String actualParameters = '$sb';
|
||||
if (_existingArgumentNames == null) {
|
||||
return "NoSuchMethodError : method not found: '$_memberName'\n"
|
||||
"Receiver: ${Error.safeToString(_receiver)}\n"
|
||||
"Arguments: [$sb]";
|
||||
return "NoSuchMethodError: method not found: '$memberName'\n"
|
||||
"Receiver: ${receiverText}\n"
|
||||
"Arguments: [$actualParameters]";
|
||||
} else {
|
||||
String actualParameters = sb.toString();
|
||||
sb = new StringBuffer();
|
||||
for (int i = 0; i < _existingArgumentNames.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.write(", ");
|
||||
}
|
||||
sb.write(_existingArgumentNames[i]);
|
||||
}
|
||||
String formalParameters = sb.toString();
|
||||
String formalParameters = _existingArgumentNames.join(', ');
|
||||
return "NoSuchMethodError: incorrect number of arguments passed to "
|
||||
"method named '$_memberName'\n"
|
||||
"Receiver: ${Error.safeToString(_receiver)}\n"
|
||||
"Tried calling: $_memberName($actualParameters)\n"
|
||||
"Found: $_memberName($formalParameters)";
|
||||
"method named '$memberName'\n"
|
||||
"Receiver: ${receiverText}\n"
|
||||
"Tried calling: $memberName($actualParameters)\n"
|
||||
"Found: $memberName($formalParameters)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -409,46 +409,32 @@ defineExtensionNames(names) =>
|
|||
|
||||
/// Install properties in prototype-first order. Properties / descriptors from
|
||||
/// more specific types should overwrite ones from less specific types.
|
||||
void _installProperties(jsProto, extProto) {
|
||||
// This relies on the Dart type literal evaluating to the JavaScript
|
||||
// constructor.
|
||||
var coreObjProto = JS('', '#.prototype', Object);
|
||||
|
||||
var parentsExtension = JS('', '(#.__proto__)[#]', jsProto, _extensionType);
|
||||
var installedParent =
|
||||
JS('', '# && #.prototype', parentsExtension, parentsExtension);
|
||||
|
||||
_installProperties2(jsProto, extProto, coreObjProto, installedParent);
|
||||
}
|
||||
|
||||
void _installProperties2(jsProto, extProto, coreObjProto, installedParent) {
|
||||
if (JS('bool', '# === #', extProto, coreObjProto)) {
|
||||
_installPropertiesForObject(jsProto, coreObjProto);
|
||||
void _installProperties(jsProto, dartType, installedParent) {
|
||||
if (JS('bool', '# === #', dartType, Object)) {
|
||||
_installPropertiesForObject(jsProto);
|
||||
return;
|
||||
}
|
||||
if (JS('bool', '# !== #', jsProto, extProto)) {
|
||||
var extParent = JS('', '#.__proto__', extProto);
|
||||
|
||||
// If the extension methods of the parent have been installed on the parent
|
||||
// of [jsProto], the methods will be available via prototype inheritance.
|
||||
|
||||
if (JS('bool', '# !== #', installedParent, extParent)) {
|
||||
_installProperties2(jsProto, extParent, coreObjProto, installedParent);
|
||||
}
|
||||
// If the extension methods of the parent have been installed on the parent
|
||||
// of [jsProto], the methods will be available via prototype inheritance.
|
||||
var dartSupertype = JS('', '#.__proto__', dartType);
|
||||
if (JS('bool', '# !== #', dartSupertype, installedParent)) {
|
||||
_installProperties(jsProto, dartSupertype, installedParent);
|
||||
}
|
||||
copyTheseProperties(jsProto, extProto, getOwnPropertySymbols(extProto));
|
||||
|
||||
var dartProto = JS('', '#.prototype', dartType);
|
||||
copyTheseProperties(jsProto, dartProto, getOwnPropertySymbols(dartProto));
|
||||
}
|
||||
|
||||
void _installPropertiesForObject(jsProto, coreObjProto) {
|
||||
void _installPropertiesForObject(jsProto) {
|
||||
// core.Object members need to be copied from the non-symbol name to the
|
||||
// symbol name.
|
||||
var coreObjProto = JS('', '#.prototype', Object);
|
||||
var names = getOwnPropertyNames(coreObjProto);
|
||||
for (int i = 0; i < JS('int', '#.length', names); ++i) {
|
||||
var name = JS('', '#[#]', names, i);
|
||||
var desc = getOwnPropertyDescriptor(coreObjProto, name);
|
||||
defineProperty(jsProto, getExtensionSymbol(name), desc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/// Copy symbols from the prototype of the source to destination.
|
||||
|
@ -460,16 +446,17 @@ registerExtension(jsType, dartExtType) => JS(
|
|||
// TODO(vsm): Not all registered js types are real.
|
||||
if (!jsType) return;
|
||||
|
||||
let extProto = $dartExtType.prototype;
|
||||
let jsProto = $jsType.prototype;
|
||||
|
||||
// TODO(vsm): This sometimes doesn't exist on FF. These types will be
|
||||
// broken.
|
||||
if (!jsProto) return;
|
||||
|
||||
$_installProperties(jsProto, $dartExtType, jsProto[$_extensionType]);
|
||||
|
||||
// Mark the JS type's instances so we can easily check for extensions.
|
||||
jsProto[$_extensionType] = $dartExtType;
|
||||
$_installProperties(jsProto, extProto);
|
||||
|
||||
function updateSig(sigF) {
|
||||
let originalDesc = $getOwnPropertyDescriptor($dartExtType, sigF);
|
||||
if (originalDesc === void 0) return;
|
||||
|
@ -546,7 +533,7 @@ setType(obj, type) {
|
|||
|
||||
/// Sets the element type of a list literal.
|
||||
list(obj, elementType) =>
|
||||
JS('', '$setType($obj, ${getGenericClass(JSArray)}($elementType))');
|
||||
setType(obj, JS('', '#(#)', getGenericClass(JSArray), elementType));
|
||||
|
||||
/// Link the extension to the type it's extending as a base class.
|
||||
setBaseClass(derived, base) {
|
||||
|
@ -555,17 +542,12 @@ setBaseClass(derived, base) {
|
|||
JS('', '#.__proto__ = #', derived, base);
|
||||
}
|
||||
|
||||
/// Like [setBaseClass] but for generic extension types, e.g. `JSArray<E>`
|
||||
setExtensionBaseClass(derived, base) {
|
||||
// Mark the generic type as an extension type and link the prototype objects
|
||||
return JS(
|
||||
'',
|
||||
'''(() => {
|
||||
if ($base) {
|
||||
$derived.prototype[$_extensionType] = $derived;
|
||||
$derived.prototype.__proto__ = $base.prototype
|
||||
}
|
||||
})()''');
|
||||
/// Like [setBaseClass], but for generic extension types such as `JSArray<E>`.
|
||||
setExtensionBaseClass(dartType, jsType) {
|
||||
// Mark the generic type as an extension type and link the prototype objects.
|
||||
var dartProto = JS('', '#.prototype', dartType);
|
||||
JS('', '#[#] = #', dartProto, _extensionType, dartType);
|
||||
JS('', '#.__proto__ = #.prototype', dartProto, jsType);
|
||||
}
|
||||
|
||||
defineEnumValues(enumClass, names) {
|
||||
|
|
|
@ -36,38 +36,52 @@ class InvocationImpl extends Invocation {
|
|||
/// If the optional `f` argument is passed in, it will be used as the method.
|
||||
/// This supports cases like `super.foo` where we need to tear off the method
|
||||
/// from the superclass, not from the `obj` directly.
|
||||
/// TODO(leafp): Consider caching the tearoff on the object?
|
||||
// TODO(leafp): Consider caching the tearoff on the object?
|
||||
bind(obj, name, f) {
|
||||
if (f == null) f = JS('', '#[#]', obj, name);
|
||||
|
||||
// TODO(jmesserly): it would be nice to do this lazily, but JS interop seems
|
||||
// to require us to be eager (the test below).
|
||||
var sig = getMethodType(getType(obj), name);
|
||||
|
||||
// JS interop case: do not bind this for compatibility with the dart2js
|
||||
// implementation where we cannot bind this reliably here until we trust
|
||||
// types more.
|
||||
if (sig == null) return f;
|
||||
|
||||
f = JS('', '#.bind(#)', f, obj);
|
||||
// Handle Object members.
|
||||
var method;
|
||||
if (JS('bool', '# === "toString"', name)) {
|
||||
method = _toString;
|
||||
f = JS('', '() => #(#)', _toString, obj);
|
||||
} else if (JS('bool', '# === "noSuchMethod"', name)) {
|
||||
method = noSuchMethod;
|
||||
f = JS('', '(i) => #(#, i)', noSuchMethod, obj);
|
||||
} else {
|
||||
// All other members
|
||||
if (f == null) f = JS('', '#[#]', obj, name);
|
||||
method = f;
|
||||
f = JS('', '#.bind(#)', f, obj);
|
||||
}
|
||||
// TODO(jmesserly): should we canonicalize tearoffs so we don't need to
|
||||
// define ==/hashCode? Then we'd only need the signature.
|
||||
// Another idea here is to have a type that maps to Function.prototype,
|
||||
// similar to all other JS types. That would give us a place to put function
|
||||
// equality, hashCode, runtimeType, noSuchMethod, and toString, rather than
|
||||
// it being a special case in every Object member helper.
|
||||
JS(
|
||||
'',
|
||||
r'''#[dartx["=="]] = function boundMethodEquals(other) {
|
||||
return other[#] === this[#] && other[#] === this[#];
|
||||
}''',
|
||||
'#[dartx["=="]] = '
|
||||
'(f) => { let eq = f[#]; return eq != null && eq(#, #); }',
|
||||
f,
|
||||
_boundMethodTarget,
|
||||
_boundMethodTarget,
|
||||
_boundMethodName,
|
||||
_boundMethodName);
|
||||
JS('', '#[#] = #', f, _boundMethodTarget, obj);
|
||||
JS('', '#[#] = #', f, _boundMethodName, name);
|
||||
tag(f, sig);
|
||||
_tearoffEquals,
|
||||
obj,
|
||||
method);
|
||||
JS('', '#[#] = (o, m) => o === # && m === #', f, _tearoffEquals, obj, method);
|
||||
JS(
|
||||
'',
|
||||
'#[#] = function() {'
|
||||
'let hash = (17 * 31 + #) & 0x1fffffff;'
|
||||
'return (hash * 31 + #) & 0x1fffffff; }',
|
||||
f,
|
||||
_tearoffHashcode,
|
||||
hashCode(obj),
|
||||
hashCode(method));
|
||||
tagLazy(f, JS('', '() => #', getMethodType(getType(obj), name)));
|
||||
return f;
|
||||
}
|
||||
|
||||
final _boundMethodTarget = JS('', 'Symbol("_boundMethodTarget")');
|
||||
final _boundMethodName = JS('', 'Symbol("_boundMethodName")');
|
||||
final _tearoffEquals = JS('', 'Symbol("_tearoffEquals")');
|
||||
final _tearoffHashcode = JS('', 'Symbol("_tearoffHashcode")');
|
||||
|
||||
/// Instantiate a generic method.
|
||||
///
|
||||
|
@ -91,7 +105,7 @@ dload(obj, field) {
|
|||
var type = getType(obj);
|
||||
|
||||
if (hasField(type, f) || hasGetter(type, f)) return JS('', '#[#]', obj, f);
|
||||
if (hasMethod(type, f)) return bind(obj, f, JS('', 'void 0'));
|
||||
if (hasMethod(type, f)) return bind(obj, f, null);
|
||||
|
||||
// Always allow for JS interop objects.
|
||||
if (isJsInterop(obj)) return JS('', '#[#]', obj, f);
|
||||
|
@ -503,7 +517,7 @@ instanceOfOrNull(obj, type) => JS(
|
|||
})()''');
|
||||
|
||||
@JSExportName('is')
|
||||
instanceOf(obj, type) => JS(
|
||||
bool instanceOf(obj, type) => JS(
|
||||
'',
|
||||
'''(() => {
|
||||
if ($obj == null) {
|
||||
|
@ -892,7 +906,8 @@ hashCode(obj) {
|
|||
// From JSBool.hashCode, see comment there.
|
||||
return JS('', '# ? (2 * 3 * 23 * 3761) : (269 * 811)', obj);
|
||||
case "function":
|
||||
// TODO(jmesserly): this doesn't work for method tear-offs.
|
||||
var hashFn = JS('', '#[#]', obj, _tearoffHashcode);
|
||||
if (hashFn != null) return JS('int', '#()', hashFn);
|
||||
return Primitives.objectHashCode(obj);
|
||||
}
|
||||
|
||||
|
@ -1010,3 +1025,10 @@ _canonicalMember(obj, name) {
|
|||
/// Libraries are not actually deferred in DDC, so this just returns a future
|
||||
/// that completes immediately.
|
||||
Future loadLibrary() => new Future.value();
|
||||
|
||||
/// Defines lazy statics.
|
||||
void defineLazy(to, from) {
|
||||
for (var name in getOwnNamesAndSymbols(from)) {
|
||||
defineLazyProperty(to, name, getOwnPropertyDescriptor(from, name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,36 +151,13 @@ _nonPrimitiveRuntimeType(obj) {
|
|||
|
||||
/// Given an internal runtime type object, wraps it in a `WrappedType` object
|
||||
/// that implements the dart:core Type interface.
|
||||
wrapType(type) {
|
||||
Type wrapType(type) {
|
||||
// If we've already wrapped this type once, use the previous wrapper. This
|
||||
// way, multiple references to the same type return an identical Type.
|
||||
if (JS('bool', '#.hasOwnProperty(#)', type, _typeObject)) {
|
||||
return JS('', '#[#]', type, _typeObject);
|
||||
}
|
||||
return JS('', '#[#] = #', type, _typeObject, new WrappedType(type));
|
||||
}
|
||||
|
||||
var _lazyJSTypes = JS('', 'new Map()');
|
||||
|
||||
lazyJSType(getJSTypeCallback, name) {
|
||||
var key = JS('String', '#.toString()', getJSTypeCallback);
|
||||
if (JS('bool', '#.has(#)', _lazyJSTypes, key)) {
|
||||
return JS('', '#.get(#)', _lazyJSTypes, key);
|
||||
}
|
||||
var ret = new LazyJSType(getJSTypeCallback, name);
|
||||
JS('', '#.set(#, #)', _lazyJSTypes, key, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO(jacobr): do not use the same LazyJSType object for anonymous JS types
|
||||
// from different libraries.
|
||||
lazyAnonymousJSType(name) {
|
||||
if (JS('bool', '#.has(#)', _lazyJSTypes, name)) {
|
||||
return JS('', '#.get(#)', _lazyJSTypes, name);
|
||||
}
|
||||
var ret = new LazyJSType(null, name);
|
||||
JS('', '#.set(#, #)', _lazyJSTypes, name, ret);
|
||||
return ret;
|
||||
return JS('Type', '#[#] = #', type, _typeObject, new WrappedType(type));
|
||||
}
|
||||
|
||||
/// Given a WrappedType, return the internal runtime type object.
|
||||
|
@ -197,12 +174,11 @@ void tag(value, t) {
|
|||
}
|
||||
|
||||
void tagComputed(value, compute) {
|
||||
JS('', '#(#, #, { get: # })', defineProperty, value, _runtimeType, compute);
|
||||
defineGetter(value, _runtimeType, compute);
|
||||
}
|
||||
|
||||
void tagLazy(value, compute) {
|
||||
JS('', '#(#, #, { get: # })', defineLazyProperty, value, _runtimeType,
|
||||
compute);
|
||||
defineMemoizedGetter(value, _runtimeType, compute);
|
||||
}
|
||||
|
||||
var _loadedModules = JS('', 'new Map()');
|
||||
|
|
|
@ -56,110 +56,104 @@ final _typeObject = JS('', 'Symbol("typeObject")');
|
|||
// TODO(jmesserly): we shouldn't implement Type here. It should be moved down
|
||||
// to AbstractFunctionType.
|
||||
class TypeRep implements Type {
|
||||
TypeRep() {
|
||||
_initialize;
|
||||
}
|
||||
String get name => this.toString();
|
||||
|
||||
@JSExportName('is')
|
||||
bool is_T(object) => instanceOf(object, this);
|
||||
|
||||
@JSExportName('as')
|
||||
as_T(object) => cast(object, this);
|
||||
|
||||
@JSExportName('_check')
|
||||
check_T(object) => check(object, this);
|
||||
}
|
||||
|
||||
class Dynamic extends TypeRep {
|
||||
toString() => 'dynamic';
|
||||
|
||||
@JSExportName('is')
|
||||
bool is_T(object) => true;
|
||||
|
||||
@JSExportName('as')
|
||||
as_T(object) => object;
|
||||
|
||||
@JSExportName('_check')
|
||||
check_T(object) => object;
|
||||
}
|
||||
|
||||
class LazyJSType extends TypeRep {
|
||||
final _jsTypeCallback;
|
||||
final _dartName;
|
||||
final Function() _rawJSType;
|
||||
final String _dartName;
|
||||
|
||||
LazyJSType(this._jsTypeCallback, this._dartName);
|
||||
LazyJSType(this._rawJSType, this._dartName);
|
||||
|
||||
get _rawJSType => JS('', '#()', _jsTypeCallback);
|
||||
toString() => typeName(_rawJSType());
|
||||
|
||||
toString() => _jsTypeCallback != null ? typeName(_rawJSType) : _dartName;
|
||||
rawJSTypeForCheck() {
|
||||
var raw = _rawJSType();
|
||||
if (raw != null) return raw;
|
||||
_warn('Cannot find native JavaScript type ($_dartName) for type check');
|
||||
return _dynamic;
|
||||
}
|
||||
|
||||
@JSExportName('is')
|
||||
bool is_T(obj) => instanceOf(obj, rawJSTypeForCheck());
|
||||
|
||||
@JSExportName('as')
|
||||
as_T(obj) => cast(obj, rawJSTypeForCheck());
|
||||
|
||||
@JSExportName('_check')
|
||||
check_T(obj) => check(obj, rawJSTypeForCheck());
|
||||
}
|
||||
|
||||
/// An anonymous JS type
|
||||
///
|
||||
/// For the purposes of subtype checks, these match any JS type.
|
||||
class AnonymousJSType extends TypeRep {
|
||||
final String _dartName;
|
||||
AnonymousJSType(this._dartName);
|
||||
toString() => _dartName;
|
||||
|
||||
@JSExportName('is')
|
||||
bool is_T(obj) => _isJSType(getReifiedType(obj));
|
||||
|
||||
@JSExportName('as')
|
||||
as_T(obj) => is_T(obj) ? obj : cast(obj, this);
|
||||
|
||||
@JSExportName('_check')
|
||||
check_T(obj) => is_T(obj) ? obj : check(obj, this);
|
||||
}
|
||||
|
||||
void _warn(arg) {
|
||||
JS('void', 'console.warn(#)', arg);
|
||||
}
|
||||
|
||||
_isInstanceOfLazyJSType(o, LazyJSType t) {
|
||||
if (t._jsTypeCallback != null) {
|
||||
if (t._rawJSType == null) {
|
||||
var expected = t._dartName;
|
||||
var actual = typeName(getReifiedType(o));
|
||||
_warn('Cannot find native JavaScript type ($expected) '
|
||||
'to type check $actual');
|
||||
return true;
|
||||
}
|
||||
return JS('bool', 'dart.is(#, #)', o, t._rawJSType);
|
||||
}
|
||||
if (o == null) return false;
|
||||
// Anonymous case: match any JS type.
|
||||
return _isJSObject(o);
|
||||
}
|
||||
|
||||
_asInstanceOfLazyJSType(o, LazyJSType t) {
|
||||
if (t._jsTypeCallback != null) {
|
||||
if (t._rawJSType == null) {
|
||||
var expected = t._dartName;
|
||||
var actual = typeName(getReifiedType(o));
|
||||
_warn('Cannot find native JavaScript type ($expected) '
|
||||
'to type check $actual');
|
||||
return o;
|
||||
}
|
||||
return JS('bool', 'dart.as(#, #)', o, t._rawJSType);
|
||||
}
|
||||
// Anonymous case: allow any JS type.
|
||||
if (o == null) return null;
|
||||
if (!_isJSObject(o)) _throwCastError(o, t, true);
|
||||
return o;
|
||||
}
|
||||
|
||||
bool _isJSObject(o) =>
|
||||
JS('bool', '!dart.getReifiedType(#)[dart._runtimeType]', o);
|
||||
|
||||
bool _isJSType(t) => JS('bool', '!#[dart._runtimeType]', t);
|
||||
|
||||
var _lazyJSTypes = JS('', 'new Map()');
|
||||
var _anonymousJSTypes = JS('', 'new Map()');
|
||||
|
||||
lazyJSType(Function() getJSTypeCallback, String name) {
|
||||
var ret = JS('', '#.get(#)', _lazyJSTypes, name);
|
||||
if (ret == null) {
|
||||
ret = new LazyJSType(getJSTypeCallback, name);
|
||||
JS('', '#.set(#, #)', _lazyJSTypes, name, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
anonymousJSType(String name) {
|
||||
var ret = JS('', '#.get(#)', _anonymousJSTypes, name);
|
||||
if (ret == null) {
|
||||
ret = new AnonymousJSType(name);
|
||||
JS('', '#.set(#, #)', _anonymousJSTypes, name, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@JSExportName('dynamic')
|
||||
final _dynamic = new Dynamic();
|
||||
|
||||
final _initialize = _initialize2();
|
||||
|
||||
_initialize2() => JS(
|
||||
'',
|
||||
'''(() => {
|
||||
// JavaScript API forwards to runtime library.
|
||||
$TypeRep.prototype.is = function is_T(object) {
|
||||
return dart.is(object, this);
|
||||
};
|
||||
$TypeRep.prototype.as = function as_T(object) {
|
||||
return dart.as(object, this);
|
||||
};
|
||||
$TypeRep.prototype._check = function check_T(object) {
|
||||
return dart.check(object, this);
|
||||
};
|
||||
|
||||
// Fast path for type `dynamic`.
|
||||
$Dynamic.prototype.is = function is_Dynamic(object) {
|
||||
return true;
|
||||
};
|
||||
$Dynamic.prototype.as = function as_Dynamic(object) {
|
||||
return object;
|
||||
};
|
||||
$Dynamic.prototype._check = function check_Dynamic(object) {
|
||||
return object;
|
||||
};
|
||||
|
||||
$LazyJSType.prototype.is = function is_T(object) {
|
||||
return $_isInstanceOfLazyJSType(object, this);
|
||||
};
|
||||
$LazyJSType.prototype.as = function as_T(object) {
|
||||
return $_asInstanceOfLazyJSType(object, this);
|
||||
};
|
||||
$LazyJSType.prototype._check = function check_T(object) {
|
||||
return $_asInstanceOfLazyJSType(object, this);
|
||||
};
|
||||
})()''');
|
||||
|
||||
class Void extends TypeRep {
|
||||
toString() => 'void';
|
||||
}
|
||||
|
@ -709,14 +703,6 @@ String typeName(type) => JS(
|
|||
bool _isFunctionType(type) => JS('bool', '# instanceof # || # === #', type,
|
||||
AbstractFunctionType, type, Function);
|
||||
|
||||
isLazyJSSubtype(t1, LazyJSType t2, isCovariant) {
|
||||
// All JS types are subtypes of anonymous JS types.
|
||||
if (t2._jsTypeCallback == null) {
|
||||
return _isJSType(t1);
|
||||
}
|
||||
return _isSubtype(t1, t2._rawJSType, isCovariant);
|
||||
}
|
||||
|
||||
/// Returns true if [ft1] <: [ft2].
|
||||
/// Returns false if [ft1] </: [ft2] in both spec and strong mode
|
||||
/// Returns null if [ft1] </: [ft2] in strong mode, but spec mode
|
||||
|
@ -891,8 +877,12 @@ bool _isSubtype(t1, t2, isCovariant) => JS(
|
|||
if (result === true || result === null) return result;
|
||||
}
|
||||
|
||||
if ($t2 instanceof $AnonymousJSType) {
|
||||
// All JS types are subtypes of anonymous JS types.
|
||||
return $_isJSType($t1);
|
||||
}
|
||||
if ($t2 instanceof $LazyJSType) {
|
||||
return $isLazyJSSubtype($t1, $t2, $isCovariant);
|
||||
return $_isSubtype($t1, $t2.rawJSTypeForCheck(), isCovariant);
|
||||
}
|
||||
|
||||
// Function subtyping.
|
||||
|
|
|
@ -16,6 +16,18 @@ defineValue(obj, name, value) {
|
|||
return value;
|
||||
}
|
||||
|
||||
void defineGetter(obj, name, getter) {
|
||||
defineProperty(obj, name, JS('', '{get: #}', getter));
|
||||
}
|
||||
|
||||
void defineMemoizedGetter(obj, name, compute) {
|
||||
defineProperty(
|
||||
obj,
|
||||
name,
|
||||
JS('', '{get: () => #, configurable: true}',
|
||||
defineValue(obj, name, JS('', '#()', compute))));
|
||||
}
|
||||
|
||||
getOwnPropertyDescriptor(obj, name) =>
|
||||
JS('', 'Object.getOwnPropertyDescriptor(#, #)', obj, name);
|
||||
|
||||
|
@ -40,7 +52,7 @@ void throwInternalError(String message) {
|
|||
JS('', 'throw Error(#)', message);
|
||||
}
|
||||
|
||||
getOwnNamesAndSymbols(obj) {
|
||||
Iterable getOwnNamesAndSymbols(obj) {
|
||||
var names = getOwnPropertyNames(obj);
|
||||
var symbols = getOwnPropertySymbols(obj);
|
||||
return JS('', '#.concat(#)', names, symbols);
|
||||
|
@ -83,27 +95,13 @@ defineLazyProperty(to, name, desc) => JS(
|
|||
return $defineProperty($to, $name, $desc);
|
||||
})()''');
|
||||
|
||||
void defineLazy(to, from) => JS(
|
||||
'',
|
||||
'''(() => {
|
||||
for (let name of $getOwnNamesAndSymbols($from)) {
|
||||
$defineLazyProperty($to, name, $getOwnPropertyDescriptor($from, name));
|
||||
copyTheseProperties(to, from, names) {
|
||||
for (var i = 0; i < JS('int', '#.length', names); ++i) {
|
||||
copyProperty(to, from, JS('', '#[#]', names, i));
|
||||
}
|
||||
})()''');
|
||||
|
||||
defineMemoizedGetter(obj, name, getter) {
|
||||
return defineLazyProperty(obj, name, JS('', '{get: #}', getter));
|
||||
return to;
|
||||
}
|
||||
|
||||
copyTheseProperties(to, from, names) => JS(
|
||||
'',
|
||||
'''(() => {
|
||||
for (let i = 0; i < $names.length; ++i) {
|
||||
$copyProperty($to, $from, $names[i]);
|
||||
}
|
||||
return $to;
|
||||
})()''');
|
||||
|
||||
copyProperty(to, from, name) {
|
||||
var desc = getOwnPropertyDescriptor(from, name);
|
||||
if (JS('bool', '# == Symbol.iterator', name)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'dart:_internal' hide Symbol;
|
|||
import 'dart:_js_helper';
|
||||
import 'dart:_foreign_helper' show JS;
|
||||
import 'dart:math' show Random;
|
||||
import 'dart:_runtime' show getGenericClass, wrapType;
|
||||
|
||||
part 'js_array.dart';
|
||||
part 'js_number.dart';
|
||||
|
|
|
@ -582,6 +582,8 @@ class JSArray<E> implements List<E>, JSIndexable<E> {
|
|||
Map<int, E> asMap() {
|
||||
return new ListMapView<E>(this);
|
||||
}
|
||||
|
||||
Type get runtimeType => wrapType(JS('', '#(#)', getGenericClass(List), E));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,6 @@ main() {
|
|||
expectNSME(null);
|
||||
expectNSME(777);
|
||||
expectNSME('hello');
|
||||
// These fail because of https://github.com/dart-lang/dev_compiler/issues/592.
|
||||
// expectNSME([]);
|
||||
// expectNSME(['a', 'b', 'c']);
|
||||
expectNSME([]);
|
||||
expectNSME(<String>['a', 'b', 'c']);
|
||||
}
|
||||
|
|
|
@ -33,4 +33,7 @@ main() {
|
|||
|
||||
Expect.isTrue(null.runtimeType is Type);
|
||||
Expect.equals(Null, null.runtimeType);
|
||||
|
||||
Expect.equals([].runtimeType.toString(), 'List');
|
||||
Expect.equals((<int>[]).runtimeType.toString(), 'List<int>');
|
||||
}
|
||||
|
|
|
@ -1,29 +1,168 @@
|
|||
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
||||
// Copyright (c) 2017, 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 'package:expect/expect.dart';
|
||||
|
||||
class Foo {
|
||||
dynamic method(int x) {}
|
||||
dynamic method2(int x) {}
|
||||
class C {
|
||||
dynamic f(int x) => x + 1;
|
||||
dynamic g(int x) => x + 2;
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
f(int x) => x + 41;
|
||||
get superF => super.f;
|
||||
get superG => super.g;
|
||||
}
|
||||
|
||||
tearoffEquals(f1, f2) {
|
||||
Expect.equals(f1, f2);
|
||||
Expect.equals(f1.hashCode, f2.hashCode);
|
||||
}
|
||||
|
||||
tearoffNotEquals(f1, f2) {
|
||||
Expect.notEquals(f1, f2);
|
||||
Expect.notEquals(f1.hashCode, f2.hashCode);
|
||||
}
|
||||
|
||||
testDynamic() {
|
||||
C c = new C();
|
||||
|
||||
dynamic f1 = c.f;
|
||||
Expect.throws(() => f1(2.5));
|
||||
Expect.equals(f1(41), 42);
|
||||
|
||||
dynamic f2 = (c as dynamic).f;
|
||||
Expect.throws(() => f2(2.5));
|
||||
Expect.equals(f2(41), 42);
|
||||
|
||||
tearoffEquals(f1, f1);
|
||||
tearoffEquals(f1, f2);
|
||||
tearoffEquals(f1, c.f);
|
||||
tearoffEquals(c.g, (c as dynamic).g);
|
||||
|
||||
tearoffNotEquals(f1, new C().f);
|
||||
tearoffNotEquals(f1, (new C() as dynamic).f);
|
||||
tearoffNotEquals(f1, c.g);
|
||||
tearoffNotEquals(f1, (c as dynamic).g);
|
||||
}
|
||||
|
||||
testSuper() {
|
||||
D d = new D();
|
||||
dynamic superF1 = d.superF;
|
||||
dynamic superF2 = (d as dynamic).superF;
|
||||
|
||||
Expect.throws(() => superF1(2.5));
|
||||
Expect.throws(() => superF2(2.5));
|
||||
Expect.equals(superF1(41), 42);
|
||||
Expect.equals(superF2(41), 42);
|
||||
|
||||
tearoffEquals(superF1, superF1);
|
||||
tearoffEquals(superF1, superF2);
|
||||
tearoffEquals(superF1, d.superF);
|
||||
tearoffEquals(d.f, (d as dynamic).f);
|
||||
|
||||
tearoffNotEquals(superF1, d.f);
|
||||
tearoffNotEquals(superF1, (d as dynamic).f);
|
||||
tearoffNotEquals(superF1, new D().superF);
|
||||
tearoffNotEquals(superF1, (new D() as dynamic).superF);
|
||||
tearoffNotEquals(superF1, d.superG);
|
||||
tearoffNotEquals(superF1, (d as dynamic).superG);
|
||||
|
||||
tearoffEquals(d.superG, (d as dynamic).superG);
|
||||
tearoffEquals(d.g, d.superG);
|
||||
}
|
||||
|
||||
class S {
|
||||
final int id;
|
||||
S(this.id);
|
||||
toString() => 'S#$id';
|
||||
}
|
||||
|
||||
testToString() {
|
||||
testType<T>(T c) {
|
||||
dynamic d = c;
|
||||
Object o = c;
|
||||
tearoffEquals(c.toString, d.toString);
|
||||
tearoffEquals(c.toString, o.toString);
|
||||
|
||||
var expected = c.toString();
|
||||
dynamic f = d.toString;
|
||||
tearoffEquals(f(), expected);
|
||||
f = o.toString;
|
||||
tearoffEquals(f(), expected);
|
||||
var g = c.toString;
|
||||
tearoffEquals(g(), expected);
|
||||
}
|
||||
|
||||
testType(new C());
|
||||
testType(new D());
|
||||
testType(new S(1));
|
||||
testType(new S(2));
|
||||
testType(new Object());
|
||||
testType(null);
|
||||
testType(Object); // Type
|
||||
testType(C); // Type
|
||||
testType(42);
|
||||
testType('hi');
|
||||
testType(true);
|
||||
testType([1, 2, 3]);
|
||||
testType({'a': 'b'});
|
||||
testType((x) => x + 1);
|
||||
testType(testType);
|
||||
}
|
||||
|
||||
class N {
|
||||
noSuchMethod(i) => i;
|
||||
}
|
||||
|
||||
testNoSuchMethod() {
|
||||
// Create an invocation.
|
||||
Invocation i = (new N() as dynamic).foo(1, bar: 2);
|
||||
tearoffEquals(i.memberName, #foo);
|
||||
|
||||
testType<T>(T c) {
|
||||
dynamic d = c;
|
||||
Object o = c;
|
||||
tearoffEquals(c.noSuchMethod, d.noSuchMethod);
|
||||
tearoffEquals(c.noSuchMethod, o.noSuchMethod);
|
||||
|
||||
var expected;
|
||||
try {
|
||||
c.noSuchMethod(i);
|
||||
} on NoSuchMethodError catch (error) {
|
||||
var nsm = '$error';
|
||||
Expect.isTrue(nsm.startsWith("NoSuchMethodError: "));
|
||||
Expect.isTrue(nsm.contains("'foo'"));
|
||||
expected = (e) => e is NoSuchMethodError && '$e' == nsm;
|
||||
}
|
||||
dynamic f = d.noSuchMethod;
|
||||
Expect.throws(() => f(i), expected);
|
||||
f = o.noSuchMethod;
|
||||
Expect.throws(() => f(i), expected);
|
||||
var g = c.noSuchMethod;
|
||||
Expect.throws(() => g(i), expected);
|
||||
}
|
||||
|
||||
testType(new C());
|
||||
testType(new D());
|
||||
testType(new S(1));
|
||||
testType(new S(2));
|
||||
testType(new Object());
|
||||
testType(null);
|
||||
testType(Object); // Type
|
||||
testType(C); // Type
|
||||
testType(42);
|
||||
testType('hi');
|
||||
testType(true);
|
||||
testType([1, 2, 3]);
|
||||
testType({'a': 'b'});
|
||||
testType((x) => x + 1);
|
||||
testType(testType);
|
||||
}
|
||||
|
||||
main() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
dynamic dynamicMethod1 = foo.method;
|
||||
Expect.throws(() => dynamicMethod1(2.5));
|
||||
|
||||
dynamic dynamicMethod2 = (foo as dynamic).method;
|
||||
Expect.throws(() => dynamicMethod2(2.5));
|
||||
|
||||
Expect.equals(dynamicMethod1, dynamicMethod1);
|
||||
Expect.equals(dynamicMethod1, dynamicMethod2);
|
||||
Expect.equals(dynamicMethod1, foo.method);
|
||||
Expect.equals(foo.method2, (foo as dynamic).method2);
|
||||
|
||||
Expect.notEquals(dynamicMethod1, new Foo().method);
|
||||
Expect.notEquals(dynamicMethod1, (new Foo() as dynamic).method);
|
||||
Expect.notEquals(dynamicMethod1, foo.method2);
|
||||
Expect.notEquals(dynamicMethod1, (foo as dynamic).method2);
|
||||
testDynamic();
|
||||
testSuper();
|
||||
testToString();
|
||||
testNoSuchMethod();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue