Straighten up invocation of an Object method on dynamic.

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

Change-Id: I9b98d9bff8d4e9b75ab7625323f62eb6b6701a94
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/164256
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2020-09-25 16:46:04 +00:00 committed by commit-bot@chromium.org
parent e423e857f8
commit f9e6e38b21
4 changed files with 77 additions and 75 deletions

View file

@ -9,7 +9,6 @@ import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/error/codes.dart';
@ -22,7 +21,6 @@ class InvocationInferenceHelper {
final ErrorReporter _errorReporter;
final FlowAnalysisHelper _flowAnalysis;
final TypeSystemImpl _typeSystem;
final TypeProviderImpl _typeProvider;
final MigrationResolutionHooks _migrationResolutionHooks;
List<DartType> _typeArgumentTypes;
@ -37,7 +35,6 @@ class InvocationInferenceHelper {
: _resolver = resolver,
_errorReporter = errorReporter,
_typeSystem = typeSystem,
_typeProvider = typeSystem.typeProvider,
_flowAnalysis = flowAnalysis,
_migrationResolutionHooks = migrationResolutionHooks;
@ -157,45 +154,6 @@ class InvocationInferenceHelper {
return null;
}
/// Given a method invocation [node], attempt to infer a better
/// type for the result if the target is dynamic and the method
/// being called is one of the object methods.
bool inferMethodInvocationObject(MethodInvocation node) {
// If we have a call like `toString()` or `libraryPrefix.toString()`, don't
// infer it.
Expression target = node.realTarget;
if (target == null ||
target is SimpleIdentifier && target.staticElement is PrefixElement) {
return false;
}
DartType nodeType = node.staticInvokeType;
if (nodeType == null ||
!nodeType.isDynamic ||
node.argumentList.arguments.isNotEmpty) {
return false;
}
// Object methods called on dynamic targets can have their types improved.
String name = node.methodName.name;
MethodElement inferredElement =
_typeProvider.objectType.element.getMethod(name);
if (inferredElement == null || inferredElement.isStatic) {
return false;
}
inferredElement = _resolver.toLegacyElement(inferredElement);
DartType inferredType = inferredElement.type;
if (inferredType is FunctionType) {
DartType returnType = inferredType.returnType;
if (inferredType.parameters.isEmpty &&
returnType is InterfaceType &&
_typeProvider.nonSubtypableClasses.contains(returnType.element)) {
node.staticInvokeType = inferredType;
recordStaticType(node, inferredType.returnType);
return true;
}
}
return false;
}
/// Given an uninstantiated generic function type, referenced by the
/// [identifier] in the tear-off [expression], try to infer the instantiated
/// generic function type from the surrounding context.

View file

@ -150,7 +150,7 @@ class MethodInvocationResolver {
}
if (receiverType is DynamicTypeImpl) {
_resolveReceiverDynamic(node, name);
_resolveReceiverDynamic(node);
return;
}
@ -291,15 +291,10 @@ class MethodInvocationResolver {
node.methodName.staticType,
);
// TODO(scheglov) Call this only when member lookup failed?
var inferred = _inferenceHelper.inferMethodInvocationObject(node);
if (!inferred) {
DartType staticStaticType = _inferenceHelper.computeInvokeReturnType(
node.staticInvokeType,
);
_inferenceHelper.recordStaticType(node, staticStaticType);
}
DartType staticStaticType = _inferenceHelper.computeInvokeReturnType(
node.staticInvokeType,
);
_inferenceHelper.recordStaticType(node, staticStaticType);
}
/// Given that we are accessing a property of the given [classElement] with the
@ -387,8 +382,33 @@ class MethodInvocationResolver {
_setResolution(node, member.type);
}
void _resolveReceiverDynamic(MethodInvocation node, String name) {
_setDynamicResolution(node);
void _resolveReceiverDynamic(MethodInvocationImpl node) {
var nameNode = node.methodName;
var objectElement = _typeSystem.typeProvider.objectElement;
var target = objectElement.getMethod(nameNode.name);
var hasMatchingObjectMethod = false;
if (target is MethodElement) {
var arguments = node.argumentList.arguments;
hasMatchingObjectMethod = arguments.length == target.parameters.length &&
!arguments.any((e) => e is NamedExpression);
if (hasMatchingObjectMethod) {
target = _resolver.toLegacyElement(target);
nameNode.staticElement = target;
node.staticInvokeType = target.type;
node.staticType = target.returnType;
}
}
if (!hasMatchingObjectMethod) {
nameNode.staticType = DynamicTypeImpl.instance;
node.staticInvokeType = DynamicTypeImpl.instance;
node.staticType = DynamicTypeImpl.instance;
}
_setExplicitTypeArgumentTypes();
node.argumentList.accept(_resolver);
}
void _resolveReceiverFunctionType(MethodInvocation node, Expression receiver,

View file

@ -1165,11 +1165,12 @@ main() {
''', [
error(CompileTimeErrorCode.USE_OF_VOID_RESULT, 23, 3),
]);
// TODO(scheglov) Resolve fully, or don't resolve at all.
assertMethodInvocation(
assertMethodInvocation2(
findNode.methodInvocation('toString()'),
null,
'String Function()',
element: null,
typeArgumentTypes: [],
invokeType: 'dynamic',
type: 'dynamic',
);
}
@ -1182,16 +1183,16 @@ main() {
''', [
error(CompileTimeErrorCode.USE_OF_VOID_RESULT, 23, 3),
]);
// TODO(scheglov) Resolve fully, or don't resolve at all.
assertMethodInvocation(
assertMethodInvocation2(
findNode.methodInvocation('toString()'),
null,
'String Function()',
element: null,
typeArgumentTypes: [],
invokeType: 'dynamic',
type: 'dynamic',
);
}
test_error_useOfVoidResult_receiver_withNull() async {
var question = typeToStringWithNullability ? '?' : '';
await assertErrorsInCode(r'''
main() {
void foo;
@ -1200,12 +1201,12 @@ main() {
''', [
error(CompileTimeErrorCode.USE_OF_VOID_RESULT, 23, 3),
]);
// TODO(scheglov) Resolve fully, or don't resolve at all.
assertMethodInvocation(
assertMethodInvocation2(
findNode.methodInvocation('toString()'),
null,
'String Function()',
expectedType: 'String$question',
element: null,
typeArgumentTypes: [],
invokeType: 'dynamic',
type: 'dynamic',
);
}
@ -1944,14 +1945,39 @@ main() {
assertType(foo, 'void Function(int)');
}
test_objectMethodOnDynamic() async {
test_objectMethodOnDynamic_argumentsDontMatch() async {
await assertNoErrorsInCode(r'''
main() {
var v;
v.toString(42);
void f(a, int b) {
a.toString(b);
}
''');
_assertUnresolvedMethodInvocation('toString(42);');
assertMethodInvocation2(
findNode.methodInvocation('toString(b)'),
element: null,
typeArgumentTypes: [],
invokeType: 'dynamic',
type: 'dynamic',
);
assertType(findNode.simple('b);'), 'int');
}
test_objectMethodOnDynamic_argumentsMatch() async {
await assertNoErrorsInCode(r'''
void f(a) {
a.toString();
}
''');
assertMethodInvocation2(
findNode.methodInvocation('toString()'),
element: elementMatcher(
objectElement.getMethod('toString'),
isLegacy: isNullSafetySdkAndLegacyLibrary,
),
typeArgumentTypes: [],
invokeType: 'String Function()',
type: 'String',
);
}
test_objectMethodOnFunction() async {

View file

@ -1710,7 +1710,6 @@ C<int, num?> f(List<int> a) => a;
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39609')
Future<void> test_dynamic_dispatch_to_object_method() async {
var content = '''
String f(dynamic x) => x.toString();
@ -1787,7 +1786,6 @@ main() {
await _checkSingleFileChanges(content, expected);
}
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39609')
Future<void> test_dynamic_toString() async {
var content = '''
String f(dynamic x) => x.toString();