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:
Jenny Messerly 2018-03-20 00:49:58 +00:00 committed by commit-bot@chromium.org
parent ac322d0fb4
commit d9878ae0da
9 changed files with 250 additions and 180 deletions

View file

@ -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) {

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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');

View file

@ -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('{}');

View file

@ -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.

View file

@ -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;
}

View file

@ -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;
}

View file

@ -93,4 +93,5 @@ main() {
regress31049();
regress31050();
regress31117();
}