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