Implement named arguments in InstanceMirror.invoke.

BUG=http://dartbug.com/12863
R=ngeoffray@google.com

Review URL: https://codereview.chromium.org//24493006

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@27936 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
ahe@google.com
2013-09-26 09:08:03 +00:00
parent b5024d1144
commit c24b1b7e78
8 changed files with 158 additions and 36 deletions
@@ -129,6 +129,14 @@ class JsBuilder {
throw new ArgumentError('expression should be an empty Map');
}
return new ObjectInitializer([]);
} else if (expression is List) {
var values = new List<ArrayElement>(expression.length);
int index = 0;
for (var entry in expression) {
values[index] = new ArrayElement(index, toExpression(entry));
index++;
}
return new ArrayInitializer(values.length, values);
} else {
throw new ArgumentError('expression should be an Expression, '
'a String, a num, a bool, or a Map');
@@ -1265,6 +1265,11 @@ class CodeEmitterTask extends CompilerTask {
var reflectable =
js(backend.isAccessibleByReflection(member) ? '1' : '0');
builder.addProperty('+$reflectionName', reflectable);
jsAst.Node defaultValues = reifyDefaultArguments(member);
if (defaultValues != null) {
String unmangledName = member.name.slowToString();
builder.addProperty('*$unmangledName', defaultValues);
}
}
code = backend.generatedBailoutCode[member];
if (code != null) {
@@ -1273,7 +1278,7 @@ class CodeEmitterTask extends CompilerTask {
FunctionElement function = member;
FunctionSignature parameters = function.computeSignature(compiler);
if (!parameters.optionalParameters.isEmpty) {
addParameterStubs(member, builder.addProperty);
addParameterStubs(function, builder.addProperty);
}
} else if (!member.isField()) {
compiler.internalError('unexpected kind: "${member.kind}"',
@@ -1376,9 +1381,9 @@ class CodeEmitterTask extends CompilerTask {
}
String namedParametersAsReflectionNames(Selector selector) {
if (selector.orderedNamedArguments.isEmpty) return '';
String names =
selector.orderedNamedArguments.map((x) => x.slowToString()).join(':');
if (selector.getOrderedNamedArguments().isEmpty) return '';
String names = selector.getOrderedNamedArguments().map(
(x) => x.slowToString()).join(':');
return ':$names';
}
@@ -2383,6 +2388,12 @@ class CodeEmitterTask extends CompilerTask {
if (reflectionName != null) {
var reflectable = backend.isAccessibleByReflection(element) ? 1 : 0;
buffer.write(',$n$n"+$reflectionName":${_}$reflectable');
jsAst.Node defaultValues = reifyDefaultArguments(element);
if (defaultValues != null) {
String unmangledName = element.name.slowToString();
buffer.write(',$n$n"*$unmangledName":${_}');
buffer.write(jsAst.prettyPrint(defaultValues, compiler));
}
}
jsAst.Expression bailoutCode = backend.generatedBailoutCode[element];
if (bailoutCode != null) {
@@ -3695,6 +3706,20 @@ class CodeEmitterTask extends CompilerTask {
});
}
jsAst.Node reifyDefaultArguments(FunctionElement function) {
FunctionSignature signature = function.computeSignature(compiler);
if (signature.optionalParameterCount == 0) return null;
List<int> defaultValues = <int>[];
for (Element element in signature.orderedOptionalParameters) {
Constant value =
compiler.constantHandler.initialVariableValues[element];
String stringRepresentation = (value == null) ? "null"
: jsAst.prettyPrint(constantReference(value), compiler).getText();
defaultValues.add(addGlobalMetadata(stringRepresentation));
}
return js.toExpression(defaultValues);
}
int reifyMetadata(MetadataAnnotation annotation) {
Constant value = annotation.value;
if (value == null) {
@@ -4267,6 +4292,9 @@ if (typeof $printHelperName === "function") {
String getReflectionDataParser() {
String metadataField = '"${namer.metadataField}"';
String reflectableField = namer.reflectableField;
String defaultValuesField = namer.defaultValuesField;
String methodsWithOptionalArgumentsField =
namer.methodsWithOptionalArgumentsField;
return '''
(function (reflectionData) {
'''
@@ -4325,6 +4353,13 @@ if (typeof $printHelperName === "function") {
} else if (firstChar === "@") {
property = property.substring(1);
${namer.CURRENT_ISOLATE}[property][$metadataField] = element;
} else if (firstChar === "*") {
globalObject[previousProperty].$defaultValuesField = element;
var optionalMethods = descriptor.$methodsWithOptionalArgumentsField;
if (!optionalMethods) {
descriptor.$methodsWithOptionalArgumentsField = optionalMethods = {}
}
optionalMethods[property] = previousProperty;
} else if (typeof element === "function") {
globalObject[previousProperty = property] = element;
functions.push(property);
@@ -4344,6 +4379,13 @@ if (typeof $printHelperName === "function") {
'''element[previousProp].$reflectableField = 1;
} else if (firstChar === "@" && prop !== "@") {
newDesc[prop.substring(1)][$metadataField] = element[prop];
} else if (firstChar === "*") {
newDesc[previousProp].$defaultValuesField = element[prop];
var optionalMethods = newDesc.$methodsWithOptionalArgumentsField;
if (!optionalMethods) {
newDesc.$methodsWithOptionalArgumentsField = optionalMethods={}
}
optionalMethods[prop] = previousProp;
} else {
newDesc[previousProp = prop] = element[prop];
}
@@ -219,6 +219,9 @@ class Namer implements ClosureNamer {
final String metadataField = '@';
final String callCatchAllName = r'call$catchAll';
final String reflectableField = r'$reflectable';
final String defaultValuesField = r'$defaultValues';
final String methodsWithOptionalArgumentsField =
r'$methodsWithOptionalArguments';
/**
* Map from top-level or static elements to their unique identifiers provided
-1
View File
@@ -706,7 +706,6 @@ class Primitives {
arguments.addAll(positionalArguments);
}
// TODO(ahe): Use JS_SOMETHING to get name from Namer.
if (JS('bool', r'# in #', JS_GET_NAME('CALL_CATCH_ALL'), function)) {
// We expect the closure to have a "call$catchAll" (the value of
// JS_GET_NAME('CALL_CATCH_ALL')) function that returns all the expected
+64 -9
View File
@@ -647,9 +647,6 @@ class JsInstanceMirror extends JsObjectMirror implements InstanceMirror {
Future<InstanceMirror> invokeAsync(Symbol memberName,
List<Object> positionalArguments,
[Map<Symbol, dynamic> namedArguments]) {
if (namedArguments != null && !namedArguments.isEmpty) {
throw new UnsupportedError('Named arguments are not implemented.');
}
return
new Future<InstanceMirror>(
() => invoke(memberName, positionalArguments, namedArguments));
@@ -658,12 +655,55 @@ class JsInstanceMirror extends JsObjectMirror implements InstanceMirror {
InstanceMirror invoke(Symbol memberName,
List positionalArguments,
[Map<Symbol,dynamic> namedArguments]) {
String name = n(memberName);
String reflectiveName;
if (namedArguments != null && !namedArguments.isEmpty) {
throw new UnsupportedError('Named arguments are not implemented.');
var methodsWithOptionalArguments =
JS('=Object', '#.\$methodsWithOptionalArguments', reflectee);
String mangledName =
JS('String|Null', '#[#]', methodsWithOptionalArguments, '*$name');
if (mangledName == null) {
// TODO(ahe): Invoke noSuchMethod.
throw new UnimplementedNoSuchMethodError(
'Invoking noSuchMethod with named arguments not implemented');
}
var defaultValueIndices =
JS('List|Null', '#[#].\$defaultValues', reflectee, mangledName);
var defaultValues =
defaultValueIndices.map((int i) => JS('', 'init.metadata[#]', i))
.iterator;
var defaultArguments = new Map();
reflectiveName = mangledNames[mangledName];
var reflectiveNames = reflectiveName.split(':');
int requiredPositionalArgumentCount =
int.parse(reflectiveNames.elementAt(1));
positionalArguments = new List.from(positionalArguments);
// Check the number of positional arguments is valid.
if (requiredPositionalArgumentCount != positionalArguments.length) {
// TODO(ahe): Invoke noSuchMethod.
throw new UnimplementedNoSuchMethodError(
'Invoking noSuchMethod with named arguments not implemented');
}
for (String parameter in reflectiveNames.skip(3)) {
defaultValues.moveNext();
defaultArguments[parameter] = defaultValues.current;
}
namedArguments.forEach((Symbol symbol, value) {
String parameter = n(symbol);
if (defaultArguments.containsKey(parameter)) {
defaultArguments[parameter] = value;
} else {
// Extraneous named argument.
// TODO(ahe): Invoke noSuchMethod.
throw new UnimplementedNoSuchMethodError(
'Invoking noSuchMethod with named arguments not implemented');
}
});
positionalArguments.addAll(defaultArguments.values);
} else {
reflectiveName =
JS('String', '# + ":" + # + ":0"', name, positionalArguments.length);
}
String reflectiveName =
JS('String', '# + ":" + # + ":0"',
n(memberName), positionalArguments.length);
// We can safely pass positionalArguments to _invoke as it will wrap it in
// a JSArray if needed.
return _invoke(memberName, JSInvocationMirror.METHOD, reflectiveName,
@@ -686,8 +726,13 @@ class JsInstanceMirror extends JsObjectMirror implements InstanceMirror {
if (cacheEntry == null) {
disableTreeShaking();
String mangledName = reflectiveNames[reflectiveName];
// TODO(ahe): Get the argument names.
List<String> argumentNames = [];
List<String> argumentNames = const [];
if (type == JSInvocationMirror.METHOD) {
// Note: [argumentNames] are not what the user actually provided, it is
// always all the named paramters.
argumentNames = reflectiveName.split(':').skip(3).toList();
}
// TODO(ahe): We don't need to create an invocation mirror here. The
// logic from JSInvocationMirror.getCachedInvocation could easily be
// inlined here.
@@ -1889,3 +1934,13 @@ class UnmodifiableMapView<K, V> implements Map<K, V> {
void clear() => _throw();
}
// TODO(ahe): Remove this class and call noSuchMethod instead.
class UnimplementedNoSuchMethodError extends Error
implements NoSuchMethodError {
final String _message;
UnimplementedNoSuchMethodError(this._message);
String toString() => "Unsupported operation: $_message";
}
+1 -1
View File
@@ -13,7 +13,7 @@ mirrors/generics_test/01: RuntimeError # Issue 12333
mirrors/hierarchy_invariants_test: RuntimeError # Issue 11863
mirrors/invoke_test: RuntimeError # Issue 11954
mirrors/invoke_closurization_test: RuntimeError # Issue 13002
mirrors/invoke_named_test: RuntimeError # Issue 10471, 12863
mirrors/invoke_named_test/none: RuntimeError # Issue 10471, 12863
mirrors/invoke_private_test: CompileTimeError # Issue 12164
mirrors/invoke_throws_test: RuntimeError # Issue 11954
mirrors/library_uri_io_test: Skip # Not intended for dart2js as it uses dart:io.
+30 -17
View File
@@ -6,9 +6,14 @@ library test.invoke_named_test;
import 'dart:mirrors';
import 'dart:async' show Future;
import 'package:expect/expect.dart';
import 'invoke_test.dart';
// TODO(ahe): Remove this variable (http://dartbug.com/12863).
bool isDart2js = false;
class C {
a(a, {b:'B', c}) => "$a-$b-$c";
b({a:'A', b, c}) => "$a-$b-$c";
@@ -58,7 +63,7 @@ testSyncInvoke(ObjectMirror om) {
Expect.throws(() => om.invoke(const Symbol('a'), ['X'], {const Symbol('undef') : 'Y'}),
isNoSuchMethodError,
'Unmatched named argument');
result = om.invoke(const Symbol('b'), []);
Expect.equals('A-null-null', result.reflectee);
result = om.invoke(const Symbol('b'), [], {const Symbol('a') : 'X'});
@@ -72,6 +77,7 @@ testSyncInvoke(ObjectMirror om) {
isNoSuchMethodError,
'Unmatched named argument');
if (!isDart2js) {
result = om.invoke(const Symbol('c'), ['X']);
Expect.equals('X-null-C', result.reflectee);
result = om.invoke(const Symbol('c'), ['X', 'Y']);
@@ -102,6 +108,7 @@ testSyncInvoke(ObjectMirror om) {
Expect.throws(() => om.invoke(const Symbol('d'), ['X'], {const Symbol('undef'): 'Y'}),
isNoSuchMethodError,
'Unmatched named argument');
}
result = om.invoke(const Symbol('e'), ['X', 'Y', 'Z']);
Expect.equals('X-Y-Z', result.reflectee);
@@ -137,8 +144,8 @@ testAsyncInvoke(ObjectMirror om) {
expectError(future, isNoSuchMethodError, 'Extra positional arguments');
future = om.invokeAsync(const Symbol('a'), ['X'], {const Symbol('undef') : 'Y'});
expectError(future, isNoSuchMethodError, 'Unmatched named argument');
future = om.invokeAsync(const Symbol('b'), []);
expectValueThen(future, (result) {
Expect.equals('A-null-null', result.reflectee);
@@ -156,7 +163,7 @@ testAsyncInvoke(ObjectMirror om) {
future = om.invokeAsync(const Symbol('b'), ['X'], {const Symbol('undef'): 'Y'});
expectError(future, isNoSuchMethodError, 'Unmatched named argument');
if (!isDart2js) {
future = om.invokeAsync(const Symbol('c'), ['X']);
expectValueThen(future, (result) {
Expect.equals('X-null-C', result.reflectee);
@@ -197,7 +204,7 @@ testAsyncInvoke(ObjectMirror om) {
expectError(future, isNoSuchMethodError, 'Extra positional arguments');
future = om.invokeAsync(const Symbol('d'), ['X'], {const Symbol('undef'): 'Y'});
expectError(future, isNoSuchMethodError, 'Unmatched named argument');
}
future = om.invokeAsync(const Symbol('e'), ['X', 'Y', 'Z']);
expectValueThen(future, (result) {
@@ -230,7 +237,7 @@ testSyncNewInstance() {
Expect.throws(() => cm.newInstance(const Symbol(''), ['X'], {const Symbol('undef') : 'Y'}),
isNoSuchMethodError,
'Unmatched named argument');
result = cm.newInstance(const Symbol('b'), []);
Expect.equals('A-null-null', result.reflectee.field);
result = cm.newInstance(const Symbol('b'), [], {const Symbol('a') : 'X'});
@@ -310,8 +317,8 @@ testAsyncNewInstance() {
expectError(future, isNoSuchMethodError, 'Extra positional arguments');
future = cm.newInstanceAsync(const Symbol(''), ['X'], {const Symbol('undef') : 'Y'});
expectError(future, isNoSuchMethodError, 'Unmatched named argument');
future = cm.newInstanceAsync(const Symbol('b'), []);
expectValueThen(future, (result) {
Expect.equals('A-null-null', result.reflectee.field);
@@ -404,7 +411,7 @@ testSyncApply() {
Expect.throws(() => cm.apply(['X'], {const Symbol('undef') : 'Y'}),
isNoSuchMethodError,
'Unmatched named argument');
cm = reflect(b);
result = cm.apply([]);
Expect.equals('A-null-null', result.reflectee);
@@ -489,7 +496,7 @@ testAsyncApply() {
expectError(future, isNoSuchMethodError, 'Extra positional arguments');
future = cm.applyAsync(['X'], {const Symbol('undef') : 'Y'});
expectError(future, isNoSuchMethodError, 'Unmatched named argument');
cm = reflect(b);
future = cm.applyAsync([]);
@@ -568,17 +575,23 @@ testAsyncApply() {
}
main() {
testSyncInvoke(reflect(new C())); // InstanceMirror
testSyncInvoke(reflectClass(D)); // ClassMirror
testSyncInvoke(reflectClass(D).owner); // LibraryMirror
isDart2js = true; /// 01: ok
testAsyncInvoke(reflect(new C())); // InstanceMirror
testAsyncInvoke(reflectClass(D)); // ClassMirror
testAsyncInvoke(reflectClass(D).owner); // LibraryMirror
testSyncInvoke(reflect(new C())); // InstanceMirror
if (!isDart2js) testSyncInvoke(reflectClass(D)); // ClassMirror
LibraryMirror lib = reflectClass(D).owner;
if (!isDart2js) testSyncInvoke(lib); // LibraryMirror
testAsyncInvoke(reflect(new C())); // InstanceMirror
if (isDart2js) return;
testAsyncInvoke(reflectClass(D)); // ClassMirror
testAsyncInvoke(lib); // LibraryMirror
testSyncNewInstance();
testAsyncNewInstance();
testSyncApply();
testAsyncApply();
}
+6 -4
View File
@@ -6,6 +6,8 @@ library test.invoke_test;
import 'dart:mirrors';
import 'dart:async' show Future;
import 'package:expect/expect.dart';
import "package:async_helper/async_helper.dart";
@@ -18,7 +20,7 @@ class C {
set setter(v) => field = 'set $v';
method(x, y, z) => '$x+$y+$z';
toString() => 'a C';
noSuchMethod(invocation) => 'DNU';
static var staticField = 'initial';
@@ -34,7 +36,7 @@ libraryFunction(x,y) => '$x$y';
Future expectValueThen(Future future, Function onValue) {
asyncStart();
wrappedOnValue(resultIn) {
wrappedOnValue(resultIn) {
var resultOut = onValue(resultIn);
asyncEnd();
return resultOut;
@@ -47,7 +49,7 @@ Future expectValueThen(Future future, Function onValue) {
Future expectError(Future future, Function errorPredicate, String reason) {
asyncStart();
onValue(result) {
onValue(result) {
Expect.fail("Error expected ($reason)");
}
onError(e) {
@@ -273,7 +275,7 @@ testAsync() {
}).then((result) {
Expect.equals('sbar', result.reflectee);
Expect.equals('sbar', C.staticField);
return cm.setFieldAsync(const Symbol('staticField'), im);;
return cm.setFieldAsync(const Symbol('staticField'), im);
}).then((result) {
Expect.equals(im.reflectee, result.reflectee);
Expect.equals(c, C.staticField);