Add test about evaluation order
This CL adds a test of the evaluation order in the case where a function
invocation `g(a)` or `r.g(a)` involves the invocation of a getter `g` that
returns a function object, and that function object is invoked with an
actual argument `a`.
The expectation in the test is that evaluation occurs left-to-right in
every case, with one exception: when `g` is a class instance getter (this
does not apply to extension instance getters) the actual argument list
is evaluated before the getter. This is the actually implemented behavior,
and the specification is being updated to specify this behavior
(cf. https://github.com/dart-lang/language/pull/2182).
------- Old description:
A piece of technical debt which has been around for several years is the
fact that the specified left-to-right evaluation order isn't implemented
everywhere.
In particular, with an ordinary invocation like `r.m(a)` where `m` is a
getter that returns a function, the argument is evaluated before the
getter is called, which is not a left-to-right ordering.
This CL adds a test (with 2 libraries) where the evaluation order is
detected, such that we can decide how to proceed.
Change-Id: Ia2619fe6b4c4cf4cec63bac9c9f834306bdefe52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238903
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Erik Ernst <eernst@google.com>
diff --git a/tests/language/call/evaluation_order_lib.dart b/tests/language/call/evaluation_order_lib.dart
new file mode 100644
index 0000000..dd06624
--- /dev/null
+++ b/tests/language/call/evaluation_order_lib.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, 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.
+
+// Used from 'evaluation_order_test.dart'.
+
+import 'evaluation_order_test.dart';
+
+get arg => argumentEffect();
+
+void Function(void) get m {
+  getterEffect();
+  return (_) {};
+}
diff --git a/tests/language/call/evaluation_order_test.dart b/tests/language/call/evaluation_order_test.dart
new file mode 100644
index 0000000..5d88df7
--- /dev/null
+++ b/tests/language/call/evaluation_order_test.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2022, 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 tests the evaluation order in the case where a function
+// invocation `m(a)` or `r.m(a)` involves the invocation of a getter `m`
+// that returns a function object, and that function object is invoked
+// with an actual argument `arg`.
+//
+// The expectation is that evaluation occurs left-to-right in every case,
+// with one exception: when `m` is a class instance getter (this does not
+// even apply to extension instance getters) the actual argument list is
+// evaluated before the getter.
+
+import 'package:expect/expect.dart';
+import 'evaluation_order_lib.dart' as lib;
+
+String effects = '';
+
+void clearEffects() {
+  effects = '';
+}
+
+void getterEffect() {
+  effects += 'G';
+}
+
+void argumentEffect() {
+  effects += 'A';
+}
+
+get arg => argumentEffect();
+
+class A {
+  void Function(void) get m {
+    getterEffect();
+    return (_) {};
+  }
+
+  static void Function(void) get n {
+    getterEffect();
+    return (_) {};
+  }
+}
+
+class B extends A {
+  void doTest() {
+    test('Instance getter on explicit this', 'AG', () => this.m(arg));
+    test('Instance getter on implicit this', 'GA', () => m(arg));
+    test('Instance getter on super', 'GA', () => super.m(arg));
+  }
+}
+
+mixin M on A {
+  void doTest() {
+    test('Instance getter on explicit this', 'AG', () => this.m(arg));
+    test('Instance getter on implicit this', 'GA', () => m(arg));
+    test('Instance getter on super', 'GA', () => super.m(arg));
+  }
+}
+
+class AM = A with M;
+class MockAM = MockA with M;
+
+class MockA implements A {
+  noSuchMethod(Invocation i) {
+    getterEffect();
+    return (_) {};
+  }
+
+  void Function(void) get m;
+}
+
+void Function(void) get m {
+  getterEffect();
+  return (_) {};
+}
+
+extension E on int {
+  void Function(void) get m {
+    getterEffect();
+    return (_) {};
+  }
+
+  static void Function(void) get n {
+    getterEffect();
+    return (_) {};
+  }
+}
+
+void test(String name, String expectation, void Function() code) {
+  clearEffects();
+  code();
+  Expect.equals(expectation, effects, name);
+}
+
+main() {
+  var a = A();
+  dynamic d = a;
+  A mockA = MockA();
+  dynamic mockD = mockA;
+
+  test('Instance getter on A', 'AG', () => a.m(arg));
+  test('Instance getter on dynamic A', 'AG', () => d.m(arg));
+  test('Instance getter on MockA', 'AG', () => mockA.m(arg));
+  test('Instance getter on dynamic MockA', 'AG', () => mockD.m(arg));
+  test('Static getter', 'GA', () => A.n(arg));
+  test('Top-level getter', 'GA', () => m(arg));
+  test('Prefix-imported getter', 'GA', () => lib.m(arg));
+  test('Extension instance getter', 'GA', () => 1.m(arg));
+  test('Extension static getter', 'GA', () => E.n(arg));
+  B().doTest();
+  AM().doTest();
+  MockAM().doTest();
+}