Infer Object members for dynamic receivers

For method, getter, and setter invocations with names of methods on
Object on expressions with static type `dynamic`, if the invocation
cannot possibly be an invocation of noSuchMethod, infer the type of
the invocation using the type of the member of Object.

This implements the feature spec
https://github.com/dart-lang/sdk/commit/472ec7780f1bb38f049d0fcc903a69bfb8ef9133

Fixes https://github.com/dart-lang/sdk/issues/32414

Change-Id: I135156346fe1468561d56a01cf3c5f0efde30739
Reviewed-on: https://dart-review.googlesource.com/56942
Commit-Queue: Kevin Millikin <kmillikin@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index 361dd77..472c8da 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -547,8 +547,14 @@
   /// Finds a member of [receiverType] called [name], and if it is found,
   /// reports it through instrumentation using [fileOffset].
   ///
-  /// For the special case where [receiverType] is a [FunctionType], and the
-  /// method name is `call`, the string `call` is returned as a sentinel object.
+  /// For the case where [receiverType] is a [FunctionType], and the name
+  /// is `call`, the string 'call' is returned as a sentinel object.
+  ///
+  /// For the case where [receiverType] is `dynamic`, and the name is declared
+  /// in Object, the member from Object is returned though the call may not end
+  /// up targeting it if the arguments do not match (the basic principle is that
+  /// the Object member is used for inferring types only if noSuchMethod cannot
+  /// be targeted due to, e.g., an incorrect argument count).
   Object findInterfaceMember(DartType receiverType, Name name, int fileOffset,
       {Template<Message Function(String, DartType)> errorTemplate,
       Expression expression,
@@ -568,16 +574,15 @@
       return 'call';
     }
 
-    Member interfaceMember;
-    if (receiverType is! DynamicType) {
-      Class classNode = receiverType is InterfaceType
-          ? receiverType.classNode
-          : coreTypes.objectClass;
-      interfaceMember = _getInterfaceMember(classNode, name, setter);
-      if (!silent && interfaceMember != null) {
-        instrumentation?.record(uri, fileOffset, 'target',
-            new InstrumentationValueForMember(interfaceMember));
-      }
+    Class classNode = receiverType is InterfaceType
+        ? receiverType.classNode
+        : coreTypes.objectClass;
+    Member interfaceMember = _getInterfaceMember(classNode, name, setter);
+    if (!silent &&
+        receiverType != const DynamicType() &&
+        interfaceMember != null) {
+      instrumentation?.record(uri, fileOffset, 'target',
+          new InstrumentationValueForMember(interfaceMember));
     }
 
     if (!isTopLevel &&
@@ -600,8 +605,8 @@
     return interfaceMember;
   }
 
-  /// Finds a member of [receiverType] called [name], and if it is found,
-  /// reports it through instrumentation and records it in [methodInvocation].
+  /// Finds a member of [receiverType] called [name] and records it in
+  /// [methodInvocation].
   Object findMethodInvocationMember(
       DartType receiverType, InvocationExpression methodInvocation,
       {bool silent: false}) {
@@ -614,11 +619,26 @@
           expression: methodInvocation,
           receiver: methodInvocation.receiver,
           silent: silent);
-      if (strongMode && interfaceMember is Member) {
+      if (receiverType == const DynamicType() && interfaceMember is Procedure) {
+        var arguments = methodInvocation.arguments;
+        var signature = interfaceMember.function;
+        if (arguments.positional.length < signature.requiredParameterCount ||
+            arguments.positional.length >
+                signature.positionalParameters.length) {
+          return null;
+        }
+        for (var argument in arguments.named) {
+          if (!signature.namedParameters
+              .any((declaration) => declaration.name == argument.name)) {
+            return null;
+          }
+        }
+      } else if (strongMode && interfaceMember is Member) {
         methodInvocation.interfaceTarget = interfaceMember;
       }
       return interfaceMember;
     } else if (methodInvocation is SuperMethodInvocation) {
+      assert(receiverType != const DynamicType());
       var interfaceMember = findInterfaceMember(
           receiverType, methodInvocation.name, methodInvocation.fileOffset,
           silent: silent);
@@ -645,11 +665,14 @@
           expression: propertyGet,
           receiver: propertyGet.receiver,
           silent: silent);
-      if (strongMode && interfaceMember is Member) {
+      if (strongMode &&
+          receiverType != const DynamicType() &&
+          interfaceMember is Member) {
         propertyGet.interfaceTarget = interfaceMember;
       }
       return interfaceMember;
     } else if (propertyGet is SuperPropertyGet) {
+      assert(receiverType != const DynamicType());
       var interfaceMember = findInterfaceMember(
           receiverType, propertyGet.name, propertyGet.fileOffset,
           silent: silent);
@@ -675,11 +698,14 @@
           receiver: propertySet.receiver,
           setter: true,
           silent: silent);
-      if (strongMode && interfaceMember is Member) {
+      if (strongMode &&
+          receiverType != const DynamicType() &&
+          interfaceMember is Member) {
         propertySet.interfaceTarget = interfaceMember;
       }
       return interfaceMember;
     } else if (propertySet is SuperPropertySet) {
+      assert(receiverType != const DynamicType());
       var interfaceMember = findInterfaceMember(
           receiverType, propertySet.name, propertySet.fileOffset,
           setter: true, silent: silent);
@@ -1353,7 +1379,9 @@
           errorTemplate: templateUndefinedGetter,
           expression: expression,
           receiver: receiver);
-      if (interfaceMember is Member) {
+      if (strongMode &&
+          receiverType != const DynamicType() &&
+          interfaceMember is Member) {
         desugaredGet.interfaceTarget = interfaceMember;
       }
     }
diff --git a/pkg/front_end/testcases/bug32414a.dart b/pkg/front_end/testcases/bug32414a.dart
new file mode 100644
index 0000000..825acca
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.
+
+void test() {
+  dynamic a = 5;
+  var b = a.toString();
+  b = 42;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/bug32414a.dart.direct.expect b/pkg/front_end/testcases/bug32414a.dart.direct.expect
new file mode 100644
index 0000000..95ea9bc
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart.direct.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+
+static method test() → void {
+  dynamic a = 5;
+  dynamic b = a.toString();
+  b = 42;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414a.dart.direct.transformed.expect b/pkg/front_end/testcases/bug32414a.dart.direct.transformed.expect
new file mode 100644
index 0000000..95ea9bc
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart.direct.transformed.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+
+static method test() → void {
+  dynamic a = 5;
+  dynamic b = a.toString();
+  b = 42;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414a.dart.outline.expect b/pkg/front_end/testcases/bug32414a.dart.outline.expect
new file mode 100644
index 0000000..a440da0
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart.outline.expect
@@ -0,0 +1,7 @@
+library;
+import self as self;
+
+static method test() → void
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/bug32414a.dart.strong.expect b/pkg/front_end/testcases/bug32414a.dart.strong.expect
new file mode 100644
index 0000000..ec5a9ff
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart.strong.expect
@@ -0,0 +1,13 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  dynamic a = 5;
+  core::String b = a.toString();
+  b = let final dynamic #t1 = let dynamic _ = null in invalid-expression "pkg/front_end/testcases/bug32414a.dart:8:7: Error: A value of type 'dart.core::int' can't be assigned to a variable of type 'dart.core::String'.
+Try changing the type of the left hand side, or casting the right hand side to 'dart.core::String'.
+  b = 42;
+      ^" in let final dynamic #t2 = 42 in null;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414a.dart.strong.transformed.expect b/pkg/front_end/testcases/bug32414a.dart.strong.transformed.expect
new file mode 100644
index 0000000..d4377bc
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414a.dart.strong.transformed.expect
@@ -0,0 +1,13 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  dynamic a = 5;
+  core::String b = a.toString();
+  b = let final dynamic #t1 = let<BottomType> _ = null in invalid-expression "pkg/front_end/testcases/bug32414a.dart:8:7: Error: A value of type 'dart.core::int' can't be assigned to a variable of type 'dart.core::String'.
+Try changing the type of the left hand side, or casting the right hand side to 'dart.core::String'.
+  b = 42;
+      ^" in let final core::int #t2 = 42 in null;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414b.dart b/pkg/front_end/testcases/bug32414b.dart
new file mode 100644
index 0000000..4f1ab51
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, 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.
+
+void test() {
+  List<dynamic> l = [1, "hello"];
+  List<String> l2 = l.map((dynamic element) => element.toString()).toList();
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/bug32414b.dart.direct.expect b/pkg/front_end/testcases/bug32414b.dart.direct.expect
new file mode 100644
index 0000000..1a8ece7
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart.direct.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  core::List<dynamic> l = <dynamic>[1, "hello"];
+  core::List<core::String> l2 = l.map((dynamic element) → dynamic => element.toString()).toList();
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414b.dart.direct.transformed.expect b/pkg/front_end/testcases/bug32414b.dart.direct.transformed.expect
new file mode 100644
index 0000000..1a8ece7
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart.direct.transformed.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  core::List<dynamic> l = <dynamic>[1, "hello"];
+  core::List<core::String> l2 = l.map((dynamic element) → dynamic => element.toString()).toList();
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414b.dart.outline.expect b/pkg/front_end/testcases/bug32414b.dart.outline.expect
new file mode 100644
index 0000000..a440da0
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart.outline.expect
@@ -0,0 +1,7 @@
+library;
+import self as self;
+
+static method test() → void
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/bug32414b.dart.strong.expect b/pkg/front_end/testcases/bug32414b.dart.strong.expect
new file mode 100644
index 0000000..b5242ff
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart.strong.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  core::List<dynamic> l = <dynamic>[1, "hello"];
+  core::List<core::String> l2 = l.{core::Iterable::map}<core::String>((dynamic element) → core::String => element.toString()).{core::Iterable::toList}();
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/bug32414b.dart.strong.transformed.expect b/pkg/front_end/testcases/bug32414b.dart.strong.transformed.expect
new file mode 100644
index 0000000..b5242ff
--- /dev/null
+++ b/pkg/front_end/testcases/bug32414b.dart.strong.transformed.expect
@@ -0,0 +1,9 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → void {
+  core::List<dynamic> l = <dynamic>[1, "hello"];
+  core::List<core::String> l2 = l.{core::Iterable::map}<core::String>((dynamic element) → core::String => element.toString()).{core::Iterable::toList}();
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/inference/dynamic_methods.dart b/pkg/front_end/testcases/inference/dynamic_methods.dart
index 76fc820..116b700 100644
--- a/pkg/front_end/testcases/inference/dynamic_methods.dart
+++ b/pkg/front_end/testcases/inference/dynamic_methods.dart
@@ -11,9 +11,9 @@
 
 test() {
   dynamic d = new Foo();
-  var /*@type=dynamic*/ get_hashCode = d.hashCode;
+  var /*@type=int*/ get_hashCode = d.hashCode;
   var /*@type=dynamic*/ call_hashCode = d.hashCode();
-  var /*@type=dynamic*/ call_toString = d.toString();
+  var /*@type=String*/ call_toString = d.toString();
   var /*@type=dynamic*/ call_toStringArg = d.toString(color: "pink");
   var /*@type=dynamic*/ call_foo0 = d.foo();
   var /*@type=dynamic*/ call_foo1 = d.foo(1);
diff --git a/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.expect b/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.expect
index 97bedda..fcc146a 100644
--- a/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.expect
+++ b/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.expect
@@ -11,9 +11,9 @@
 }
 static method test() → dynamic {
   dynamic d = new self::Foo::•();
-  dynamic get_hashCode = d.hashCode;
+  core::int get_hashCode = d.hashCode;
   dynamic call_hashCode = d.hashCode();
-  dynamic call_toString = d.toString();
+  core::String call_toString = d.toString();
   dynamic call_toStringArg = d.toString(color: "pink");
   dynamic call_foo0 = d.foo();
   dynamic call_foo1 = d.foo(1);
diff --git a/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.transformed.expect b/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.transformed.expect
index 97bedda..fcc146a 100644
--- a/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/inference/dynamic_methods.dart.strong.transformed.expect
@@ -11,9 +11,9 @@
 }
 static method test() → dynamic {
   dynamic d = new self::Foo::•();
-  dynamic get_hashCode = d.hashCode;
+  core::int get_hashCode = d.hashCode;
   dynamic call_hashCode = d.hashCode();
-  dynamic call_toString = d.toString();
+  core::String call_toString = d.toString();
   dynamic call_toStringArg = d.toString(color: "pink");
   dynamic call_foo0 = d.foo();
   dynamic call_foo1 = d.foo(1);
diff --git a/pkg/vm/testcases/bytecode/boostrapping.dart b/pkg/vm/testcases/bytecode/bootstrapping.dart
similarity index 100%
rename from pkg/vm/testcases/bytecode/boostrapping.dart
rename to pkg/vm/testcases/bytecode/bootstrapping.dart
diff --git a/pkg/vm/testcases/bytecode/boostrapping.dart.expect b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
similarity index 99%
rename from pkg/vm/testcases/bytecode/boostrapping.dart.expect
rename to pkg/vm/testcases/bytecode/bootstrapping.dart.expect
index f84988b..32d9de3 100644
--- a/pkg/vm/testcases/bytecode/boostrapping.dart.expect
+++ b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
@@ -573,7 +573,7 @@
   [3] = Null
 }
 ]static method _print(dynamic arg) → void {
-  self::_printString(arg.toString() as{TypeError} core::String);
+  self::_printString(arg.toString());
 }
 [@vm.bytecode=
 Bytecode {
diff --git a/tests/language_2/language_2_dartdevc.status b/tests/language_2/language_2_dartdevc.status
index 11456f2..290e42a 100644
--- a/tests/language_2/language_2_dartdevc.status
+++ b/tests/language_2/language_2_dartdevc.status
@@ -290,6 +290,7 @@
 [ $compiler == dartdevk ]
 additional_interface_adds_optional_args_concrete_subclass_test: MissingCompileTimeError
 additional_interface_adds_optional_args_concrete_test: MissingCompileTimeError
+assert_with_message_test: RuntimeError # Issue 33293
 async_or_generator_return_type_stacktrace_test/01: MissingCompileTimeError
 async_or_generator_return_type_stacktrace_test/02: MissingCompileTimeError
 async_or_generator_return_type_stacktrace_test/03: MissingCompileTimeError