| // Copyright (c) 2017, 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.dart'; |
| import '../common/elements.dart'; |
| import '../constants/values.dart'; |
| import '../elements/entities.dart'; |
| import '../ir/annotations.dart'; |
| import '../js_backend/native_data.dart'; |
| import '../native/resolver.dart'; |
| |
| import 'element_map.dart'; |
| |
| class KernelAnnotationProcessor { |
| final KernelToElementMap elementMap; |
| final NativeBasicDataBuilder _nativeBasicDataBuilder; |
| final IrAnnotationData annotationData; |
| |
| KernelAnnotationProcessor( |
| this.elementMap, |
| this._nativeBasicDataBuilder, |
| this.annotationData, |
| ); |
| |
| void extractNativeAnnotations(LibraryEntity library) { |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; |
| |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { |
| ir.Class node = elementMap.getClassNode(cls); |
| String? annotationName = annotationData.getNativeClassName(node); |
| if (annotationName != null) { |
| _nativeBasicDataBuilder.setNativeClassTagInfo(cls, annotationName); |
| } |
| }); |
| } |
| |
| String? getJsInteropName( |
| Spannable spannable, |
| Iterable<ConstantValue> metadata, |
| ) { |
| KCommonElements commonElements = elementMap.commonElements; |
| String? annotationName; |
| for (ConstantValue value in metadata) { |
| String? name; |
| List<ClassEntity?> jsAnnotationClasses = [ |
| commonElements.jsAnnotationClass1, |
| commonElements.jsAnnotationClass2, |
| commonElements.jsAnnotationClass3, |
| ]; |
| for (ClassEntity? jsAnnotationClass in jsAnnotationClasses) { |
| if (jsAnnotationClass != null) { |
| name = readAnnotationName( |
| commonElements.dartTypes, |
| spannable, |
| value, |
| jsAnnotationClass, |
| defaultValue: '', |
| ); |
| if (name != null) break; |
| } |
| } |
| if (annotationName == null) { |
| annotationName = name; |
| } else if (name != null) { |
| // TODO(johnniwinther): This should be an error, not a crash. |
| failedAt(spannable, 'Too many name annotations.'); |
| } |
| } |
| return annotationName; |
| } |
| |
| void extractJsInteropAnnotations(LibraryEntity library) { |
| // Unused reporter, add back in if uncommenting report lines down below. |
| // DiagnosticReporter reporter = elementMap.reporter; |
| KElementEnvironment elementEnvironment = elementMap.elementEnvironment; |
| |
| ir.Library libraryNode = elementMap.getLibraryNode(library); |
| String? libraryName = annotationData.getJsInteropLibraryName(libraryNode); |
| final bool isExplicitlyJsLibrary = libraryName != null; |
| bool isJsLibrary = isExplicitlyJsLibrary; |
| |
| elementEnvironment.forEachLibraryMember(library, (MemberEntity member) { |
| ir.Member memberNode = elementMap.getMemberNode(member); |
| String? memberName = annotationData.getJsInteropMemberName(memberNode); |
| if (member is FieldEntity) { |
| if (memberName != null) { |
| // TODO(34174): Disallow js-interop fields. |
| /*reporter.reportErrorMessage( |
| member, MessageKind.JS_INTEROP_FIELD_NOT_SUPPORTED);*/ |
| } |
| } else { |
| FunctionEntity function = member as FunctionEntity; |
| // We need this explicit check as object literal constructors in |
| // extension types do not need an `@JS()` annotation on them, their |
| // extension type, or their library. JS interop checks assert that the |
| // only extension type interop member that has named parameters is an |
| // object literal constructor. |
| // TODO(54968): We should handle the lowering for object literal |
| // constructors in the interop transformer somehow instead and avoid |
| // assuming all such members are object literal constructors or |
| // otherwise paying the cost to verify by indexing extension types. |
| bool isObjectLiteralConstructor = |
| (memberNode.isExtensionTypeMember && |
| memberNode.function?.namedParameters.isNotEmpty == true); |
| if (function.isExternal && |
| (isExplicitlyJsLibrary || isObjectLiteralConstructor)) { |
| // External members of explicit js-interop library are implicitly |
| // js-interop members. |
| memberName ??= function.name; |
| } |
| if (memberName != null) { |
| if (!function.isExternal) { |
| // TODO(johnniwinther): Disallow non-external js-interop members. |
| /*reporter.reportErrorMessage( |
| function, MessageKind.JS_INTEROP_NON_EXTERNAL_MEMBER);*/ |
| } else { |
| _nativeBasicDataBuilder.markAsJsInteropMember(function, memberName); |
| // TODO(johnniwinther): It is unclear whether library can be |
| // implicitly js-interop. For now we allow it. |
| isJsLibrary = true; |
| } |
| } |
| } |
| }); |
| |
| elementEnvironment.forEachClass(library, (ClassEntity cls) { |
| ir.Class classNode = elementMap.getClassNode(cls); |
| String? className = annotationData.getJsInteropClassName(classNode); |
| if (className != null) { |
| bool isAnonymous = annotationData.isAnonymousJsInteropClass(classNode); |
| bool isStaticInterop = annotationData.isStaticInteropClass(classNode); |
| // TODO(johnniwinther): Report an error if the class is anonymous but |
| // has a non-empty name. |
| _nativeBasicDataBuilder.markAsJsInteropClass( |
| cls, |
| name: className, |
| isAnonymous: isAnonymous, |
| isStaticInterop: isStaticInterop, |
| ); |
| // TODO(johnniwinther): It is unclear whether library can be implicitly |
| // js-interop. For now we allow it. |
| isJsLibrary = true; |
| |
| elementEnvironment.forEachLocalClassMember(cls, (MemberEntity member) { |
| if (member is FieldEntity) { |
| // TODO(34174): Disallow js-interop fields. |
| /*reporter.reportErrorMessage( |
| member, MessageKind.IMPLICIT_JS_INTEROP_FIELD_NOT_SUPPORTED);*/ |
| } else { |
| FunctionEntity function = member as FunctionEntity; |
| ir.Member memberNode = elementMap.getMemberNode(member); |
| // Members that are not annotated and not external will result in |
| // null here. For example, the default constructor which is not |
| // user-specified. |
| String? memberName = annotationData.getJsInteropMemberName( |
| memberNode, |
| ); |
| if (function.isExternal) { |
| memberName ??= function.name; |
| } |
| if (memberName != null) { |
| // TODO(johnniwinther): The documentation states that explicit |
| // member name annotations are not allowed on instance members. |
| _nativeBasicDataBuilder.markAsJsInteropMember( |
| function, |
| memberName, |
| ); |
| } |
| } |
| }); |
| elementEnvironment.forEachConstructor(cls, ( |
| ConstructorEntity constructor, |
| ) { |
| String? memberName = getJsInteropName( |
| library, |
| elementEnvironment.getMemberMetadata(constructor), |
| ); |
| if (constructor.isExternal) { |
| // TODO(johnniwinther): It should probably be an error to have a |
| // no-name constructor without a @JS() annotation. |
| memberName ??= constructor.name; |
| } |
| if (memberName != null) { |
| // TODO(johnniwinther): The documentation states that explicit |
| // member name annotations are not allowed on instance members. |
| _nativeBasicDataBuilder.markAsJsInteropMember( |
| constructor, |
| memberName, |
| ); |
| } |
| }); |
| } |
| }); |
| |
| if (isJsLibrary) { |
| // TODO(johnniwinther): It is unclear whether library can be implicitly |
| // js-interop. For now we allow it and assume the empty name. |
| libraryName ??= ''; |
| _nativeBasicDataBuilder.markAsJsInteropLibrary( |
| library, |
| name: libraryName, |
| ); |
| } |
| } |
| } |