Test an odd interaction of implicit `.call` tearoffs with promotion.

Currently, we decide whether to insert an implicit `.call` tearoff on
an expression based on the type the expression is being assigned to,
whereas the spec says that we decide it based on the context type.

In most cases, these are the same type, however when assigning to a
promoted variable, the context type is the promoted type, whereas the
"type being assigned to" is the unpromoted type.

Previously, we had no language tests of this behavior; it just emerged
naturally from the way it was implemented in the CFE.  This test locks
down the behavior so that we won't change it by mistake.

We may, at some future time, decide to change it on purpose though.
Discussion about this behavior is ongoing on the langauge team.

Change-Id: I82f54c8875fd3b5829c5f75a676fdaf5eea64703
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/233650
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/tests/language/call/implicit_tearoff_local_assignment_test.dart b/tests/language/call/implicit_tearoff_local_assignment_test.dart
new file mode 100644
index 0000000..0f77761
--- /dev/null
+++ b/tests/language/call/implicit_tearoff_local_assignment_test.dart
@@ -0,0 +1,123 @@
+// 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 verifies that when considering whether to perform a `.call` tearoff
+// on the RHS of an assignment, the implementations use the unpromoted type of
+// the variable (rather than the promoted type).
+
+// NOTICE: This test checks the currently implemented behavior, even though the
+// implemented behavior does not match the language specification.  Until an
+// official decision has been made about whether to change the implementation to
+// match the specification, or vice versa, this regression test is intended to
+// protect against inadvertent implementation changes.
+
+import "package:expect/expect.dart";
+
+import '../static_type_helper.dart';
+
+class B {
+  Object call() => 'B.call called';
+}
+
+class C extends B {
+  String call() => 'C.call called';
+}
+
+void testClassPromoted() {
+  B x = C();
+  x as C;
+  x.expectStaticType<Exactly<C>>();
+  var y = x = C(); // No implicit tearoff of `.call`, no demotion
+  x.expectStaticType<Exactly<C>>();
+  Expect.type<C>(x);
+  Expect.equals('C.call called', x());
+  y.expectStaticType<Exactly<C>>();
+  Expect.type<C>(y);
+  Expect.equals('C.call called', y());
+}
+
+void testClassUnpromoted() {
+  B x = B();
+  var y = x = C(); // No implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<B>>();
+  Expect.type<C>(x);
+  Expect.equals('C.call called', x());
+  y.expectStaticType<Exactly<C>>();
+  Expect.type<C>(y);
+  Expect.equals('C.call called', y());
+}
+
+void testFunctionPromoted() {
+  String f() => 'f called';
+  Object Function() x = f;
+  x as String Function();
+  x.expectStaticType<Exactly<String Function()>>();
+  var y = x = C(); // Implicit tearoff of `.call`, no demotion
+  x.expectStaticType<Exactly<String Function()>>();
+  Expect.type<String Function()>(x);
+  Expect.equals('C.call called', x());
+  y.expectStaticType<Exactly<String Function()>>();
+  Expect.type<String Function()>(y);
+  Expect.equals('C.call called', y());
+}
+
+void testFunctionUnpromoted() {
+  Object f() => 'f called';
+  Object Function() x = f;
+  var y = x = B(); // Implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<Object Function()>>();
+  Expect.type<Object Function()>(x);
+  Expect.equals('B.call called', x());
+  y.expectStaticType<Exactly<Object Function()>>();
+  Expect.type<Object Function()>(y);
+  Expect.equals('B.call called', y());
+}
+
+void testObjectPromotedToClass() {
+  Object x = B();
+  x as B;
+  x.expectStaticType<Exactly<B>>();
+  var y = x = C(); // No implicit tearoff of `.call`, x remains promoted
+  x.expectStaticType<Exactly<B>>();
+  Expect.type<C>(x);
+  Expect.equals('C.call called', x());
+  y.expectStaticType<Exactly<C>>();
+  Expect.type<C>(y);
+  Expect.equals('C.call called', y());
+}
+
+void testObjectPromotedToFunction() {
+  Object f() => 'f called';
+  Object x = f;
+  x as Object Function();
+  x.expectStaticType<Exactly<Object Function()>>();
+  var y = x = B(); // No implicit tearoff of `.call`, demotes x
+  x.expectStaticType<Exactly<Object>>();
+  Expect.type<B>(x);
+  Expect.equals('B.call called', (x as B)());
+  y.expectStaticType<Exactly<B>>();
+  Expect.type<B>(y);
+  Expect.equals('B.call called', y());
+}
+
+void testObjectUnpromoted() {
+  Object x = 'initial value';
+  var y = x = B(); // No implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<Object>>();
+  Expect.type<B>(x);
+  Expect.equals('B.call called', (x as B)());
+  y.expectStaticType<Exactly<B>>();
+  Expect.type<B>(y);
+  Expect.equals('B.call called', y());
+}
+
+main() {
+  testClassPromoted();
+  testClassUnpromoted();
+  testFunctionPromoted();
+  testFunctionUnpromoted();
+  testObjectPromotedToClass();
+  testObjectPromotedToFunction();
+  testObjectUnpromoted();
+}
diff --git a/tests/language_2/call/implicit_tearoff_local_assignment_test.dart b/tests/language_2/call/implicit_tearoff_local_assignment_test.dart
new file mode 100644
index 0000000..210ace0
--- /dev/null
+++ b/tests/language_2/call/implicit_tearoff_local_assignment_test.dart
@@ -0,0 +1,65 @@
+// 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 is the pre-null-safety variant of the null safety test
+// `implicit_tearoff_local_assignment_test.dart`, which verifies that when
+// considering whether to perform a `.call` tearoff on the RHS of an assignment,
+// the implementations use the unpromoted type of the variable (rather than the
+// promoted type).  For the pre-null-safety variant, the same logic doesn't
+// really apply, because a variable can't be promoted in a block that contains
+// an assignment to it.  But we can still test the unpromoted cases.
+
+// @dart = 2.9
+
+import "package:expect/expect.dart";
+
+import '../static_type_helper.dart';
+
+class B {
+  Object call() => 'B.call called';
+}
+
+class C extends B {
+  String call() => 'C.call called';
+}
+
+void testClassUnpromoted() {
+  B x = B();
+  var y = x = C(); // No implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<B>>();
+  Expect.type<C>(x);
+  Expect.equals('C.call called', x());
+  y.expectStaticType<Exactly<C>>();
+  Expect.type<C>(y);
+  Expect.equals('C.call called', y());
+}
+
+void testFunctionUnpromoted() {
+  Object f() => 'f called';
+  Object Function() x = f;
+  var y = x = B(); // Implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<Object Function()>>();
+  Expect.type<Object Function()>(x);
+  Expect.equals('B.call called', x());
+  y.expectStaticType<Exactly<Object Function()>>();
+  Expect.type<Object Function()>(y);
+  Expect.equals('B.call called', y());
+}
+
+void testObjectUnpromoted() {
+  Object x = 'initial value';
+  var y = x = B(); // No implicit tearoff of `.call`, no promotion
+  x.expectStaticType<Exactly<Object>>();
+  Expect.type<B>(x);
+  Expect.equals('B.call called', (x as B)());
+  y.expectStaticType<Exactly<B>>();
+  Expect.type<B>(y);
+  Expect.equals('B.call called', y());
+}
+
+main() {
+  testClassUnpromoted();
+  testFunctionUnpromoted();
+  testObjectUnpromoted();
+}