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