Add @anonymous annotation and restrict object literal constructors to only anonymous classes. This frees up defining factory constructors that do not correspond to object literals on JS interop classes.

BUG=
R=sra@google.com

Review URL: https://codereview.chromium.org/1409033005 .
This commit is contained in:
Jacob Richman 2015-10-29 19:18:19 -07:00
parent 688b69fb65
commit e0d2263bb3
7 changed files with 126 additions and 13 deletions

View file

@ -278,6 +278,8 @@ enum MessageKind {
INVALID_USE_OF_SUPER,
JS_INTEROP_CLASS_CANNOT_EXTEND_DART_CLASS,
JS_INTEROP_CLASS_NON_EXTERNAL_MEMBER,
JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS,
JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS,
LIBRARY_NAME_MISMATCH,
LIBRARY_NOT_FOUND,
LIBRARY_NOT_SUPPORTED,
@ -2172,6 +2174,51 @@ main() => A.A = 1;
}
"""]),
MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS:
const MessageTemplate(
MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS,
"Js-interop method '#{method}' has named arguments but is not "
"a factory constructor of an @anonymous @JS class.",
howToFix: "Remove all named arguments from js-interop method or "
"in the case of a factory constructor annotate the class "
"as @anonymous.",
examples: const [
"""
import 'package:js/js.dart';
@JS()
class Foo {
external bar(foo, {baz});
}
main() {
new Foo().bar(4, baz: 5);
}
"""]),
MessageKind.JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS:
const MessageTemplate(
MessageKind.JS_OBJECT_LITERAL_CONSTRUCTOR_WITH_POSITIONAL_ARGUMENTS,
"Parameter '#{parameter}' in anonymous js-interop class '#{cls}' "
"object literal constructor is positional instead of named."
".",
howToFix: "Make all arguments in external factory object literal "
"constructors named.",
examples: const [
"""
import 'package:js/js.dart';
@anonymous
@JS()
class Foo {
external factory Foo(foo, {baz});
}
main() {
new Foo(5, baz: 5);
}
"""]),
MessageKind.LIBRARY_NOT_FOUND:
const MessageTemplate(MessageKind.LIBRARY_NOT_FOUND,
"Library not found '#{resolvedUri}'."),

View file

@ -130,6 +130,7 @@ class BackendHelpers {
ClassElement irRepresentationClass;
ClassElement jsAnnotationClass;
ClassElement jsAnonymousClass;
Element getInterceptorMethod;
@ -303,6 +304,7 @@ class BackendHelpers {
typedArrayOfIntClass = findClass('NativeTypedArrayOfInt');
} else if (uri == PACKAGE_JS) {
jsAnnotationClass = find(library, 'JS');
jsAnonymousClass = find(library, '_Anonymous');
}
}

View file

@ -23,6 +23,7 @@ import '../elements/elements.dart'
FieldElement,
FunctionElement,
LibraryElement,
ParameterElement,
MetadataAnnotation;
import '../js/js.dart' as jsAst;
@ -83,11 +84,39 @@ class JsInteropAnalysis {
}
}
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 (!element.isClass || !backend.isJsInterop(element)) return;
if (!backend.isJsInterop(element)) return;
if (element is FunctionElement) {
_checkFunctionParameters(element);
}
if (!element.isClass) return;
ClassElement classElement = element;
@ -108,12 +137,30 @@ class JsInteropAnalysis {
backend.isJsInterop(classElement) &&
member is FunctionElement) {
FunctionElement fn = member;
if (!fn.isExternal && !fn.isAbstract) {
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);
}
}
});
});

View file

@ -5843,6 +5843,12 @@ class SsaBuilder extends ast.Visitor
}
}
bool _hasNamedParameters(FunctionElement function) {
FunctionSignature params = function.functionSignature;
return params.optionalParameterCount > 0
&& params.optionalParametersAreNamed;
}
HForeignCode invokeJsInteropFunction(Element element,
List<HInstruction> arguments,
SourceInformation sourceInformation) {
@ -5850,9 +5856,10 @@ class SsaBuilder extends ast.Visitor
nativeEmitter.nativeMethods.add(element);
String templateString;
if (element.isFactoryConstructor) {
// Treat factory constructors as syntactic sugar for creating object
// literals.
if (element.isFactoryConstructor &&
backend.jsInteropAnalysis.hasAnonymousAnnotation(element.contextClass)) {
// Factory constructor that is syntactic sugar for creating a JavaScript
// object literal.
ConstructorElement constructor = element;
FunctionSignature params = constructor.functionSignature;
int i = 0;
@ -5863,13 +5870,6 @@ class SsaBuilder extends ast.Visitor
// TODO(jacobr): throw if parameter names do not match names of property
// names in the class.
assert (parameter.isNamed);
if (!parameter.isNamed) {
reporter.reportErrorMessage(
parameter, MessageKind.GENERIC,
{'text': 'All arguments to external constructors of JavaScript '
'interop classes must be named as these constructors '
'are syntactic sugar for object literals.'});
}
HInstruction argument = arguments[i];
if (argument != null) {
filteredArguments.add(argument);
@ -5908,10 +5908,14 @@ class SsaBuilder extends ast.Visitor
} else if (element.isSetter) {
codeTemplate = js.js.parseForeignJS("# = #");
} else {
FunctionElement function = element;
FunctionSignature params = function.functionSignature;
var argsStub = <String>[];
for (int i = 0; i < arguments.length; i++) {
argsStub.add('#');
}
if (element.isConstructor) {
codeTemplate = js.js.parseForeignJS("new #(${argsStub.join(",")})");
} else {

View file

@ -18,3 +18,15 @@ class JS {
final String name;
const JS([this.name]);
}
class _Anonymous {
const _Anonymous();
}
/// A metadata annotation that indicates that a @JS annotated class is
/// structural and does not have a known JavaScript prototype.
///
/// Factory constructors for anonymous JavaScript classes desugar to creating
/// JavaScript object literals with name-value pairs corresponding to the
/// parameter names and values.
const _Anonymous anonymous = const _Anonymous();

View file

@ -1,5 +1,5 @@
name: js
version: 0.6.0-beta.6
version: 0.6.0-beta.7
authors:
- Dart Team <misc@dartlang.org>
description: Access JavaScript from Dart.

View file

@ -114,6 +114,7 @@ class Foo {
external static int multiplyDefault2(int a, [int b]);
}
@anonymous
@JS()
class ExampleLiteral {
external factory ExampleLiteral({int x, String y, num z});