diff --git a/sdk/lib/_internal/compiler/implementation/js/builder.dart b/sdk/lib/_internal/compiler/implementation/js/builder.dart index b0424c6195d..36433e82a3f 100644 --- a/sdk/lib/_internal/compiler/implementation/js/builder.dart +++ b/sdk/lib/_internal/compiler/implementation/js/builder.dart @@ -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(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'); diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart index 881df3e193b..dada5b1370a 100644 --- a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart +++ b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart @@ -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 defaultValues = []; + 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]; } diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart b/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart index 10c423dede8..141cd89dcad 100644 --- a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart +++ b/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart @@ -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 diff --git a/sdk/lib/_internal/lib/js_helper.dart b/sdk/lib/_internal/lib/js_helper.dart index 9964012bb7c..ddeb666771c 100644 --- a/sdk/lib/_internal/lib/js_helper.dart +++ b/sdk/lib/_internal/lib/js_helper.dart @@ -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 diff --git a/sdk/lib/_internal/lib/js_mirrors.dart b/sdk/lib/_internal/lib/js_mirrors.dart index 27233312a89..8453ab67504 100644 --- a/sdk/lib/_internal/lib/js_mirrors.dart +++ b/sdk/lib/_internal/lib/js_mirrors.dart @@ -647,9 +647,6 @@ class JsInstanceMirror extends JsObjectMirror implements InstanceMirror { Future invokeAsync(Symbol memberName, List positionalArguments, [Map namedArguments]) { - if (namedArguments != null && !namedArguments.isEmpty) { - throw new UnsupportedError('Named arguments are not implemented.'); - } return new Future( () => invoke(memberName, positionalArguments, namedArguments)); @@ -658,12 +655,55 @@ class JsInstanceMirror extends JsObjectMirror implements InstanceMirror { InstanceMirror invoke(Symbol memberName, List positionalArguments, [Map 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 argumentNames = []; + List 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 implements Map { 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"; +} diff --git a/tests/lib/lib.status b/tests/lib/lib.status index 2877fe9510b..18ef5db7a4f 100644 --- a/tests/lib/lib.status +++ b/tests/lib/lib.status @@ -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. diff --git a/tests/lib/mirrors/invoke_named_test.dart b/tests/lib/mirrors/invoke_named_test.dart index 2405ba8c932..7da881bbe78 100644 --- a/tests/lib/mirrors/invoke_named_test.dart +++ b/tests/lib/mirrors/invoke_named_test.dart @@ -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(); } diff --git a/tests/lib/mirrors/invoke_test.dart b/tests/lib/mirrors/invoke_test.dart index 3ba673251c8..2d225d18499 100644 --- a/tests/lib/mirrors/invoke_test.dart +++ b/tests/lib/mirrors/invoke_test.dart @@ -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);