mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:44:27 +00:00
fix regression in DDC handling of top-level field named 'name'
Also refactors the code to make this sort of issue less likely. Change-Id: Iff72e53720ba78e84c3d219e69ccd0d6a47c09a0 Reviewed-on: https://dart-review.googlesource.com/47188 Commit-Queue: Jenny Messerly <jmesserly@google.com> Reviewed-by: Vijay Menon <vsm@google.com>
This commit is contained in:
parent
ac322d0fb4
commit
d9878ae0da
9 changed files with 250 additions and 180 deletions
|
@ -461,13 +461,23 @@ class CodeGenerator extends Object
|
|||
e is PropertyInducingElement &&
|
||||
((e.getter?.isExternal ?? false) || (e.setter?.isExternal ?? false));
|
||||
|
||||
bool _isJSElement(Element e) =>
|
||||
/// Returns true iff this element is a JS interop member.
|
||||
///
|
||||
/// The element's library must have `@JS(...)` annotation from `package:js`.
|
||||
///
|
||||
/// If the element is a class, it must also be marked with `@JS`. Other
|
||||
/// elements, such as class members and top-level functions/accessors, should
|
||||
/// be marked `external`.
|
||||
//
|
||||
// TODO(jmesserly): if the element is a member, shouldn't we check that the
|
||||
// class is a JS interop class?
|
||||
bool _usesJSInterop(Element e) =>
|
||||
e?.library != null &&
|
||||
_isJSNative(e.library) &&
|
||||
(_isExternal(e) || e is ClassElement && _isJSNative(e));
|
||||
_hasJSInteropAnnotation(e.library) &&
|
||||
(_isExternal(e) || e is ClassElement && _hasJSInteropAnnotation(e));
|
||||
|
||||
String _getJSNameWithoutGlobal(Element e) {
|
||||
if (!_isJSElement(e)) return null;
|
||||
if (!_usesJSInterop(e)) return null;
|
||||
var libraryJSName = getAnnotationName(e.library, isPublicJSAnnotation);
|
||||
var jsName =
|
||||
getAnnotationName(e, isPublicJSAnnotation) ?? _getElementName(e);
|
||||
|
@ -492,7 +502,7 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
JS.Expression _emitJSInteropStaticMemberName(Element e) {
|
||||
if (!_isJSElement(e)) return null;
|
||||
if (!_usesJSInterop(e)) return null;
|
||||
var name = getAnnotationName(e, isPublicJSAnnotation);
|
||||
if (name != null) {
|
||||
if (name.contains('.')) {
|
||||
|
@ -873,7 +883,7 @@ class CodeGenerator extends Object
|
|||
JS.Statement _emitClassDeclaration(Declaration classNode,
|
||||
ClassElement classElem, List<ClassMember> members) {
|
||||
// If this class is annotated with `@JS`, then there is nothing to emit.
|
||||
if (findAnnotation(classElem, isPublicJSAnnotation) != null) return null;
|
||||
if (_hasJSInteropAnnotation(classElem)) return null;
|
||||
|
||||
// If this is a JavaScript type, emit it now and then exit.
|
||||
var jsTypeDef = _emitJSType(classElem);
|
||||
|
@ -921,7 +931,7 @@ class CodeGenerator extends Object
|
|||
body.addAll(jsCtors);
|
||||
|
||||
// Emit things that come after the ES6 `class ... { ... }`.
|
||||
var jsPeerNames = _getJSPeerNames(classElem);
|
||||
var jsPeerNames = _extensionTypes.getNativePeers(classElem);
|
||||
if (jsPeerNames.length == 1 && classElem.typeParameters.isNotEmpty) {
|
||||
// Special handling for JSArray<E>
|
||||
body.add(_callHelperStatement('setExtensionBaseClass(#, #.global.#);',
|
||||
|
@ -1466,7 +1476,7 @@ class CodeGenerator extends Object
|
|||
var virtualFields = _classProperties.virtualFields;
|
||||
|
||||
var jsMethods = <JS.Method>[];
|
||||
bool hasJsPeer = findAnnotation(classElem, isJsPeerInterface) != null;
|
||||
bool hasJsPeer = _extensionTypes.isNativeClass(classElem);
|
||||
bool hasIterator = false;
|
||||
|
||||
if (type.isObject) {
|
||||
|
@ -1875,25 +1885,6 @@ class CodeGenerator extends Object
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the JS peer for this Dart type if any, otherwise null.
|
||||
///
|
||||
/// For example for dart:_interceptors `JSArray` this will return "Array",
|
||||
/// referring to the JavaScript built-in `Array` type.
|
||||
List<String> _getJSPeerNames(ClassElement classElem) {
|
||||
var jsPeerNames = getAnnotationName(
|
||||
classElem,
|
||||
(a) =>
|
||||
isJsPeerInterface(a) ||
|
||||
isNativeAnnotation(a) && _extensionTypes.isNativeClass(classElem));
|
||||
if (classElem.type.isObject) return ['Object'];
|
||||
if (jsPeerNames == null) return [];
|
||||
|
||||
// Omit the special name "!nonleaf" and any future hacks starting with "!"
|
||||
var result =
|
||||
jsPeerNames.split(',').where((peer) => !peer.startsWith("!")).toList();
|
||||
return result;
|
||||
}
|
||||
|
||||
void _registerExtensionType(
|
||||
ClassElement classElem, String jsPeerName, List<JS.Statement> body) {
|
||||
var className = _emitTopLevelName(classElem);
|
||||
|
@ -2015,7 +2006,7 @@ class CodeGenerator extends Object
|
|||
var type = classElem.type;
|
||||
void addField(FieldElement e, JS.Expression value) {
|
||||
var args = [
|
||||
_emitStaticAccess(classElem),
|
||||
_emitStaticClassName(classElem),
|
||||
_declareMemberName(e.getter),
|
||||
value
|
||||
];
|
||||
|
@ -2033,7 +2024,7 @@ class CodeGenerator extends Object
|
|||
if (f.type != type) continue;
|
||||
// static const E id_i = const E(i);
|
||||
values.add(new JS.PropertyAccess(
|
||||
_emitStaticAccess(classElem), _declareMemberName(f.getter)));
|
||||
_emitStaticClassName(classElem), _declareMemberName(f.getter)));
|
||||
var enumValue = _callHelper('const(new (#.#)(#))', [
|
||||
_emitConstructorAccess(type),
|
||||
_constructorName(''),
|
||||
|
@ -2051,7 +2042,8 @@ class CodeGenerator extends Object
|
|||
.map((f) => members[f] as VariableDeclaration)
|
||||
.toList();
|
||||
if (lazyStatics.isNotEmpty) {
|
||||
body.add(_emitLazyFields(_emitTopLevelName(classElem), lazyStatics));
|
||||
body.add(_emitLazyFields(_emitStaticClassName(classElem), lazyStatics,
|
||||
(e) => _emitStaticMemberName(e.name, e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3105,12 +3097,20 @@ class CodeGenerator extends Object
|
|||
|
||||
// A static native element should just forward directly to the
|
||||
// JS type's member.
|
||||
//
|
||||
// TODO(jmesserly): this code path seems broken. It doesn't exist
|
||||
// elsewhere, such as [_emitAccess], so it will only take affect for
|
||||
// unqualified static access inside of the the same class.
|
||||
//
|
||||
// If we want this feature to work, we'll need to implement it in the
|
||||
// standard [_emitStaticClassName] code path, which will need to know the
|
||||
// member we're calling so it can determine whether to use the Dart class
|
||||
// name or the native JS class name.
|
||||
if (isStatic && _isExternal(element)) {
|
||||
var nativeName = getAnnotationName(classElem, isNativeAnnotation);
|
||||
if (nativeName != null) {
|
||||
var nativeName = _extensionTypes.getNativePeers(classElem);
|
||||
if (nativeName.isNotEmpty) {
|
||||
var memberName = getAnnotationName(element, isJSName) ?? member;
|
||||
return js
|
||||
.call('#.#.#', [_callHelper('global'), nativeName, memberName]);
|
||||
return _callHelper('global.#.#', [nativeName[0], memberName]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3118,16 +3118,16 @@ class CodeGenerator extends Object
|
|||
// For method tear-offs, we ensure it's a bound method.
|
||||
if (element is MethodElement &&
|
||||
!inInvocationContext(node) &&
|
||||
!_isJSNative(classElem)) {
|
||||
!_hasJSInteropAnnotation(classElem)) {
|
||||
if (isStatic) {
|
||||
// TODO(jmesserly): instead of looking up the function type, we could
|
||||
// simply emit it here.
|
||||
return _callHelper(
|
||||
'tagStatic(#, #)', [_emitStaticAccess(classElem), member]);
|
||||
'tagStatic(#, #)', [_emitStaticClassName(classElem), member]);
|
||||
}
|
||||
return _callHelper('bind(this, #)', member);
|
||||
}
|
||||
var target = isStatic ? _emitStaticAccess(classElem) : new JS.This();
|
||||
var target = isStatic ? _emitStaticClassName(classElem) : new JS.This();
|
||||
return new JS.PropertyAccess(target, member);
|
||||
}
|
||||
|
||||
|
@ -3318,10 +3318,10 @@ class CodeGenerator extends Object
|
|||
return _emitJSInterop(type.element) ?? _emitType(type, nameType: nameType);
|
||||
}
|
||||
|
||||
/// Emits an expression that lets you access statics on an [element] from code.
|
||||
JS.Expression _emitStaticAccess(ClassElement element) {
|
||||
_declareBeforeUse(element);
|
||||
return _emitTopLevelName(element);
|
||||
/// Emits an expression that lets you access statics on an [c] from code.
|
||||
JS.Expression _emitStaticClassName(ClassElement c) {
|
||||
_declareBeforeUse(c);
|
||||
return _emitTopLevelName(c);
|
||||
}
|
||||
|
||||
/// Emits a Dart [type] into code.
|
||||
|
@ -3406,9 +3406,17 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
JS.PropertyAccess _emitTopLevelNameNoInterop(Element e, {String suffix: ''}) {
|
||||
var name = getJSExportName(e) ?? _getElementName(e);
|
||||
return new JS.PropertyAccess(
|
||||
emitLibraryName(e.library), _propertyName(name + suffix));
|
||||
emitLibraryName(e.library), _emitTopLevelMemberName(e, suffix: suffix));
|
||||
}
|
||||
|
||||
/// Emits the member name portion of a top-level member.
|
||||
///
|
||||
/// NOTE: usually you should use [_emitTopLevelName] instead of this. This
|
||||
/// function does not handle JS interop.
|
||||
JS.Expression _emitTopLevelMemberName(Element e, {String suffix: ''}) {
|
||||
var name = getJSExportName(e) ?? _getElementName(e);
|
||||
return _propertyName(name + suffix);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3592,7 +3600,7 @@ class CodeGenerator extends Object
|
|||
var member = _emitMemberName(field.name,
|
||||
isStatic: isStatic, type: classElem.type, element: field.setter);
|
||||
jsTarget = isStatic
|
||||
? (new JS.PropertyAccess(_emitStaticAccess(classElem), member)
|
||||
? (new JS.PropertyAccess(_emitStaticClassName(classElem), member)
|
||||
..sourceInformation = _nodeSpan(id))
|
||||
: _emitTargetAccess(jsTarget, member, field.setter, id);
|
||||
return _visitExpression(right).toAssignExpression(jsTarget);
|
||||
|
@ -3688,12 +3696,12 @@ class CodeGenerator extends Object
|
|||
if (member is PropertyAccessorElement) {
|
||||
var field = member.variable;
|
||||
if (field is FieldElement) {
|
||||
return _emitStaticAccess(field.enclosingElement)
|
||||
return _emitStaticClassName(field.enclosingElement)
|
||||
..sourceInformation = _nodeSpan(target);
|
||||
}
|
||||
}
|
||||
if (member is MethodElement) {
|
||||
return _emitStaticAccess(member.enclosingElement)
|
||||
return _emitStaticClassName(member.enclosingElement)
|
||||
..sourceInformation = _nodeSpan(target);
|
||||
}
|
||||
}
|
||||
|
@ -4241,7 +4249,8 @@ class CodeGenerator extends Object
|
|||
|
||||
/// Emits a list of top-level field.
|
||||
void _emitTopLevelFields(List<VariableDeclaration> fields) {
|
||||
_moduleItems.add(_emitLazyFields(emitLibraryName(currentLibrary), fields));
|
||||
_moduleItems.add(_emitLazyFields(
|
||||
emitLibraryName(currentLibrary), fields, _emitTopLevelMemberName));
|
||||
}
|
||||
|
||||
/// Treat dart:_runtime fields as safe to eagerly evaluate.
|
||||
|
@ -4284,18 +4293,19 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
JS.Statement _emitLazyFields(
|
||||
JS.Expression objExpr, List<VariableDeclaration> fields) {
|
||||
JS.Expression objExpr,
|
||||
List<VariableDeclaration> fields,
|
||||
JS.Expression Function(Element e) emitFieldName) {
|
||||
var accessors = <JS.Method>[];
|
||||
|
||||
for (var node in fields) {
|
||||
var name = node.name.name;
|
||||
var element = node.element;
|
||||
var access = _emitStaticMemberName(name, element);
|
||||
var access = emitFieldName(element);
|
||||
accessors.add(closureAnnotate(
|
||||
new JS.Method(
|
||||
access,
|
||||
js.call('function() { return #; }',
|
||||
_visitInitializer(node.initializer, node.element)) as JS.Fun,
|
||||
_visitInitializer(node.initializer, element)) as JS.Fun,
|
||||
isGetter: true)
|
||||
..sourceInformation = _hoverComment(
|
||||
new JS.PropertyAccess(objExpr, access), node.name),
|
||||
|
@ -4390,7 +4400,7 @@ class CodeGenerator extends Object
|
|||
// Native factory constructors are JS constructors - use new here.
|
||||
var ctor = _emitConstructorName(element, type);
|
||||
if (ctorNode != null) ctor.sourceInformation = _nodeSpan(ctorNode);
|
||||
return element.isFactory && !_isJSNative(classElem)
|
||||
return element.isFactory && !_hasJSInteropAnnotation(classElem)
|
||||
? new JS.Call(ctor, args)
|
||||
: new JS.New(ctor, args);
|
||||
}
|
||||
|
@ -4399,11 +4409,22 @@ class CodeGenerator extends Object
|
|||
}
|
||||
|
||||
bool _isObjectLiteral(Element classElem) {
|
||||
return _isJSNative(classElem) &&
|
||||
return _hasJSInteropAnnotation(classElem) &&
|
||||
findAnnotation(classElem, isJSAnonymousAnnotation) != null;
|
||||
}
|
||||
|
||||
bool _isJSNative(Element e) =>
|
||||
/// Returns true iff the class has an `@JS(...)` annotation from `package:js`.
|
||||
///
|
||||
/// Note: usually [_usesJSInterop] should be used instead of this.
|
||||
//
|
||||
// TODO(jmesserly): I think almost all uses of this should be replaced with
|
||||
// [_usesJSInterop], which also checks that the library is marked with `@JS`.
|
||||
//
|
||||
// Right now we have inconsistencies: sometimes we'll respect `@JS` on the
|
||||
// class itself, other places we require it on the library. Also members are
|
||||
// inconsistent: sometimes they need to have `@JS` on them, other times they
|
||||
// need to be `external` in an `@JS` class.
|
||||
bool _hasJSInteropAnnotation(Element e) =>
|
||||
findAnnotation(e, isPublicJSAnnotation) != null;
|
||||
|
||||
JS.Expression _emitObjectLiteral(ArgumentList argumentList) {
|
||||
|
@ -5219,7 +5240,7 @@ class CodeGenerator extends Object
|
|||
result = _callHelper('#(#)', [memberName, jsTarget]);
|
||||
}
|
||||
} else if (accessor is MethodElement &&
|
||||
!_isJSNative(accessor.enclosingElement)) {
|
||||
!_hasJSInteropAnnotation(accessor.enclosingElement)) {
|
||||
if (isStatic) {
|
||||
result = _callHelper('tagStatic(#, #)', [jsTarget, jsName]);
|
||||
} else if (isSuper) {
|
||||
|
|
|
@ -246,3 +246,12 @@ bool isUnsupportedFactoryConstructor(ConstructorDeclaration node) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isBuiltinAnnotation(
|
||||
DartObjectImpl value, String libraryName, String annotationName) {
|
||||
var e = value?.type?.element;
|
||||
if (e?.name != annotationName) return false;
|
||||
var uri = e.source.uri;
|
||||
var path = uri.pathSegments[0];
|
||||
return uri.scheme == 'dart' && path == libraryName;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:analyzer/dart/element/element.dart'
|
|||
show ClassElement, CompilationUnitElement, Element;
|
||||
import 'package:analyzer/dart/element/type.dart' show DartType, InterfaceType;
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
|
||||
import 'element_helpers.dart' show getAnnotationName, isBuiltinAnnotation;
|
||||
|
||||
/// Contains information about native JS types (those types provided by the
|
||||
/// implementation) that are also provided by the Dart SDK.
|
||||
|
@ -150,4 +151,21 @@ class ExtensionTypeSet {
|
|||
|
||||
bool hasNativeSubtype(DartType type) =>
|
||||
isNativeInterface(type.element) || isNativeClass(type.element);
|
||||
|
||||
/// Gets the JS peer for this Dart type if any, otherwise null.
|
||||
///
|
||||
/// For example for dart:_interceptors `JSArray` this will return "Array",
|
||||
/// referring to the JavaScript built-in `Array` type.
|
||||
List<String> getNativePeers(ClassElement classElem) {
|
||||
if (classElem.type.isObject) return ['Object'];
|
||||
var names = getAnnotationName(
|
||||
classElem,
|
||||
(a) =>
|
||||
isBuiltinAnnotation(a, '_js_helper', 'JsPeerInterface') ||
|
||||
isBuiltinAnnotation(a, '_js_helper', 'Native'));
|
||||
if (names == null) return [];
|
||||
|
||||
// Omit the special name "!nonleaf" and any future hacks starting with "!"
|
||||
return names.split(',').where((peer) => !peer.startsWith("!")).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ bool _isJsLib(LibraryElement e) {
|
|||
var uri = e.source.uri;
|
||||
if (uri.scheme == 'package' && uri.path.startsWith('js/')) return true;
|
||||
if (uri.scheme == 'dart') {
|
||||
// TODO(jmesserly): this needs cleanup: many of the annotations don't exist
|
||||
// in these libraries.
|
||||
return uri.path == '_js_helper' || uri.path == '_foreign_helper';
|
||||
}
|
||||
return false;
|
||||
|
@ -47,15 +49,6 @@ bool isPublicJSAnnotation(DartObjectImpl value) =>
|
|||
bool isJSAnonymousAnnotation(DartObjectImpl value) =>
|
||||
_isJsLibType('_Anonymous', value.type.element);
|
||||
|
||||
bool isBuiltinAnnotation(
|
||||
DartObjectImpl value, String libraryName, String annotationName) {
|
||||
var e = value?.type?.element;
|
||||
if (e?.name != annotationName) return false;
|
||||
var uri = e.source.uri;
|
||||
var path = uri.pathSegments[0];
|
||||
return uri.scheme == 'dart' && path == libraryName;
|
||||
}
|
||||
|
||||
/// Whether [value] is a `@JSExportName` (internal annotation used in SDK
|
||||
/// instead of `@JS` from `package:js`).
|
||||
bool isJSExportNameAnnotation(DartObjectImpl value) =>
|
||||
|
@ -64,12 +57,6 @@ bool isJSExportNameAnnotation(DartObjectImpl value) =>
|
|||
bool isJSName(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', 'JSName');
|
||||
|
||||
bool isJsPeerInterface(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', 'JsPeerInterface');
|
||||
|
||||
bool isNativeAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', 'Native');
|
||||
|
||||
bool isNotNullAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', '_NotNull');
|
||||
|
||||
|
|
|
@ -431,19 +431,27 @@ class ProgramCompiler
|
|||
assert(_currentLibrary == null);
|
||||
_currentLibrary = library;
|
||||
|
||||
// `dart:_runtime` uses a different order for bootstrapping.
|
||||
bool bootstrap = isSdkInternalRuntime(library);
|
||||
if (bootstrap) _emitLibraryProcedures(library);
|
||||
|
||||
library.classes.forEach(_emitClass);
|
||||
library.typedefs.forEach(_emitTypedef);
|
||||
if (bootstrap) {
|
||||
_moduleItems.add(_emitInternalSdkFields(library.fields));
|
||||
} else {
|
||||
if (isSdkInternalRuntime(library)) {
|
||||
// `dart:_runtime` uses a different order for bootstrapping.
|
||||
//
|
||||
// Functions are first because we use them to associate type info
|
||||
// (such as `dart.fn`), then classes/typedefs, then fields
|
||||
// (which instantiate classes).
|
||||
//
|
||||
// For other libraries, we start with classes/types, because functions
|
||||
// often use classes/types from the library in their signature.
|
||||
//
|
||||
// TODO(jmesserly): we can merge these once we change signatures to be
|
||||
// lazily associated at the tear-off point for top-level functions.
|
||||
_emitLibraryProcedures(library);
|
||||
var fields = library.fields;
|
||||
if (fields.isNotEmpty)
|
||||
_moduleItems.add(_emitLazyFields(emitLibraryName(library), fields));
|
||||
library.classes.forEach(_emitClass);
|
||||
library.typedefs.forEach(_emitTypedef);
|
||||
_emitTopLevelFields(library.fields);
|
||||
} else {
|
||||
library.classes.forEach(_emitClass);
|
||||
library.typedefs.forEach(_emitTypedef);
|
||||
_emitLibraryProcedures(library);
|
||||
_emitTopLevelFields(library.fields);
|
||||
}
|
||||
|
||||
_currentLibrary = null;
|
||||
|
@ -551,7 +559,7 @@ class ProgramCompiler
|
|||
body.addAll(jsCtors);
|
||||
|
||||
// Emit things that come after the ES6 `class ... { ... }`.
|
||||
var jsPeerNames = _getJSPeerNames(c);
|
||||
var jsPeerNames = _extensionTypes.getNativePeers(c);
|
||||
if (jsPeerNames.length == 1 && c.typeParameters.isNotEmpty) {
|
||||
// Special handling for JSArray<E>
|
||||
body.add(_callHelperStatement('setExtensionBaseClass(#, #.global.#);',
|
||||
|
@ -1102,7 +1110,8 @@ class ProgramCompiler
|
|||
.toStatement());
|
||||
}
|
||||
} else if (fields.isNotEmpty) {
|
||||
body.add(_emitLazyFields(_emitTopLevelName(c), fields));
|
||||
body.add(_emitLazyFields(_emitTopLevelName(c), fields,
|
||||
(n) => _emitStaticMemberName(n.name.name)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1567,7 +1576,7 @@ class ProgramCompiler
|
|||
var virtualFields = _classProperties.virtualFields;
|
||||
|
||||
var jsMethods = <JS.Method>[];
|
||||
bool hasJsPeer = findAnnotation(c, isJsPeerInterface) != null;
|
||||
bool hasJsPeer = _extensionTypes.isNativeClass(c);
|
||||
bool hasIterator = false;
|
||||
|
||||
if (c == coreTypes.objectClass) {
|
||||
|
@ -2056,25 +2065,6 @@ class ProgramCompiler
|
|||
JS.Expression _instantiateAnnotation(Expression node) =>
|
||||
_visitExpression(node);
|
||||
|
||||
/// Gets the JS peer for this Dart type if any, otherwise null.
|
||||
///
|
||||
/// For example for dart:_interceptors `JSArray` this will return "Array",
|
||||
/// referring to the JavaScript built-in `Array` type.
|
||||
List<String> _getJSPeerNames(Class c) {
|
||||
var jsPeerNames = getAnnotationName(
|
||||
c,
|
||||
(a) =>
|
||||
isJsPeerInterface(a) ||
|
||||
isNativeAnnotation(a) && _extensionTypes.isNativeClass(c));
|
||||
if (c == coreTypes.objectClass) return ['Object'];
|
||||
if (jsPeerNames == null) return [];
|
||||
|
||||
// Omit the special name "!nonleaf" and any future hacks starting with "!"
|
||||
var result =
|
||||
jsPeerNames.split(',').where((peer) => !peer.startsWith("!")).toList();
|
||||
return result;
|
||||
}
|
||||
|
||||
void _registerExtensionType(
|
||||
Class c, String jsPeerName, List<JS.Statement> body) {
|
||||
var className = _emitTopLevelName(c);
|
||||
|
@ -2115,50 +2105,55 @@ class ProgramCompiler
|
|||
_moduleItems.add(result);
|
||||
}
|
||||
|
||||
/// Treat dart:_runtime fields as safe to eagerly evaluate.
|
||||
// TODO(jmesserly): it'd be nice to avoid this special case.
|
||||
JS.Statement _emitInternalSdkFields(Iterable<Field> fields) {
|
||||
var lazyFields = <Field>[];
|
||||
var savedUri = _currentUri;
|
||||
void _emitTopLevelFields(List<Field> fields) {
|
||||
if (isSdkInternalRuntime(_currentLibrary)) {
|
||||
/// Treat dart:_runtime fields as safe to eagerly evaluate.
|
||||
// TODO(jmesserly): it'd be nice to avoid this special case.
|
||||
var lazyFields = <Field>[];
|
||||
var savedUri = _currentUri;
|
||||
for (var field in fields) {
|
||||
// Skip our magic undefined constant.
|
||||
if (field.name.name == 'undefined') continue;
|
||||
|
||||
for (var field in fields) {
|
||||
// Skip our magic undefined constant.
|
||||
if (field.name.name == 'undefined') continue;
|
||||
|
||||
var init = field.initializer;
|
||||
if (init == null ||
|
||||
init is BasicLiteral ||
|
||||
init is StaticInvocation && isInlineJS(init.target) ||
|
||||
init is ConstructorInvocation &&
|
||||
isSdkInternalRuntime(init.target.enclosingLibrary)) {
|
||||
_currentUri = field.fileUri;
|
||||
_moduleItems.add(js.statement('# = #;', [
|
||||
_emitTopLevelName(field),
|
||||
_visitInitializer(init, field.annotations)
|
||||
]));
|
||||
} else {
|
||||
lazyFields.add(field);
|
||||
var init = field.initializer;
|
||||
if (init == null ||
|
||||
init is BasicLiteral ||
|
||||
init is StaticInvocation && isInlineJS(init.target) ||
|
||||
init is ConstructorInvocation &&
|
||||
isSdkInternalRuntime(init.target.enclosingLibrary)) {
|
||||
_currentUri = field.fileUri;
|
||||
_moduleItems.add(js.statement('# = #;', [
|
||||
_emitTopLevelName(field),
|
||||
_visitInitializer(init, field.annotations)
|
||||
]));
|
||||
} else {
|
||||
lazyFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
_currentUri = savedUri;
|
||||
fields = lazyFields;
|
||||
}
|
||||
|
||||
_currentUri = savedUri;
|
||||
return _emitLazyFields(emitLibraryName(_currentLibrary), lazyFields);
|
||||
if (fields.isEmpty) return;
|
||||
_moduleItems.add(_emitLazyFields(
|
||||
emitLibraryName(_currentLibrary), fields, _emitTopLevelMemberName));
|
||||
}
|
||||
|
||||
JS.Statement _emitLazyFields(JS.Expression objExpr, Iterable<Field> fields) {
|
||||
JS.Statement _emitLazyFields(JS.Expression objExpr, Iterable<Field> fields,
|
||||
JS.Expression Function(Field f) emitFieldName) {
|
||||
var accessors = <JS.Method>[];
|
||||
var savedUri = _currentUri;
|
||||
|
||||
for (var field in fields) {
|
||||
_currentUri = field.fileUri;
|
||||
var name = field.name.name;
|
||||
var access = _emitStaticMemberName(name);
|
||||
var access = emitFieldName(field);
|
||||
accessors.add(new JS.Method(access, _emitStaticFieldInitializer(field),
|
||||
isGetter: true)
|
||||
..sourceInformation = _hoverComment(
|
||||
new JS.PropertyAccess(objExpr, access),
|
||||
field.fileOffset,
|
||||
name.length));
|
||||
field.name.name.length));
|
||||
|
||||
// TODO(jmesserly): currently uses a dummy setter to indicate writable.
|
||||
if (!field.isFinal && !field.isConst) {
|
||||
|
@ -2415,7 +2410,7 @@ class ProgramCompiler
|
|||
}
|
||||
|
||||
JS.Expression _emitJSInteropStaticMemberName(NamedNode n) {
|
||||
if (!isJSElement(n)) return null;
|
||||
if (!usesJSInterop(n)) return null;
|
||||
var name = getAnnotationName(n, isPublicJSAnnotation);
|
||||
if (name != null) {
|
||||
if (name.contains('.')) {
|
||||
|
@ -2431,13 +2426,21 @@ class ProgramCompiler
|
|||
|
||||
JS.PropertyAccess _emitTopLevelNameNoInterop(NamedNode n,
|
||||
{String suffix: ''}) {
|
||||
return new JS.PropertyAccess(emitLibraryName(getLibrary(n)),
|
||||
_emitTopLevelMemberName(n, suffix: suffix));
|
||||
}
|
||||
|
||||
/// Emits the member name portion of a top-level member.
|
||||
///
|
||||
/// NOTE: usually you should use [_emitTopLevelName] instead of this. This
|
||||
/// function does not handle JS interop.
|
||||
JS.Expression _emitTopLevelMemberName(NamedNode n, {String suffix: ''}) {
|
||||
var name = getJSExportName(n) ?? getTopLevelName(n);
|
||||
return new JS.PropertyAccess(
|
||||
emitLibraryName(getLibrary(n)), _propertyName(name + suffix));
|
||||
return _propertyName(name + suffix);
|
||||
}
|
||||
|
||||
String _getJSNameWithoutGlobal(NamedNode n) {
|
||||
if (!isJSElement(n)) return null;
|
||||
if (!usesJSInterop(n)) return null;
|
||||
var libraryJSName = getAnnotationName(getLibrary(n), isPublicJSAnnotation);
|
||||
var jsName =
|
||||
getAnnotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n);
|
||||
|
@ -2695,8 +2698,8 @@ class ProgramCompiler
|
|||
_emitConstructorAccess(type), _constructorName(c.name.name));
|
||||
}
|
||||
|
||||
/// Emits an expression that lets you access statics on an [element] from code.
|
||||
JS.Expression _emitStaticAccess(Class c) {
|
||||
/// Emits an expression that lets you access statics on an [c] from code.
|
||||
JS.Expression _emitStaticClassName(Class c) {
|
||||
_declareBeforeUse(c);
|
||||
return _emitTopLevelName(c);
|
||||
}
|
||||
|
@ -3799,7 +3802,7 @@ class ProgramCompiler
|
|||
}
|
||||
} else if (member is Procedure &&
|
||||
!member.isAccessor &&
|
||||
!_isJSNative(member.enclosingClass)) {
|
||||
!hasJSInteropAnnotation(member.enclosingClass)) {
|
||||
return _callHelper('bind(#, #)', [jsReceiver, jsName]);
|
||||
} else {
|
||||
return new JS.PropertyAccess(jsReceiver, jsName);
|
||||
|
@ -3828,7 +3831,7 @@ class ProgramCompiler
|
|||
var jsTarget = _emitSuperTarget(target);
|
||||
if (target is Procedure &&
|
||||
!target.isAccessor &&
|
||||
!_isJSNative(target.enclosingClass)) {
|
||||
!hasJSInteropAnnotation(target.enclosingClass)) {
|
||||
return _callHelper('bind(this, #, #)', [jsTarget.selector, jsTarget]);
|
||||
}
|
||||
return jsTarget;
|
||||
|
@ -4329,7 +4332,7 @@ class ProgramCompiler
|
|||
JS.Expression _emitStaticTarget(Member target) {
|
||||
var c = target.enclosingClass;
|
||||
if (c != null) {
|
||||
return new JS.PropertyAccess(_emitStaticAccess(c),
|
||||
return new JS.PropertyAccess(_emitStaticClassName(c),
|
||||
_emitStaticMemberName(target.name.name, target));
|
||||
}
|
||||
return _emitTopLevelName(target);
|
||||
|
@ -4530,7 +4533,7 @@ class ProgramCompiler
|
|||
var args = node.arguments;
|
||||
var ctor = node.target;
|
||||
var ctorClass = ctor.enclosingClass;
|
||||
if (ctor.isExternal && _isJSNative(ctorClass)) {
|
||||
if (ctor.isExternal && hasJSInteropAnnotation(ctorClass)) {
|
||||
return _emitJSInteropNew(ctor, args);
|
||||
}
|
||||
|
||||
|
@ -4607,7 +4610,7 @@ class ProgramCompiler
|
|||
|
||||
JS.Expression _emitJSInteropNew(Member ctor, Arguments args) {
|
||||
var ctorClass = ctor.enclosingClass;
|
||||
if (_isObjectLiteral(ctorClass)) return _emitObjectLiteral(args);
|
||||
if (isJSAnonymousType(ctorClass)) return _emitObjectLiteral(args);
|
||||
return new JS.New(_emitConstructorName(ctorClass.rawType, ctor),
|
||||
_emitArgumentList(args, types: false));
|
||||
}
|
||||
|
@ -4628,12 +4631,6 @@ class ProgramCompiler
|
|||
return _emitType(new InterfaceType(c, typeArgs));
|
||||
}
|
||||
|
||||
bool _isObjectLiteral(Class c) {
|
||||
return _isJSNative(c) && c.annotations.any(isJSAnonymousAnnotation);
|
||||
}
|
||||
|
||||
bool _isJSNative(Class c) => c.annotations.any(isPublicJSAnnotation);
|
||||
|
||||
JS.Expression _emitObjectLiteral(Arguments node) {
|
||||
var args = _emitArgumentList(node);
|
||||
if (args.isEmpty) return js.call('{}');
|
||||
|
|
|
@ -45,7 +45,7 @@ bool isJSAnnotation(Expression value) =>
|
|||
bool isPublicJSAnnotation(Expression value) =>
|
||||
_annotationIsFromJSLibrary('JS', value);
|
||||
|
||||
bool isJSAnonymousAnnotation(Expression value) =>
|
||||
bool _isJSAnonymousAnnotation(Expression value) =>
|
||||
_annotationIsFromJSLibrary('_Anonymous', value);
|
||||
|
||||
bool _isBuiltinAnnotation(
|
||||
|
@ -80,21 +80,37 @@ bool isNativeAnnotation(Expression value) =>
|
|||
_isBuiltinAnnotation(value, '_js_helper', 'Native');
|
||||
|
||||
bool isJSAnonymousType(Class namedClass) {
|
||||
return _isJSNative(namedClass) &&
|
||||
findAnnotation(namedClass, isJSAnonymousAnnotation) != null;
|
||||
return hasJSInteropAnnotation(namedClass) &&
|
||||
findAnnotation(namedClass, _isJSAnonymousAnnotation) != null;
|
||||
}
|
||||
|
||||
// TODO(jmesserly): rename this after port
|
||||
bool isJSElement(NamedNode n) {
|
||||
/// Returns true iff the class has an `@JS(...)` annotation from `package:js`.
|
||||
///
|
||||
/// Note: usually [_usesJSInterop] should be used instead of this.
|
||||
//
|
||||
// TODO(jmesserly): I think almost all uses of this should be replaced with
|
||||
// [_usesJSInterop], which also checks that the library is marked with `@JS`.
|
||||
//
|
||||
// Right now we have inconsistencies: sometimes we'll respect `@JS` on the
|
||||
// class itself, other places we require it on the library. Also members are
|
||||
// inconsistent: sometimes they need to have `@JS` on them, other times they
|
||||
// need to be `external` in an `@JS` class.
|
||||
bool hasJSInteropAnnotation(Class c) => c.annotations.any(isPublicJSAnnotation);
|
||||
|
||||
/// Returns true iff this element is a JS interop member.
|
||||
///
|
||||
/// The element's library must have `@JS(...)` annotation from `package:js`.
|
||||
/// If the element is a class, it must also be marked with `@JS`. Other
|
||||
/// elements, such as class members and top-level functions/accessors, should
|
||||
/// be marked `external`.
|
||||
bool usesJSInterop(NamedNode n) {
|
||||
var library = getLibrary(n);
|
||||
return library != null &&
|
||||
_isJSNative(library) &&
|
||||
(n is Procedure && n.isExternal || n is Class && _isJSNative(n));
|
||||
library.annotations.any(isPublicJSAnnotation) &&
|
||||
(n is Procedure && n.isExternal ||
|
||||
n is Class && n.annotations.any(isPublicJSAnnotation));
|
||||
}
|
||||
|
||||
bool _isJSNative(NamedNode n) =>
|
||||
findAnnotation(n, isPublicJSAnnotation) != null;
|
||||
|
||||
/// Returns the name value of the `JSExportName` annotation (when compiling
|
||||
/// the SDK), or `null` if there's none. This is used to control the name
|
||||
/// under which functions are compiled and exported.
|
||||
|
|
|
@ -92,6 +92,14 @@ bool isBuiltinAnnotation(
|
|||
/// If [node] has annotation matching [test] and the first argument is a
|
||||
/// string, this returns the string value.
|
||||
///
|
||||
/// Calls [findAnnotation] followed by [getNameFromAnnotation].
|
||||
String getAnnotationName(NamedNode node, bool test(Expression value)) {
|
||||
return getNameFromAnnotation(findAnnotation(node, test));
|
||||
}
|
||||
|
||||
/// If [node] is an annotation and the first argument is a string, this returns
|
||||
/// the string value.
|
||||
///
|
||||
/// For example
|
||||
///
|
||||
/// class MyAnnotation {
|
||||
|
@ -103,15 +111,11 @@ bool isBuiltinAnnotation(
|
|||
/// @MyAnnotation('FooBar')
|
||||
/// main() { ... }
|
||||
///
|
||||
/// If we match the annotation for the `@MyAnnotation('FooBar')` this will
|
||||
/// return the string `'FooBar'`.
|
||||
String getAnnotationName(NamedNode node, bool test(Expression value)) {
|
||||
var match = findAnnotation(node, test);
|
||||
if (match is ConstructorInvocation && match.arguments.positional.isNotEmpty) {
|
||||
var first = _followConstFields(match.arguments.positional[0]);
|
||||
if (first is StringLiteral) {
|
||||
return first.value;
|
||||
}
|
||||
/// Given teh node for `@MyAnnotation('FooBar')` this will return `'FooBar'`.
|
||||
String getNameFromAnnotation(ConstructorInvocation node) {
|
||||
if (node != null && node.arguments.positional.isNotEmpty) {
|
||||
var first = _followConstFields(node.arguments.positional[0]);
|
||||
if (first is StringLiteral) return first.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:collection';
|
|||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/kernel.dart';
|
||||
import 'package:kernel/library_index.dart';
|
||||
import 'kernel_helpers.dart' show getNameFromAnnotation;
|
||||
|
||||
/// Contains information about native JS types (those types provided by the
|
||||
/// implementation) that are also provided by the Dart SDK.
|
||||
|
@ -64,18 +65,6 @@ class NativeTypeSet {
|
|||
_addPendingExtensionTypes(sdk.getLibrary('dart:web_sql'));
|
||||
}
|
||||
|
||||
bool _isNative(Class c) {
|
||||
for (var annotation in c.annotations) {
|
||||
if (annotation is ConstructorInvocation) {
|
||||
var c = annotation.target.enclosingClass;
|
||||
if (c.name == 'Native' || c.name == 'JsPeerInterface') {
|
||||
if (c.enclosingLibrary.importUri.scheme == 'dart') return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _addExtensionType(Class c, [bool mustBeNative = false]) {
|
||||
if (c == coreTypes.objectClass) return;
|
||||
if (_extensibleTypes.contains(c) || _nativeTypes.contains(c)) {
|
||||
|
@ -129,4 +118,32 @@ class NativeTypeSet {
|
|||
_processPending(c) && _extensibleTypes.contains(c);
|
||||
|
||||
bool hasNativeSubtype(Class c) => isNativeInterface(c) || isNativeClass(c);
|
||||
|
||||
/// Gets the JS peer for this Dart type if any, otherwise null.
|
||||
///
|
||||
/// For example for dart:_interceptors `JSArray` this will return "Array",
|
||||
/// referring to the JavaScript built-in `Array` type.
|
||||
List<String> getNativePeers(Class c) {
|
||||
if (c == coreTypes.objectClass) return ['Object'];
|
||||
var names = getNameFromAnnotation(_getNativeAnnotation(c));
|
||||
if (names == null) return const [];
|
||||
|
||||
// Omit the special name "!nonleaf" and any future hacks starting with "!"
|
||||
return names.split(',').where((peer) => !peer.startsWith("!")).toList();
|
||||
}
|
||||
}
|
||||
|
||||
bool _isNative(Class c) => _getNativeAnnotation(c) != null;
|
||||
|
||||
ConstructorInvocation _getNativeAnnotation(Class c) {
|
||||
for (var annotation in c.annotations) {
|
||||
if (annotation is ConstructorInvocation) {
|
||||
var c = annotation.target.enclosingClass;
|
||||
if ((c.name == 'Native' || c.name == 'JsPeerInterface') &&
|
||||
c.enclosingLibrary.importUri.scheme == 'dart') {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -93,4 +93,5 @@ main() {
|
|||
|
||||
regress31049();
|
||||
regress31050();
|
||||
regress31117();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue