blob: fc20c85600a9b8e113518f365a33b56f368705d9 [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 'package:front_end/src/fasta/scanner.dart' show StringToken, Token;
import '../common.dart';
import '../common/backend_api.dart';
import '../compiler.dart' show Compiler;
import '../constants/values.dart';
import '../elements/elements.dart'
show
ClassElement,
Element,
FieldElement,
LibraryElement,
MemberElement,
MetadataAnnotation,
MethodElement;
import '../elements/modelx.dart' show FunctionElementX, MetadataAnnotationX;
import '../elements/resolution_types.dart' show ResolutionDartType;
import '../js_backend/js_backend.dart';
import '../js_backend/native_data.dart';
import '../patch_parser.dart';
import '../tree/tree.dart';
import 'behavior.dart';
abstract class NativeDataResolver {
/// Returns `true` if [element] is a JsInterop member.
bool isJsInteropMember(MemberElement element);
/// Computes whether [element] is native or JsInterop and, if so, registers
/// its [NativeBehavior]s to [registry].
void resolveNativeMember(MemberElement element, NativeRegistry registry);
}
class NativeDataResolverImpl implements NativeDataResolver {
static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$');
final Compiler _compiler;
NativeDataResolverImpl(this._compiler);
JavaScriptBackend get _backend => _compiler.backend;
DiagnosticReporter get _reporter => _compiler.reporter;
NativeBasicData get _nativeBaseData => _backend.nativeBaseData;
NativeDataBuilder get _nativeDataBuilder => _backend.nativeDataBuilder;
@override
bool isJsInteropMember(MemberElement element) {
// TODO(johnniwinther): Avoid computing this twice for external function;
// once from JavaScriptBackendTarget.resolveExternalFunction and once
// through JavaScriptBackendTarget.resolveNativeMember.
bool isJsInterop =
checkJsInteropMemberAnnotations(_compiler, element, _nativeDataBuilder);
// TODO(johnniwinther): Avoid this duplication of logic from
// NativeData.isJsInterop.
if (!isJsInterop && element is MethodElement && element.isExternal) {
if (element.enclosingClass != null) {
isJsInterop = _nativeBaseData.isJsInteropClass(element.enclosingClass);
} else {
isJsInterop = _nativeBaseData.isJsInteropLibrary(element.library);
}
}
return isJsInterop;
}
void resolveNativeMember(MemberElement element, NativeRegistry registry) {
bool isJsInterop = isJsInteropMember(element);
if (element.isFunction ||
element.isConstructor ||
element.isGetter ||
element.isSetter) {
MethodElement method = element;
bool isNative = _processMethodAnnotations(method);
if (isNative || isJsInterop) {
NativeBehavior behavior = NativeBehavior
.ofMethodElement(method, _compiler, isJsInterop: isJsInterop);
_nativeDataBuilder.setNativeMethodBehavior(method, behavior);
registry.registerNativeData(behavior);
}
} else if (element.isField) {
FieldElement field = element;
bool isNative = _processFieldAnnotations(field);
if (isNative || isJsInterop) {
NativeBehavior fieldLoadBehavior = NativeBehavior
.ofFieldElementLoad(field, _compiler, isJsInterop: isJsInterop);
NativeBehavior fieldStoreBehavior =
NativeBehavior.ofFieldElementStore(field, _compiler);
_nativeDataBuilder.setNativeFieldLoadBehavior(field, fieldLoadBehavior);
_nativeDataBuilder.setNativeFieldStoreBehavior(
field, fieldStoreBehavior);
// TODO(sra): Process fields for storing separately.
// We have to handle both loading and storing to the field because we
// only get one look at each member and there might be a load or store
// we have not seen yet.
registry.registerNativeData(fieldLoadBehavior);
registry.registerNativeData(fieldStoreBehavior);
}
}
}
/// Process the potentially native [field]. Adds information from metadata
/// attributes. Returns `true` of [method] is native.
bool _processFieldAnnotations(Element element) {
if (_compiler.serialization.isDeserialized(element)) {
return false;
}
if (element.isInstanceMember &&
_backend.nativeBaseData.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;
}
return false;
}
/// Process the potentially native [method]. Adds information from metadata
/// attributes. Returns `true` of [method] is native.
bool _processMethodAnnotations(Element method) {
if (_compiler.serialization.isDeserialized(method)) {
return false;
}
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(MemberElement 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(MethodElement element) {
String name = _findJsNameFromAnnotation(element);
if (name == null) name = element.name;
if (_isIdentifier(name)) {
List<String> nativeNames =
_nativeBaseData.getNativeTagsOfClass(element.enclosingClass);
if (nativeNames.length != 1) {
_reporter.internalError(
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);
bool _isNativeMethod(FunctionElementX element) {
if (!_backend.canLibraryUseNative(element.library)) return false;
// Native method?
return _reporter.withCurrentElement(element, () {
Node node = element.parseNode(_compiler.resolution.parsingContext);
if (node is! FunctionExpression) return false;
FunctionExpression functionExpression = node;
node = functionExpression.body;
Token token = node.getBeginToken();
if (identical(token.stringValue, 'native')) return true;
return false;
});
}
/// Returns the JSName annotation string or `null` if no JSName annotation is
/// present.
String _findJsNameFromAnnotation(Element element) {
String name = null;
ClassElement annotationClass = _backend.helpers.annotationJSNameClass;
for (MetadataAnnotation annotation in element.implementation.metadata) {
annotation.ensureResolved(_compiler.resolution);
ConstantValue value =
_compiler.constants.getConstantValue(annotation.constant);
if (!value.isConstructedObject) continue;
ConstructedConstantValue constructedObject = value;
if (constructedObject.type.element != annotationClass) continue;
Iterable<ConstantValue> fields = constructedObject.fields.values;
// TODO(sra): Better validation of the constant.
if (fields.length != 1 || fields.single is! StringConstantValue) {
_reporter.internalError(
annotation, 'Annotations needs one string: ${annotation}');
}
StringConstantValue specStringConstant = fields.single;
String specString = specStringConstant.toDartString().slowToString();
if (name == null) {
name = specString;
} else {
_reporter.internalError(
annotation, 'Too many JSName annotations: ${annotation}');
}
}
return name;
}
}
/// Check whether [cls] has a `@Native(...)` annotation, and if so, set its
/// native name from the annotation.
checkNativeAnnotation(Compiler compiler, ClassElement cls,
NativeBasicDataBuilder nativeBaseDataBuilder) {
EagerAnnotationHandler.checkAnnotation(
compiler, cls, new NativeAnnotationHandler(nativeBaseDataBuilder));
}
/// Annotation handler for pre-resolution detection of `@Native(...)`
/// annotations.
class NativeAnnotationHandler extends EagerAnnotationHandler<String> {
final NativeBasicDataBuilder _nativeBaseDataBuilder;
NativeAnnotationHandler(this._nativeBaseDataBuilder);
String getNativeAnnotation(MetadataAnnotationX annotation) {
if (annotation.beginToken != null &&
annotation.beginToken.next.lexeme == 'Native') {
// Skipping '@', 'Native', and '('.
Token argument = annotation.beginToken.next.next.next;
if (argument is StringToken) {
return argument.lexeme;
}
}
return null;
}
String apply(
Compiler compiler, Element element, MetadataAnnotation annotation) {
if (element.isClass) {
ClassElement cls = element;
String native = getNativeAnnotation(annotation);
if (native != null) {
_nativeBaseDataBuilder.setNativeClassTagInfo(cls, native);
return native;
}
}
return null;
}
void validate(Compiler compiler, Element element,
MetadataAnnotation annotation, ConstantValue constant) {
ResolutionDartType annotationType =
constant.getType(compiler.commonElements);
if (annotationType.element !=
compiler.backend.helpers.nativeAnnotationClass) {
DiagnosticReporter reporter = compiler.reporter;
reporter.internalError(annotation, 'Invalid @Native(...) annotation.');
}
}
}
void checkJsInteropClassAnnotations(Compiler compiler, LibraryElement library,
NativeBasicDataBuilder nativeBaseDataBuilder) {
bool checkJsInteropAnnotation(Element element) {
return EagerAnnotationHandler.checkAnnotation(
compiler, element, const JsInteropAnnotationHandler());
}
if (checkJsInteropAnnotation(library)) {
nativeBaseDataBuilder.markAsJsInteropLibrary(library);
}
library.forEachLocalMember((Element element) {
if (element.isClass) {
ClassElement cls = element;
if (checkJsInteropAnnotation(element)) {
nativeBaseDataBuilder.markAsJsInteropClass(cls);
}
}
});
}
bool checkJsInteropMemberAnnotations(Compiler compiler, MemberElement element,
NativeDataBuilder nativeDataBuilder) {
bool isJsInterop = EagerAnnotationHandler.checkAnnotation(
compiler, element, const JsInteropAnnotationHandler());
if (isJsInterop) {
nativeDataBuilder.markAsJsInteropMember(element);
}
return isJsInterop;
}
/// Annotation handler for pre-resolution detection of `@JS(...)`
/// annotations.
class JsInteropAnnotationHandler implements EagerAnnotationHandler<bool> {
const JsInteropAnnotationHandler();
bool hasJsNameAnnotation(MetadataAnnotationX annotation) =>
annotation.beginToken != null &&
annotation.beginToken.next.lexeme == 'JS';
bool apply(
Compiler compiler, Element element, MetadataAnnotation annotation) {
return hasJsNameAnnotation(annotation);
}
@override
void validate(Compiler compiler, Element element,
MetadataAnnotation annotation, ConstantValue constant) {
JavaScriptBackend backend = compiler.backend;
ResolutionDartType type = constant.getType(compiler.commonElements);
if (type.element != backend.helpers.jsAnnotationClass) {
compiler.reporter
.internalError(annotation, 'Invalid @JS(...) annotation.');
}
}
bool get defaultResult => false;
}