blob: 3b75b49a16138fe5eb2748ad49360a64ff8f7673 [file] [log] [blame]
// 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/world_builder.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.resolverWorld.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()));
}
}