blob: d7b0a2d2e445a4194b3581a5b352207085dd7f8c [file] [log] [blame] [edit]
// 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,
);
}
}
}