Implement Function.apply in dart2js.

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@13409 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
ngeoffray@google.com 2012-10-09 12:02:04 +00:00
parent b38daebd36
commit e24c0e7af5
8 changed files with 140 additions and 62 deletions

View file

@ -131,6 +131,7 @@ abstract class Compiler implements DiagnosticListener {
ClassElement listClass;
Element assertMethod;
Element identicalFunction;
Element functionApplyMethod;
Element get currentElement => _currentElement;
withCurrentElement(Element element, f()) {
@ -178,6 +179,7 @@ abstract class Compiler implements DiagnosticListener {
const SourceString('startRootIsolate');
bool enabledNoSuchMethod = false;
bool enabledRuntimeType = false;
bool enabledFunctionApply = false;
Stopwatch progress;
@ -384,6 +386,10 @@ abstract class Compiler implements DiagnosticListener {
identicalFunction = coreLibrary.find(const SourceString('identical'));
initializeSpecialClasses();
functionClass.ensureResolved(this);
functionApplyMethod =
functionClass.lookupLocalMember(const SourceString('apply'));
}
void loadCoreImplLibrary() {

View file

@ -101,6 +101,8 @@ class Enqueuer {
// runtime type.
if (element.isGetter() && element.name == Compiler.RUNTIME_TYPE) {
compiler.enabledRuntimeType = true;
} else if (element == compiler.functionApplyMethod) {
compiler.enabledFunctionApply = true;
}
// Enable isolate support if we start using something from the

View file

@ -476,20 +476,97 @@ function(prototype, staticName, fieldName, getterName, lazyValue) {
//
// We need to generate a stub for (5) because the order of the
// stub arguments and the real method may be different.
Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
if (selectors == null) return;
// Keep a cache of which stubs have already been generated, to
// avoid duplicates. Note that even if selectors are
// canonicalized, we would still need this cache: a typed selector
// on A and a typed selector on B could yield the same stub.
Set<String> generatedStubNames = new Set<String>();
for (Selector selector in selectors) {
if (!selector.applies(member, compiler)) continue;
addParameterStub(
member, selector, defineInstanceMember, generatedStubNames);
if (compiler.enabledFunctionApply
&& member.name == Namer.CLOSURE_INVOCATION_NAME) {
// If [Function.apply] is called, we pessimistically compile all
// possible stubs for this closure.
// TODO(5074): This functionality only supports the new
// parameter specification, and this comment should be removed
// once the old specification is not supported.
FunctionSignature signature = member.computeSignature(compiler);
Set<Selector> selectors = signature.optionalParametersAreNamed
? computeNamedSelectors(signature, member)
: computeOptionalSelectors(signature, member);
for (Selector selector in selectors) {
addParameterStub(
member, selector, defineInstanceMember, generatedStubNames);
}
} else {
Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
if (selectors == null) return;
for (Selector selector in selectors) {
if (!selector.applies(member, compiler)) continue;
addParameterStub(
member, selector, defineInstanceMember, generatedStubNames);
}
}
}
/**
* Compute the set of possible selectors in the presence of named
* parameters.
*/
Set<Selector> computeNamedSelectors(FunctionSignature signature,
FunctionElement element) {
Set<Selector> selectors = new Set<Selector>();
// Add the selector that does not have any optional argument.
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount,
<SourceString>[]));
// For each optional parameter, we iterator over the set of
// already computed selectors and create new selectors with that
// parameter now being passed.
signature.forEachOptionalParameter((Element element) {
Set<Selector> newSet = new Set<Selector>();
selectors.forEach((Selector other) {
List<SourceString> namedArguments = [element.name];
namedArguments.addAll(other.namedArguments);
newSet.add(new Selector(other.kind,
other.name,
other.library,
other.argumentCount + 1,
namedArguments));
});
selectors.addAll(newSet);
});
return selectors;
}
/**
* Compute the set of possible selectors in the presence of optional
* non-named parameters.
*/
Set<Selector> computeOptionalSelectors(FunctionSignature signature,
FunctionElement element) {
Set<Selector> selectors = new Set<Selector>();
// Add the selector that does not have any optional argument.
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount,
<SourceString>[]));
// For each optional parameter, we increment the number of passed
// argument.
for (int i = 1; i <= signature.optionalParameterCount; i++) {
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount + i,
<SourceString>[]));
}
return selectors;
}
bool instanceFieldNeedsGetter(Element member) {
assert(member.isField());
return compiler.codegenWorld.hasInvokedGetter(member, compiler);

View file

@ -34,7 +34,8 @@ patch class Function {
patch static apply(Function function,
List positionalArguments,
[Map<String,Dynamic> namedArguments]) {
throw new NotImplementedException("Function.apply is not implemented");
return Primitives.applyFunction(
function, positionalArguments, namedArguments);
}
}

View file

@ -610,6 +610,46 @@ class Primitives {
}
JS('void', '#[#] = #', object, key, value);
}
static applyFunction(Function function,
List positionalArguments,
Map<String, Dynamic> namedArguments) {
int argumentCount = 0;
StringBuffer buffer = new StringBuffer();
List arguments = [];
if (positionalArguments != null) {
argumentCount += positionalArguments.length;
arguments.addAll(positionalArguments);
}
// Sort the named arguments to get the right selector name and
// arguments order.
if (namedArguments != null && !namedArguments.isEmpty()) {
// Call new List.from to make sure we get a JavaScript array.
List<String> listOfNamedArguments =
new List<String>.from(namedArguments.getKeys());
argumentCount += namedArguments.length;
// We're sorting on strings, and the behavior is the same between
// Dart string sort and JS string sort. To avoid needing the Dart
// sort implementation, we use the JavaScript one instead.
JS('void', '#.sort()', listOfNamedArguments);
listOfNamedArguments.forEach((String name) {
buffer.add('\$$name');
arguments.add(namedArguments[name]);
});
}
String selectorName = 'call\$$argumentCount$buffer';
var jsFunction = JS('var', '#[#]', function, selectorName);
if (jsFunction == null) {
throw new NoSuchMethodError(function, selectorName, arguments);
}
// We bound 'this' to [function] because of how we compile
// closures: escaped local variables are stored and accessed through
// [function].
return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
}
}
/**

View file

@ -1,46 +1,10 @@
// Copyright (c) 2012, 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.
//
// This test should move to a language test once the apply method
// gets into the specification.
apply(Function function, ArgumentDescriptor args) {
int argumentCount = 0;
StringBuffer buffer = new StringBuffer();
List arguments = [];
if (args.positionalArguments != null) {
argumentCount += args.positionalArguments.length;
arguments.addAll(args.positionalArguments);
}
// Sort the named arguments to get the right selector name and
// arguments order.
if (args.namedArguments != null && !args.namedArguments.isEmpty()) {
// Call new List.from to make sure we get a JavaScript array.
List<String> namedArguments =
new List<String>.from(args.namedArguments.getKeys());
argumentCount += namedArguments.length;
// We're sorting on strings, and the behavior is the same between
// Dart string sort and JS String sort. To avoid needing the Dart
// sort implementation, we use the JavaScript one instead.
JS('void', '#.sort()', namedArguments);
namedArguments.forEach((String name) {
buffer.add('\$$name');
arguments.add(args.namedArguments[name]);
});
}
String selectorName = 'call\$$argumentCount$buffer';
var jsFunction = JS('var', '#[#]', function, selectorName);
if (jsFunction == null) {
throw new NoSuchMethodError(function, selectorName, arguments);
}
// We bound 'this' to [function] because of how we compile
// closures: escaped local variables are stored and accessed through
// [function].
return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
return Function.apply(
function, args.positionalArguments, args.namedArguments);
}
class ArgumentDescriptor {
@ -62,17 +26,6 @@ main() {
var c5 = ({a: 1, b: 2}) => 'c5 $a $b';
var c6 = ({b: 1, a: 2}) => 'c6 $a $b';
// TODO(ngeoffray): Remove these calls. They are currently needed
// because otherwise we would not generate the stubs. Once apply is
// in the specification, we should change the compiler to emit stubs
// for closures once it sees an 'apply' selector.
c1();
c2(1);
c3(); c3(1);
c4(); c4(a: 1);
c5(); c5(a: 1); c5(b: 2); c5(a:1, b: 2);
c6(); c6(a: 1); c6(b: 2); c6(a:1, b: 2);
Expect.equals('c1', apply(c1, new ArgumentDescriptor(null, null)));
Expect.equals('c1', apply(c1, new ArgumentDescriptor([], null)));
Expect.equals('c1', apply(c1, new ArgumentDescriptor([], {})));
@ -91,9 +44,7 @@ main() {
Expect.equals('c3 1', apply(c3, new ArgumentDescriptor([], null)));
Expect.equals('c3 2', apply(c3, new ArgumentDescriptor([2], {})));
throwsNSME(() => apply(c3, new ArgumentDescriptor([1, 2], null)));
// TODO(ngeoffray): Should be throwsNSME with the new parameter
// specification.
Expect.equals('c3 1', apply(c3, new ArgumentDescriptor(null, {'a': 1})));
throwsNSME(() => apply(c3, new ArgumentDescriptor(null, {'a': 1})));
Expect.equals('c4 1', apply(c4, new ArgumentDescriptor([], null)));
Expect.equals('c4 2', apply(c4, new ArgumentDescriptor([], {'a': 2})));

View file

@ -48,7 +48,7 @@ main() {
Expect.equals(res, Function.apply(func, list, map));
}
testList(42, test0, null);
testList(42, test0, []]);
testList(42, test0, []);
testMap(42, test0a, {"a": 5});
testList(42, test1, [41]);
test(42, test1a, [20], {"a": 22});

View file

@ -9,6 +9,7 @@ compare_to2_test: Fail # Bug 4018
null_test: Fail # Bug 5511
null_nosuchmethod_test: Fail # Bug 5518
apply_test: Fail # Bug 5670
apply2_test: Fail # Bug 5670
[ $compiler == dartc ]
apply_test: Fail # Bug 5670
@ -38,7 +39,6 @@ unicode_test: Fail
[ $compiler == dart2js ]
math_parse_double_test: Fail # Expect.equals(expected: <78187493520>, actual: <0>)
math_test: Fail # issue 3333
apply_test: Fail # issue 5669
# Bad test, assumes RegExp.allMatches returns a Collection.
reg_exp_all_matches_test: Fail, OK # NoSuchMethodError : method not found: 'forEach'
@ -66,7 +66,8 @@ reg_exp_all_matches_test: Fail # BUG(3304): Maybe this doesn't time out?
string_base_vm_test: Fail # BUG(3304): Maybe this doesn't time out?
[ $compiler == dart2dart ]
apply_test: Fail # inherited from dart2js
apply_test: Fail # inherited from VM
apply2_test: Fail # inherited from VM
compare_to2_test: Fail # inherited from VM
null_nosuchmethod_test: Fail # inherited from VM
null_test: Fail # inherited from VM