| // Copyright (c) 2015, 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. |
| |
| /// 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 '../dart_types.dart' |
| show |
| DartType, |
| DynamicType, |
| FunctionType; |
| import '../diagnostics/messages.dart' show MessageKind; |
| import '../elements/elements.dart' |
| show |
| ClassElement, |
| Element, |
| FieldElement, |
| FunctionElement, |
| LibraryElement, |
| ParameterElement, |
| MetadataAnnotation; |
| import '../js/js.dart' as jsAst; |
| import '../js/js.dart' show js; |
| import '../universe/selector.dart' show Selector; |
| import '../universe/universe.dart' show SelectorConstraints; |
| import 'backend_helpers.dart' show BackendHelpers; |
| 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); |
| |
| BackendHelpers get helpers => backend.helpers; |
| |
| void onQueueClosed() { |
| if (_inCodegen) return; |
| |
| if (helpers.jsAnnotationClass != null) { |
| nameField = helpers.jsAnnotationClass.lookupMember('name'); |
| backend.compiler.libraryLoader.libraries |
| .forEach(processJsInteropAnnotationsInLibrary); |
| } |
| } |
| |
| void onCodegenStart() { |
| _inCodegen = true; |
| } |
| |
| void processJsInteropAnnotation(Element e) { |
| for (MetadataAnnotation annotation in e.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 == helpers.jsAnnotationClass) { |
| ConstantValue value = constructedConstant.fields[nameField]; |
| if (value.isString) { |
| StringConstantValue stringValue = value; |
| backend.nativeData |
| .setJsInteropName(e, stringValue.primitiveValue.slowToString()); |
| } else { |
| // TODO(jacobr): report a warning if the value is not a String. |
| backend.nativeData.setJsInteropName(e, ''); |
| } |
| enabledJsInterop = true; |
| return; |
| } |
| } |
| } |
| |
| bool hasAnonymousAnnotation(Element element) { |
| if (backend.helpers.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.helpers.jsAnonymousClass; |
| }); |
| } |
| |
| void _checkFunctionParameters(FunctionElement 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) { |
| processJsInteropAnnotation(library); |
| library.implementation.forEachLocalMember((Element element) { |
| processJsInteropAnnotation(element); |
| if (!backend.isJsInterop(element)) return; |
| if (element is FunctionElement) { |
| _checkFunctionParameters(element); |
| } |
| |
| if (!element.isClass) return; |
| |
| ClassElement classElement = element; |
| |
| // Skip classes that are completely unreachable. This should only happen |
| // when all of jsinterop types are unreachable from main. |
| if (!backend.compiler.world.isImplemented(classElement)) return; |
| |
| if (!classElement.implementsInterface(helpers.jsJavaScriptObjectClass)) { |
| backend.reporter.reportErrorMessage(classElement, |
| MessageKind.JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS, { |
| 'cls': classElement.name, |
| 'superclass': classElement.superclass.name |
| }); |
| } |
| |
| classElement.forEachMember((ClassElement classElement, Element member) { |
| processJsInteropAnnotation(member); |
| |
| if (!member.isSynthesized && |
| backend.isJsInterop(classElement) && |
| member is FunctionElement) { |
| FunctionElement 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 (!enabledJsInterop) return null; |
| List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| backend.compiler.codegenWorld.forEachInvokedName( |
| (String name, Map<Selector, SelectorConstraints> selectors) { |
| selectors.forEach((Selector selector, SelectorConstraints constraints) { |
| if (selector.isClosureCall) { |
| // TODO(jacobr): support named arguments. |
| if (selector.namedArgumentCount > 0) return; |
| int argumentCount = selector.argumentCount; |
| var candidateParameterNames = |
| 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
| var parameters = new List<String>.generate( |
| argumentCount, (i) => candidateParameterNames[i]); |
| |
| var name = backend.namer.invocationName(selector); |
| statements.add(js.statement( |
| 'Function.prototype.# = function(#) { return this(#) }', |
| [name, parameters, parameters])); |
| } |
| }); |
| }); |
| return new jsAst.Block(statements); |
| } |
| |
| FunctionType buildJsFunctionType() { |
| // TODO(jacobr): consider using codegenWorld.isChecks to determine the |
| // range of positional arguments that need to be supported by JavaScript |
| // function types. |
| return new FunctionType.synthesized( |
| const DynamicType(), |
| [], |
| new List<DartType>.filled(16, const DynamicType())); |
| } |
| } |