| // 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'; | |
| import 'modular.dart'; | |
| class IrAnnotationData { | |
| final Map<ir.Class, String> _nativeClassNames = {}; | |
| final Set<ir.Member> _nativeMembers = {}; | |
| final Map<ir.Member, String> _nativeMemberNames = {}; | |
| final Map<ir.Member, List<String>> _createsAnnotations = {}; | |
| final Map<ir.Member, List<String>> _returnsAnnotations = {}; | |
| final Map<ir.Library, String> _jsInteropLibraryNames = {}; | |
| final Map<ir.Class, String> _jsInteropClassNames = {}; | |
| final Set<ir.Class> _anonymousJsInteropClasses = {}; | |
| final Map<ir.Member, String> _jsInteropMemberNames = {}; | |
| final Set<ir.Class> _staticInteropClasses = {}; | |
| final 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 `true` if [node] is annotated with `@staticInterop`. | |
| bool isStaticInteropClass(ir.Class node) => | |
| _staticInteropClasses.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, { | |
| required bool isAnonymous, | |
| required bool isStaticInterop, | |
| }) | |
| f, | |
| ) { | |
| _jsInteropClassNames.forEach((ir.Class node, String name) { | |
| f( | |
| node, | |
| name, | |
| isAnonymous: isAnonymousJsInteropClass(node), | |
| isStaticInterop: isStaticInteropClass(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.text); | |
| } | |
| } | |
| }); | |
| _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.text; | |
| } | |
| 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.text; | |
| 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.text; | |
| f( | |
| field, | |
| name, | |
| getCreatesAnnotations(field), | |
| getReturnsAnnotations(field), | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| IrAnnotationData processAnnotations(ModularCore modularCore) { | |
| ir.Component component = modularCore.component; | |
| IrAnnotationData data = 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); | |
| } | |
| Iterable<PragmaAnnotationData> pragmaAnnotation = _pragmaAnnotations( | |
| constant, | |
| ); | |
| if (pragmaAnnotations == null) { | |
| data._memberPragmaAnnotations[member] = pragmaAnnotations = []; | |
| } | |
| pragmaAnnotations.addAll(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); | |
| } | |
| bool isStaticInteropClass = _isStaticInterop(constant); | |
| if (isStaticInteropClass) { | |
| data._staticInteropClasses.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.dartJSHelper) { | |
| 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.dartInternal; | |
| } | |
| String? _getNativeMemberName(ir.Constant constant) { | |
| if (constant is ir.InstanceConstant && | |
| constant.classNode.name == 'JSName' && | |
| constant.classNode.enclosingLibrary.importUri == Uris.dartJSHelper) { | |
| 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.dartJSHelper) { | |
| 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.dartJSHelper) { | |
| 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.packageJS || | |
| constant.classNode.enclosingLibrary.importUri == | |
| Uris.dartJSAnnotations || | |
| constant.classNode.enclosingLibrary.importUri == | |
| Uris.dartJSInterop)) { | |
| 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) { | |
| if (constant is ir.InstanceConstant && | |
| constant.classNode.name == '_Anonymous') { | |
| final importUri = constant.classNode.enclosingLibrary.importUri; | |
| return importUri == Uris.packageJS || importUri == Uris.dartJSInterop; | |
| } | |
| return false; | |
| } | |
| bool _isStaticInterop(ir.Constant constant) { | |
| if (constant is ir.InstanceConstant && | |
| constant.classNode.name == '_StaticInterop') { | |
| final importUri = constant.classNode.enclosingLibrary.importUri; | |
| return (importUri == Uris.packageJS || importUri == Uris.dartJSInterop); | |
| } | |
| return false; | |
| } | |
| class PragmaAnnotationData { | |
| // TODO(johnniwinther): Support non 'dart2js:' pragma names if necessary. | |
| final String suffix; | |
| // TODO(johnniwinther): Support options objects when necessary. | |
| final ir.Constant? options; | |
| const PragmaAnnotationData(this.suffix, {this.options}); | |
| String get name => 'dart2js:$suffix'; | |
| @override | |
| String toString() => 'PragmaAnnotationData($name)'; | |
| @override | |
| int get hashCode => Object.hash(suffix, options); | |
| @override | |
| bool operator ==(Object other) { | |
| if (identical(this, other)) return true; | |
| if (other is! PragmaAnnotationData) return false; | |
| return suffix == other.suffix && options == other.options; | |
| } | |
| } | |
| Iterable<PragmaAnnotationData> _pragmaAnnotations(ir.Constant constant) { | |
| if (constant is! ir.InstanceConstant) return const []; | |
| ir.InstanceConstant value = constant; | |
| ir.Class cls = value.classNode; | |
| Uri uri = cls.enclosingLibrary.importUri; | |
| if (uri == Uris.packageMetaDart2js) { | |
| if (cls.name == '_NoInline') { | |
| return const [PragmaAnnotationData('noInline')]; | |
| } else if (cls.name == '_TryInline') { | |
| return const [PragmaAnnotationData('tryInline')]; | |
| } | |
| } else if (uri == Uris.dartCore && 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.text == 'name') { | |
| nameValue = fieldValue; | |
| } else if (field.name.text == 'options') { | |
| optionsValue = fieldValue; | |
| } | |
| }); | |
| final nameValueFinal = nameValue; | |
| if (nameValueFinal is! ir.StringConstant) return const []; | |
| ir.StringConstant stringValue = nameValueFinal; | |
| String name = stringValue.value; | |
| String prefix = 'dart2js:'; | |
| if (!name.startsWith(prefix)) return const []; | |
| String suffix = name.substring(prefix.length); | |
| return [ | |
| PragmaAnnotationData( | |
| suffix, | |
| options: optionsValue is ir.NullConstant ? null : optionsValue, | |
| ), | |
| ]; | |
| } else if (uri == Uris.packageMeta) { | |
| if (cls.name == 'RecordUse') { | |
| return const [PragmaAnnotationData('record-use')]; | |
| } | |
| } | |
| return const []; | |
| } | |
| List<PragmaAnnotationData> computePragmaAnnotationDataFromIr( | |
| ir.Annotatable node, | |
| ) { | |
| List<PragmaAnnotationData> annotations = []; | |
| for (ir.Expression metadata in node.annotations) { | |
| if (metadata is! ir.ConstantExpression) continue; | |
| ir.ConstantExpression constantExpression = metadata; | |
| ir.Constant constant = constantExpression.constant; | |
| assert( | |
| constant is! ir.UnevaluatedConstant, | |
| "Unexpected unevaluated constant on $node: $metadata", | |
| ); | |
| Iterable<PragmaAnnotationData> data = _pragmaAnnotations(constant); | |
| annotations.addAll(data); | |
| } | |
| return annotations; | |
| } |