in pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart [2195:2612]
InvocationInferenceResult _inferInvocation(
DartType typeContext,
int offset,
FunctionType calleeType,
ArgumentsImpl arguments,
List<VariableDeclaration>? hoistedExpressions,
{bool isSpecialCasedBinaryOperator: false,
bool isSpecialCasedTernaryOperator: false,
DartType? receiverType,
bool skipTypeArgumentInference: false,
bool isConst: false,
bool isImplicitExtensionMember: false,
required bool isImplicitCall,
Member? staticTarget,
bool isExtensionMemberInvocation: false}) {
// [receiverType] must be provided for special-cased operators.
assert(!isSpecialCasedBinaryOperator && !isSpecialCasedTernaryOperator ||
receiverType != null);
List<TypeParameter> calleeTypeParameters = calleeType.typeParameters;
if (calleeTypeParameters.isNotEmpty) {
// It's possible that one of the callee type parameters might match a type
// that already exists as part of inference (e.g. the type of an
// argument). This might happen, for instance, in the case where a
// function or method makes a recursive call to itself. To avoid the
// callee type parameters accidentally matching a type that already
// exists, and creating invalid inference results, we need to create fresh
// type parameters for the callee (see dartbug.com/31759).
// TODO(paulberry): is it possible to find a narrower set of circumstances
// in which me must do this, to avoid a performance regression?
FreshTypeParameters fresh = getFreshTypeParameters(calleeTypeParameters);
calleeType = fresh.applyToFunctionType(calleeType);
calleeTypeParameters = fresh.freshTypeParameters;
}
List<DartType>? explicitTypeArguments = getExplicitTypeArguments(arguments);
bool inferenceNeeded = !skipTypeArgumentInference &&
explicitTypeArguments == null &&
calleeTypeParameters.isNotEmpty;
bool typeChecksNeeded = !isTopLevel;
List<DartType>? inferredTypes;
Substitution? substitution;
List<DartType>? formalTypes;
List<DartType>? actualTypes;
if (inferenceNeeded || typeChecksNeeded) {
formalTypes = [];
actualTypes = [];
}
List<VariableDeclaration>? localHoistedExpressions;
if (library.enableNamedArgumentsAnywhereInLibrary &&
arguments.argumentsOriginalOrder != null &&
hoistedExpressions == null &&
!isTopLevel) {
hoistedExpressions = localHoistedExpressions = <VariableDeclaration>[];
}
if (inferenceNeeded) {
// ignore: unnecessary_null_comparison
if (isConst && typeContext != null) {
typeContext = new TypeVariableEliminator(
bottomType,
isNonNullableByDefault
? coreTypes.objectNullableRawType
: coreTypes.objectLegacyRawType)
.substituteType(typeContext);
}
inferredTypes = new List<DartType>.filled(
calleeTypeParameters.length, const UnknownType());
typeSchemaEnvironment.inferGenericFunctionOrType(
isNonNullableByDefault
? calleeType.returnType
: legacyErasure(calleeType.returnType),
calleeTypeParameters,
null,
null,
typeContext,
inferredTypes,
library.library);
substitution =
Substitution.fromPairs(calleeTypeParameters, inferredTypes);
} else if (explicitTypeArguments != null &&
calleeTypeParameters.length == explicitTypeArguments.length) {
substitution =
Substitution.fromPairs(calleeTypeParameters, explicitTypeArguments);
} else if (calleeTypeParameters.length != 0) {
substitution = Substitution.fromPairs(
calleeTypeParameters,
new List<DartType>.filled(
calleeTypeParameters.length, const DynamicType()));
}
bool isIdentical =
staticTarget == typeSchemaEnvironment.coreTypes.identicalProcedure;
// TODO(paulberry): if we are doing top level inference and type arguments
// were omitted, report an error.
List<Object?> argumentsEvaluationOrder;
if (library.enableNamedArgumentsAnywhereInLibrary &&
arguments.argumentsOriginalOrder != null) {
if (staticTarget?.isExtensionMember ?? false) {
// Add the receiver.
argumentsEvaluationOrder = <Object?>[
arguments.positional[0],
...arguments.argumentsOriginalOrder!
];
} else {
argumentsEvaluationOrder = arguments.argumentsOriginalOrder!;
}
} else {
argumentsEvaluationOrder = <Object?>[
...arguments.positional,
...arguments.named
];
}
arguments.argumentsOriginalOrder = null;
// The following loop determines how many argument expressions should be
// hoisted to preserve the evaluation order. The computation is based on the
// following observation: the largest suffix of the argument vector, such
// that every positional argument in that suffix comes before any named
// argument, retains the evaluation order after the rest of the arguments
// are hoisted, and therefore doesn't need to be hoisted itself. The loop
// below finds the starting position of such suffix and stores it in the
// [hoistingEndIndex] variable. In case all positional arguments come
// before all named arguments, the suffix coincides with the entire argument
// vector, and none of the arguments is hoisted. That way the legacy
// behavior is preserved.
int hoistingEndIndex;
if (library.enableNamedArgumentsAnywhereInLibrary) {
hoistingEndIndex = argumentsEvaluationOrder.length - 1;
for (int i = argumentsEvaluationOrder.length - 2;
i >= 0 && hoistingEndIndex == i + 1;
i--) {
int previousWeight =
argumentsEvaluationOrder[i + 1] is NamedExpression ? 1 : 0;
int currentWeight =
argumentsEvaluationOrder[i] is NamedExpression ? 1 : 0;
if (currentWeight <= previousWeight) {
--hoistingEndIndex;
}
}
} else {
hoistingEndIndex = 0;
}
int positionalIndex = 0;
int namedIndex = 0;
for (int evaluationOrderIndex = 0;
evaluationOrderIndex < argumentsEvaluationOrder.length;
evaluationOrderIndex++) {
Object? argument = argumentsEvaluationOrder[evaluationOrderIndex];
assert(
argument is Expression || argument is NamedExpression,
"Expected the argument to be either an Expression "
"or a NamedExpression, got '${argument.runtimeType}'.");
if (argument is Expression) {
int index = positionalIndex++;
DartType formalType = getPositionalParameterType(calleeType, index);
DartType inferredFormalType = substitution != null
? substitution.substituteType(formalType)
: formalType;
DartType inferredType;
if (isImplicitExtensionMember && index == 0) {
assert(
receiverType != null,
"No receiver type provided for implicit extension member "
"invocation.");
continue;
} else {
if (isSpecialCasedBinaryOperator) {
inferredFormalType = typeSchemaEnvironment
.getContextTypeOfSpecialCasedBinaryOperator(
typeContext, receiverType!, inferredFormalType,
isNonNullableByDefault: isNonNullableByDefault);
} else if (isSpecialCasedTernaryOperator) {
inferredFormalType = typeSchemaEnvironment
.getContextTypeOfSpecialCasedTernaryOperator(
typeContext, receiverType!, inferredFormalType,
isNonNullableByDefault: isNonNullableByDefault);
}
ExpressionInferenceResult result = inferExpression(
arguments.positional[index],
isNonNullableByDefault
? inferredFormalType
: legacyErasure(inferredFormalType),
inferenceNeeded ||
isSpecialCasedBinaryOperator ||
isSpecialCasedTernaryOperator ||
typeChecksNeeded);
inferredType = identical(result.inferredType, noInferredType) ||
isNonNullableByDefault
? result.inferredType
: legacyErasure(result.inferredType);
if (localHoistedExpressions != null &&
evaluationOrderIndex >= hoistingEndIndex) {
hoistedExpressions = null;
}
Expression expression =
_hoist(result.expression, inferredType, hoistedExpressions);
if (isIdentical && arguments.positional.length == 2) {
if (index == 0) {
flowAnalysis.equalityOp_rightBegin(expression, inferredType);
} else {
flowAnalysis.equalityOp_end(
arguments.parent as Expression, expression, inferredType);
}
}
arguments.positional[index] = expression..parent = arguments;
}
if (inferenceNeeded || typeChecksNeeded) {
formalTypes!.add(formalType);
actualTypes!.add(inferredType);
}
} else {
assert(argument is NamedExpression);
int index = namedIndex++;
NamedExpression namedArgument = arguments.named[index];
DartType formalType =
getNamedParameterType(calleeType, namedArgument.name);
DartType inferredFormalType = substitution != null
? substitution.substituteType(formalType)
: formalType;
ExpressionInferenceResult result = inferExpression(
namedArgument.value,
isNonNullableByDefault
? inferredFormalType
: legacyErasure(inferredFormalType),
inferenceNeeded ||
isSpecialCasedBinaryOperator ||
typeChecksNeeded);
DartType inferredType =
identical(result.inferredType, noInferredType) ||
isNonNullableByDefault
? result.inferredType
: legacyErasure(result.inferredType);
if (localHoistedExpressions != null &&
evaluationOrderIndex >= hoistingEndIndex) {
hoistedExpressions = null;
}
Expression expression =
_hoist(result.expression, inferredType, hoistedExpressions);
namedArgument.value = expression..parent = namedArgument;
if (inferenceNeeded || typeChecksNeeded) {
formalTypes!.add(formalType);
actualTypes!.add(inferredType);
}
}
}
assert(
positionalIndex == arguments.positional.length,
"Expected 'positionalIndex' to be ${arguments.positional.length}, "
"got ${positionalIndex}.");
assert(
namedIndex == arguments.named.length,
"Expected 'namedIndex' to be ${arguments.named.length}, "
"got ${namedIndex}.");
if (isSpecialCasedBinaryOperator) {
calleeType = replaceReturnType(
calleeType,
typeSchemaEnvironment.getTypeOfSpecialCasedBinaryOperator(
receiverType!, actualTypes![0],
isNonNullableByDefault: isNonNullableByDefault));
} else if (isSpecialCasedTernaryOperator) {
calleeType = replaceReturnType(
calleeType,
typeSchemaEnvironment.getTypeOfSpecialCasedTernaryOperator(
receiverType!, actualTypes![0], actualTypes[1], library.library));
}
// Check for and remove duplicated named arguments.
List<NamedExpression> named = arguments.named;
if (named.length == 2) {
if (named[0].name == named[1].name) {
String name = named[1].name;
Expression error = helper!.wrapInProblem(
_createDuplicateExpression(
named[0].fileOffset, named[0].value, named[1].value),
templateDuplicatedNamedArgument.withArguments(name),
named[1].fileOffset,
name.length);
arguments.named = [new NamedExpression(named[1].name, error)];
formalTypes!.removeLast();
actualTypes!.removeLast();
}
} else if (named.length > 2) {
Map<String, NamedExpression> seenNames = <String, NamedExpression>{};
bool hasProblem = false;
int namedTypeIndex = arguments.positional.length;
List<NamedExpression> uniqueNamed = <NamedExpression>[];
for (NamedExpression expression in named) {
String name = expression.name;
if (seenNames.containsKey(name)) {
hasProblem = true;
NamedExpression prevNamedExpression = seenNames[name]!;
prevNamedExpression.value = helper!.wrapInProblem(
_createDuplicateExpression(prevNamedExpression.fileOffset,
prevNamedExpression.value, expression.value),
templateDuplicatedNamedArgument.withArguments(name),
expression.fileOffset,
name.length)
..parent = prevNamedExpression;
formalTypes!.removeAt(namedTypeIndex);
actualTypes!.removeAt(namedTypeIndex);
} else {
seenNames[name] = expression;
uniqueNamed.add(expression);
namedTypeIndex++;
}
}
if (hasProblem) {
arguments.named = uniqueNamed;
}
}
if (inferenceNeeded) {
typeSchemaEnvironment.inferGenericFunctionOrType(
calleeType.returnType,
calleeTypeParameters,
formalTypes,
actualTypes,
typeContext,
inferredTypes!,
library.library);
assert(inferredTypes.every((type) => isKnown(type)),
"Unknown type(s) in inferred types: $inferredTypes.");
assert(inferredTypes.every((type) => !hasPromotedTypeVariable(type)),
"Promoted type variable(s) in inferred types: $inferredTypes.");
substitution =
Substitution.fromPairs(calleeTypeParameters, inferredTypes);
instrumentation?.record(uriForInstrumentation, offset, 'typeArgs',
new InstrumentationValueForTypeArgs(inferredTypes));
arguments.types.clear();
arguments.types.addAll(inferredTypes);
if (dataForTesting != null) {
assert(arguments.fileOffset != TreeNode.noOffset);
dataForTesting!.typeInferenceResult.inferredTypeArguments[arguments] =
inferredTypes;
}
}
List<DartType> positionalArgumentTypes = [];
List<NamedType> namedArgumentTypes = [];
if (typeChecksNeeded && !identical(calleeType, unknownFunction)) {
LocatedMessage? argMessage = helper!.checkArgumentsForType(
calleeType, arguments, offset,
isExtensionMemberInvocation: isExtensionMemberInvocation);
if (argMessage != null) {
return new WrapInProblemInferenceResult(
const InvalidType(),
const InvalidType(),
argMessage.messageObject,
argMessage.charOffset,
argMessage.length,
helper!,
isInapplicable: true);
} else {
// Argument counts and names match. Compare types.
int positionalShift = isImplicitExtensionMember ? 1 : 0;
int numPositionalArgs = arguments.positional.length - positionalShift;
for (int i = 0; i < formalTypes!.length; i++) {
DartType formalType = formalTypes[i];
DartType expectedType = substitution != null
? substitution.substituteType(formalType)
: formalType;
DartType actualType = actualTypes![i];
Expression expression;
NamedExpression? namedExpression;
if (i < numPositionalArgs) {
expression = arguments.positional[positionalShift + i];
positionalArgumentTypes.add(actualType);
} else {
namedExpression = arguments.named[i - numPositionalArgs];
expression = namedExpression.value;
namedArgumentTypes
.add(new NamedType(namedExpression.name, actualType));
}
expression = ensureAssignable(expectedType, actualType, expression,
isVoidAllowed: expectedType is VoidType,
// TODO(johnniwinther): Specialize message for operator
// invocations.
errorTemplate: templateArgumentTypeNotAssignable,
nullabilityErrorTemplate:
templateArgumentTypeNotAssignableNullability,
nullabilityPartErrorTemplate:
templateArgumentTypeNotAssignablePartNullability,
nullabilityNullErrorTemplate:
templateArgumentTypeNotAssignableNullabilityNull,
nullabilityNullTypeErrorTemplate:
templateArgumentTypeNotAssignableNullabilityNullType);
if (namedExpression == null) {
arguments.positional[positionalShift + i] = expression
..parent = arguments;
} else {
namedExpression.value = expression..parent = namedExpression;
}
}
}
}
DartType inferredType;
if (substitution != null) {
calleeType = substitution.substituteType(calleeType.withoutTypeParameters)
as FunctionType;
}
inferredType = calleeType.returnType;
assert(
!containsFreeFunctionTypeVariables(inferredType),
"Inferred return type $inferredType contains free variables."
"Inferred function type: $calleeType.");
if (!isNonNullableByDefault) {
inferredType = legacyErasure(inferredType);
calleeType = legacyErasure(calleeType) as FunctionType;
}
return new SuccessfulInferenceResult(inferredType, calleeType,
hoistedArguments: localHoistedExpressions,
inferredReceiverType: receiverType);
}