blob: e30010d00103453b7db4de0a2a9263f3c266a236 [file] [log] [blame]
// 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,
Entity,
FieldElement,
FunctionElement,
MemberElement;
import '../native/behavior.dart' show NativeBehavior;
/// 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>{};
/// Cache for [NativeBehavior]s for calling native methods.
Map<FunctionElement, NativeBehavior> nativeMethodBehavior =
<FunctionElement, NativeBehavior>{};
/// Cache for [NativeBehavior]s for reading from native fields.
Map<MemberElement, NativeBehavior> nativeFieldLoadBehavior =
<FieldElement, NativeBehavior>{};
/// Cache for [NativeBehavior]s for writing to native fields.
Map<MemberElement, NativeBehavior> nativeFieldStoreBehavior =
<FieldElement, NativeBehavior>{};
/// Prefix used to escape JS names that are not valid Dart names
/// when using JSInterop.
static const String _jsInteropEscapePrefix = r'JS$';
/// 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' : getUnescapedJSInteropName(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(Entity entity) {
// TODO(johnniwinther): Remove this assignment from [Entity] to [Element]
// when `.declaration` is no longer needed.
Element element = entity;
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');
}
/// Returns the [NativeBehavior] for calling the native [method].
NativeBehavior getNativeMethodBehavior(FunctionElement method) {
assert(invariant(method, nativeMethodBehavior.containsKey(method),
message: "No native method behavior has been computed for $method."));
return nativeMethodBehavior[method];
}
/// Returns the [NativeBehavior] for reading from the native [field].
NativeBehavior getNativeFieldLoadBehavior(FieldElement field) {
assert(invariant(field, nativeFieldLoadBehavior.containsKey(field),
message: "No native field load behavior has been "
"computed for $field."));
return nativeFieldLoadBehavior[field];
}
/// Returns the [NativeBehavior] for writing to the native [field].
NativeBehavior getNativeFieldStoreBehavior(FieldElement field) {
assert(invariant(field, nativeFieldStoreBehavior.containsKey(field),
message: "No native field store behavior has been "
"computed for $field."));
return nativeFieldStoreBehavior[field];
}
/// Registers the [behavior] for calling the native [method].
void setNativeMethodBehavior(
FunctionElement method, NativeBehavior behavior) {
nativeMethodBehavior[method] = behavior;
}
/// Registers the [behavior] for reading from the native [field].
void setNativeFieldLoadBehavior(FieldElement field, NativeBehavior behavior) {
nativeFieldLoadBehavior[field] = behavior;
}
/// Registers the [behavior] for writing to the native [field].
void setNativeFieldStoreBehavior(
FieldElement field, NativeBehavior behavior) {
nativeFieldStoreBehavior[field] = behavior;
}
/// Apply JS$ escaping scheme to convert possible escaped Dart names into
/// JS names.
String getUnescapedJSInteropName(String name) {
return name.startsWith(_jsInteropEscapePrefix)
? name.substring(_jsInteropEscapePrefix.length)
: name;
}
}