mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:00:09 +00:00
[ddc] Add interop classes for static members
Fixes https://github.com/dart-lang/sdk/issues/46967 Creates classes for non-external factories and static members, and modifies invocations to point to these members instead. Tear-offs of interop constructors (external or otherwise) are now supported since they're just non-external static methods. Change-Id: Id754fb4bc872051a8df4169aefd4bdc078452fb5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270501 Reviewed-by: Nicholas Shahan <nshahan@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
parent
49f9a85be2
commit
293ae7dabe
|
@ -716,9 +716,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
_currentTypeEnvironment = _currentTypeEnvironment.extend(c.typeParameters);
|
_currentTypeEnvironment = _currentTypeEnvironment.extend(c.typeParameters);
|
||||||
|
|
||||||
// Mixins are unrolled in _defineClass.
|
// Mixins are unrolled in _defineClass.
|
||||||
// If this class is annotated with `@JS`, then there is nothing to emit.
|
if (!c.isAnonymousMixin) {
|
||||||
if (!c.isAnonymousMixin && !hasJSInteropAnnotation(c)) {
|
// If this class is annotated with `@JS`, then we only need to emit the
|
||||||
moduleItems.add(_emitClassDeclaration(c));
|
// non-external factories and static members.
|
||||||
|
if (!hasJSInteropAnnotation(c)) {
|
||||||
|
moduleItems.add(_emitClassDeclaration(c));
|
||||||
|
} else {
|
||||||
|
var interopClassDef = _emitJSInteropClassNonExternalMembers(c);
|
||||||
|
if (interopClassDef != null) moduleItems.add(interopClassDef);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The const table depends on dart.defineLazy, so emit it after the SDK.
|
// The const table depends on dart.defineLazy, so emit it after the SDK.
|
||||||
|
@ -883,6 +889,56 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
return js_ast.Statement.from(body);
|
return js_ast.Statement.from(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a class declaration for the JS interop class [c] for any
|
||||||
|
/// non-external factories or static members.
|
||||||
|
///
|
||||||
|
/// If [c] is not an interop class or does not contain non-external factories
|
||||||
|
/// or static members, returns null.
|
||||||
|
js_ast.Statement? _emitJSInteropClassNonExternalMembers(Class c) {
|
||||||
|
if (!hasJSInteropAnnotation(c)) return null;
|
||||||
|
// Generic JS interop classes are emitted like Dart generic classes, where
|
||||||
|
// the type arguments need to be instantiated.
|
||||||
|
var className = c.typeParameters.isNotEmpty
|
||||||
|
? _emitTemporaryId(getLocalClassName(c))
|
||||||
|
: _emitTopLevelNameNoExternalInterop(c);
|
||||||
|
|
||||||
|
var nonExternalMethods = <js_ast.Method>[];
|
||||||
|
for (var procedure in c.procedures) {
|
||||||
|
if (procedure.isExternal) continue;
|
||||||
|
if (procedure.isFactory && !procedure.isRedirectingFactory) {
|
||||||
|
// Skip redirecting factories (they've already been resolved).
|
||||||
|
var factory = _emitFactoryConstructor(procedure);
|
||||||
|
if (factory != null) nonExternalMethods.add(factory);
|
||||||
|
} else if (procedure.isStatic) {
|
||||||
|
var staticMethod = _emitMethodDeclaration(procedure);
|
||||||
|
if (staticMethod != null) nonExternalMethods.add(staticMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit static fields, if there are any.
|
||||||
|
var fieldInitialization = <js_ast.Statement>[];
|
||||||
|
_emitStaticFieldsAndAccessors(c, fieldInitialization);
|
||||||
|
|
||||||
|
// Avoid unnecessary code emission if there are no members we care about.
|
||||||
|
if (nonExternalMethods.isNotEmpty || fieldInitialization.isNotEmpty) {
|
||||||
|
// Note that this class has no heritage. This class should never be used
|
||||||
|
// as a type. It's merely a placeholder for static members.
|
||||||
|
var body = <js_ast.Statement>[
|
||||||
|
_emitClassStatement(c, className, null, nonExternalMethods)
|
||||||
|
.toStatement()
|
||||||
|
];
|
||||||
|
var classDef = js_ast.Statement.from(body);
|
||||||
|
var typeFormals = c.typeParameters;
|
||||||
|
if (typeFormals.isNotEmpty) {
|
||||||
|
classDef =
|
||||||
|
_defineClassTypeArguments(c, typeFormals, classDef, className, []);
|
||||||
|
}
|
||||||
|
body = [classDef, ...fieldInitialization];
|
||||||
|
return js_ast.Statement.from(body);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps a possibly generic class in its type arguments.
|
/// Wraps a possibly generic class in its type arguments.
|
||||||
js_ast.Statement _defineClassTypeArguments(
|
js_ast.Statement _defineClassTypeArguments(
|
||||||
NamedNode c,
|
NamedNode c,
|
||||||
|
@ -920,9 +976,13 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
? runtimeCall('normalizeFutureOr(#)', [genericArgs])
|
? runtimeCall('normalizeFutureOr(#)', [genericArgs])
|
||||||
: runtimeCall('generic(#)', [genericArgs]);
|
: runtimeCall('generic(#)', [genericArgs]);
|
||||||
|
|
||||||
var genericName = _emitTopLevelNameNoInterop(c, suffix: '\$');
|
var genericName = _emitTopLevelNameNoExternalInterop(c, suffix: '\$');
|
||||||
return js.statement('{ # = #; # = #(); }',
|
return js.statement('{ # = #; # = #(); }', [
|
||||||
[genericName, genericCall, _emitTopLevelName(c), genericName]);
|
genericName,
|
||||||
|
genericCall,
|
||||||
|
_emitTopLevelNameNoExternalInterop(c),
|
||||||
|
genericName
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
js_ast.Expression _emitVariance(TypeParameter typeParameter) {
|
js_ast.Expression _emitVariance(TypeParameter typeParameter) {
|
||||||
|
@ -1460,18 +1520,19 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
_superHelpers.clear();
|
_superHelpers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits static fields for a class, and initialize them eagerly if possible,
|
/// Emits non-external static fields for a class, and initialize them eagerly
|
||||||
/// otherwise define them as lazy properties.
|
/// if possible, otherwise define them as lazy properties.
|
||||||
void _emitStaticFieldsAndAccessors(Class c, List<js_ast.Statement> body) {
|
void _emitStaticFieldsAndAccessors(Class c, List<js_ast.Statement> body) {
|
||||||
var fields = c.fields
|
var fields = c.fields
|
||||||
.where((f) => f.isStatic && !isRedirectingFactoryField(f))
|
.where(
|
||||||
|
(f) => f.isStatic && !f.isExternal && !isRedirectingFactoryField(f))
|
||||||
.toList();
|
.toList();
|
||||||
var fieldNames = Set.from(fields.map((f) => f.name));
|
var fieldNames = Set.from(fields.map((f) => f.name));
|
||||||
var staticSetters = c.procedures.where(
|
var staticSetters = c.procedures.where(
|
||||||
(p) => p.isStatic && p.isAccessor && fieldNames.contains(p.name));
|
(p) => p.isStatic && p.isAccessor && fieldNames.contains(p.name));
|
||||||
var members = [...fields, ...staticSetters];
|
var members = [...fields, ...staticSetters];
|
||||||
if (fields.isNotEmpty) {
|
if (fields.isNotEmpty) {
|
||||||
body.add(_emitLazyMembers(_emitTopLevelName(c), members,
|
body.add(_emitLazyMembers(_emitTopLevelNameNoExternalInterop(c), members,
|
||||||
(n) => _emitStaticMemberName(n.name.text)));
|
(n) => _emitStaticMemberName(n.name.text)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2577,7 +2638,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
}
|
}
|
||||||
|
|
||||||
js_ast.PropertyAccess _emitTopLevelName(NamedNode n, {String suffix = ''}) {
|
js_ast.PropertyAccess _emitTopLevelName(NamedNode n, {String suffix = ''}) {
|
||||||
return _emitJSInterop(n) ?? _emitTopLevelNameNoInterop(n, suffix: suffix);
|
return _emitJSInterop(n) ??
|
||||||
|
_emitTopLevelNameNoExternalInterop(n, suffix: suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [_emitMemberName], but for declaration sites.
|
/// Like [_emitMemberName], but for declaration sites.
|
||||||
|
@ -2754,7 +2816,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
|
|
||||||
js_ast.LiteralString _emitStaticMemberName(String name, [NamedNode? member]) {
|
js_ast.LiteralString _emitStaticMemberName(String name, [NamedNode? member]) {
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
var jsName = _emitJSInteropStaticMemberName(member);
|
var jsName = _emitJSInteropExternalStaticMemberName(member);
|
||||||
if (jsName != null) return jsName;
|
if (jsName != null) return jsName;
|
||||||
|
|
||||||
// Allow the Dart SDK to assign names to statics with the @JSExportName
|
// Allow the Dart SDK to assign names to statics with the @JSExportName
|
||||||
|
@ -2806,15 +2868,19 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
js_ast.LiteralString? _emitJSInteropStaticMemberName(NamedNode n) {
|
/// Emit the name associated with external static members of interop classes.
|
||||||
|
js_ast.LiteralString? _emitJSInteropExternalStaticMemberName(NamedNode n) {
|
||||||
if (!usesJSInterop(n)) return null;
|
if (!usesJSInterop(n)) return null;
|
||||||
|
if (n is Member && !n.isExternal) return null;
|
||||||
var name = _annotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n);
|
var name = _annotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n);
|
||||||
assert(!name.contains('.'),
|
assert(!name.contains('.'),
|
||||||
'JS interop checker rejects dotted names on static class members');
|
'JS interop checker rejects dotted names on static class members');
|
||||||
return js.escapedString(name, "'");
|
return js.escapedString(name, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
js_ast.PropertyAccess _emitTopLevelNameNoInterop(NamedNode n,
|
/// Emit the top-level name associated with [n], which should not be an
|
||||||
|
/// external interop member.
|
||||||
|
js_ast.PropertyAccess _emitTopLevelNameNoExternalInterop(NamedNode n,
|
||||||
{String suffix = ''}) {
|
{String suffix = ''}) {
|
||||||
// Some native tests use top-level native methods.
|
// Some native tests use top-level native methods.
|
||||||
var isTopLevelNative = n is Member && isNative(n);
|
var isTopLevelNative = n is Member && isNative(n);
|
||||||
|
@ -3234,7 +3300,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeRep ??= _emitTopLevelNameNoInterop(type.classNode);
|
typeRep ??= _emitTopLevelNameNoExternalInterop(type.classNode);
|
||||||
|
|
||||||
// Avoid emitting the null safety wrapper types when:
|
// Avoid emitting the null safety wrapper types when:
|
||||||
// * This specific InterfaceType is known to be from a context where
|
// * This specific InterfaceType is known to be from a context where
|
||||||
|
@ -3261,7 +3327,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
return _typeTable.nameType(type, typeRep);
|
return _typeTable.nameType(type, typeRep);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits the a reference to the class described by [type].
|
/// Emits a reference to the class described by [type].
|
||||||
///
|
///
|
||||||
/// The nullability of [type] is not considered because it is meaningless when
|
/// The nullability of [type] is not considered because it is meaningless when
|
||||||
/// describing a reference to the class itself.
|
/// describing a reference to the class itself.
|
||||||
|
@ -3270,33 +3336,20 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
/// function that defines the class and will pass the type parameters as
|
/// function that defines the class and will pass the type parameters as
|
||||||
/// arguments. The nullability of the type parameters does have meaning so it
|
/// arguments. The nullability of the type parameters does have meaning so it
|
||||||
/// is encoded.
|
/// is encoded.
|
||||||
|
///
|
||||||
|
/// Note that for `package:js` types, this will emit the class we emitted
|
||||||
|
/// using `_emitJSInteropClassNonExternalMembers`, and not the runtime type
|
||||||
|
/// that we synthesize for `package:js` types.
|
||||||
js_ast.Expression _emitClassRef(InterfaceType type) {
|
js_ast.Expression _emitClassRef(InterfaceType type) {
|
||||||
var cls = type.classNode;
|
var cls = type.classNode;
|
||||||
_declareBeforeUse(cls);
|
_declareBeforeUse(cls);
|
||||||
// Type parameters don't matter as JS interop types cannot be reified.
|
|
||||||
// package:js types fall under `@JS`, `@anonymous`, or `@staticInterop`
|
|
||||||
// types. `@JS` types are used to correspond to JS types that exist, but we
|
|
||||||
// do not use the underlying type for type checks, so they operate virtually
|
|
||||||
// the same as `@anonymous` types. `@staticInterop` types, however, can be
|
|
||||||
// casted to other `package:js` types as well as any type that inherits
|
|
||||||
// `JavaScriptObject`. This is to match the behavior across the other
|
|
||||||
// backends that use erasure. We represent `@JS` and `@anonymous` types with
|
|
||||||
// a NonStaticInteropType and `@staticInterop` with a StaticInteropType to
|
|
||||||
// make this distinction at runtime.
|
|
||||||
var jsName = isJSAnonymousType(cls)
|
|
||||||
? getLocalClassName(cls)
|
|
||||||
: _emitJsNameWithoutGlobal(cls);
|
|
||||||
if (jsName != null) {
|
|
||||||
return runtimeCall('packageJSType(#, #)',
|
|
||||||
[js.escapedString(jsName), js.boolean(isStaticInteropType(cls))]);
|
|
||||||
}
|
|
||||||
var args = type.typeArguments;
|
var args = type.typeArguments;
|
||||||
Iterable<js_ast.Expression>? jsArgs;
|
Iterable<js_ast.Expression>? jsArgs;
|
||||||
if (args.any((a) => a != const DynamicType())) {
|
if (args.any((a) => a != const DynamicType())) {
|
||||||
jsArgs = args.map(_emitType);
|
jsArgs = args.map(_emitType);
|
||||||
}
|
}
|
||||||
if (jsArgs != null) return _emitGenericClassType(type, jsArgs);
|
if (jsArgs != null) return _emitGenericClassType(type, jsArgs);
|
||||||
return _emitTopLevelNameNoInterop(type.classNode);
|
return _emitTopLevelNameNoExternalInterop(type.classNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits the representation of a FutureOr [type].
|
/// Emits the representation of a FutureOr [type].
|
||||||
|
@ -3374,7 +3427,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
|
|
||||||
js_ast.Expression _emitGenericClassType(
|
js_ast.Expression _emitGenericClassType(
|
||||||
InterfaceType t, Iterable<js_ast.Expression> typeArgs) {
|
InterfaceType t, Iterable<js_ast.Expression> typeArgs) {
|
||||||
var genericName = _emitTopLevelNameNoInterop(t.classNode, suffix: '\$');
|
var genericName =
|
||||||
|
_emitTopLevelNameNoExternalInterop(t.classNode, suffix: '\$');
|
||||||
return js.call('#(#)', [genericName, typeArgs]);
|
return js.call('#(#)', [genericName, typeArgs]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3514,23 +3568,32 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits an expression that lets you access statics on a [type] from code.
|
/// Emits an expression that lets you access statics on a [type] from code.
|
||||||
js_ast.Expression _emitConstructorAccess(InterfaceType type) {
|
|
||||||
return _emitJSInterop(type.classNode) ??
|
|
||||||
(_options.newRuntimeTypes
|
|
||||||
? _emitClassRef(type)
|
|
||||||
: _emitInterfaceType(type, emitNullability: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
js_ast.Expression _emitConstructorName(InterfaceType type, Member c) {
|
js_ast.Expression _emitConstructorName(InterfaceType type, Member c) {
|
||||||
return _emitJSInterop(type.classNode) ??
|
var isSyntheticDefault =
|
||||||
js_ast.PropertyAccess(
|
c is Constructor && c.isSynthetic && c.name.text.isEmpty;
|
||||||
_emitConstructorAccess(type), _constructorName(c.name.text));
|
// If it's an external constructor or synthetic default, use the JS
|
||||||
|
// constructor.
|
||||||
|
var jsConstructor = _emitJSInterop(type.classNode);
|
||||||
|
if (jsConstructor != null && (c.isExternal || isSyntheticDefault)) {
|
||||||
|
return jsConstructor;
|
||||||
|
}
|
||||||
|
// If it's non-external but belongs to an interop class, we want the class
|
||||||
|
// reference we defined in `_emitJSInteropClassNonExternalMembers`.
|
||||||
|
return js_ast.PropertyAccess(
|
||||||
|
_options.newRuntimeTypes || usesJSInterop(type.classNode)
|
||||||
|
? _emitClassRef(type)
|
||||||
|
: _emitInterfaceType(type, emitNullability: false),
|
||||||
|
_constructorName(c.name.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits an expression that lets you access statics on an [c] from code.
|
/// Emits an expression that lets you access statics on [c] from code.
|
||||||
js_ast.Expression _emitStaticClassName(Class c) {
|
///
|
||||||
|
/// If [isExternal] is false, emits the non-external name.
|
||||||
|
js_ast.Expression _emitStaticClassName(Class c, bool isExternal) {
|
||||||
_declareBeforeUse(c);
|
_declareBeforeUse(c);
|
||||||
return _emitTopLevelName(c);
|
return isExternal
|
||||||
|
? _emitTopLevelName(c)
|
||||||
|
: _emitTopLevelNameNoExternalInterop(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits named parameters in the form '{name: type}'.
|
/// Emits named parameters in the form '{name: type}'.
|
||||||
|
@ -5979,7 +6042,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
}
|
}
|
||||||
if (name == 'getGenericClassStatic') {
|
if (name == 'getGenericClassStatic') {
|
||||||
if (type is InterfaceType) {
|
if (type is InterfaceType) {
|
||||||
return _emitTopLevelNameNoInterop(type.classNode, suffix: '\$');
|
return _emitTopLevelNameNoExternalInterop(type.classNode,
|
||||||
|
suffix: '\$');
|
||||||
}
|
}
|
||||||
if (type is FutureOrType) {
|
if (type is FutureOrType) {
|
||||||
return _emitFutureOrNameNoInterop(suffix: '\$');
|
return _emitFutureOrNameNoInterop(suffix: '\$');
|
||||||
|
@ -6088,7 +6152,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
// A static native element should just forward directly to the JS type's
|
// A static native element should just forward directly to the JS type's
|
||||||
// member, for example `Css.supports(...)` in dart:html should be replaced
|
// member, for example `Css.supports(...)` in dart:html should be replaced
|
||||||
// by a direct call to the DOM API: `global.CSS.supports`.
|
// by a direct call to the DOM API: `global.CSS.supports`.
|
||||||
if (_isExternal(target) && (target as Procedure).isStatic) {
|
var isExternal = _isExternal(target);
|
||||||
|
if (isExternal && (target as Procedure).isStatic) {
|
||||||
var nativeName = _extensionTypes.getNativePeers(c);
|
var nativeName = _extensionTypes.getNativePeers(c);
|
||||||
if (nativeName.isNotEmpty) {
|
if (nativeName.isNotEmpty) {
|
||||||
var memberName = _annotationName(target, isJSName) ??
|
var memberName = _annotationName(target, isJSName) ??
|
||||||
|
@ -6096,7 +6161,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||||
return runtimeCall('global.#.#', [nativeName[0], memberName]);
|
return runtimeCall('global.#.#', [nativeName[0], memberName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return js_ast.PropertyAccess(_emitStaticClassName(c),
|
return js_ast.PropertyAccess(_emitStaticClassName(c, isExternal),
|
||||||
_emitStaticMemberName(target.name.text, target));
|
_emitStaticMemberName(target.name.text, target));
|
||||||
}
|
}
|
||||||
return _emitTopLevelName(target);
|
return _emitTopLevelName(target);
|
||||||
|
|
|
@ -4602,6 +4602,32 @@ Value:
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"li",
|
||||||
|
{
|
||||||
|
"style": "padding-left: 13px;"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
"style": "background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px"
|
||||||
|
},
|
||||||
|
"ExampleJSClass: "
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
"style": "margin-left: 13px"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"object",
|
||||||
|
{
|
||||||
|
"object": "<OBJECT>",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"li",
|
"li",
|
||||||
{
|
{
|
||||||
|
|
41
tests/dartdevc/js_interop_non_external_lib.dart
Normal file
41
tests/dartdevc/js_interop_non_external_lib.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
// Define a JS class in a different library to test that name resolution works
|
||||||
|
// for non-external factories and static methods. Also uses class type
|
||||||
|
// parameters to make sure we generate a generic class.
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
library js_interop_non_external_lib;
|
||||||
|
|
||||||
|
import 'package:js/js.dart';
|
||||||
|
|
||||||
|
@JS('JSClass')
|
||||||
|
class OtherJSClass<T> {
|
||||||
|
external OtherJSClass.cons(T t);
|
||||||
|
factory OtherJSClass(T t) {
|
||||||
|
field = 'unnamed';
|
||||||
|
return OtherJSClass.cons(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory OtherJSClass.named(T t) {
|
||||||
|
field = 'named';
|
||||||
|
return OtherJSClass.cons(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory OtherJSClass.redirecting(T t) = OtherJSClass;
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
static String get getSet {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
static set getSet(String val) {
|
||||||
|
field = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
|
||||||
|
static T genericMethod<T>(T t) => t;
|
||||||
|
}
|
166
tests/dartdevc/js_interop_non_external_test.dart
Normal file
166
tests/dartdevc/js_interop_non_external_test.dart
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
// Test non-external factories and static methods of JS interop classes.
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
library js_interop_non_external_test;
|
||||||
|
|
||||||
|
import 'package:expect/minitest.dart';
|
||||||
|
import 'package:js/js.dart';
|
||||||
|
|
||||||
|
import 'js_interop_non_external_lib.dart';
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
external dynamic eval(String code);
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
class JSClass {
|
||||||
|
external JSClass.cons();
|
||||||
|
factory JSClass() {
|
||||||
|
field = 'unnamed';
|
||||||
|
return JSClass.cons();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory JSClass.named() {
|
||||||
|
field = 'named';
|
||||||
|
return JSClass.cons();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory JSClass.redirecting() = JSClass;
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
static String get getSet {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
static set getSet(String val) {
|
||||||
|
field = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
|
||||||
|
static T genericMethod<T>(T t) => t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JS('JSClass')
|
||||||
|
@staticInterop
|
||||||
|
class StaticInterop {
|
||||||
|
external factory StaticInterop();
|
||||||
|
factory StaticInterop.named() {
|
||||||
|
field = 'named';
|
||||||
|
return StaticInterop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
@anonymous
|
||||||
|
class Anonymous {
|
||||||
|
external factory Anonymous();
|
||||||
|
|
||||||
|
factory Anonymous.named() {
|
||||||
|
field = 'named';
|
||||||
|
return Anonymous();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testLocal() {
|
||||||
|
// Test field is initialized and modified.
|
||||||
|
expect(JSClass.field, '');
|
||||||
|
JSClass.field = 'modified';
|
||||||
|
expect(JSClass.field, 'modified');
|
||||||
|
|
||||||
|
// Test factories and side-effects to make sure body is executed. Test their
|
||||||
|
// tear-offs too.
|
||||||
|
JSClass();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
JSClass.named();
|
||||||
|
expect(JSClass.field, 'named');
|
||||||
|
JSClass.redirecting();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
(JSClass.cons)();
|
||||||
|
(JSClass.new)();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
(JSClass.named)();
|
||||||
|
expect(JSClass.field, 'named');
|
||||||
|
(JSClass.redirecting)();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
// Test getter and setter.
|
||||||
|
expect(JSClass.getSet, JSClass.field);
|
||||||
|
JSClass.getSet = 'set';
|
||||||
|
expect(JSClass.field, 'set');
|
||||||
|
|
||||||
|
// Test methods and their tear-offs.
|
||||||
|
expect(JSClass.method(), JSClass.field);
|
||||||
|
expect(JSClass.genericMethod(JSClass.field), JSClass.field);
|
||||||
|
|
||||||
|
expect((JSClass.method)(), JSClass.field);
|
||||||
|
expect((JSClass.genericMethod)(JSClass.field), JSClass.field);
|
||||||
|
|
||||||
|
// Briefly check that other interop classes work too.
|
||||||
|
expect(StaticInterop.field, '');
|
||||||
|
StaticInterop.field = 'modified';
|
||||||
|
expect(StaticInterop.field, 'modified');
|
||||||
|
StaticInterop.named();
|
||||||
|
expect(StaticInterop.field, 'named');
|
||||||
|
expect(StaticInterop.method(), StaticInterop.field);
|
||||||
|
|
||||||
|
expect(Anonymous.field, '');
|
||||||
|
Anonymous.field = 'modified';
|
||||||
|
expect(Anonymous.field, 'modified');
|
||||||
|
Anonymous.named();
|
||||||
|
expect(Anonymous.field, 'named');
|
||||||
|
expect(Anonymous.method(), Anonymous.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the same tests as `testLocal`, but with a class in a different library,
|
||||||
|
// and with class type args.
|
||||||
|
void testNonLocal() {
|
||||||
|
expect(OtherJSClass.field, '');
|
||||||
|
OtherJSClass.field = 'modified';
|
||||||
|
expect(OtherJSClass.field, 'modified');
|
||||||
|
|
||||||
|
OtherJSClass<int>(0);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
OtherJSClass.named('');
|
||||||
|
expect(OtherJSClass.field, 'named');
|
||||||
|
OtherJSClass<bool>.redirecting(true);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
(OtherJSClass.cons)(0);
|
||||||
|
(OtherJSClass<int>.new)(0);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
(OtherJSClass.named)('');
|
||||||
|
expect(OtherJSClass.field, 'named');
|
||||||
|
(OtherJSClass<bool>.redirecting)(true);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
expect(OtherJSClass.getSet, OtherJSClass.field);
|
||||||
|
OtherJSClass.getSet = 'set';
|
||||||
|
expect(OtherJSClass.field, 'set');
|
||||||
|
|
||||||
|
expect(OtherJSClass.method(), OtherJSClass.field);
|
||||||
|
expect(OtherJSClass.genericMethod(OtherJSClass.field), OtherJSClass.field);
|
||||||
|
|
||||||
|
expect((OtherJSClass.method)(), OtherJSClass.field);
|
||||||
|
expect((OtherJSClass.genericMethod)(OtherJSClass.field), OtherJSClass.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
eval('''
|
||||||
|
function JSClass(arg) {}
|
||||||
|
''');
|
||||||
|
testLocal();
|
||||||
|
testNonLocal();
|
||||||
|
}
|
|
@ -4602,6 +4602,32 @@ Value:
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"li",
|
||||||
|
{
|
||||||
|
"style": "padding-left: 13px;"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
"style": "background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px"
|
||||||
|
},
|
||||||
|
"ExampleJSClass: "
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
"style": "margin-left: 13px"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"object",
|
||||||
|
{
|
||||||
|
"object": "<OBJECT>",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"li",
|
"li",
|
||||||
{
|
{
|
||||||
|
|
43
tests/dartdevc_2/js_interop_non_external_lib.dart
Normal file
43
tests/dartdevc_2/js_interop_non_external_lib.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
// @dart = 2.9
|
||||||
|
|
||||||
|
// Define a JS class in a different library to test that name resolution works
|
||||||
|
// for non-external factories and static methods. Also uses class type
|
||||||
|
// parameters to make sure we generate a generic class.
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
library js_interop_non_external_lib;
|
||||||
|
|
||||||
|
import 'package:js/js.dart';
|
||||||
|
|
||||||
|
@JS('JSClass')
|
||||||
|
class OtherJSClass<T> {
|
||||||
|
external OtherJSClass.cons(T t);
|
||||||
|
factory OtherJSClass(T t) {
|
||||||
|
field = 'unnamed';
|
||||||
|
return OtherJSClass.cons(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory OtherJSClass.named(T t) {
|
||||||
|
field = 'named';
|
||||||
|
return OtherJSClass.cons(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory OtherJSClass.redirecting(T t) = OtherJSClass;
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
static String get getSet {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
static set getSet(String val) {
|
||||||
|
field = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
|
||||||
|
static T genericMethod<T>(T t) => t;
|
||||||
|
}
|
151
tests/dartdevc_2/js_interop_non_external_test.dart
Normal file
151
tests/dartdevc_2/js_interop_non_external_test.dart
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright (c) 2022, 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.
|
||||||
|
|
||||||
|
// @dart = 2.9
|
||||||
|
|
||||||
|
// Test non-external factories and static methods of JS interop classes.
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
library js_interop_non_external_test;
|
||||||
|
|
||||||
|
import 'package:expect/minitest.dart';
|
||||||
|
import 'package:js/js.dart';
|
||||||
|
|
||||||
|
import 'js_interop_non_external_lib.dart';
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
external dynamic eval(String code);
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
class JSClass {
|
||||||
|
external JSClass.cons();
|
||||||
|
factory JSClass() {
|
||||||
|
field = 'unnamed';
|
||||||
|
return JSClass.cons();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory JSClass.named() {
|
||||||
|
field = 'named';
|
||||||
|
return JSClass.cons();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory JSClass.redirecting() = JSClass;
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
static String get getSet {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
static set getSet(String val) {
|
||||||
|
field = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
|
||||||
|
static T genericMethod<T>(T t) => t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JS('JSClass')
|
||||||
|
@staticInterop
|
||||||
|
class StaticInterop {
|
||||||
|
external factory StaticInterop();
|
||||||
|
factory StaticInterop.named() {
|
||||||
|
field = 'named';
|
||||||
|
return StaticInterop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JS()
|
||||||
|
@anonymous
|
||||||
|
class Anonymous {
|
||||||
|
external factory Anonymous();
|
||||||
|
|
||||||
|
factory Anonymous.named() {
|
||||||
|
field = 'named';
|
||||||
|
return Anonymous();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String field = '';
|
||||||
|
|
||||||
|
static String method() => field;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testLocal() {
|
||||||
|
// Test field is initialized and modified.
|
||||||
|
expect(JSClass.field, '');
|
||||||
|
JSClass.field = 'modified';
|
||||||
|
expect(JSClass.field, 'modified');
|
||||||
|
|
||||||
|
// Test factories and side-effects to make sure body is executed.
|
||||||
|
JSClass();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
JSClass.named();
|
||||||
|
expect(JSClass.field, 'named');
|
||||||
|
JSClass.redirecting();
|
||||||
|
expect(JSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
// Test getter and setter.
|
||||||
|
expect(JSClass.getSet, JSClass.field);
|
||||||
|
JSClass.getSet = 'set';
|
||||||
|
expect(JSClass.field, 'set');
|
||||||
|
|
||||||
|
// Test methods and their tear-offs.
|
||||||
|
expect(JSClass.method(), JSClass.field);
|
||||||
|
expect(JSClass.genericMethod(JSClass.field), JSClass.field);
|
||||||
|
|
||||||
|
expect((JSClass.method)(), JSClass.field);
|
||||||
|
expect((JSClass.genericMethod)(JSClass.field), JSClass.field);
|
||||||
|
|
||||||
|
// Briefly check that other interop classes work too.
|
||||||
|
expect(StaticInterop.field, '');
|
||||||
|
StaticInterop.field = 'modified';
|
||||||
|
expect(StaticInterop.field, 'modified');
|
||||||
|
StaticInterop.named();
|
||||||
|
expect(StaticInterop.field, 'named');
|
||||||
|
expect(StaticInterop.method(), StaticInterop.field);
|
||||||
|
|
||||||
|
expect(Anonymous.field, '');
|
||||||
|
Anonymous.field = 'modified';
|
||||||
|
expect(Anonymous.field, 'modified');
|
||||||
|
Anonymous.named();
|
||||||
|
expect(Anonymous.field, 'named');
|
||||||
|
expect(Anonymous.method(), Anonymous.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the same tests as `testLocal`, but with a class in a different library,
|
||||||
|
// and with class type args.
|
||||||
|
void testNonLocal() {
|
||||||
|
expect(OtherJSClass.field, '');
|
||||||
|
OtherJSClass.field = 'modified';
|
||||||
|
expect(OtherJSClass.field, 'modified');
|
||||||
|
|
||||||
|
OtherJSClass<int>(0);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
OtherJSClass.named('');
|
||||||
|
expect(OtherJSClass.field, 'named');
|
||||||
|
OtherJSClass<bool>.redirecting(true);
|
||||||
|
expect(OtherJSClass.field, 'unnamed');
|
||||||
|
|
||||||
|
expect(OtherJSClass.getSet, OtherJSClass.field);
|
||||||
|
OtherJSClass.getSet = 'set';
|
||||||
|
expect(OtherJSClass.field, 'set');
|
||||||
|
|
||||||
|
expect(OtherJSClass.method(), OtherJSClass.field);
|
||||||
|
expect(OtherJSClass.genericMethod(OtherJSClass.field), OtherJSClass.field);
|
||||||
|
|
||||||
|
expect((OtherJSClass.method)(), OtherJSClass.field);
|
||||||
|
expect((OtherJSClass.genericMethod)(OtherJSClass.field), OtherJSClass.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
eval('''
|
||||||
|
function JSClass(arg) {}
|
||||||
|
''');
|
||||||
|
testLocal();
|
||||||
|
testNonLocal();
|
||||||
|
}
|
Loading…
Reference in a new issue