in pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart [2151:2590]
MapLiteralEntry inferMapEntry(
MapLiteralEntry entry,
TreeNode parent,
DartType inferredKeyType,
DartType inferredValueType,
DartType spreadContext,
List<DartType> actualTypes,
List<DartType> actualTypesForSet,
Map<TreeNode, DartType> inferredSpreadTypes,
Map<Expression, DartType> inferredConditionTypes,
bool inferenceNeeded,
bool typeChecksNeeded) {
if (entry is SpreadMapEntry) {
ExpressionInferenceResult spreadResult = inferrer.inferExpression(
entry.expression, spreadContext, inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
if (entry.isNullAware) {
reportNonNullableInNullAwareWarningIfNeeded(
spreadResult.inferredType, "...?", entry.expression.fileOffset);
}
entry.expression = spreadResult.expression..parent = entry;
DartType spreadType = spreadResult.inferredType;
inferredSpreadTypes[entry.expression] = spreadType;
int length = actualTypes.length;
actualTypes.add(noInferredType);
actualTypes.add(noInferredType);
storeSpreadMapEntryElementTypes(
spreadType, entry.isNullAware, actualTypes, length);
DartType? actualKeyType = actualTypes[length];
DartType? actualValueType = actualTypes[length + 1];
DartType spreadTypeBound = inferrer.resolveTypeParameter(spreadType);
DartType? actualElementType =
getSpreadElementType(spreadType, spreadTypeBound, entry.isNullAware);
MapLiteralEntry replacement = entry;
if (typeChecksNeeded) {
if (actualKeyType == noInferredType) {
if (inferrer.coreTypes.isNull(spreadTypeBound) &&
!entry.isNullAware) {
replacement = new MapLiteralEntry(
inferrer.helper!.buildProblem(
templateNonNullAwareSpreadIsNull.withArguments(
spreadType, inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1),
new NullLiteral())
..fileOffset = entry.fileOffset;
} else if (actualElementType != null) {
if (inferrer.isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!entry.isNullAware) {
Expression receiver = entry.expression;
Expression problem = inferrer.helper!.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: inferrer.getWhyNotPromotedContext(
inferrer.flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, problem);
replacement = new SpreadMapEntry(problem, isNullAware: false)
..fileOffset = entry.fileOffset;
}
// Don't report the error here, it might be an ambiguous Set. The
// error is reported in checkMapEntry if it's disambiguated as map.
iterableSpreadType = spreadType;
} else {
Expression receiver = entry.expression;
Expression problem = inferrer.helper!.buildProblem(
templateSpreadMapEntryTypeMismatch.withArguments(
spreadType, inferrer.isNonNullableByDefault),
receiver.fileOffset,
1,
context: inferrer.getWhyNotPromotedContext(
inferrer.flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, problem);
replacement = new MapLiteralEntry(problem, new NullLiteral())
..fileOffset = entry.fileOffset;
}
} else if (spreadTypeBound is InterfaceType) {
Expression? keyError;
Expression? valueError;
if (!inferrer.isAssignable(inferredKeyType, actualKeyType)) {
if (inferrer.isNonNullableByDefault) {
IsSubtypeOf subtypeCheckResult = inferrer.typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(
actualKeyType, inferredKeyType);
if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) {
if (actualKeyType == subtypeCheckResult.subtype &&
inferredKeyType == subtypeCheckResult.supertype) {
keyError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatchNullability
.withArguments(actualKeyType, inferredKeyType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
} else {
keyError = inferrer.helper!.buildProblem(
// ignore: lines_longer_than_80_chars
templateSpreadMapEntryElementKeyTypeMismatchPartNullability
.withArguments(
actualKeyType,
inferredKeyType,
subtypeCheckResult.subtype!,
subtypeCheckResult.supertype!,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
keyError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatch.withArguments(
actualKeyType,
inferredKeyType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
keyError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementKeyTypeMismatch.withArguments(
actualKeyType,
inferredKeyType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
}
if (!inferrer.isAssignable(inferredValueType, actualValueType)) {
if (inferrer.isNonNullableByDefault) {
IsSubtypeOf subtypeCheckResult = inferrer.typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(
actualValueType, inferredValueType);
if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) {
if (actualValueType == subtypeCheckResult.subtype &&
inferredValueType == subtypeCheckResult.supertype) {
valueError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementValueTypeMismatchNullability
.withArguments(actualValueType, inferredValueType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
} else {
valueError = inferrer.helper!.buildProblem(
// ignore: lines_longer_than_80_chars
templateSpreadMapEntryElementValueTypeMismatchPartNullability
.withArguments(
actualValueType,
inferredValueType,
subtypeCheckResult.subtype!,
subtypeCheckResult.supertype!,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
valueError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementValueTypeMismatch
.withArguments(actualValueType, inferredValueType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
} else {
valueError = inferrer.helper!.buildProblem(
templateSpreadMapEntryElementValueTypeMismatch.withArguments(
actualValueType,
inferredValueType,
inferrer.isNonNullableByDefault),
entry.expression.fileOffset,
1);
}
}
if (inferrer.isNonNullableByDefault &&
spreadType.isPotentiallyNullable &&
spreadType is! DynamicType &&
spreadType is! NullType &&
!entry.isNullAware) {
Expression receiver = entry.expression;
keyError = inferrer.helper!.buildProblem(
messageNullableSpreadError, receiver.fileOffset, 1,
context: inferrer.getWhyNotPromotedContext(
inferrer.flowAnalysis.whyNotPromoted(receiver)(),
entry,
(type) => !type.isPotentiallyNullable));
_copyNonPromotionReasonToReplacement(entry, keyError);
}
if (keyError != null || valueError != null) {
keyError ??= new NullLiteral();
valueError ??= new NullLiteral();
replacement = new MapLiteralEntry(keyError, valueError)
..fileOffset = entry.fileOffset;
}
}
}
// Use 'dynamic' for error recovery.
if (actualKeyType == noInferredType) {
actualKeyType = actualTypes[length] = const DynamicType();
actualValueType = actualTypes[length + 1] = const DynamicType();
}
// Store the type in case of an ambiguous Set. Use 'dynamic' for error
// recovery.
actualTypesForSet.add(actualElementType ?? const DynamicType());
mapEntryClass ??=
inferrer.coreTypes.index.getClass('dart:core', 'MapEntry');
// TODO(dmitryas): Handle the case of an ambiguous Set.
entry.entryType = new InterfaceType(
mapEntryClass!,
inferrer.library.nonNullable,
<DartType>[actualKeyType, actualValueType]);
bool isMap = inferrer.typeSchemaEnvironment.isSubtypeOf(
spreadType,
inferrer.coreTypes.mapRawType(inferrer.library.nullable),
SubtypeCheckMode.withNullabilities);
bool isIterable = inferrer.typeSchemaEnvironment.isSubtypeOf(
spreadType,
inferrer.coreTypes.iterableRawType(inferrer.library.nullable),
SubtypeCheckMode.withNullabilities);
if (isMap && !isIterable) {
mapSpreadOffset = entry.fileOffset;
}
if (!isMap && isIterable) {
iterableSpreadOffset = entry.expression.fileOffset;
}
return replacement;
} else if (entry is IfMapEntry) {
inferrer.flowAnalysis.ifStatement_conditionBegin();
DartType boolType =
inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
ExpressionInferenceResult conditionResult = inferrer.inferExpression(
entry.condition, boolType, typeChecksNeeded,
isVoidAllowed: false);
Expression condition =
inferrer.ensureAssignableResult(boolType, conditionResult);
entry.condition = condition..parent = entry;
inferrer.flowAnalysis.ifStatement_thenBegin(condition, entry);
// Note that this recursive invocation of inferMapEntry will add two types
// to actualTypes; they are the actual types of the current invocation if
// the 'else' branch is empty.
MapLiteralEntry then = inferMapEntry(
entry.then,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
inferenceNeeded,
typeChecksNeeded);
entry.then = then..parent = entry;
MapLiteralEntry otherwise;
if (entry.otherwise != null) {
inferrer.flowAnalysis.ifStatement_elseBegin();
// We need to modify the actual types added in the recursive call to
// inferMapEntry.
DartType? actualValueType = actualTypes.removeLast();
DartType? actualKeyType = actualTypes.removeLast();
DartType actualTypeForSet = actualTypesForSet.removeLast();
otherwise = inferMapEntry(
entry.otherwise!,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
inferenceNeeded,
typeChecksNeeded);
int length = actualTypes.length;
actualTypes[length - 2] = inferrer.typeSchemaEnvironment
.getStandardUpperBound(actualKeyType, actualTypes[length - 2],
inferrer.library.library);
actualTypes[length - 1] = inferrer.typeSchemaEnvironment
.getStandardUpperBound(actualValueType, actualTypes[length - 1],
inferrer.library.library);
int lengthForSet = actualTypesForSet.length;
actualTypesForSet[lengthForSet - 1] = inferrer.typeSchemaEnvironment
.getStandardUpperBound(actualTypeForSet,
actualTypesForSet[lengthForSet - 1], inferrer.library.library);
entry.otherwise = otherwise..parent = entry;
}
inferrer.flowAnalysis.ifStatement_end(entry.otherwise != null);
return entry;
} else if (entry is ForMapEntry) {
// TODO(johnniwinther): Use _visitStatements instead.
List<VariableDeclaration>? variables;
for (int index = 0; index < entry.variables.length; index++) {
VariableDeclaration variable = entry.variables[index];
if (variable.name == null) {
if (variable.initializer != null) {
ExpressionInferenceResult result = inferrer.inferExpression(
variable.initializer!,
variable.type,
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
variable.initializer = result.expression..parent = variable;
variable.type = result.inferredType;
}
} else {
StatementInferenceResult variableResult =
inferrer.inferStatement(variable);
if (variableResult.hasChanged) {
if (variables == null) {
variables = <VariableDeclaration>[];
variables.addAll(entry.variables.sublist(0, index));
}
if (variableResult.statementCount == 1) {
variables.add(variableResult.statement as VariableDeclaration);
} else {
for (Statement variable in variableResult.statements) {
variables.add(variable as VariableDeclaration);
}
}
} else if (variables != null) {
variables.add(variable);
}
}
}
if (variables != null) {
entry.variables.clear();
entry.variables.addAll(variables);
setParents(variables, entry);
}
inferrer.flowAnalysis.for_conditionBegin(entry);
if (entry.condition != null) {
ExpressionInferenceResult conditionResult = inferrer.inferExpression(
entry.condition!,
inferrer.coreTypes.boolRawType(inferrer.library.nonNullable),
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: false);
entry.condition = conditionResult.expression..parent = entry;
// TODO(johnniwinther): Ensure assignability of condition?
inferredConditionTypes[entry.condition!] = conditionResult.inferredType;
}
inferrer.flowAnalysis.for_bodyBegin(null, entry.condition);
// Actual types are added by the recursive call.
MapLiteralEntry body = inferMapEntry(
entry.body,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
inferenceNeeded,
typeChecksNeeded);
entry.body = body..parent = entry;
inferrer.flowAnalysis.for_updaterBegin();
for (int index = 0; index < entry.updates.length; index++) {
ExpressionInferenceResult updateResult = inferrer.inferExpression(
entry.updates[index],
const UnknownType(),
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
entry.updates[index] = updateResult.expression..parent = entry;
}
inferrer.flowAnalysis.for_end();
return entry;
} else if (entry is ForInMapEntry) {
ForInResult result;
if (entry.variable.name == null) {
result = handleForInWithoutVariable(entry, entry.variable,
entry.iterable, entry.syntheticAssignment!, entry.expressionEffects,
isAsync: entry.isAsync, hasProblem: entry.problem != null);
} else {
result = handleForInDeclaringVariable(
entry, entry.variable, entry.iterable, entry.expressionEffects,
isAsync: entry.isAsync);
}
entry.variable = result.variable..parent = entry;
entry.iterable = result.iterable..parent = entry;
// TODO(johnniwinther): Use ?.. here instead.
entry.syntheticAssignment = result.syntheticAssignment;
result.syntheticAssignment?.parent = entry;
// TODO(johnniwinther): Use ?.. here instead.
entry.expressionEffects = result.expressionSideEffects;
result.expressionSideEffects?.parent = entry;
if (entry.problem != null) {
ExpressionInferenceResult problemResult = inferrer.inferExpression(
entry.problem!,
const UnknownType(),
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
entry.problem = problemResult.expression..parent = entry;
}
// Actual types are added by the recursive call.
MapLiteralEntry body = inferMapEntry(
entry.body,
entry,
inferredKeyType,
inferredValueType,
spreadContext,
actualTypes,
actualTypesForSet,
inferredSpreadTypes,
inferredConditionTypes,
inferenceNeeded,
typeChecksNeeded);
entry.body = body..parent = entry;
// This is matched by the call to [forEach_bodyBegin] in
// [handleForInWithoutVariable] or [handleForInDeclaringVariable].
inferrer.flowAnalysis.forEach_end();
return entry;
} else {
ExpressionInferenceResult keyResult = inferrer.inferExpression(
entry.key, inferredKeyType, true,
isVoidAllowed: true);
Expression key = inferrer.ensureAssignableResult(
inferredKeyType, keyResult,
isVoidAllowed: inferredKeyType is VoidType);
entry.key = key..parent = entry;
ExpressionInferenceResult valueResult = inferrer.inferExpression(
entry.value, inferredValueType, true,
isVoidAllowed: true);
Expression value = inferrer.ensureAssignableResult(
inferredValueType, valueResult,
isVoidAllowed: inferredValueType is VoidType);
entry.value = value..parent = entry;
actualTypes.add(keyResult.inferredType);
actualTypes.add(valueResult.inferredType);
// Use 'dynamic' for error recovery.
actualTypesForSet.add(const DynamicType());
mapEntryOffset = entry.fileOffset;
return entry;
}
}