blob: f621d9eba0a56e40a8451b500a9f1e82cb33038c [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../common.dart';
import '../common_elements.dart' show CommonElements, KElementEnvironment;
import '../constants/values.dart';
import '../elements/entities.dart';
import '../js_backend/native_data.dart';
import 'behavior.dart';
/// Interface for computing native members.
abstract class NativeMemberResolver {
/// Computes whether [element] is native or JsInterop.
void resolveNativeMember(MemberEntity element);
}
abstract class NativeMemberResolverBase implements NativeMemberResolver {
static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$');
KElementEnvironment get elementEnvironment;
CommonElements get commonElements;
NativeBasicData get nativeBasicData;
NativeDataBuilder get nativeDataBuilder;
bool isJsInteropMember(covariant MemberEntity element);
bool isNativeMethod(covariant FunctionEntity element);
NativeBehavior computeNativeMethodBehavior(covariant FunctionEntity function,
{bool isJsInterop});
NativeBehavior computeNativeFieldLoadBehavior(covariant FieldEntity field,
{bool isJsInterop});
NativeBehavior computeNativeFieldStoreBehavior(covariant FieldEntity field);
@override
void resolveNativeMember(MemberEntity element) {
bool isJsInterop = isJsInteropMember(element);
if (element.isFunction ||
element.isConstructor ||
element.isGetter ||
element.isSetter) {
FunctionEntity method = element;
bool isNative = _processMethodAnnotations(method);
if (isNative || isJsInterop) {
NativeBehavior behavior =
computeNativeMethodBehavior(method, isJsInterop: isJsInterop);
nativeDataBuilder.setNativeMethodBehavior(method, behavior);
}
} else if (element.isField) {
FieldEntity field = element;
bool isNative = _processFieldAnnotations(field);
if (isNative || isJsInterop) {
NativeBehavior fieldLoadBehavior =
computeNativeFieldLoadBehavior(field, isJsInterop: isJsInterop);
NativeBehavior fieldStoreBehavior =
computeNativeFieldStoreBehavior(field);
nativeDataBuilder.setNativeFieldLoadBehavior(field, fieldLoadBehavior);
nativeDataBuilder.setNativeFieldStoreBehavior(
field, fieldStoreBehavior);
}
}
}
/// Process the potentially native [field]. Adds information from metadata
/// attributes. Returns `true` of [method] is native.
bool _processFieldAnnotations(covariant FieldEntity element) {
if (element.isInstanceMember &&
nativeBasicData.isNativeClass(element.enclosingClass)) {
// Exclude non-instance (static) fields - they are not really native and
// are compiled as isolate globals. Access of a property of a constructor
// function or a non-method property in the prototype chain, must be coded
// using a JS-call.
_setNativeName(element);
return true;
} else {
String name = _findJsNameFromAnnotation(element);
if (name != null) {
failedAt(element,
'@JSName(...) annotation is not supported for static fields.');
}
}
return false;
}
/// Process the potentially native [method]. Adds information from metadata
/// attributes. Returns `true` of [method] is native.
bool _processMethodAnnotations(covariant FunctionEntity method) {
if (isNativeMethod(method)) {
if (method.isStatic) {
_setNativeNameForStaticMethod(method);
} else {
_setNativeName(method);
}
return true;
}
return false;
}
/// Sets the native name of [element], either from an annotation, or
/// defaulting to the Dart name.
void _setNativeName(MemberEntity element) {
String name = _findJsNameFromAnnotation(element);
if (name == null) name = element.name;
nativeDataBuilder.setNativeMemberName(element, name);
}
/// Sets the native name of the static native method [element], using the
/// following rules:
/// 1. If [element] has a @JSName annotation that is an identifier, qualify
/// that identifier to the @Native name of the enclosing class
/// 2. If [element] has a @JSName annotation that is not an identifier,
/// use the declared @JSName as the expression
/// 3. If [element] does not have a @JSName annotation, qualify the name of
/// the method with the @Native name of the enclosing class.
void _setNativeNameForStaticMethod(FunctionEntity element) {
String name = _findJsNameFromAnnotation(element);
if (name == null) name = element.name;
if (_isIdentifier(name)) {
List<String> nativeNames =
nativeBasicData.getNativeTagsOfClass(element.enclosingClass);
if (nativeNames.length != 1) {
failedAt(
element,
'Unable to determine a native name for the enclosing class, '
'options: $nativeNames');
}
nativeDataBuilder.setNativeMemberName(element, '${nativeNames[0]}.$name');
} else {
nativeDataBuilder.setNativeMemberName(element, name);
}
}
bool _isIdentifier(String s) => _identifier.hasMatch(s);
/// Returns the JSName annotation string or `null` if no JSName annotation is
/// present.
String _findJsNameFromAnnotation(MemberEntity element) {
String jsName = null;
for (ConstantValue value in elementEnvironment.getMemberMetadata(element)) {
String name = readAnnotationName(
element, value, commonElements.annotationJSNameClass);
if (jsName == null) {
jsName = name;
} else if (name != null) {
failedAt(element, 'Too many JSName annotations: ${value.toDartText()}');
}
}
return jsName;
}
}
/// Determines all native classes in a set of libraries.
abstract class NativeClassFinder {
/// Returns the set of all native classes declared in [libraries].
Iterable<ClassEntity> computeNativeClasses(Iterable<Uri> libraries);
}
class BaseNativeClassFinder implements NativeClassFinder {
final KElementEnvironment _elementEnvironment;
final NativeBasicData _nativeBasicData;
Map<String, ClassEntity> _tagOwner = new Map<String, ClassEntity>();
BaseNativeClassFinder(this._elementEnvironment, this._nativeBasicData);
Iterable<ClassEntity> computeNativeClasses(Iterable<Uri> libraries) {
Set<ClassEntity> nativeClasses = new Set<ClassEntity>();
libraries.forEach((uri) => _processNativeClassesInLibrary(
_elementEnvironment.lookupLibrary(uri), nativeClasses));
_processSubclassesOfNativeClasses(libraries, nativeClasses);
return nativeClasses;
}
/// Adds all directly native classes declared in [library] to [nativeClasses].
void _processNativeClassesInLibrary(
LibraryEntity library, Set<ClassEntity> nativeClasses) {
_elementEnvironment.forEachClass(library, (ClassEntity cls) {
if (_nativeBasicData.isNativeClass(cls)) {
_processNativeClass(cls, nativeClasses);
}
});
}
/// Adds [cls] to [nativeClasses] and performs further processing of [cls],
/// if necessary.
void _processNativeClass(
covariant ClassEntity cls, Set<ClassEntity> nativeClasses) {
nativeClasses.add(cls);
// Js Interop interfaces do not have tags.
if (_nativeBasicData.isJsInteropClass(cls)) return;
// Since we map from dispatch tags to classes, a dispatch tag must be used
// on only one native class.
for (String tag in _nativeBasicData.getNativeTagsOfClass(cls)) {
ClassEntity owner = _tagOwner[tag];
if (owner != null) {
if (owner != cls) {
failedAt(cls, "Tag '$tag' already in use by '${owner.name}'");
}
} else {
_tagOwner[tag] = cls;
}
}
}
/// Returns the name of the super class of [cls] or `null` of [cls] has
/// no explicit superclass.
String _findExtendsNameOfClass(covariant ClassEntity cls) {
return _elementEnvironment
.getSuperClass(cls, skipUnnamedMixinApplications: true)
?.name;
}
/// Adds all subclasses of [nativeClasses] found in [libraries] to
/// [nativeClasses].
void _processSubclassesOfNativeClasses(
Iterable<Uri> libraries, Set<ClassEntity> nativeClasses) {
Set<ClassEntity> nativeClassesAndSubclasses = new Set<ClassEntity>();
// Collect potential subclasses, e.g.
//
// class B extends foo.A {}
//
// String "A" has a potential subclass B.
Map<String, Set<ClassEntity>> potentialExtends =
<String, Set<ClassEntity>>{};
libraries.forEach((Uri uri) {
LibraryEntity library = _elementEnvironment.lookupLibrary(uri);
_elementEnvironment.forEachClass(library, (ClassEntity cls) {
String extendsName = _findExtendsNameOfClass(cls);
if (extendsName != null) {
Set<ClassEntity> potentialSubclasses = potentialExtends.putIfAbsent(
extendsName, () => new Set<ClassEntity>());
potentialSubclasses.add(cls);
}
});
});
// Resolve all the native classes and any classes that might extend them in
// [potentialExtends], and then check that the properly resolved class is in
// fact a subclass of a native class.
ClassEntity nativeSuperclassOf(ClassEntity cls) {
if (_nativeBasicData.isNativeClass(cls)) return cls;
ClassEntity superclass = _elementEnvironment.getSuperClass(cls);
if (superclass == null) return null;
return nativeSuperclassOf(superclass);
}
void walkPotentialSubclasses(ClassEntity element) {
if (nativeClassesAndSubclasses.contains(element)) return;
ClassEntity nativeSuperclass = nativeSuperclassOf(element);
if (nativeSuperclass != null) {
nativeClassesAndSubclasses.add(element);
Set<ClassEntity> potentialSubclasses = potentialExtends[element.name];
if (potentialSubclasses != null) {
potentialSubclasses.forEach(walkPotentialSubclasses);
}
}
}
nativeClasses.forEach(walkPotentialSubclasses);
nativeClasses.addAll(nativeClassesAndSubclasses);
}
}
/// Returns `true` if [value] is named annotation based on [annotationClass].
bool isAnnotation(
Spannable spannable, ConstantValue value, ClassEntity annotationClass) {
if (!value.isConstructedObject) return null;
ConstructedConstantValue constructedObject = value;
return constructedObject.type.element == annotationClass;
}
/// Extracts the name if [value] is a named annotation based on
/// [annotationClass], otherwise returns `null`.
String readAnnotationName(
Spannable spannable, ConstantValue value, ClassEntity annotationClass,
{String defaultValue}) {
if (!value.isConstructedObject) return null;
ConstructedConstantValue constructedObject = value;
if (constructedObject.type.element != annotationClass) return null;
Iterable<ConstantValue> fields = constructedObject.fields.values;
// TODO(sra): Better validation of the constant.
if (fields.length != 1) {
failedAt(
spannable, 'Annotations needs one string: ${value.toStructuredText()}');
return null;
} else if (fields.single is StringConstantValue) {
StringConstantValue specStringConstant = fields.single;
return specStringConstant.stringValue;
} else if (defaultValue != null && fields.single is NullConstantValue) {
return defaultValue;
} else {
failedAt(
spannable, 'Annotations needs one string: ${value.toStructuredText()}');
return null;
}
}