blob: ae07571d317af62149549a668001d9602c232013 [file] [log] [blame]
// Copyright (c) 2019, 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:kernel/ast.dart' as ir;
import '../common/names.dart';
class IrAnnotationData {
Map<ir.Class, String> _nativeClassNames = {};
Set<ir.Member> _nativeMembers = {};
Map<ir.Member, String> _nativeMemberNames = {};
Map<ir.Member, List<String>> _createsAnnotations = {};
Map<ir.Member, List<String>> _returnsAnnotations = {};
Map<ir.Library, String> _jsInteropLibraryNames = {};
Map<ir.Class, String> _jsInteropClassNames = {};
Set<ir.Class> _anonymousJsInteropClasses = {};
Map<ir.Member, String> _jsInteropMemberNames = {};
Map<ir.Member, List<PragmaAnnotationData>> _memberPragmaAnnotations = {};
// Returns the text from the `@Native(<text>)` annotation of [node], if any.
String getNativeClassName(ir.Class node) => _nativeClassNames[node];
// Returns `true` if [node] has a native body, as in `method() native;`.
bool hasNativeBody(ir.Member node) => _nativeMembers.contains(node);
// Returns the text from the `@JSName(<text>)` annotation of [node], if any.
String getNativeMemberName(ir.Member node) => _nativeMemberNames[node];
// Returns a list of the text from the `@Creates(<text>)` annotation of
// [node].
List<String> getCreatesAnnotations(ir.Member node) =>
_createsAnnotations[node] ?? const [];
// Returns a list of the text from the `@Returns(<text>)` annotation of
// [node].
List<String> getReturnsAnnotations(ir.Member node) =>
_returnsAnnotations[node] ?? const [];
// Returns the text from the `@JS(<text>)` annotation of [node], if any.
String getJsInteropLibraryName(ir.Library node) =>
_jsInteropLibraryNames[node];
// Returns the text from the `@JS(<text>)` annotation of [node], if any.
String getJsInteropClassName(ir.Class node) => _jsInteropClassNames[node];
// Returns `true` if [node] is annotated with `@anonymous`.
bool isAnonymousJsInteropClass(ir.Class node) =>
_anonymousJsInteropClasses.contains(node);
// Returns the text from the `@JS(<text>)` annotation of [node], if any.
String getJsInteropMemberName(ir.Member node) => _jsInteropMemberNames[node];
// Returns a list of the `@pragma('dart2js:<suffix>')` annotations on [node].
List<PragmaAnnotationData> getMemberPragmaAnnotationData(ir.Member node) =>
_memberPragmaAnnotations[node] ?? const [];
void forEachNativeClass(void Function(ir.Class, String) f) {
_nativeClassNames.forEach(f);
}
void forEachJsInteropLibrary(void Function(ir.Library, String) f) {
_jsInteropLibraryNames.forEach(f);
}
void forEachJsInteropClass(
void Function(ir.Class, String, {bool isAnonymous}) f) {
_jsInteropClassNames.forEach((ir.Class node, String name) {
f(node, name, isAnonymous: isAnonymousJsInteropClass(node));
});
}
void forEachJsInteropMember(void Function(ir.Member, String) f) {
_jsInteropLibraryNames.forEach((ir.Library library, _) {
for (ir.Member member in library.members) {
if (member.isExternal) {
f(member, _jsInteropMemberNames[member] ?? member.name.name);
}
}
});
_jsInteropClassNames.forEach((ir.Class cls, _) {
for (ir.Member member in cls.members) {
if (member is ir.Field) continue;
String name = _jsInteropMemberNames[member];
if (member.isExternal) {
name ??= member.name.name;
}
f(member, name);
}
});
}
void forEachNativeMethodData(
void Function(ir.Member, String name, Iterable<String> createsAnnotations,
Iterable<String> returnsAnnotations)
f) {
for (ir.Member node in _nativeMembers) {
if (node is! ir.Field) {
String name = _nativeMemberNames[node] ?? node.name.name;
f(node, name, getCreatesAnnotations(node), getReturnsAnnotations(node));
}
}
}
void forEachNativeFieldData(
void Function(ir.Member, String name, Iterable<String> createsAnnotations,
Iterable<String> returnsAnnotations)
f) {
for (ir.Class cls in _nativeClassNames.keys) {
for (ir.Field field in cls.fields) {
if (field.isInstanceMember) {
String name = _nativeMemberNames[field] ?? field.name.name;
f(field, name, getCreatesAnnotations(field),
getReturnsAnnotations(field));
}
}
}
}
}
IrAnnotationData processAnnotations(ir.Component component) {
IrAnnotationData data = new IrAnnotationData();
void processMember(ir.Member member) {
List<PragmaAnnotationData> pragmaAnnotations;
List<String> createsAnnotations;
List<String> returnsAnnotations;
for (ir.Expression annotation in member.annotations) {
if (annotation is ir.ConstantExpression) {
ir.Constant constant = annotation.constant;
String jsName = _getJsInteropName(constant);
if (jsName != null) {
data._jsInteropMemberNames[member] = jsName;
}
bool isNativeMember = _isNativeMember(constant);
if (isNativeMember) {
data._nativeMembers.add(member);
}
String nativeName = _getNativeMemberName(constant);
if (nativeName != null) {
data._nativeMemberNames[member] = nativeName;
}
String creates = _getCreatesAnnotation(constant);
if (creates != null) {
if (createsAnnotations == null) {
data._createsAnnotations[member] = createsAnnotations = [];
}
createsAnnotations.add(creates);
}
String returns = _getReturnsAnnotation(constant);
if (returns != null) {
if (returnsAnnotations == null) {
data._returnsAnnotations[member] = returnsAnnotations = [];
}
returnsAnnotations.add(returns);
}
PragmaAnnotationData pragmaAnnotation = _getPragmaAnnotation(constant);
if (pragmaAnnotation != null) {
if (pragmaAnnotations == null) {
data._memberPragmaAnnotations[member] = pragmaAnnotations = [];
}
pragmaAnnotations.add(pragmaAnnotation);
}
}
}
}
for (ir.Library library in component.libraries) {
for (ir.Expression annotation in library.annotations) {
if (annotation is ir.ConstantExpression) {
ir.Constant constant = annotation.constant;
String jsName = _getJsInteropName(constant);
if (jsName != null) {
data._jsInteropLibraryNames[library] = jsName;
}
}
}
for (ir.Class cls in library.classes) {
for (ir.Expression annotation in cls.annotations) {
if (annotation is ir.ConstantExpression) {
ir.Constant constant = annotation.constant;
String nativeClassName = _getNativeClassName(constant);
if (nativeClassName != null) {
data._nativeClassNames[cls] = nativeClassName;
}
String jsName = _getJsInteropName(constant);
if (jsName != null) {
data._jsInteropClassNames[cls] = jsName;
}
bool isAnonymousJsInteropClass = _isAnonymousJsInterop(constant);
if (isAnonymousJsInteropClass) {
data._anonymousJsInteropClasses.add(cls);
}
}
}
for (ir.Member member in cls.members) {
processMember(member);
}
}
for (ir.Member member in library.members) {
processMember(member);
}
}
return data;
}
String _getNativeClassName(ir.Constant constant) {
if (constant is ir.InstanceConstant) {
// TODO(johnniwinther): Add an IrCommonElements for these queries; i.e.
// `commonElements.isNativeAnnotationClass(constant.classNode)`.
if (constant.classNode.name == 'Native' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
if (constant.fieldValues.length == 1) {
ir.Constant fieldValue = constant.fieldValues.values.single;
String name;
if (fieldValue is ir.StringConstant) {
name = fieldValue.value;
}
if (name != null) {
return name;
}
}
}
}
return null;
}
bool _isNativeMember(ir.Constant constant) {
return constant is ir.InstanceConstant &&
constant.classNode.name == 'ExternalName' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__internal;
}
String _getNativeMemberName(ir.Constant constant) {
if (constant is ir.InstanceConstant &&
constant.classNode.name == 'JSName' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
assert(constant.fieldValues.length == 1);
ir.Constant fieldValue = constant.fieldValues.values.single;
if (fieldValue is ir.StringConstant) {
return fieldValue.value;
}
}
return null;
}
String _getCreatesAnnotation(ir.Constant constant) {
if (constant is ir.InstanceConstant &&
constant.classNode.name == 'Creates' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
assert(constant.fieldValues.length == 1);
ir.Constant fieldValue = constant.fieldValues.values.single;
if (fieldValue is ir.StringConstant) {
return fieldValue.value;
}
}
return null;
}
String _getReturnsAnnotation(ir.Constant constant) {
if (constant is ir.InstanceConstant &&
constant.classNode.name == 'Returns' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_helper) {
assert(constant.fieldValues.length == 1);
ir.Constant fieldValue = constant.fieldValues.values.single;
if (fieldValue is ir.StringConstant) {
return fieldValue.value;
}
}
return null;
}
String _getJsInteropName(ir.Constant constant) {
if (constant is ir.InstanceConstant &&
constant.classNode.name == 'JS' &&
constant.classNode.enclosingLibrary.importUri == Uris.package_js) {
assert(constant.fieldValues.length == 1);
ir.Constant fieldValue = constant.fieldValues.values.single;
if (fieldValue is ir.NullConstant) {
return '';
} else if (fieldValue is ir.StringConstant) {
return fieldValue.value;
}
}
return null;
}
bool _isAnonymousJsInterop(ir.Constant constant) {
return constant is ir.InstanceConstant &&
constant.classNode.name == '_Anonymous' &&
constant.classNode.enclosingLibrary.importUri == Uris.package_js;
}
class PragmaAnnotationData {
// TODO(johnniwinther): Support non 'dart2js:' pragma names if necessary.
final String suffix;
// TODO(johnniwinther): Support options objects when necessary.
final bool hasOptions;
const PragmaAnnotationData(this.suffix, {this.hasOptions: false});
String get name => 'dart2js:$suffix';
@override
String toString() => 'PragmaAnnotationData($name)';
}
PragmaAnnotationData _getPragmaAnnotation(ir.Constant constant) {
if (constant is! ir.InstanceConstant) return null;
ir.InstanceConstant value = constant;
ir.Class cls = value.classNode;
Uri uri = cls.enclosingLibrary.importUri;
if (uri == Uris.package_meta_dart2js) {
if (cls.name == '_NoInline') {
return const PragmaAnnotationData('noInline');
} else if (cls.name == '_TryInline') {
return const PragmaAnnotationData('tryInline');
}
} else if (uri == Uris.dart_core && cls.name == 'pragma') {
ir.Constant nameValue;
ir.Constant optionsValue;
value.fieldValues.forEach((ir.Reference reference, ir.Constant fieldValue) {
ir.Field field = reference.asField;
if (field.name.name == 'name') {
nameValue = fieldValue;
} else if (field.name.name == 'options') {
optionsValue = fieldValue;
}
});
if (nameValue is! ir.StringConstant) return null;
ir.StringConstant stringValue = nameValue;
String name = stringValue.value;
String prefix = 'dart2js:';
if (!name.startsWith(prefix)) return null;
String suffix = name.substring(prefix.length);
return new PragmaAnnotationData(suffix,
hasOptions: optionsValue is! ir.NullConstant);
}
return null;
}
List<PragmaAnnotationData> computePragmaAnnotationDataFromIr(ir.Member member) {
List<PragmaAnnotationData> annotations = [];
for (ir.Expression metadata in member.annotations) {
if (metadata is! ir.ConstantExpression) continue;
ir.ConstantExpression constantExpression = metadata;
ir.Constant constant = constantExpression.constant;
PragmaAnnotationData data = _getPragmaAnnotation(constant);
if (data != null) {
annotations.add(data);
}
}
return annotations;
}