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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
Reference in New Issue
Block a user