support @JS annotation from SDK

Attempt at fixing https://github.com/dart-lang/sdk/issues/39740 to allow the flutter web engine to use @JS
interop to avoid the overhead of the SDK available js interop.

Bug: 39740
Change-Id: I7ba9c8981e639cd267bee3086ba900b89bfc0d6f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150501
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Harry Terkelsen <het@google.com>
This commit is contained in:
Harry Terkelsen 2020-06-09 17:10:15 +00:00 committed by commit-bot@chromium.org
parent e3fac1dde2
commit 54fe29e965
17 changed files with 400 additions and 20 deletions

View file

@ -4,18 +4,21 @@
import 'package:kernel/kernel.dart';
/// Returns true iff the class has an `@JS(...)` annotation from `package:js`.
/// Returns true iff the class has an `@JS(...)` annotation from `package:js`
/// or from the internal `dart:_js_annotations`.
bool hasJSInteropAnnotation(Class c) =>
c.annotations.any(_isPublicJSAnnotation);
final _packageJs = Uri.parse('package:js/js.dart');
final _internalJs = Uri.parse('dart:_js_annotations');
/// Returns [true] if [e] is the `JS` annotation from `package:js`.
bool _isPublicJSAnnotation(Expression value) {
var c = _annotationClass(value);
return c != null &&
c.name == 'JS' &&
c.enclosingLibrary.importUri == _packageJs;
(c.enclosingLibrary.importUri == _packageJs ||
c.enclosingLibrary.importUri == _internalJs);
}
/// Returns the class of the instance referred to by metadata annotation [node].

View file

@ -256,6 +256,10 @@ class Uris {
/// The URI for 'package:js'.
static final Uri package_js = new Uri(scheme: 'package', path: 'js/js.dart');
/// The URI for 'dart:_js_annotations'.
static final Uri dart__js_annotations =
Uri(scheme: 'dart', path: '_js_annotations');
/// The URI for 'package:meta/dart2js.dart'.
static final Uri package_meta_dart2js =
new Uri(scheme: 'package', path: 'meta/dart2js.dart');

View file

@ -111,6 +111,9 @@ abstract class CommonElements {
/// The package:js library.
LibraryEntity get packageJsLibrary;
/// The dart:_js_annotations library.
LibraryEntity get dartJsAnnotationsLibrary;
/// The `NativeTypedData` class from dart:typed_data.
ClassEntity get typedDataClass;
@ -571,13 +574,35 @@ abstract class CommonElements {
ClassEntity cls, NativeBasicData nativeBasicData);
// From package:js
FunctionEntity get jsAllowInterop;
FunctionEntity get jsAllowInterop1;
// From dart:_js_annotations;
FunctionEntity get jsAllowInterop2;
/// Returns `true` if [function] is `allowInterop`.
///
/// This function can come from either `package:js` or `dart:_js_annotations`.
bool isJsAllowInterop(FunctionEntity function);
}
abstract class KCommonElements implements CommonElements {
// From package:js
ClassEntity get jsAnnotationClass;
ClassEntity get jsAnonymousClass;
ClassEntity get jsAnnotationClass1;
ClassEntity get jsAnonymousClass1;
// From dart:_js_annotations
ClassEntity get jsAnnotationClass2;
ClassEntity get jsAnonymousClass2;
/// Returns `true` if [cls] is a @JS() annotation.
///
/// The class can come from either `package:js` or `dart:_js_annotations`.
bool isJsAnnotationClass(ClassEntity cls);
/// Returns `true` if [cls] is an @anonymous annotation.
///
/// The class can come from either `package:js` or `dart:_js_annotations`.
bool isJsAnonymousClass(ClassEntity cls);
ClassEntity get pragmaClass;
FieldEntity get pragmaClassNameField;
@ -824,6 +849,11 @@ class CommonElementsImpl
LibraryEntity get packageJsLibrary =>
_packageJsLibrary ??= _env.lookupLibrary(Uris.package_js);
LibraryEntity _dartJsAnnotationsLibrary;
@override
LibraryEntity get dartJsAnnotationsLibrary => _dartJsAnnotationsLibrary ??=
_env.lookupLibrary(Uris.dart__js_annotations);
ClassEntity _typedDataClass;
@override
ClassEntity get typedDataClass =>
@ -1481,22 +1511,57 @@ class CommonElementsImpl
_jsConstClass ??= _findClass(foreignLibrary, 'JS_CONST');
// From dart:js
FunctionEntity _jsAllowInterop;
FunctionEntity _jsAllowInterop1;
@override
FunctionEntity get jsAllowInterop => _jsAllowInterop ??=
FunctionEntity get jsAllowInterop1 => _jsAllowInterop1 ??=
_findLibraryMember(dartJsLibrary, 'allowInterop', required: false);
// From package:js
ClassEntity _jsAnnotationClass;
// From dart:_js_annotations
FunctionEntity _jsAllowInterop2;
@override
ClassEntity get jsAnnotationClass => _jsAnnotationClass ??=
FunctionEntity get jsAllowInterop2 => _jsAllowInterop2 ??= _findLibraryMember(
dartJsAnnotationsLibrary, 'allowInterop',
required: false);
@override
bool isJsAllowInterop(FunctionEntity function) {
return function == jsAllowInterop1 || function == jsAllowInterop2;
}
// From package:js
ClassEntity _jsAnnotationClass1;
@override
ClassEntity get jsAnnotationClass1 => _jsAnnotationClass1 ??=
_findClass(packageJsLibrary, 'JS', required: false);
ClassEntity _jsAnonymousClass;
// From dart:_js_annotations
ClassEntity _jsAnnotationClass2;
@override
ClassEntity get jsAnonymousClass => _jsAnonymousClass ??=
ClassEntity get jsAnnotationClass2 => _jsAnnotationClass2 ??=
_findClass(dartJsAnnotationsLibrary, 'JS', required: false);
@override
bool isJsAnnotationClass(ClassEntity cls) {
return cls == jsAnnotationClass1 || cls == jsAnnotationClass2;
}
// From dart:js
ClassEntity _jsAnonymousClass1;
@override
ClassEntity get jsAnonymousClass1 => _jsAnonymousClass1 ??=
_findClass(packageJsLibrary, '_Anonymous', required: false);
// From dart:_js_annotations
ClassEntity _jsAnonymousClass2;
@override
ClassEntity get jsAnonymousClass2 => _jsAnonymousClass2 ??=
_findClass(dartJsAnnotationsLibrary, '_Anonymous', required: false);
@override
bool isJsAnonymousClass(ClassEntity cls) {
return cls == jsAnonymousClass1 || cls == jsAnonymousClass2;
}
@override
FunctionEntity findHelperFunction(String name) => _findHelperFunction(name);

View file

@ -295,7 +295,9 @@ String _getReturnsAnnotation(ir.Constant constant) {
String _getJsInteropName(ir.Constant constant) {
if (constant is ir.InstanceConstant &&
constant.classNode.name == 'JS' &&
constant.classNode.enclosingLibrary.importUri == Uris.package_js) {
(constant.classNode.enclosingLibrary.importUri == Uris.package_js ||
constant.classNode.enclosingLibrary.importUri ==
Uris.dart__js_annotations)) {
assert(constant.fieldValues.length == 1);
ir.Constant fieldValue = constant.fieldValues.values.single;
if (fieldValue is ir.NullConstant) {
@ -310,7 +312,9 @@ String _getJsInteropName(ir.Constant constant) {
bool _isAnonymousJsInterop(ir.Constant constant) {
return constant is ir.InstanceConstant &&
constant.classNode.name == '_Anonymous' &&
constant.classNode.enclosingLibrary.importUri == Uris.package_js;
(constant.classNode.enclosingLibrary.importUri == Uris.package_js ||
constant.classNode.enclosingLibrary.importUri ==
Uris.dart__js_annotations);
}
class PragmaAnnotationData {

View file

@ -562,7 +562,8 @@ class BackendImpacts {
BackendImpact get allowInterop => _allowInterop ??= BackendImpact(
staticUses: [
_commonElements.jsAllowInterop,
_commonElements.jsAllowInterop1,
_commonElements.jsAllowInterop2,
],
features: EnumSet<BackendFeature>.fromValues([
BackendFeature.needToInitializeIsolateAffinityTag,

View file

@ -33,8 +33,10 @@ class KernelAnnotationProcessor implements AnnotationProcessor {
String annotationName;
for (ConstantValue value in metadata) {
String name = readAnnotationName(commonElements.dartTypes, spannable,
value, commonElements.jsAnnotationClass,
defaultValue: '');
value, commonElements.jsAnnotationClass1, defaultValue: '') ??
readAnnotationName(commonElements.dartTypes, spannable, value,
commonElements.jsAnnotationClass2,
defaultValue: '');
if (annotationName == null) {
annotationName = name;
} else if (name != null) {

View file

@ -2084,7 +2084,8 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
List<js.Expression> arguments = visitArguments(node.inputs, start: 0);
if (element == _commonElements.jsAllowInterop) {
if (element == _commonElements.jsAllowInterop1 ||
element == _commonElements.jsAllowInterop2) {
_nativeData.registerAllowInterop();
}

View file

@ -0,0 +1,218 @@
// Copyright (c) 2016, 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.7
library jsinterop.internal_annotations_test;
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/common_elements.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/entities.dart' show ClassEntity;
import 'package:compiler/src/elements/names.dart';
import 'package:compiler/src/universe/class_hierarchy.dart';
import 'package:compiler/src/universe/selector.dart';
import 'package:compiler/src/world.dart';
import '../helpers/element_lookup.dart';
import '../helpers/memory_compiler.dart';
void main() {
asyncTest(() async {
await testClasses('package:js/js.dart', 'dart:_js_annotations');
await testClasses('package:js/js.dart', 'package:js/js.dart');
await testClasses('dart:_js_annotations', 'dart:_js_annotations');
});
}
testClasses(String import1, String import2) async {
test(String mainSource,
{List<String> directlyInstantiated: const <String>[],
List<String> abstractlyInstantiated: const <String>[],
List<String> indirectlyInstantiated: const <String>[]}) async {
String mainFile = 'sdk/tests/dart2js_2/native/main.dart';
Uri entryPoint = Uri.parse('memory:$mainFile');
CompilationResult result =
await runCompiler(entryPoint: entryPoint, memorySourceFiles: {
mainFile: """
import '$import1' as js1;
import '$import2' as js2;
@js1.JS()
class A {
external get foo;
external A(var foo);
}
@js2.JS('BClass')
class B {
external get foo;
external B(var foo);
}
@js1.JS()
@js1.anonymous
class C {
external get foo;
external factory C({foo});
}
@js2.JS()
@js2.anonymous
class D {
external get foo;
external factory D({foo});
}
class E {
final foo;
E(this.foo);
}
class F {
final foo;
F(this.foo);
}
newA() => new A(0);
newB() => new B(1);
newC() => new C(foo: 2);
newD() => new D(foo: 3);
newE() => new E(4);
newF() => new F(5);
$mainSource
"""
});
Compiler compiler = result.compiler;
Map<String, ClassEntity> classEnvironment = <String, ClassEntity>{};
ClassEntity registerClass(ClassEntity cls) {
classEnvironment[cls.name] = cls;
return cls;
}
JClosedWorld world = compiler.backendClosedWorldForTesting;
ElementEnvironment elementEnvironment = world.elementEnvironment;
ClassEntity Object_ = registerClass(world.commonElements.objectClass);
ClassEntity Interceptor =
registerClass(world.commonElements.jsInterceptorClass);
ClassEntity JavaScriptObject =
registerClass(world.commonElements.jsJavaScriptObjectClass);
ClassEntity A = registerClass(findClass(world, 'A'));
ClassEntity B = registerClass(findClass(world, 'B'));
ClassEntity C = registerClass(findClass(world, 'C'));
ClassEntity D = registerClass(findClass(world, 'D'));
ClassEntity E = registerClass(findClass(world, 'E'));
ClassEntity F = registerClass(findClass(world, 'F'));
Selector nonExisting = new Selector.getter(const PublicName('nonExisting'));
Expect.equals(elementEnvironment.getSuperClass(Interceptor), Object_);
Expect.equals(
elementEnvironment.getSuperClass(JavaScriptObject), Interceptor);
Expect.equals(elementEnvironment.getSuperClass(A), JavaScriptObject);
Expect.equals(elementEnvironment.getSuperClass(B), JavaScriptObject);
Expect.equals(elementEnvironment.getSuperClass(C), JavaScriptObject);
Expect.equals(elementEnvironment.getSuperClass(D), JavaScriptObject);
Expect.equals(elementEnvironment.getSuperClass(E), Object_);
Expect.equals(elementEnvironment.getSuperClass(F), Object_);
Expect.isFalse(world.nativeData.isJsInteropClass(Object_));
Expect.isTrue(world.nativeData.isJsInteropClass(A));
Expect.isTrue(world.nativeData.isJsInteropClass(B));
Expect.isTrue(world.nativeData.isJsInteropClass(C));
Expect.isTrue(world.nativeData.isJsInteropClass(D));
Expect.isFalse(world.nativeData.isJsInteropClass(E));
Expect.isFalse(world.nativeData.isJsInteropClass(F));
Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(Object_));
Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(A));
Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(B));
Expect.isTrue(world.nativeData.isAnonymousJsInteropClass(C));
Expect.isTrue(world.nativeData.isAnonymousJsInteropClass(D));
Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(E));
Expect.isFalse(world.nativeData.isAnonymousJsInteropClass(F));
Expect.equals('', world.nativeData.getJsInteropClassName(A));
Expect.equals('BClass', world.nativeData.getJsInteropClassName(B));
Expect.equals('', world.nativeData.getJsInteropClassName(C));
Expect.equals('', world.nativeData.getJsInteropClassName(D));
for (String name in classEnvironment.keys) {
ClassEntity cls = classEnvironment[name];
bool isInstantiated = false;
if (directlyInstantiated.contains(name)) {
isInstantiated = true;
Expect.isTrue(
world.classHierarchy.isDirectlyInstantiated(cls),
"Expected $name to be directly instantiated in `${mainSource}`:"
"\n${world.classHierarchy.dump(cls)}");
}
if (abstractlyInstantiated.contains(name)) {
isInstantiated = true;
Expect.isTrue(
world.classHierarchy.isAbstractlyInstantiated(cls),
"Expected $name to be abstractly instantiated in `${mainSource}`:"
"\n${world.classHierarchy.dump(cls)}");
Expect.isTrue(
world.needsNoSuchMethod(cls, nonExisting, ClassQuery.EXACT),
"Expected $name to need noSuchMethod for $nonExisting.");
Expect.isTrue(
world.needsNoSuchMethod(cls, nonExisting, ClassQuery.SUBCLASS),
"Expected $name to need noSuchMethod for $nonExisting.");
Expect.isTrue(
world.needsNoSuchMethod(cls, nonExisting, ClassQuery.SUBTYPE),
"Expected $name to need noSuchMethod for $nonExisting.");
}
if (indirectlyInstantiated.contains(name)) {
isInstantiated = true;
Expect.isTrue(
world.classHierarchy.isIndirectlyInstantiated(cls),
"Expected $name to be indirectly instantiated in `${mainSource}`:"
"\n${world.classHierarchy.dump(cls)}");
}
if (!isInstantiated && (name != 'Object' && name != 'Interceptor')) {
Expect.isFalse(
world.classHierarchy.isInstantiated(cls),
"Expected $name to be uninstantiated in `${mainSource}`:"
"\n${world.classHierarchy.dump(cls)}");
}
}
}
await test('main() {}');
await test('main() => newA();',
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newB();',
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newC();',
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newD();',
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newE();', directlyInstantiated: ['E']);
await test('main() => newF();', directlyInstantiated: ['F']);
await test('main() => [newD(), newE()];',
directlyInstantiated: ['E'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
}

View file

@ -23,8 +23,12 @@ bool _isLibrary(Library library, List<String> candidates) {
/// Returns true if [library] represents any library from `package:js` or is the
/// internal `dart:_js_helper` library.
bool _isJSLibrary(Library library) => _isLibrary(
library, ['package:js', 'dart:_js_helper', 'dart:_foreign_helper']);
bool _isJSLibrary(Library library) => _isLibrary(library, [
'package:js',
'dart:_js_helper',
'dart:_foreign_helper',
'dart:_js_annotations'
]);
/// Whether [node] is a direct call to `allowInterop`.
bool isAllowInterop(Expression node) {

View file

@ -190,6 +190,8 @@ const Map<String, LibraryInfo> libraries = const {
platforms: DART2JS_PLATFORM),
"_metadata": const LibraryInfo("html/html_common/metadata.dart",
categories: "", documented: false, platforms: DART2JS_PLATFORM),
"_js_annotations": const LibraryInfo("js/_js_annotations.dart",
categories: "", documented: false, platforms: DART2JS_PLATFORM),
};
/**

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015, 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.
// An implementation of the JS interop classes which are usable from the
// Dart SDK. These types need to stay in-sync with
// https://github.com/dart-lang/sdk/blob/master/pkg/js/lib/js.dart
library _js_annotations;
class JS {
final String name;
const JS([this.name]);
}
class _Anonymous {
const _Anonymous();
}
const _Anonymous anonymous = const _Anonymous();

View file

@ -211,6 +211,9 @@
"uri": "js/_js.dart",
"patches": "js/_js_client.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"js_util": {
"uri": "js_util/js_util.dart"
},
@ -321,6 +324,9 @@
"uri": "js/_js.dart",
"patches": "js/_js_server.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"js_util": {
"uri": "js_util/js_util.dart"
},
@ -397,6 +403,9 @@
"_isolate_helper": {
"uri": "_internal/js_dev_runtime/private/isolate_helper.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"_js_helper": {
"uri": "_internal/js_dev_runtime/private/js_helper.dart"
},

View file

@ -208,6 +208,9 @@ dart2js:
uri: "js/_js.dart"
patches: "js/_js_client.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
js_util:
uri: "js_util/js_util.dart"
@ -316,6 +319,9 @@ dart2js_server:
uri: "js/_js.dart"
patches: "js/_js_server.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
js_util:
uri: "js_util/js_util.dart"
@ -390,6 +396,9 @@ dartdevc:
_isolate_helper:
uri: "_internal/js_dev_runtime/private/isolate_helper.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
_js_helper:
uri: "_internal/js_dev_runtime/private/js_helper.dart"

View file

@ -190,6 +190,8 @@ const Map<String, LibraryInfo> libraries = const {
platforms: DART2JS_PLATFORM),
"_metadata": const LibraryInfo("html/html_common/metadata.dart",
categories: "", documented: false, platforms: DART2JS_PLATFORM),
"_js_annotations": const LibraryInfo("js/_js_annotations.dart",
categories: "", documented: false, platforms: DART2JS_PLATFORM),
};
/**

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015, 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.
// An implementation of the JS interop classes which are usable from the
// Dart SDK. These types need to stay in-sync with
// https://github.com/dart-lang/sdk/blob/master/pkg/js/lib/js.dart
library _js_annotations;
class JS {
final String name;
const JS([this.name]);
}
class _Anonymous {
const _Anonymous();
}
const _Anonymous anonymous = const _Anonymous();

View file

@ -211,6 +211,9 @@
"uri": "js/_js.dart",
"patches": "js/_js_client.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"js_util": {
"uri": "js_util/js_util.dart"
},
@ -321,6 +324,9 @@
"uri": "js/_js.dart",
"patches": "js/_js_server.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"js_util": {
"uri": "js_util/js_util.dart"
},
@ -397,6 +403,9 @@
"_isolate_helper": {
"uri": "_internal/js_dev_runtime/private/isolate_helper.dart"
},
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
"_js_helper": {
"uri": "_internal/js_dev_runtime/private/js_helper.dart"
},

View file

@ -208,6 +208,9 @@ dart2js:
uri: "js/_js.dart"
patches: "js/_js_client.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
js_util:
uri: "js_util/js_util.dart"
@ -316,6 +319,9 @@ dart2js_server:
uri: "js/_js.dart"
patches: "js/_js_server.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
js_util:
uri: "js_util/js_util.dart"
@ -390,6 +396,9 @@ dartdevc:
_isolate_helper:
uri: "_internal/js_dev_runtime/private/isolate_helper.dart"
_js_annotations:
uri: "js/_js_annotations.dart"
_js_helper:
uri: "_internal/js_dev_runtime/private/js_helper.dart"