blob: db2da5f138bf2adb59ec5cf277a7e9bc48257150 [file] [log] [blame]
// Copyright (c) 2020, 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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.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/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/generated/element_type_provider.dart';
import 'package:analyzer/src/generated/migration.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:meta/meta.dart';
class InvocationInferenceHelper {
final LibraryElementImpl _definingLibrary;
final ElementTypeProvider _elementTypeProvider;
final ErrorReporter _errorReporter;
final FlowAnalysisHelper _flowAnalysis;
final TypeSystemImpl _typeSystem;
final TypeProviderImpl _typeProvider;
InvocationInferenceHelper({
@required LibraryElementImpl definingLibrary,
@required ElementTypeProvider elementTypeProvider,
@required ErrorReporter errorReporter,
@required FlowAnalysisHelper flowAnalysis,
@required TypeSystemImpl typeSystem,
}) : _definingLibrary = definingLibrary,
_elementTypeProvider = elementTypeProvider,
_errorReporter = errorReporter,
_typeSystem = typeSystem,
_typeProvider = typeSystem.typeProvider,
_flowAnalysis = flowAnalysis;
/// Compute the return type of the method or function represented by the given
/// type that is being invoked.
DartType /*!*/ computeInvokeReturnType(DartType type,
{@required bool isNullAware}) {
TypeImpl /*!*/ returnType;
if (type is InterfaceType) {
MethodElement callMethod = type.lookUpMethod2(
FunctionElement.CALL_METHOD_NAME, _definingLibrary);
returnType =
_elementTypeProvider.safeExecutableType(callMethod)?.returnType ??
DynamicTypeImpl.instance;
} else if (type is FunctionType) {
returnType = type.returnType ?? DynamicTypeImpl.instance;
} else {
returnType = DynamicTypeImpl.instance;
}
if (isNullAware && _typeSystem.isNonNullableByDefault) {
returnType = _typeSystem.makeNullable(returnType);
}
return returnType;
}
FunctionType inferArgumentTypesForGeneric(AstNode inferenceNode,
DartType uninstantiatedType, TypeArgumentList typeArguments,
{AstNode errorNode, bool isConst = false}) {
errorNode ??= inferenceNode;
if (typeArguments == null &&
uninstantiatedType is FunctionType &&
uninstantiatedType.typeFormals.isNotEmpty) {
var typeArguments = _typeSystem.inferGenericFunctionOrType(
typeParameters: uninstantiatedType.typeFormals,
parameters: const <ParameterElement>[],
declaredReturnType: uninstantiatedType.returnType,
argumentTypes: const <DartType>[],
contextReturnType: InferenceContext.getContext(inferenceNode),
downwards: true,
isConst: isConst,
errorReporter: _errorReporter,
errorNode: errorNode,
);
if (typeArguments != null) {
return uninstantiatedType.instantiate(typeArguments);
}
}
return null;
}
void inferArgumentTypesForInvocation(
InvocationExpression node,
DartType type,
) {
DartType inferred =
inferArgumentTypesForGeneric(node, type, node.typeArguments);
InferenceContext.setType(
node.argumentList, inferred ?? node.staticInvokeType);
}
/// Given a possibly generic invocation like `o.m(args)` or `(f)(args)` try to
/// infer the instantiated generic function type.
///
/// This takes into account both the context type, as well as information from
/// the argument types.
void inferGenericInvocationExpression(
InvocationExpression node,
DartType type,
) {
ArgumentList arguments = node.argumentList;
var freshType = _getFreshType(type);
FunctionType inferred = inferGenericInvoke(
node, freshType, node.typeArguments, arguments, node.function);
if (inferred != null && inferred != node.staticInvokeType) {
// Fix up the parameter elements based on inferred method.
arguments.correspondingStaticParameters =
ResolverVisitor.resolveArgumentsToParameters(
arguments, inferred.parameters, null);
node.staticInvokeType = inferred;
}
}
/// Given a possibly generic invocation or instance creation, such as
/// `o.m(args)` or `(f)(args)` or `new T(args)` try to infer the instantiated
/// generic function type.
///
/// This takes into account both the context type, as well as information from
/// the argument types.
FunctionType inferGenericInvoke(
Expression node,
DartType fnType,
TypeArgumentList typeArguments,
ArgumentList argumentList,
AstNode errorNode,
{bool isConst = false}) {
if (typeArguments == null &&
fnType is FunctionType &&
fnType.typeFormals.isNotEmpty) {
// Get the parameters that correspond to the uninstantiated generic.
List<ParameterElement> rawParameters =
ResolverVisitor.resolveArgumentsToParameters(
argumentList, fnType.parameters, null);
List<ParameterElement> params = <ParameterElement>[];
List<DartType> argTypes = <DartType>[];
for (int i = 0, length = rawParameters.length; i < length; i++) {
ParameterElement parameter = rawParameters[i];
if (parameter != null) {
params.add(parameter);
argTypes.add(argumentList.arguments[i].staticType);
}
}
var typeArgs = _typeSystem.inferGenericFunctionOrType(
typeParameters: fnType.typeFormals,
parameters: params,
declaredReturnType: fnType.returnType,
argumentTypes: argTypes,
contextReturnType: InferenceContext.getContext(node),
isConst: isConst,
errorReporter: _errorReporter,
errorNode: errorNode,
);
if (node is InvocationExpressionImpl) {
node.typeArgumentTypes = typeArgs;
}
if (typeArgs != null) {
return fnType.instantiate(typeArgs);
}
return fnType;
}
// There is currently no other place where we set type arguments
// for FunctionExpressionInvocation(s), so set it here, if not inferred.
if (node is FunctionExpressionInvocationImpl) {
if (typeArguments != null) {
var typeArgs = typeArguments.arguments.map((n) => n.type).toList();
node.typeArgumentTypes = typeArgs;
} else {
node.typeArgumentTypes = const <DartType>[];
}
}
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;
}
DartType inferredType =
_elementTypeProvider.getExecutableType(inferredElement);
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;
}
/// Record that the static type of the given node is the given type.
///
/// @param expression the node whose type is to be recorded
/// @param type the static type of the node
///
/// TODO(scheglov) this is duplication
void recordStaticType(Expression expression, DartType type) {
var elementTypeProvider = this._elementTypeProvider;
if (elementTypeProvider is MigrationResolutionHooks) {
// TODO(scheglov) type cannot be null
type = elementTypeProvider.modifyExpressionType(
expression,
type ?? DynamicTypeImpl.instance,
);
}
// TODO(scheglov) type cannot be null
if (type == null) {
expression.staticType = DynamicTypeImpl.instance;
} else {
expression.staticType = type;
if (identical(type, NeverTypeImpl.instance)) {
_flowAnalysis?.flow?.handleExit();
}
}
}
static DartType _getFreshType(DartType type) {
if (type is FunctionType) {
var parameters = getFreshTypeParameters(type.typeFormals);
return parameters.applyToFunctionType(type);
} else {
return type;
}
}
}