| Index: pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
|
| diff --git a/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart b/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
|
| index b4f0c76a502dc05dc87e7f9ec477df2dfdfb4c76..480b75d87ad4ac7a31951aa895749aafdc75a3c9 100644
|
| --- a/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
|
| +++ b/pkg/compiler/lib/src/js_backend/js_interop_analysis.dart
|
| @@ -5,8 +5,22 @@
|
| /// Analysis to determine how to generate code for typed JavaScript interop.
|
| library compiler.src.js_backend.js_interop_analysis;
|
|
|
| +import '../common.dart';
|
| +import '../constants/values.dart'
|
| + show ConstantValue, ConstructedConstantValue, StringConstantValue;
|
| import '../elements/resolution_types.dart'
|
| show ResolutionDartType, ResolutionDynamicType, ResolutionFunctionType;
|
| +import '../diagnostics/messages.dart' show MessageKind;
|
| +import '../elements/elements.dart'
|
| + show
|
| + ClassElement,
|
| + Element,
|
| + FieldElement,
|
| + LibraryElement,
|
| + ParameterElement,
|
| + MemberElement,
|
| + MethodElement,
|
| + MetadataAnnotation;
|
| import '../js/js.dart' as jsAst;
|
| import '../js/js.dart' show js;
|
| import '../universe/selector.dart' show Selector;
|
| @@ -16,10 +30,167 @@ import 'js_backend.dart' show JavaScriptBackend;
|
| class JsInteropAnalysis {
|
| final JavaScriptBackend backend;
|
|
|
| + /// The resolved [FieldElement] for `Js.name`.
|
| + FieldElement nameField;
|
| + bool enabledJsInterop = false;
|
| +
|
| + /// Whether the backend is currently processing the codegen queue.
|
| + bool _inCodegen = false;
|
| +
|
| JsInteropAnalysis(this.backend);
|
|
|
| + void onQueueClosed() {
|
| + if (_inCodegen) return;
|
| +
|
| + if (backend.compiler.commonElements.jsAnnotationClass != null) {
|
| + ClassElement cls = backend.compiler.commonElements.jsAnnotationClass;
|
| + nameField = cls.lookupMember('name');
|
| + backend.compiler.libraryLoader.libraries
|
| + .forEach(processJsInteropAnnotationsInLibrary);
|
| + }
|
| + }
|
| +
|
| + void onCodegenStart() {
|
| + _inCodegen = true;
|
| + }
|
| +
|
| + /// Resolves the metadata of [element] and returns the name of the `JS(...)`
|
| + /// annotation for js interop, if found.
|
| + String processJsInteropAnnotation(Element element) {
|
| + for (MetadataAnnotation annotation in element.implementation.metadata) {
|
| + // TODO(johnniwinther): Avoid processing unresolved elements.
|
| + if (annotation.constant == null) continue;
|
| + ConstantValue constant =
|
| + backend.compiler.constants.getConstantValue(annotation.constant);
|
| + if (constant == null || constant is! ConstructedConstantValue) continue;
|
| + ConstructedConstantValue constructedConstant = constant;
|
| + if (constructedConstant.type.element ==
|
| + backend.compiler.commonElements.jsAnnotationClass) {
|
| + ConstantValue value = constructedConstant.fields[nameField];
|
| + String name;
|
| + if (value.isString) {
|
| + StringConstantValue stringValue = value;
|
| + name = stringValue.primitiveValue.slowToString();
|
| + } else {
|
| + // TODO(jacobr): report a warning if the value is not a String.
|
| + name = '';
|
| + }
|
| + enabledJsInterop = true;
|
| + return name;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + bool hasAnonymousAnnotation(Element element) {
|
| + if (backend.compiler.commonElements.jsAnonymousClass == null) return false;
|
| + return element.metadata.any((MetadataAnnotation annotation) {
|
| + ConstantValue constant =
|
| + backend.compiler.constants.getConstantValue(annotation.constant);
|
| + if (constant == null || constant is! ConstructedConstantValue)
|
| + return false;
|
| + ConstructedConstantValue constructedConstant = constant;
|
| + return constructedConstant.type.element ==
|
| + backend.compiler.commonElements.jsAnonymousClass;
|
| + });
|
| + }
|
| +
|
| + void _checkFunctionParameters(MethodElement fn) {
|
| + if (fn.hasFunctionSignature &&
|
| + fn.functionSignature.optionalParametersAreNamed) {
|
| + backend.reporter.reportErrorMessage(
|
| + fn,
|
| + MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS,
|
| + {'method': fn.name});
|
| + }
|
| + }
|
| +
|
| + void processJsInteropAnnotationsInLibrary(LibraryElement library) {
|
| + String libraryName = processJsInteropAnnotation(library);
|
| + if (libraryName != null) {
|
| + backend.nativeDataBuilder.setJsInteropLibraryName(library, libraryName);
|
| + }
|
| + library.implementation.forEachLocalMember((Element element) {
|
| + if (element is MemberElement) {
|
| + String memberName = processJsInteropAnnotation(element);
|
| + if (memberName != null) {
|
| + backend.nativeDataBuilder.setJsInteropMemberName(element, memberName);
|
| + }
|
| +
|
| + if (element is MethodElement) {
|
| + if (!backend.nativeData.isJsInteropMember(element)) return;
|
| + _checkFunctionParameters(element);
|
| + }
|
| + }
|
| +
|
| + if (!element.isClass) return;
|
| +
|
| + ClassElement classElement = element;
|
| + String className = processJsInteropAnnotation(classElement);
|
| + if (className != null) {
|
| + backend.nativeDataBuilder
|
| + .setJsInteropClassName(classElement, className);
|
| + }
|
| + if (!backend.nativeData.isJsInteropClass(classElement)) return;
|
| +
|
| + // Skip classes that are completely unreachable. This should only happen
|
| + // when all of jsinterop types are unreachable from main.
|
| + if (!backend.compiler.resolutionWorldBuilder
|
| + .isImplemented(classElement)) {
|
| + return;
|
| + }
|
| +
|
| + if (!classElement.implementsInterface(
|
| + backend.compiler.commonElements.jsJavaScriptObjectClass)) {
|
| + backend.reporter.reportErrorMessage(classElement,
|
| + MessageKind.JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS, {
|
| + 'cls': classElement.name,
|
| + 'superclass': classElement.superclass.name
|
| + });
|
| + }
|
| +
|
| + classElement
|
| + .forEachMember((ClassElement classElement, MemberElement member) {
|
| + String memberName = processJsInteropAnnotation(member);
|
| + if (memberName != null) {
|
| + backend.nativeDataBuilder.setJsInteropMemberName(member, memberName);
|
| + }
|
| +
|
| + if (!member.isSynthesized &&
|
| + backend.nativeData.isJsInteropClass(classElement) &&
|
| + member is MethodElement) {
|
| + MethodElement fn = member;
|
| + if (!fn.isExternal &&
|
| + !fn.isAbstract &&
|
| + !fn.isConstructor &&
|
| + !fn.isStatic) {
|
| + backend.reporter.reportErrorMessage(
|
| + fn,
|
| + MessageKind.JS_INTEROP_CLASS_NON_EXTERNAL_MEMBER,
|
| + {'cls': classElement.name, 'member': member.name});
|
| + }
|
| +
|
| + if (fn.isFactoryConstructor && hasAnonymousAnnotation(classElement)) {
|
| + fn.functionSignature
|
| + .orderedForEachParameter((ParameterElement parameter) {
|
| + if (!parameter.isNamed) {
|
| + backend.reporter.reportErrorMessage(
|
| + parameter,
|
| + MessageKind
|
| + .JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS,
|
| + {'parameter': parameter.name, 'cls': classElement.name});
|
| + }
|
| + });
|
| + } else {
|
| + _checkFunctionParameters(fn);
|
| + }
|
| + }
|
| + });
|
| + });
|
| + }
|
| +
|
| jsAst.Statement buildJsInteropBootstrap() {
|
| - if (!backend.nativeBasicData.isJsInteropUsed) return null;
|
| + if (!enabledJsInterop) return null;
|
| List<jsAst.Statement> statements = <jsAst.Statement>[];
|
| backend.compiler.codegenWorldBuilder.forEachInvokedName(
|
| (String name, Map<Selector, SelectorConstraints> selectors) {
|
|
|