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
472ec7780f

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>
This commit is contained in:
Kevin Millikin 2018-05-31 12:43:22 +00:00 committed by commit-bot@chromium.org
parent b3bae4262d
commit b62b438e8a
19 changed files with 169 additions and 25 deletions

View file

@ -547,8 +547,14 @@ abstract class TypeInferrerImpl extends TypeInferrer {
/// 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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
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 @@ abstract class TypeInferrerImpl extends TypeInferrer {
errorTemplate: templateUndefinedGetter,
expression: expression,
receiver: receiver);
if (interfaceMember is Member) {
if (strongMode &&
receiverType != const DynamicType() &&
interfaceMember is Member) {
desugaredGet.interfaceTarget = interfaceMember;
}
}

View file

@ -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() {}

View file

@ -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 {}

View file

@ -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 {}

View file

@ -0,0 +1,7 @@
library;
import self as self;
static method test() → void
;
static method main() → void
;

View file

@ -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 {}

View file

@ -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 {}

View file

@ -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() {}

View file

@ -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 {}

View file

@ -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 {}

View file

@ -0,0 +1,7 @@
library;
import self as self;
static method test() → void
;
static method main() → void
;

View file

@ -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 {}

View file

@ -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 {}

View file

@ -11,9 +11,9 @@ class Foo {
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);

View file

@ -11,9 +11,9 @@ class Foo extends core::Object {
}
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);

View file

@ -11,9 +11,9 @@ class Foo extends core::Object {
}
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);

View file

@ -573,7 +573,7 @@ ConstantPool {
[3] = Null
}
]static method _print(dynamic arg) → void {
self::_printString(arg.toString() as{TypeError} core::String);
self::_printString(arg.toString());
}
[@vm.bytecode=
Bytecode {

View file

@ -290,6 +290,7 @@ void_type_usage_test/paren_void_init: MissingCompileTimeError
[ $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