Version 1.3.0-dev.7.9

svn merge -c 34598 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@34646 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/reflection_data_parser.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/reflection_data_parser.dart
index 8605883..1e7210f 100644
--- a/sdk/lib/_internal/compiler/implementation/js_emitter/reflection_data_parser.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_emitter/reflection_data_parser.dart
@@ -105,6 +105,7 @@
 
     if (getterStubName) {
       f = tearOff(funcs, array, isStatic, name, isIntercepted);
+      f.getterStub = true;
 '''
       /* Used to create an isolate using spawnFunction.*/
 '''
@@ -114,28 +115,29 @@
       if (getterStubName) functions.push(getterStubName);
       f.\$stubName = getterStubName;
       f.\$callName = null;
+      if (isIntercepted) init.interceptedNames[getterStubName] = true;
     }
     if (isReflectable) {
       for (var i = 0; i < funcs.length; i++) {
         funcs[i].$reflectableField = 1;
         funcs[i].$reflectionInfoField = array;
       }
-    }
-    if (isReflectable) {
+      var mangledNames = isStatic ? init.mangledGlobalNames : init.mangledNames;
       var unmangledName = ${readString("array", "unmangledNameIndex")};
-      var reflectionName =''' // Break long line.
-      ''' unmangledName + ":" + requiredParameterCount +''' // Break long line.
+'''
+      // The function is either a getter, a setter, or a method.
+      // If it is a method, it might also have a tear-off closure.
+      // The unmangledName is the same as the getter-name.
+'''
+      var reflectionName = unmangledName;
+      if (getterStubName) mangledNames[getterStubName] = reflectionName;
+      if (isSetter) {
+        reflectionName += "=";
+      } else if (!isGetter) {
+        reflectionName += ":" + requiredParameterCount +''' // Break long line.
       ''' ":" + optionalParameterCount;
-      if (isGetter) {
-        reflectionName = unmangledName;
-      } else if (isSetter) {
-        reflectionName = unmangledName + "=";
       }
-      if (isStatic) {
-        init.mangledGlobalNames[name] = reflectionName;
-      } else {
-        init.mangledNames[name] = reflectionName;
-      }
+      mangledNames[name] = reflectionName;
       funcs[0].$reflectionNameField = reflectionName;
       funcs[0].$metadataIndexField = unmangledNameIndex + 1;
       if (optionalParameterCount) descriptor[unmangledName + "*"] = funcs[0];
@@ -200,6 +202,7 @@
   if (!init.statics) init.statics = map();
   if (!init.typeInformation) init.typeInformation = map();
   if (!init.globalFunctions) init.globalFunctions = map();
+  if (!init.interceptedNames) init.interceptedNames = map();
   var libraries = init.libraries;
   var mangledNames = init.mangledNames;
   var mangledGlobalNames = init.mangledGlobalNames;
diff --git a/sdk/lib/_internal/lib/js_helper.dart b/sdk/lib/_internal/lib/js_helper.dart
index ed7f3c9..1ee856e 100644
--- a/sdk/lib/_internal/lib/js_helper.dart
+++ b/sdk/lib/_internal/lib/js_helper.dart
@@ -182,8 +182,14 @@
     // critical, we might want to dynamically change [interceptedNames]
     // to be a JavaScript object with intercepted names as property
     // instead of a JavaScript array.
+    // TODO(floitsch): we already add stubs (tear-off getters) as properties
+    // in init.interceptedNames.
+    // Finish the transition and always use the object as hashtable.
     bool isIntercepted =
-        JS('int', '#.indexOf(#)', interceptedNames, name) != -1;
+        JS("bool",
+            'Object.prototype.hasOwnProperty.call(init.interceptedNames, #) ||'
+            '#.indexOf(#) !== -1',
+            name, interceptedNames, name);
     if (isIntercepted) {
       receiver = interceptor;
       if (JS('bool', '# === #', object, interceptor)) {
@@ -210,7 +216,12 @@
       isCatchAll = true;
     }
     if (JS('bool', 'typeof # == "function"', method)) {
-      if (!hasReflectableProperty(method)) {
+      // TODO(floitsch): bound or tear-off closure does not guarantee that the
+      // function is reflectable.
+      bool isReflectable = hasReflectableProperty(method) ||
+          object is BoundClosure ||
+          object is TearOffClosure;
+      if (!isReflectable) {
         throwInvalidReflectionError(_symbol_dev.Symbol.getName(memberName));
       }
       if (isCatchAll) {
@@ -1970,6 +1981,7 @@
         isIntercepted = true;
       }
       trampoline = forwardCallTo(receiver, function, isIntercepted);
+      JS('', '#.\$reflectionInfo = #', trampoline, reflectionInfo);
     } else {
       JS('', '#.\$name = #', prototype, propertyName);
     }
@@ -2007,7 +2019,7 @@
       }
     }
 
-    JS('', '#["call*"] = #', prototype, function);
+    JS('', '#["call*"] = #', prototype, trampoline);
 
     return constructor;
   }
diff --git a/sdk/lib/_internal/lib/js_mirrors.dart b/sdk/lib/_internal/lib/js_mirrors.dart
index 6876993..f42ea42 100644
--- a/sdk/lib/_internal/lib/js_mirrors.dart
+++ b/sdk/lib/_internal/lib/js_mirrors.dart
@@ -386,10 +386,13 @@
       String name = _functions[i];
       var jsFunction = JS('', '#[#]', _globalObject, name);
       String unmangledName = mangledGlobalNames[name];
-      if (unmangledName == null) {
+      if (unmangledName == null ||
+          JS('bool', "!!#['getterStub']", jsFunction)) {
         // If there is no unmangledName, [jsFunction] is either a synthetic
         // implementation detail, or something that is excluded
         // by @MirrorsUsed.
+        // If it has a getterStub property it is a synthetic stub.
+        // TODO(floitsch): Remove the getterStub hack.
         continue;
       }
       bool isConstructor = unmangledName.startsWith('new ');
diff --git a/tests/compiler/dart2js_extra/dart2js_extra.status b/tests/compiler/dart2js_extra/dart2js_extra.status
index ae5c042..409e87d 100644
--- a/tests/compiler/dart2js_extra/dart2js_extra.status
+++ b/tests/compiler/dart2js_extra/dart2js_extra.status
@@ -10,6 +10,7 @@
 no_such_method_test: Fail # Wrong Invocation.memberName.
 deferred/deferred_constant_test: Fail # http://dartbug.com/11138
 constant_javascript_semantics4_test: Fail, OK
+mirrors_used_closure_test: Fail # Issue 17939
 
 [ $compiler == dart2js && $runtime == jsshell ]
 mirror_printer_test: Pass, Slow # Issue 16473
diff --git a/tests/compiler/dart2js_extra/mirrors_used_closure_test.dart b/tests/compiler/dart2js_extra/mirrors_used_closure_test.dart
new file mode 100644
index 0000000..28c19a8
--- /dev/null
+++ b/tests/compiler/dart2js_extra/mirrors_used_closure_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2014, 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.
+
+library MirrorsTest;
+
+import 'package:expect/expect.dart';
+
+@MirrorsUsed(targets: const ['A.foo', 'B.bar'])
+import 'dart:mirrors';
+
+class A {
+  foo() => 42;
+  bar() => 499;
+}
+
+class B {
+  bar() => 33;
+}
+
+// Uses DateTime.now to make it impossible to predict.
+// Uses recursive call to make it harder to inline.
+confuse(x) {
+  if (new DateTime.now().millisecondsSinceEpoch == 42) return confuse(x + 1);
+  return x;
+}
+
+main() {
+  var f = [new A(), new B()][confuse(0)].bar;
+  Expect.throws(() => reflect(f).invoke(#call, [], {}),
+                (e) => e is UnsupportedError);
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 4d091a2..f55445e 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -36,7 +36,6 @@
 mirrors/constructor_kinds_test: RuntimeError # Issue 13799
 mirrors/constructor_private_name_test: CompileTimeError # Issue 13597
 mirrors/delegate_call_through_getter_test: RuntimeError # Issue 15138
-mirrors/delegate_function_invocation_test: RuntimeError # Issue 6490
 mirrors/equality_test/02: RuntimeError # Issue 12785
 mirrors/fake_function_with_call_test: RuntimeError # Issue 11612
 mirrors/fake_function_without_call_test: RuntimeError # Issue 11612
@@ -64,7 +63,7 @@
 mirrors/invoke_call_through_getter_test: RuntimeError # Issue 15138
 mirrors/invoke_call_through_implicit_getter_previously_accessed_test: RuntimeError # Issue 15138
 mirrors/invoke_call_through_implicit_getter_test: RuntimeError # Issue 15138
-mirrors/invoke_closurization_test: RuntimeError # Issue 13002
+mirrors/invoke_closurization_test/static: RuntimeError # Issue 13002. When updating this status, please remove the "///" lines in the test.
 mirrors/invoke_named_test/none: RuntimeError # Issue 12863
 mirrors/invoke_private_test: CompileTimeError # Issue 12164
 mirrors/invoke_private_wrong_library_test: CompileTimeError # Issue 12164
diff --git a/tests/lib/mirrors/invoke_closurization2_test.dart b/tests/lib/mirrors/invoke_closurization2_test.dart
new file mode 100644
index 0000000..b768fba
--- /dev/null
+++ b/tests/lib/mirrors/invoke_closurization2_test.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2014, 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.
+
+library test.invoke_closurization_test;
+
+import 'dart:mirrors';
+
+import 'package:expect/expect.dart';
+
+class A {
+  foo() => "foo";
+  bar([x]) => "bar-$x";
+  gee({named}) => "gee-$named";
+
+  // Methods that must be intercepted.
+
+  // Tear-offs we will also get without mirrors.
+  codeUnitAt(x) => "codeUnitAt-$x";
+  toUpperCase() => "toUpperCase";
+  // indexOf takes an optional argument in String.
+  indexOf(x) => "indexOf-$x";
+  // lastIndexOf matches signature from String.
+  lastIndexOf(x, [y]) => "lastIndexOf-$x,$y";
+  // splitMapJoin matches signature from String.
+  splitMapJoin(x, {onMatch, onNonMatch}) =>
+      "splitMapJoin-$x,$onMatch,$onNonMatch";
+  // Same name as intercepted, but with named argument.
+  trim({named}) => "trim-$named";
+
+  // Tear-offs we will not call directly.
+  endsWith(x) => "endsWith-$x";
+  toLowerCase() => "toLowerCase";
+  // matchAsPrefix matches signature from String.
+  matchAsPrefix(x, [y = 0]) => "matchAsPrefix-$x,$y";
+  // Matches signature from List
+  toList({growable: true}) => "toList-$growable";
+  // Same name as intercepted, but with named argument.
+  toSet({named}) => "toSet-$named";
+}
+
+// The recursive call makes inlining difficult.
+// The use of DateTime.now makes the result unpredictable.
+confuse(x) {
+  if (new DateTime.now().millisecondsSinceEpoch == 42) {
+    return confuse(new DateTime.now().millisecondsSinceEpoch);
+  }
+  return x;
+}
+
+main() {
+  var list = ["foo", new List(), new A()];
+
+  getAMirror() => reflect(list[confuse(2)]);
+
+  // Tear-off without mirrors.
+  var f = confuse(getAMirror().reflectee.codeUnitAt);
+  Expect.equals("codeUnitAt-42", f(42));
+  f = confuse(getAMirror().reflectee.toUpperCase);
+  Expect.equals("toUpperCase", f());
+  f = confuse(getAMirror().reflectee.indexOf);
+  Expect.equals("indexOf-499", f(499));
+  f = confuse(getAMirror().reflectee.lastIndexOf);
+  Expect.equals("lastIndexOf-FOO,BAR", f("FOO", "BAR"));
+  f = confuse(getAMirror().reflectee.splitMapJoin);
+  Expect.equals("splitMapJoin-1,2,3", f(1, onMatch: 2, onNonMatch: 3));
+  f = confuse(getAMirror().reflectee.trim);
+  Expect.equals("trim-true", f(named: true));
+
+  // Now the same thing through mirrors.
+  f = getAMirror().getField(#codeUnitAt).reflectee;
+  Expect.equals("codeUnitAt-42", f(42));
+  f = getAMirror().getField(#toUpperCase).reflectee;
+  Expect.equals("toUpperCase", f());
+  f = getAMirror().getField(#indexOf).reflectee;
+  Expect.equals("indexOf-499", f(499));
+  f = getAMirror().getField(#lastIndexOf).reflectee;
+  Expect.equals("lastIndexOf-FOO,BAR", f("FOO", "BAR"));
+  f = getAMirror().getField(#splitMapJoin).reflectee;
+  Expect.equals("splitMapJoin-1,2,3", f(1, onMatch: 2, onNonMatch: 3));
+  f = getAMirror().getField(#trim).reflectee;
+  Expect.equals("trim-true", f(named: true));
+
+  // Now the same thing through mirrors and mirror-invocation.
+  f = getAMirror().getField(#codeUnitAt);
+  Expect.equals("codeUnitAt-42", f.invoke(#call, [42], {}).reflectee);
+  f = getAMirror().getField(#toUpperCase);
+  Expect.equals("toUpperCase", f.invoke(#call, [], {}).reflectee);
+  f = getAMirror().getField(#indexOf);
+  Expect.equals("indexOf-499", f.invoke(#call, [499], {}).reflectee);
+  f = getAMirror().getField(#lastIndexOf);
+  Expect.equals("lastIndexOf-FOO,BAR",
+                f.invoke(#call, ["FOO", "BAR"]).reflectee);
+  f = getAMirror().getField(#splitMapJoin);
+  Expect.equals("splitMapJoin-1,2,3",
+                f.invoke(#call, [1], {#onMatch: 2, #onNonMatch: 3}).reflectee);
+  f = getAMirror().getField(#trim);
+  Expect.equals("trim-true", f.invoke(#call, [], {#named: true}).reflectee);
+
+  // Tear-offs only through mirrors. (No direct selector in the code).
+  // --------
+
+  f = getAMirror().getField(#endsWith).reflectee;
+  Expect.equals("endsWith-42", f(42));
+  f = getAMirror().getField(#toLowerCase).reflectee;
+  Expect.equals("toLowerCase", f());
+  f = getAMirror().getField(#indexOf).reflectee;
+  Expect.equals("indexOf-499", f(499));
+  f = getAMirror().getField(#matchAsPrefix).reflectee;
+  Expect.equals("matchAsPrefix-FOO,BAR", f("FOO", "BAR"));
+  f = getAMirror().getField(#toList).reflectee;
+  Expect.equals("toList-1", f(growable: 1));
+  f = getAMirror().getField(#toSet).reflectee;
+  Expect.equals("toSet-true", f(named: true));
+
+  f = getAMirror().getField(#endsWith);
+  Expect.equals("endsWith-42", f.invoke(#call, [42], {}).reflectee);
+  f = getAMirror().getField(#toLowerCase);
+  Expect.equals("toLowerCase", f.invoke(#call, [], {}).reflectee);
+  f = getAMirror().getField(#indexOf);
+  Expect.equals("indexOf-499", f.invoke(#call, [499], {}).reflectee);
+  f = getAMirror().getField(#matchAsPrefix);
+  Expect.equals("matchAsPrefix-FOO,BAR",
+                f.invoke(#call, ["FOO", "BAR"]).reflectee);
+  f = getAMirror().getField(#toList);
+  Expect.equals("toList-1",
+                f.invoke(#call, [], {#growable: 1}).reflectee);
+  f = getAMirror().getField(#toSet);
+  Expect.equals("toSet-true", f.invoke(#call, [], {#named: true}).reflectee);
+}
diff --git a/tests/lib/mirrors/invoke_closurization_test.dart b/tests/lib/mirrors/invoke_closurization_test.dart
index 1039161..0b25ff0 100644
--- a/tests/lib/mirrors/invoke_closurization_test.dart
+++ b/tests/lib/mirrors/invoke_closurization_test.dart
@@ -7,7 +7,6 @@
 import 'dart:mirrors';
 
 import 'package:expect/expect.dart';
-import 'invoke_test.dart';
 
 class C {
   instanceMethod(x, y, z) => '$x+$y+$z';
@@ -25,14 +24,14 @@
   Expect.equals("A+B+C", result.reflectee('A', 'B', 'C'));
 
   ClassMirror cm = reflectClass(C);
-  result = cm.getField(#staticFunction);
-  Expect.isTrue(result.reflectee is Function, "Should be closure");
-  Expect.equals("A-B-C", result.reflectee('A', 'B', 'C'));
+  result = cm.getField(#staticFunction);                             /// static: ok
+  Expect.isTrue(result.reflectee is Function, "Should be closure");  /// static: continued
+  Expect.equals("A-B-C", result.reflectee('A', 'B', 'C'));           /// static: continued
 
   LibraryMirror lm = cm.owner;
-  result = lm.getField(#libraryFunction);
-  Expect.isTrue(result.reflectee is Function, "Should be closure");
-  Expect.equals("A:B:C", result.reflectee('A', 'B', 'C'));
+  result = lm.getField(#libraryFunction);                            /// static: continued
+  Expect.isTrue(result.reflectee is Function, "Should be closure");  /// static: continued
+  Expect.equals("A:B:C", result.reflectee('A', 'B', 'C'));           /// static: continued
 }
 
 main() {
diff --git a/tools/VERSION b/tools/VERSION
index 78d81a4..22844ac 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 3
 PATCH 0
 PRERELEASE 7
-PRERELEASE_PATCH 8
+PRERELEASE_PATCH 9