| Index: pkg/compiler/lib/src/native/resolver.dart
|
| diff --git a/pkg/compiler/lib/src/native/resolver.dart b/pkg/compiler/lib/src/native/resolver.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b4da28b51b0f736e3fb92a2bd9e886279ed1ff5a
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/native/resolver.dart
|
| @@ -0,0 +1,325 @@
|
| +// 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,
|
| + 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;
|
| + NativeClassData get _nativeClassData => _backend.nativeClassData;
|
| + 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 = _nativeClassData.isJsInteropClass(element.enclosingClass);
|
| + } else {
|
| + isJsInterop = _nativeClassData.isJsInteropLibrary(element.library);
|
| + }
|
| + }
|
| + return isJsInterop;
|
| + }
|
| +
|
| + void resolveNativeMember(MemberElement element, NativeRegistry registry) {
|
| + bool isJsInterop = isJsInteropMember(element);
|
| + if (element.isFunction ||
|
| + element.isConstructor ||
|
| + element.isGetter ||
|
| + element.isSetter) {
|
| + bool isNative = _processMethodAnnotations(element);
|
| + if (isNative || isJsInterop) {
|
| + NativeBehavior behavior = NativeBehavior
|
| + .ofMethodElement(element, _compiler, isJsInterop: isJsInterop);
|
| + _nativeDataBuilder.setNativeMethodBehavior(element, behavior);
|
| + registry.registerNativeData(behavior);
|
| + }
|
| + } else if (element.isField) {
|
| + bool isNative = _processFieldAnnotations(element);
|
| + if (isNative || isJsInterop) {
|
| + NativeBehavior fieldLoadBehavior = NativeBehavior
|
| + .ofFieldElementLoad(element, _compiler, isJsInterop: isJsInterop);
|
| + NativeBehavior fieldStoreBehavior =
|
| + NativeBehavior.ofFieldElementStore(element, _compiler);
|
| + _nativeDataBuilder.setNativeFieldLoadBehavior(
|
| + element, fieldLoadBehavior);
|
| + _nativeDataBuilder.setNativeFieldStoreBehavior(
|
| + element, 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.nativeClassData.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 =
|
| + _nativeDataBuilder.getNativeTagsOfClassRaw(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,
|
| + NativeClassDataBuilder nativeClassDataBuilder) {
|
| + EagerAnnotationHandler.checkAnnotation(
|
| + compiler, cls, new NativeAnnotationHandler(nativeClassDataBuilder));
|
| +}
|
| +
|
| +/// Annotation handler for pre-resolution detection of `@Native(...)`
|
| +/// annotations.
|
| +class NativeAnnotationHandler extends EagerAnnotationHandler<String> {
|
| + final NativeClassDataBuilder _nativeClassDataBuilder;
|
| +
|
| + NativeAnnotationHandler(this._nativeClassDataBuilder);
|
| +
|
| + String getNativeAnnotation(MetadataAnnotationX annotation) {
|
| + if (annotation.beginToken != null &&
|
| + annotation.beginToken.next.value == 'Native') {
|
| + // Skipping '@', 'Native', and '('.
|
| + Token argument = annotation.beginToken.next.next.next;
|
| + if (argument is StringToken) {
|
| + return argument.value;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + String apply(
|
| + Compiler compiler, Element element, MetadataAnnotation annotation) {
|
| + if (element.isClass) {
|
| + String native = getNativeAnnotation(annotation);
|
| + if (native != null) {
|
| + _nativeClassDataBuilder.setNativeClassTagInfo(element, 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,
|
| + NativeClassDataBuilder nativeClassDataBuilder) {
|
| + bool checkJsInteropAnnotation(Element element) {
|
| + return EagerAnnotationHandler.checkAnnotation(
|
| + compiler, element, const JsInteropAnnotationHandler());
|
| + }
|
| +
|
| + if (checkJsInteropAnnotation(library)) {
|
| + nativeClassDataBuilder.markAsJsInteropLibrary(library);
|
| + }
|
| + library.forEachLocalMember((Element element) {
|
| + if (element.isClass) {
|
| + if (checkJsInteropAnnotation(element)) {
|
| + nativeClassDataBuilder.markAsJsInteropClass(element);
|
| + }
|
| + }
|
| + });
|
| +}
|
| +
|
| +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.value == '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;
|
| +}
|
|
|