| // Copyright (c) 2016, 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. |
| |
| library js_backend.native_data; |
| |
| import '../common.dart'; |
| import '../elements/elements.dart' |
| show ClassElement, Element, FunctionElement, MemberElement; |
| |
| /// Additional element information for native classes and methods and js-interop |
| /// methods. |
| class NativeData { |
| /// The JavaScript names for elements implemented via typed JavaScript |
| /// interop. |
| Map<Element, String> jsInteropNames = <Element, String>{}; |
| |
| /// The JavaScript names for native JavaScript elements implemented. |
| Map<Element, String> nativeMemberName = <Element, String>{}; |
| |
| /// Tag info for native JavaScript classes names. See |
| /// [setNativeClassTagInfo]. |
| Map<ClassElement, String> nativeClassTagInfo = <ClassElement, String>{}; |
| |
| /// Returns `true` if [element] is explicitly marked as part of JsInterop. |
| bool _isJsInterop(Element element) { |
| return jsInteropNames.containsKey(element.declaration); |
| } |
| |
| /// Returns [element] as an explicit part of JsInterop. The js interop name is |
| /// expected to be computed later. |
| void markAsJsInterop(Element element) { |
| jsInteropNames[element.declaration] = null; |
| } |
| |
| /// Sets the explicit js interop [name] for [element]. |
| void setJsInteropName(Element element, String name) { |
| assert(invariant(element, isJsInterop(element), |
| message: |
| 'Element $element is not js interop but given a js interop name.')); |
| jsInteropNames[element.declaration] = name; |
| } |
| |
| /// Returns the explicit js interop name for [element]. |
| String getJsInteropName(Element element) { |
| return jsInteropNames[element.declaration]; |
| } |
| |
| /// Returns `true` if [element] is part of JsInterop. |
| bool isJsInterop(Element element) { |
| // An function is part of JsInterop in the following cases: |
| // * It has a jsInteropName annotation |
| // * It is external member of a class or library tagged as JsInterop. |
| if (element.isFunction || element.isConstructor || element.isAccessor) { |
| FunctionElement function = element; |
| if (!function.isExternal) return false; |
| |
| if (_isJsInterop(function)) return true; |
| if (function.isClassMember) return isJsInterop(function.contextClass); |
| if (function.isTopLevel) return isJsInterop(function.library); |
| return false; |
| } else { |
| return _isJsInterop(element); |
| } |
| } |
| |
| /// Returns `true` if the name of [element] is fixed for the generated |
| /// JavaScript. |
| bool hasFixedBackendName(Element element) { |
| return isJsInterop(element) || |
| nativeMemberName.containsKey(element.declaration); |
| } |
| |
| String _jsNameHelper(Element element) { |
| String jsInteropName = jsInteropNames[element.declaration]; |
| assert(invariant(element, !(_isJsInterop(element) && jsInteropName == null), |
| message: |
| 'Element $element is js interop but js interop name has not yet ' |
| 'been computed.')); |
| if (jsInteropName != null && jsInteropName.isNotEmpty) { |
| return jsInteropName; |
| } |
| return element.isLibrary ? 'self' : element.name; |
| } |
| |
| /// Computes the name for [element] to use in the generated JavaScript. This |
| /// is either given through a native annotation or a js interop annotation. |
| String getFixedBackendName(Element element) { |
| String name = nativeMemberName[element.declaration]; |
| if (name == null && isJsInterop(element)) { |
| // If an element isJsInterop but _isJsInterop is false that means it is |
| // considered interop as the parent class is interop. |
| name = _jsNameHelper( |
| element.isConstructor ? element.enclosingClass : element); |
| nativeMemberName[element.declaration] = name; |
| } |
| return name; |
| } |
| |
| /// Whether [element] corresponds to a native JavaScript construct either |
| /// through the native mechanism (`@Native(...)` or the `native` pseudo |
| /// keyword) which is only allowed for internal libraries or via the typed |
| /// JavaScriptInterop mechanism which is allowed for user libraries. |
| bool isNative(Element element) { |
| if (isJsInterop(element)) return true; |
| if (element.isClass) { |
| return nativeClassTagInfo.containsKey(element.declaration); |
| } else { |
| return nativeMemberName.containsKey(element.declaration); |
| } |
| } |
| |
| /// Sets the native [name] for the member [element]. This name is used for |
| /// [element] in the generated JavaScript. |
| void setNativeMemberName(MemberElement element, String name) { |
| // TODO(johnniwinther): Avoid setting this more than once. The enqueuer |
| // might enqueue [element] several times (before processing it) and computes |
| // name on each call to `internalAddToWorkList`. |
| assert(invariant( |
| element, |
| nativeMemberName[element.declaration] == null || |
| nativeMemberName[element.declaration] == name, |
| message: "Native member name set inconsistently on $element: " |
| "Existing name '${nativeMemberName[element.declaration]}', " |
| "new name '$name'.")); |
| nativeMemberName[element.declaration] = name; |
| } |
| |
| /// Sets the native tag info for [cls]. |
| /// |
| /// The tag info string contains comma-separated 'words' which are either |
| /// dispatch tags (having JavaScript identifier syntax) and directives that |
| /// begin with `!`. |
| void setNativeClassTagInfo(ClassElement cls, String tagInfo) { |
| // TODO(johnniwinther): Assert that this is only called once. The memory |
| // compiler copies pre-processed elements into a new compiler through |
| // [Compiler.onLibraryScanned] and thereby causes multiple calls to this |
| // method. |
| assert(invariant( |
| cls, |
| nativeClassTagInfo[cls.declaration] == null || |
| nativeClassTagInfo[cls.declaration] == tagInfo, |
| message: "Native tag info set inconsistently on $cls: " |
| "Existing tag info '${nativeClassTagInfo[cls.declaration]}', " |
| "new tag info '$tagInfo'.")); |
| nativeClassTagInfo[cls.declaration] = tagInfo; |
| } |
| |
| /// Returns the list of native tag words for [cls]. |
| List<String> getNativeTagsOfClassRaw(ClassElement cls) { |
| String quotedName = nativeClassTagInfo[cls.declaration]; |
| return quotedName.substring(1, quotedName.length - 1).split(','); |
| } |
| |
| /// Returns the list of non-directive native tag words for [cls]. |
| List<String> getNativeTagsOfClass(ClassElement cls) { |
| return getNativeTagsOfClassRaw(cls) |
| .where((s) => !s.startsWith('!')) |
| .toList(); |
| } |
| |
| /// Returns `true` if [cls] has a `!nonleaf` tag word. |
| bool hasNativeTagsForcedNonLeaf(ClassElement cls) { |
| return getNativeTagsOfClassRaw(cls).contains('!nonleaf'); |
| } |
| } |