blob: 93e1544b22dbc1124b1132bd247f99ab0171d211 [file] [log] [blame]
// Copyright (c) 2017, 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.md file.
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/testing/id.dart';
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/canonical_name.dart' as kernel;
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:kernel/src/bounds_checks.dart' show calculateBounds;
import 'package:kernel/src/future_value_type.dart';
import 'package:kernel/src/legacy_erasure.dart';
import '../../base/instrumentation.dart'
show
Instrumentation,
InstrumentationValueForMember,
InstrumentationValueForType,
InstrumentationValueForTypeArgs;
import '../../base/nnbd_mode.dart';
import '../../testing/id_extractor.dart';
import '../../testing/id_testing_utils.dart';
import '../builder/constructor_builder.dart';
import '../builder/extension_builder.dart';
import '../builder/member_builder.dart';
import '../fasta_codes.dart';
import '../kernel/class_hierarchy_builder.dart' show ClassMember;
import '../kernel/constructor_tearoff_lowering.dart';
import '../kernel/kernel_helper.dart';
import '../kernel/inference_visitor.dart';
import '../kernel/internal_ast.dart';
import '../kernel/invalid_type.dart';
import '../kernel/type_algorithms.dart' show hasAnyTypeVariables;
import '../names.dart';
import '../problems.dart' show internalProblem, unexpected, unhandled;
import '../source/source_library_builder.dart' show SourceLibraryBuilder;
import 'inference_helper.dart' show InferenceHelper;
import 'type_constraint_gatherer.dart' show TypeConstraintGatherer;
import 'type_demotion.dart';
import 'type_inference_engine.dart';
import 'type_schema.dart' show isKnown, UnknownType;
import 'type_schema_elimination.dart' show greatestClosure;
import 'type_schema_environment.dart'
show
getNamedParameterType,
getPositionalParameterType,
TypeConstraint,
TypeVariableEliminator,
TypeSchemaEnvironment;
part 'closure_context.dart';
/// Given a [FunctionNode], gets the named parameter identified by [name], or
/// `null` if there is no parameter with the given name.
VariableDeclaration? getNamedFormal(FunctionNode function, String name) {
for (VariableDeclaration formal in function.namedParameters) {
if (formal.name == name) return formal;
}
return null;
}
/// Given a [FunctionNode], gets the [i]th positional formal parameter, or
/// `null` if there is no parameter with that index.
VariableDeclaration? getPositionalFormal(FunctionNode function, int i) {
if (i < function.positionalParameters.length) {
return function.positionalParameters[i];
} else {
return null;
}
}
bool isOverloadableArithmeticOperator(String name) {
return identical(name, '+') ||
identical(name, '-') ||
identical(name, '*') ||
identical(name, '%');
}
/// Enum denoting the kinds of contravariance check that might need to be
/// inserted for a method call.
enum MethodContravarianceCheckKind {
/// No contravariance check is needed.
none,
/// The return value from the method call needs to be checked.
checkMethodReturn,
/// The method call needs to be desugared into a getter call, followed by an
/// "as" check, followed by an invocation of the resulting function object.
checkGetterReturn,
}
/// Keeps track of the local state for the type inference that occurs during
/// compilation of a single method body or top level initializer.
///
/// This class describes the interface for use by clients of type inference
/// (e.g. BodyBuilder). Derived classes should derive from [TypeInferrerImpl].
abstract class TypeInferrer {
SourceLibraryBuilder get library;
/// Gets the [TypeSchemaEnvironment] being used for type inference.
TypeSchemaEnvironment get typeSchemaEnvironment;
/// Returns the [FlowAnalysis] used during inference.
FlowAnalysis<TreeNode, Statement, Expression, VariableDeclaration, DartType>
get flowAnalysis;
/// The URI of the code for which type inference is currently being
/// performed--this is used for testing.
Uri get uriForInstrumentation;
AssignedVariables<TreeNode, VariableDeclaration> get assignedVariables;
/// Performs full type inference on the given field initializer.
Expression inferFieldInitializer(
InferenceHelper helper, DartType declaredType, Expression initializer);
/// Returns the type used as the inferred type of a variable declaration,
/// based on the static type of the initializer expression, given by
/// [initializerType].
DartType inferDeclarationType(DartType initializerType);
/// Performs type inference on [expression].
ExpressionInferenceResult inferExpression(
Expression expression, DartType typeContext, bool typeNeeded,
{bool isVoidAllowed: false, bool forEffect: false});
/// Performs type inference on the given function body.
InferredFunctionBody inferFunctionBody(InferenceHelper helper, int fileOffset,
DartType returnType, AsyncMarker asyncMarker, Statement body);
/// Performs type inference on the given constructor initializer.
InitializerInferenceResult inferInitializer(
InferenceHelper helper, Initializer initializer);
/// Performs type inference on the given metadata annotations.
void inferMetadata(
InferenceHelper helper, TreeNode? parent, List<Expression>? annotations);
/// Performs type inference on the given metadata annotations keeping the
/// existing helper if possible.
void inferMetadataKeepingHelper(
TreeNode parent, List<Expression> annotations);
/// Performs type inference on the given function parameter initializer
/// expression.
Expression inferParameterInitializer(
InferenceHelper helper,
Expression initializer,
DartType declaredType,
bool hasDeclaredInitializer);
/// Ensures that all parameter types of [constructor] have been inferred.
// TODO(johnniwinther): We are still parameters on synthesized mixin
// application constructors.
void inferConstructorParameterTypes(Constructor constructor);
}
/// Concrete implementation of [TypeInferrer] specialized to work with kernel
/// objects.
class TypeInferrerImpl implements TypeInferrer {
/// Marker object to indicate that a function takes an unknown number
/// of arguments.
final FunctionType unknownFunction;
final TypeInferenceEngine engine;
@override
final FlowAnalysis<TreeNode, Statement, Expression, VariableDeclaration,
DartType> flowAnalysis;
@override
final AssignedVariables<TreeNode, VariableDeclaration> assignedVariables;
final InferenceDataForTesting? dataForTesting;
@override
final Uri uriForInstrumentation;
/// Indicates whether the construct we are currently performing inference for
/// is outside of a method body, and hence top level type inference rules
/// should apply.
final bool isTopLevel;
final ClassHierarchy classHierarchy;
final Instrumentation? instrumentation;
@override
final TypeSchemaEnvironment typeSchemaEnvironment;
final InterfaceType? thisType;
@override
final SourceLibraryBuilder library;
InferenceHelper? helper;
/// Context information for the current closure, or `null` if we are not
/// inside a closure.
ClosureContext? closureContext;
TypeInferrerImpl(this.engine, this.uriForInstrumentation, bool topLevel,
this.thisType, this.library, this.assignedVariables, this.dataForTesting)
// ignore: unnecessary_null_comparison
: assert(library != null),
unknownFunction = new FunctionType(
const [], const DynamicType(), library.nonNullable),
classHierarchy = engine.classHierarchy,
instrumentation = topLevel ? null : engine.instrumentation,
typeSchemaEnvironment = engine.typeSchemaEnvironment,
isTopLevel = topLevel,
flowAnalysis = library.isNonNullableByDefault
? new FlowAnalysis(
new TypeOperationsCfe(engine.typeSchemaEnvironment),
assignedVariables,
respectImplicitlyTypedVarInitializers:
library.enableConstructorTearOffsInLibrary)
: new FlowAnalysis.legacy(
new TypeOperationsCfe(engine.typeSchemaEnvironment),
assignedVariables) {}
CoreTypes get coreTypes => engine.coreTypes;
bool get isNonNullableByDefault => library.isNonNullableByDefault;
NnbdMode get nnbdMode => library.loader.nnbdMode;
DartType get bottomType =>
isNonNullableByDefault ? const NeverType.nonNullable() : const NullType();
DartType computeGreatestClosure(DartType type) {
return greatestClosure(type, const DynamicType(), bottomType);
}
DartType computeGreatestClosure2(DartType type) {
return greatestClosure(
type,
isNonNullableByDefault
? coreTypes.objectNullableRawType
: const DynamicType(),
bottomType);
}
DartType computeNullable(DartType type) {
if (type is NullType || type is NeverType) {
return const NullType();
}
return type.withDeclaredNullability(library.nullable);
}
Expression createReachabilityError(
int fileOffset, Message errorMessage, Message warningMessage) {
if (library.loader.target.context.options.warnOnReachabilityCheck &&
// ignore: unnecessary_null_comparison
warningMessage != null) {
helper?.addProblem(warningMessage, fileOffset, noLength);
}
Arguments arguments;
// ignore: unnecessary_null_comparison
if (errorMessage != null) {
arguments = new Arguments([
new StringLiteral(errorMessage.problemMessage)..fileOffset = fileOffset
])
..fileOffset = fileOffset;
} else {
arguments = new Arguments([])..fileOffset = fileOffset;
}
// ignore: unnecessary_null_comparison
assert(coreTypes.reachabilityErrorConstructor != null);
return new Throw(
new ConstructorInvocation(
coreTypes.reachabilityErrorConstructor, arguments)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
}
/// Computes a list of context messages explaining why [receiver] was not
/// promoted, to be used when reporting an error for a larger expression
/// containing [receiver]. [node] is the containing tree node.
List<LocatedMessage>? getWhyNotPromotedContext(
Map<DartType, NonPromotionReason>? whyNotPromoted,
TreeNode node,
bool Function(DartType) typeFilter) {
List<LocatedMessage>? context;
if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
_WhyNotPromotedVisitor whyNotPromotedVisitor =
new _WhyNotPromotedVisitor(this);
for (MapEntry<DartType, NonPromotionReason> entry
in whyNotPromoted.entries) {
if (!typeFilter(entry.key)) continue;
LocatedMessage? message = entry.value.accept(whyNotPromotedVisitor);
if (dataForTesting != null) {
String nonPromotionReasonText = entry.value.shortName;
List<String> args = <String>[];
if (whyNotPromotedVisitor.propertyReference != null) {
Id id = computeMemberId(whyNotPromotedVisitor.propertyReference!);
args.add('target: $id');
}
if (whyNotPromotedVisitor.propertyType != null) {
String typeText = typeToText(whyNotPromotedVisitor.propertyType!,
TypeRepresentation.analyzerNonNullableByDefault);
args.add('type: $typeText');
}
if (args.isNotEmpty) {
nonPromotionReasonText += '(${args.join(', ')})';
}
TreeNode origNode = node;
while (origNode is VariableGet &&
origNode.variable.name == null &&
origNode.variable.initializer != null) {
// This is a read of a synthetic variable, presumably from a "let".
// Find the original expression.
// TODO(johnniwinther): add a general solution for getting the
// original node for testing.
origNode = origNode.variable.initializer!;
}
dataForTesting!.flowAnalysisResult.nonPromotionReasons[origNode] =
nonPromotionReasonText;
}
// Note: this will always pick the first viable reason (only). I
// (paulberry) believe this is the one that will be the most relevant,
// but I need to do more testing to validate that. I can't do that
// additional testing yet because at the moment we only handle failed
// promotions to non-nullable.
// TODO(paulberry): do more testing and then expand on the comment
// above.
if (message != null) {
context = [message];
}
break;
}
}
return context;
}
/// Returns `true` if exceptions should be thrown in paths reachable only due
/// to unsoundness in flow analysis in mixed mode.
bool get shouldThrowUnsoundnessException =>
isNonNullableByDefault && nnbdMode != NnbdMode.Strong;
void registerIfUnreachableForTesting(TreeNode node, {bool? isReachable}) {
if (dataForTesting == null) return;
isReachable ??= flowAnalysis.isReachable;
if (!isReachable) {
dataForTesting!.flowAnalysisResult.unreachableNodes.add(node);
}
}
/// Ensures that the type of [member] has been computed.
void ensureMemberType(Member member) {
if (member is Constructor) {
inferConstructorParameterTypes(member);
}
TypeDependency? typeDependency = engine.typeDependencies.remove(member);
if (typeDependency != null) {
ensureMemberType(typeDependency.original);
typeDependency.copyInferred();
}
}
@override
void inferConstructorParameterTypes(Constructor target) {
ConstructorBuilder? constructor = engine.beingInferred[target];
if (constructor != null) {
// There is a cyclic dependency where inferring the types of the
// initializing formals of a constructor required us to infer the
// corresponding field type which required us to know the type of the
// constructor.
String name = target.enclosingClass.name;
if (target.name.text.isNotEmpty) {
// TODO(ahe): Use `inferrer.helper.constructorNameForDiagnostics`
// instead. However, `inferrer.helper` may be null.
name += ".${target.name.text}";
}
constructor.library.addProblem(
templateCantInferTypeDueToCircularity.withArguments(name),
target.fileOffset,
name.length,
target.fileUri);
// TODO(johnniwinther): Is this needed? VariableDeclaration.type is
// non-nullable so the loops have no effect.
/*for (VariableDeclaration declaration
in target.function.positionalParameters) {
declaration.type ??= const InvalidType();
}
for (VariableDeclaration declaration in target.function.namedParameters) {
declaration.type ??= const InvalidType();
}*/
} else if ((constructor = engine.toBeInferred[target]) != null) {
engine.toBeInferred.remove(target);
engine.beingInferred[target] = constructor!;
constructor.inferFormalTypes();
engine.beingInferred.remove(target);
}
}
@override
InitializerInferenceResult inferInitializer(
InferenceHelper helper, Initializer initializer) {
this.helper = helper;
// Use polymorphic dispatch on [KernelInitializer] to perform whatever
// kind of type inference is correct for this kind of initializer.
// TODO(paulberry): experiment to see if dynamic dispatch would be better,
// so that the type hierarchy will be simpler (which may speed up "is"
// checks).
InitializerInferenceResult inferenceResult;
if (initializer is InitializerJudgment) {
inferenceResult = initializer.acceptInference(new InferenceVisitor(this));
} else {
inferenceResult = initializer.accept(new InferenceVisitor(this));
}
this.helper = null;
return inferenceResult;
}
bool isDoubleContext(DartType typeContext) {
// A context is a double context if double is assignable to it but int is
// not. That is the type context is a double context if it is:
// * double
// * FutureOr<T> where T is a double context
//
// We check directly, rather than using isAssignable because it's simpler.
while (typeContext is FutureOrType) {
FutureOrType type = typeContext;
typeContext = type.typeArgument;
}
return typeContext is InterfaceType &&
typeContext.classNode == coreTypes.doubleClass;
}
bool isAssignable(DartType contextType, DartType expressionType) {
if (isNonNullableByDefault) {
if (expressionType is DynamicType) return true;
return typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(expressionType, contextType)
.isSubtypeWhenUsingNullabilities();
}
return typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(expressionType, contextType)
.orSubtypeCheckFor(contextType, expressionType, typeSchemaEnvironment)
.isSubtypeWhenIgnoringNullabilities();
}
Expression ensureAssignableResult(
DartType expectedType, ExpressionInferenceResult result,
{int? fileOffset,
bool isVoidAllowed: false,
Template<Message Function(DartType, DartType, bool)>? errorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityErrorTemplate,
Template<Message Function(DartType, bool)>? nullabilityNullErrorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityNullTypeErrorTemplate,
Template<Message Function(DartType, DartType, DartType, DartType, bool)>?
nullabilityPartErrorTemplate}) {
return ensureAssignable(
expectedType, result.inferredType, result.expression,
fileOffset: fileOffset,
isVoidAllowed: isVoidAllowed,
errorTemplate: errorTemplate,
nullabilityErrorTemplate: nullabilityErrorTemplate,
nullabilityNullErrorTemplate: nullabilityNullErrorTemplate,
nullabilityNullTypeErrorTemplate: nullabilityNullTypeErrorTemplate,
nullabilityPartErrorTemplate: nullabilityPartErrorTemplate);
}
/// Ensures that [expressionType] is assignable to [contextType].
///
/// Checks whether [expressionType] can be assigned to the greatest closure of
/// [contextType], and inserts an implicit downcast, inserts a tear-off, or
/// reports an error if appropriate.
///
/// If [declaredContextType] is provided, this is used instead of
/// [contextType] for reporting the type against which [expressionType] isn't
/// assignable. This is used when checking the assignability of return
/// statements in async functions in which the assignability is checked
/// against the future value type but the reporting should refer to the
/// declared return type.
///
/// If [runtimeCheckedType] is provided, this is used for the implicit cast,
/// otherwise [contextType] is used. This is used for return from async
/// where the returned expression is wrapped in a `Future`, if necessary,
/// before returned and therefore shouldn't be checked to be a `Future`
/// directly.
Expression ensureAssignable(
DartType contextType, DartType expressionType, Expression expression,
{int? fileOffset,
DartType? declaredContextType,
DartType? runtimeCheckedType,
bool isVoidAllowed: false,
Template<Message Function(DartType, DartType, bool)>? errorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityErrorTemplate,
Template<Message Function(DartType, bool)>? nullabilityNullErrorTemplate,
Template<Message Function(DartType, DartType, bool)>?
nullabilityNullTypeErrorTemplate,
Template<Message Function(DartType, DartType, DartType, DartType, bool)>?
nullabilityPartErrorTemplate,
Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
// ignore: unnecessary_null_comparison
assert(contextType != null);
// [errorTemplate], [nullabilityErrorTemplate], and
// [nullabilityPartErrorTemplate] should be provided together.
assert((errorTemplate == null) == (nullabilityErrorTemplate == null) &&
(nullabilityErrorTemplate == null) ==
(nullabilityPartErrorTemplate == null));
// [nullabilityNullErrorTemplate] and [nullabilityNullTypeErrorTemplate]
// should be provided together.
assert((nullabilityNullErrorTemplate == null) ==
(nullabilityNullTypeErrorTemplate == null));
errorTemplate ??= templateInvalidAssignmentError;
if (nullabilityErrorTemplate == null) {
// Use [templateInvalidAssignmentErrorNullabilityNull] only if no
// specific [nullabilityErrorTemplate] template was passed.
nullabilityNullErrorTemplate ??=
templateInvalidAssignmentErrorNullabilityNull;
}
nullabilityNullTypeErrorTemplate ??= nullabilityErrorTemplate ??
templateInvalidAssignmentErrorNullabilityNullType;
nullabilityErrorTemplate ??= templateInvalidAssignmentErrorNullability;
nullabilityPartErrorTemplate ??=
templateInvalidAssignmentErrorPartNullability;
// We don't need to insert assignability checks when doing top level type
// inference since top level type inference only cares about the type that
// is inferred (the kernel code is discarded).
if (isTopLevel) return expression;
fileOffset ??= expression.fileOffset;
contextType = computeGreatestClosure(contextType);
DartType initialContextType = runtimeCheckedType ?? contextType;
Template<Message Function(DartType, DartType, bool)>?
preciseTypeErrorTemplate = _getPreciseTypeErrorTemplate(expression);
AssignabilityResult assignabilityResult = _computeAssignabilityKind(
contextType, expressionType,
isNonNullableByDefault: isNonNullableByDefault,
isVoidAllowed: isVoidAllowed,
isExpressionTypePrecise: preciseTypeErrorTemplate != null);
if (assignabilityResult.needsTearOff) {
TypedTearoff typedTearoff =
_tearOffCall(expression, expressionType as InterfaceType, fileOffset);
expression = typedTearoff.tearoff;
expressionType = typedTearoff.tearoffType;
}
if (assignabilityResult.implicitInstantiation != null) {
ExpressionInferenceResult instantiationResult =
_applyImplicitInstantiation(assignabilityResult.implicitInstantiation,
expressionType, expression);
expression = instantiationResult.expression;
expressionType = instantiationResult.inferredType;
}
Expression result;
switch (assignabilityResult.kind) {
case AssignabilityKind.assignable:
result = expression;
break;
case AssignabilityKind.assignableCast:
// Insert an implicit downcast.
result = new AsExpression(expression, initialContextType)
..isTypeError = true
..isForNonNullableByDefault = isNonNullableByDefault
..isForDynamic = expressionType is DynamicType
..fileOffset = fileOffset;
break;
case AssignabilityKind.unassignable:
// Error: not assignable. Perform error recovery.
result = _wrapUnassignableExpression(
expression,
expressionType,
contextType,
errorTemplate.withArguments(expressionType,
declaredContextType ?? contextType, isNonNullableByDefault));
break;
case AssignabilityKind.unassignableVoid:
// Error: not assignable. Perform error recovery.
result = helper!.wrapInProblem(
expression, messageVoidExpression, expression.fileOffset, noLength);
break;
case AssignabilityKind.unassignablePrecise:
// The type of the expression is known precisely, so an implicit
// downcast is guaranteed to fail. Insert a compile-time error.
result = helper!.wrapInProblem(
expression,
preciseTypeErrorTemplate!.withArguments(
expressionType, contextType, isNonNullableByDefault),
expression.fileOffset,
noLength);
break;
case AssignabilityKind.unassignableCantTearoff:
result = _wrapTearoffErrorExpression(
expression, contextType, templateNullableTearoffError);
break;
case AssignabilityKind.unassignableNullability:
if (expressionType == assignabilityResult.subtype &&
contextType == assignabilityResult.supertype) {
if (expression is NullLiteral &&
nullabilityNullErrorTemplate != null) {
result = _wrapUnassignableExpression(
expression,
expressionType,
contextType,
nullabilityNullErrorTemplate.withArguments(
declaredContextType ?? contextType,
isNonNullableByDefault));
} else if (expressionType is NullType) {
result = _wrapUnassignableExpression(
expression,
expressionType,
contextType,
nullabilityNullTypeErrorTemplate.withArguments(
expressionType,
declaredContextType ?? contextType,
isNonNullableByDefault));
} else {
whyNotPromoted ??= flowAnalysis.whyNotPromoted(expression);
result = _wrapUnassignableExpression(
expression,
expressionType,
contextType,
nullabilityErrorTemplate.withArguments(expressionType,
declaredContextType ?? contextType, isNonNullableByDefault),
context: getWhyNotPromotedContext(
whyNotPromoted.call(),
expression,
(type) => typeSchemaEnvironment.isSubtypeOf(type,
contextType, SubtypeCheckMode.withNullabilities)));
}
} else {
result = _wrapUnassignableExpression(
expression,
expressionType,
contextType,
nullabilityPartErrorTemplate.withArguments(
expressionType,
declaredContextType ?? contextType,
assignabilityResult.subtype!,
assignabilityResult.supertype!,
isNonNullableByDefault));
}
break;
default:
return unhandled("${assignabilityResult}", "ensureAssignable",
fileOffset, helper!.uri);
}
if (!identical(result, expression)) {
flowAnalysis.forwardExpression(result, expression);
}
return result;
}
Expression _wrapTearoffErrorExpression(Expression expression,
DartType contextType, Template<Message Function(String)> template) {
// ignore: unnecessary_null_comparison
assert(template != null);
Expression errorNode = new AsExpression(
expression,
// TODO(ahe): The outline phase doesn't correctly remove invalid
// uses of type variables, for example, on static members. Once
// that has been fixed, we should always be able to use
// [contextType] directly here.
hasAnyTypeVariables(contextType)
? const NeverType.nonNullable()
: contextType)
..isTypeError = true
..fileOffset = expression.fileOffset;
if (contextType is! InvalidType) {
errorNode = helper!.wrapInProblem(
errorNode,
template.withArguments(callName.text),
errorNode.fileOffset,
noLength);
}
return errorNode;
}
Expression _wrapUnassignableExpression(Expression expression,
DartType expressionType, DartType contextType, Message message,
{List<LocatedMessage>? context}) {
Expression errorNode = new AsExpression(
expression,
// TODO(ahe): The outline phase doesn't correctly remove invalid
// uses of type variables, for example, on static members. Once
// that has been fixed, we should always be able to use
// [contextType] directly here.
hasAnyTypeVariables(contextType)
? const NeverType.nonNullable()
: contextType)
..isTypeError = true
..isForNonNullableByDefault = isNonNullableByDefault
..fileOffset = expression.fileOffset;
if (contextType is! InvalidType && expressionType is! InvalidType) {
errorNode = helper!.wrapInProblem(
errorNode, message, errorNode.fileOffset, noLength,
context: context);
}
return errorNode;
}
TypedTearoff _tearOffCall(
Expression expression, InterfaceType expressionType, int fileOffset) {
Class classNode = expressionType.classNode;
Member callMember = classHierarchy.getInterfaceMember(classNode, callName)!;
assert(callMember is Procedure && callMember.kind == ProcedureKind.Method);
// Replace expression with:
// `let t = expression in t == null ? null : t.call`
VariableDeclaration t =
new VariableDeclaration.forValue(expression, type: expressionType)
..fileOffset = fileOffset;
// TODO(johnniwinther): Avoid null-check for non-nullable expressions.
Expression nullCheck =
new EqualsNull(new VariableGet(t)..fileOffset = fileOffset)
..fileOffset = fileOffset;
DartType tearoffType =
getGetterTypeForMemberTarget(callMember, expressionType)
.withDeclaredNullability(expressionType.nullability);
Expression tearOff = new InstanceTearOff(
InstanceAccessKind.Instance, new VariableGet(t), callName,
interfaceTarget: callMember as Procedure, resultType: tearoffType)
..fileOffset = fileOffset;
ConditionalExpression conditional = new ConditionalExpression(nullCheck,
new NullLiteral()..fileOffset = fileOffset, tearOff, tearoffType);
return new TypedTearoff(
tearoffType, new Let(t, conditional)..fileOffset = fileOffset);
}
/// Computes the assignability kind of [expressionType] to [contextType].
///
/// The computation is side-effect free.
AssignabilityResult _computeAssignabilityKind(
DartType contextType, DartType expressionType,
{required bool isNonNullableByDefault,
required bool isVoidAllowed,
required bool isExpressionTypePrecise}) {
// ignore: unnecessary_null_comparison
assert(isNonNullableByDefault != null);
// ignore: unnecessary_null_comparison
assert(isVoidAllowed != null);
// ignore: unnecessary_null_comparison
assert(isExpressionTypePrecise != null);
// If an interface type is being assigned to a function type, see if we
// should tear off `.call`.
// TODO(paulberry): use resolveTypeParameter. See findInterfaceMember.
bool needsTearoff = false;
if (expressionType is InterfaceType) {
Class classNode = expressionType.classNode;
Member? callMember =
classHierarchy.getInterfaceMember(classNode, callName);
if (callMember is Procedure && callMember.kind == ProcedureKind.Method) {
if (_shouldTearOffCall(contextType, expressionType)) {
needsTearoff = true;
if (isNonNullableByDefault && expressionType.isPotentiallyNullable) {
return const AssignabilityResult(
AssignabilityKind.unassignableCantTearoff,
needsTearOff: false);
}
expressionType =
getGetterTypeForMemberTarget(callMember, expressionType)
.withDeclaredNullability(expressionType.nullability);
}
}
}
ImplicitInstantiation? implicitInstantiation;
if (library.enableConstructorTearOffsInLibrary) {
implicitInstantiation =
computeImplicitInstantiation(expressionType, contextType);
if (implicitInstantiation != null) {
expressionType = implicitInstantiation.instantiatedType;
}
}
if (expressionType is VoidType && !isVoidAllowed) {
assert(implicitInstantiation == null);
assert(!needsTearoff);
return const AssignabilityResult(AssignabilityKind.unassignableVoid,
needsTearOff: false);
}
IsSubtypeOf isDirectSubtypeResult = typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(expressionType, contextType);
bool isDirectlyAssignable = isNonNullableByDefault
? isDirectSubtypeResult.isSubtypeWhenUsingNullabilities()
: isDirectSubtypeResult.isSubtypeWhenIgnoringNullabilities();
if (isDirectlyAssignable) {
return new AssignabilityResult(AssignabilityKind.assignable,
needsTearOff: needsTearoff,
implicitInstantiation: implicitInstantiation);
}
bool isIndirectlyAssignable = isNonNullableByDefault
? expressionType is DynamicType
: typeSchemaEnvironment
.performNullabilityAwareSubtypeCheck(contextType, expressionType)
.isSubtypeWhenIgnoringNullabilities();
if (!isIndirectlyAssignable) {
if (isNonNullableByDefault &&
isDirectSubtypeResult.isSubtypeWhenIgnoringNullabilities()) {
return new AssignabilityResult.withTypes(
AssignabilityKind.unassignableNullability,
isDirectSubtypeResult.subtype,
isDirectSubtypeResult.supertype,
needsTearOff: needsTearoff,
implicitInstantiation: implicitInstantiation);
} else {
return new AssignabilityResult(AssignabilityKind.unassignable,
needsTearOff: needsTearoff,
implicitInstantiation: implicitInstantiation);
}
}
if (isExpressionTypePrecise) {
// The type of the expression is known precisely, so an implicit
// downcast is guaranteed to fail. Insert a compile-time error.
assert(implicitInstantiation == null);
assert(!needsTearoff);
return const AssignabilityResult(AssignabilityKind.unassignablePrecise,
needsTearOff: false);
}
// Insert an implicit downcast.
return new AssignabilityResult(AssignabilityKind.assignableCast,
needsTearOff: needsTearoff,
implicitInstantiation: implicitInstantiation);
}
bool isNull(DartType type) {
return type is NullType;
}
/// Computes the type arguments for an access to an extension instance member
/// on [extension] with the static [receiverType]. If [explicitTypeArguments]
/// are provided, these are returned, otherwise type arguments are inferred
/// using [receiverType].
List<DartType> computeExtensionTypeArgument(Extension extension,
List<DartType>? explicitTypeArguments, DartType receiverType) {
if (explicitTypeArguments != null) {
assert(explicitTypeArguments.length == extension.typeParameters.length);
return explicitTypeArguments;
} else if (extension.typeParameters.isEmpty) {
assert(explicitTypeArguments == null);
return const <DartType>[];
} else {
return inferExtensionTypeArguments(extension, receiverType);
}
}
/// Infers the type arguments for an access to an extension instance member
/// on [extension] with the static [receiverType].
List<DartType> inferExtensionTypeArguments(
Extension extension, DartType receiverType) {
List<TypeParameter> typeParameters = extension.typeParameters;
DartType onType = extension.onType;
List<DartType> inferredTypes =
new List<DartType>.filled(typeParameters.length, const UnknownType());
typeSchemaEnvironment.inferGenericFunctionOrType(null, typeParameters,
[onType], [receiverType], null, inferredTypes, library.library);
return inferredTypes;
}
ObjectAccessTarget _findShownExtensionTypeMember(
ExtensionType receiverType, Name name, int fileOffset,
{required ObjectAccessTarget defaultTarget,
required CallSiteAccessKind callSiteAccessKind,
required bool isPotentiallyNullable}) {
Extension extension = receiverType.extension;
ExtensionTypeShowHideClause? showHideClause = extension.showHideClause;
if (showHideClause == null) return defaultTarget;
kernel.Reference? reference = showHideClause.findShownReference(
name, callSiteAccessKind, classHierarchy);
if (reference != null) {
return new ObjectAccessTarget.interfaceMember(reference.asMember,
isPotentiallyNullable: isPotentiallyNullable);
} else {
return defaultTarget;
}
}
/// Returns extension member declared immediately for [receiverType].
///
/// If none is found, [defaultTarget] is returned.
ObjectAccessTarget _findDirectExtensionTypeMember(
ExtensionType receiverType, Name name, int fileOffset,
{required ObjectAccessTarget defaultTarget, required bool isSetter}) {
Member? targetMember;
Member? targetTearoff;
ProcedureKind? targetKind;
for (ExtensionMemberDescriptor descriptor
in receiverType.extension.members) {
if (descriptor.name == name) {
switch (descriptor.kind) {
case ExtensionMemberKind.Method:
if (!isSetter) {
targetMember = descriptor.member.asMember;
targetTearoff ??= targetMember;
targetKind = ProcedureKind.Method;
}
break;
case ExtensionMemberKind.TearOff:
if (!isSetter) {
targetTearoff = descriptor.member.asMember;
}
break;
case ExtensionMemberKind.Getter:
if (!isSetter) {
targetMember = descriptor.member.asMember;
targetTearoff = null;
targetKind = ProcedureKind.Getter;
}
break;
case ExtensionMemberKind.Setter:
if (isSetter) {
targetMember = descriptor.member.asMember;
targetTearoff = null;
targetKind = ProcedureKind.Setter;
}
break;
case ExtensionMemberKind.Operator:
if (!isSetter) {
targetMember = descriptor.member.asMember;
targetTearoff = null;
targetKind = ProcedureKind.Operator;
}
break;
default:
unhandled("${descriptor.kind}", "_findDirectExtensionMember",
fileOffset, library.fileUri);
}
}
}
if (targetMember != null) {
assert(targetKind != null);
return new ObjectAccessTarget.extensionMember(
targetMember, targetTearoff, targetKind!, receiverType.typeArguments);
} else {
return defaultTarget;
}
}
/// Returns the extension member access by the given [name] for a receiver
/// with the static [receiverType].
///
/// If none is found, [defaultTarget] is returned.
///
/// If multiple are found, none more specific, an
/// [AmbiguousExtensionAccessTarget] is returned. This access kind results in
/// a compile-time error, but is used to provide a better message than just
/// reporting that the receiver does not have a member by the given name.
///
/// If [isPotentiallyNullableAccess] is `true`, the returned extension member
/// is flagged as a nullable extension member access. This access kind results
/// in a compile-time error, but is used to provide a better message than just
/// reporting that the receiver does not have a member by the given name.
ObjectAccessTarget? _findExtensionMember(
DartType receiverType, Class classNode, Name name, int fileOffset,
{bool setter: false,
ObjectAccessTarget? defaultTarget,
bool isPotentiallyNullableAccess: false}) {
Name otherName = name;
bool otherIsSetter;
if (name == indexGetName) {
// [] must be checked against []=.
otherName = indexSetName;
otherIsSetter = false;
} else if (name == indexSetName) {
// []= must be checked against [].
otherName = indexGetName;
otherIsSetter = false;
} else {
otherName = name;
otherIsSetter = !setter;
}
Member? otherMember =
_getInterfaceMember(classNode, otherName, otherIsSetter, fileOffset);
if (otherMember != null) {
// If we're looking for `foo` and `foo=` can be found or vice-versa then
// extension methods should not be found.
return defaultTarget;
}
ExtensionAccessCandidate? bestSoFar;
List<ExtensionAccessCandidate> noneMoreSpecific = [];
library.forEachExtensionInScope((ExtensionBuilder extensionBuilder) {
MemberBuilder? thisBuilder = extensionBuilder
.lookupLocalMemberByName(name, setter: setter) as MemberBuilder?;
MemberBuilder? otherBuilder = extensionBuilder.lookupLocalMemberByName(
otherName,
setter: otherIsSetter) as MemberBuilder?;
if ((thisBuilder != null && !thisBuilder.isStatic) ||
(otherBuilder != null && !otherBuilder.isStatic)) {
DartType onType;
DartType onTypeInstantiateToBounds;
List<DartType> inferredTypeArguments;
if (extensionBuilder.extension.typeParameters.isEmpty) {
onTypeInstantiateToBounds =
onType = extensionBuilder.extension.onType;
inferredTypeArguments = const <DartType>[];
} else {
List<TypeParameter> typeParameters =
extensionBuilder.extension.typeParameters;
inferredTypeArguments = inferExtensionTypeArguments(
extensionBuilder.extension, receiverType);
Substitution inferredSubstitution =
Substitution.fromPairs(typeParameters, inferredTypeArguments);
for (int index = 0; index < typeParameters.length; index++) {
TypeParameter typeParameter = typeParameters[index];
DartType typeArgument = inferredTypeArguments[index];
DartType bound =
inferredSubstitution.substituteType(typeParameter.bound);
if (!typeSchemaEnvironment.isSubtypeOf(
typeArgument, bound, SubtypeCheckMode.withNullabilities)) {
return;
}
}
onType = inferredSubstitution
.substituteType(extensionBuilder.extension.onType);
List<DartType> instantiateToBoundTypeArguments = calculateBounds(
typeParameters, coreTypes.objectClass, library.library);
Substitution instantiateToBoundsSubstitution = Substitution.fromPairs(
typeParameters, instantiateToBoundTypeArguments);
onTypeInstantiateToBounds = instantiateToBoundsSubstitution
.substituteType(extensionBuilder.extension.onType);
}
if (typeSchemaEnvironment.isSubtypeOf(
receiverType, onType, SubtypeCheckMode.withNullabilities)) {
ObjectAccessTarget target = const ObjectAccessTarget.missing();
if (thisBuilder != null && !thisBuilder.isStatic) {
if (thisBuilder.isField) {
if (thisBuilder.isExternal) {
target = new ObjectAccessTarget.extensionMember(
setter ? thisBuilder.writeTarget! : thisBuilder.readTarget!,
thisBuilder.readTarget,
setter ? ProcedureKind.Setter : ProcedureKind.Getter,
inferredTypeArguments,
isPotentiallyNullable: isPotentiallyNullableAccess);
}
} else {
target = new ObjectAccessTarget.extensionMember(
setter ? thisBuilder.writeTarget! : thisBuilder.invokeTarget!,
thisBuilder.readTarget,
thisBuilder.kind!,
inferredTypeArguments,
isPotentiallyNullable: isPotentiallyNullableAccess);
}
}
ExtensionAccessCandidate candidate = new ExtensionAccessCandidate(
(thisBuilder ?? otherBuilder)!,
onType,
onTypeInstantiateToBounds,
target,
isPlatform: extensionBuilder.library.importUri.scheme == 'dart');
if (noneMoreSpecific.isNotEmpty) {
bool isMostSpecific = true;
for (ExtensionAccessCandidate other in noneMoreSpecific) {
bool? isMoreSpecific =
candidate.isMoreSpecificThan(typeSchemaEnvironment, other);
if (isMoreSpecific != true) {
isMostSpecific = false;
break;
}
}
if (isMostSpecific) {
bestSoFar = candidate;
noneMoreSpecific.clear();
} else {
noneMoreSpecific.add(candidate);
}
} else if (bestSoFar == null) {
bestSoFar = candidate;
} else {
bool? isMoreSpecific =
candidate.isMoreSpecificThan(typeSchemaEnvironment, bestSoFar!);
if (isMoreSpecific == true) {
bestSoFar = candidate;
} else if (isMoreSpecific == null) {
noneMoreSpecific.add(bestSoFar!);
noneMoreSpecific.add(candidate);
bestSoFar = null;
}
}
}
}
});
if (bestSoFar != null) {
return bestSoFar!.target;
} else {
if (noneMoreSpecific.isNotEmpty) {
return new AmbiguousExtensionAccessTarget(noneMoreSpecific);
}
}
return defaultTarget;
}
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation using [fileOffset].
///
/// 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).
ObjectAccessTarget findInterfaceMember(
DartType receiverType, Name name, int fileOffset,
{required CallSiteAccessKind callSiteAccessKind,
bool instrumented: true,
bool includeExtensionMethods: false}) {
// ignore: unnecessary_null_comparison
assert(receiverType != null && isKnown(receiverType));
bool isSetter = callSiteAccessKind == CallSiteAccessKind.setterInvocation;
DartType receiverBound = resolveTypeParameter(receiverType);
bool isReceiverTypePotentiallyNullable = isNonNullableByDefault &&
receiverType.isPotentiallyNullable &&
// Calls to `==` are always on a non-null receiver.
name != equalsName;
Class classNode = receiverBound is InterfaceType
? receiverBound.classNode
: coreTypes.objectClass;
if (isReceiverTypePotentiallyNullable) {
Member? member = _getInterfaceMember(
coreTypes.objectClass, name, isSetter, fileOffset);
if (member != null) {
// Null implements all Object members so this is not considered a
// potentially nullable access.
return new ObjectAccessTarget.objectMember(member);
}
if (includeExtensionMethods && receiverBound is! DynamicType) {
ObjectAccessTarget? target = _findExtensionMember(
isNonNullableByDefault ? receiverType : receiverBound,
coreTypes.objectClass,
name,
fileOffset,
setter: isSetter);
if (target != null) {
return target;
}
}
}
if (receiverBound is FunctionType && name == callName) {
return isReceiverTypePotentiallyNullable
? const ObjectAccessTarget.nullableCallFunction()
: const ObjectAccessTarget.callFunction();
} else if (receiverBound is NeverType) {
switch (receiverBound.nullability) {
case Nullability.nonNullable:
return const ObjectAccessTarget.never();
case Nullability.nullable:
case Nullability.legacy:
// Never? and Never* are equivalent to Null.
return findInterfaceMember(const NullType(), name, fileOffset,
callSiteAccessKind: callSiteAccessKind);
case Nullability.undetermined:
return internalProblem(
templateInternalProblemUnsupportedNullability.withArguments(
"${receiverBound.nullability}",
receiverBound,
isNonNullableByDefault),
fileOffset,
library.fileUri);
}
}
ObjectAccessTarget? target;
Member? interfaceMember =
_getInterfaceMember(classNode, name, isSetter, fileOffset);
if (interfaceMember != null) {
target = new ObjectAccessTarget.interfaceMember(interfaceMember,
isPotentiallyNullable: isReceiverTypePotentiallyNullable);
} else if (receiverBound is DynamicType) {
target = const ObjectAccessTarget.dynamic();
} else if (receiverBound is InvalidType) {
target = const ObjectAccessTarget.invalid();
} else if (receiverBound is InterfaceType &&
receiverBound.classNode == coreTypes.functionClass &&
name == callName) {
target = isReceiverTypePotentiallyNullable
? const ObjectAccessTarget.nullableCallFunction()
: const ObjectAccessTarget.callFunction();
} else if (library.enableExtensionTypesInLibrary &&
receiverBound is ExtensionType) {
target = _findDirectExtensionTypeMember(receiverBound, name, fileOffset,
isSetter: isSetter,
defaultTarget: const ObjectAccessTarget.missing());
if (target.kind == ObjectAccessTargetKind.missing) {
target = _findShownExtensionTypeMember(receiverBound, name, fileOffset,
callSiteAccessKind: callSiteAccessKind,
isPotentiallyNullable: isReceiverTypePotentiallyNullable,
defaultTarget: const ObjectAccessTarget.missing());
}
} else {
target = const ObjectAccessTarget.missing();
}
if (instrumented &&
receiverBound != const DynamicType() &&
(target.isInstanceMember || target.isObjectMember)) {
instrumentation?.record(uriForInstrumentation, fileOffset, 'target',
new InstrumentationValueForMember(target.member!));
}
if (target.isMissing && includeExtensionMethods) {
if (isReceiverTypePotentiallyNullable) {
// When the receiver type is potentially nullable we would have found
// the extension member above, if available. Therefore we know that we
// are in an erroneous case and instead look up the extension member on
// the non-nullable receiver bound but flag the found target as a
// nullable extension member access. This is done to provide the better
// error message that the extension member exists but that the access is
// invalid.
target = _findExtensionMember(
isNonNullableByDefault
? receiverType.toNonNull()
: receiverBound.toNonNull(),
classNode,
name,
fileOffset,
setter: isSetter,
defaultTarget: target,
isPotentiallyNullableAccess: true)!;
} else {
target = _findExtensionMember(
isNonNullableByDefault ? receiverType : receiverBound,
classNode,
name,
fileOffset,
setter: isSetter,
defaultTarget: target)!;
}
}
return target;
}
/// If target is missing on a non-dynamic receiver, an error is reported
/// using [errorTemplate] and an invalid expression is returned.
Expression? reportMissingInterfaceMember(
ObjectAccessTarget target,
DartType receiverType,
Name name,
int fileOffset,
Template<Message Function(String, DartType, bool)> errorTemplate) {
// ignore: unnecessary_null_comparison
assert(receiverType != null && isKnown(receiverType));
// ignore: unnecessary_null_comparison
if (!isTopLevel && target.isMissing && errorTemplate != null) {
int length = name.text.length;
if (identical(name.text, callName.text) ||
identical(name.text, unaryMinusName.text)) {
length = 1;
}
return helper!.buildProblem(
errorTemplate.withArguments(name.text,
resolveTypeParameter(receiverType), isNonNullableByDefault),
fileOffset,
length);
}
return null;
}
/// Returns [type] as passed from [superClass] to the current class.
///
/// If a legacy class occurs between the current class and [superClass] then
/// [type] needs to be legacy erased. For instance
///
/// // Opt in:
/// class Super {
/// int extendedMethod(int i, {required int j}) => i;
/// }
/// class Mixin {
/// int mixedInMethod(int i, {required int j}) => i;
/// }
/// // Opt out:
/// class Legacy extends Super with Mixin {}
/// // Opt in:
/// class Class extends Legacy {
/// test() {
/// // Ok to call `Legacy.extendedMethod` since its type is
/// // `int* Function(int*, {int* j})`.
/// super.extendedMethod(null);
/// // Ok to call `Legacy.mixedInMethod` since its type is
/// // `int* Function(int*, {int* j})`.
/// super.mixedInMethod(null);
/// }
/// }
///
DartType computeTypeFromSuperClass(Class superClass, DartType type) {
if (needsLegacyErasure(thisType!.classNode, superClass)) {
type = legacyErasure(type);
}
return type;
}
/// Returns the type of [target] when accessed as a getter on [receiverType].
///
/// For instance
///
/// class Class<T> {
/// T method() {}
/// T getter => null;
/// }
///
/// Class<int> c = ...
/// c.method; // The getter type is `int Function()`.
/// c.getter; // The getter type is `int`.
///
DartType getGetterType(ObjectAccessTarget target, DartType receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
return receiverType;
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
return const DynamicType();
case ObjectAccessTargetKind.never:
return const NeverType.nonNullable();
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
return getGetterTypeForMemberTarget(target.member!, receiverType);
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Method:
case ProcedureKind.Operator:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
List<TypeParameter> extensionTypeParameters = functionType
.typeParameters
.take(target.inferredExtensionTypeArguments.length)
.toList();
Substitution substitution = Substitution.fromPairs(
extensionTypeParameters, target.inferredExtensionTypeArguments);
DartType resultType = substitution.substituteType(new FunctionType(
functionType.positionalParameters.skip(1).toList(),
functionType.returnType,
library.nonNullable,
namedParameters: functionType.namedParameters,
typeParameters: functionType.typeParameters
.skip(target.inferredExtensionTypeArguments.length)
.toList(),
requiredParameterCount:
functionType.requiredParameterCount - 1));
if (!isNonNullableByDefault) {
resultType = legacyErasure(resultType);
}
return resultType;
case ProcedureKind.Getter:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
List<TypeParameter> extensionTypeParameters = functionType
.typeParameters
.take(target.inferredExtensionTypeArguments.length)
.toList();
Substitution substitution = Substitution.fromPairs(
extensionTypeParameters, target.inferredExtensionTypeArguments);
DartType resultType =
substitution.substituteType(functionType.returnType);
if (!isNonNullableByDefault) {
resultType = legacyErasure(resultType);
}
return resultType;
case ProcedureKind.Setter:
case ProcedureKind.Factory:
break;
}
}
throw unhandled('$target', 'getGetterType', -1, null);
}
/// Returns the getter type of [interfaceMember] on a receiver of type
/// [receiverType].
///
/// For instance
///
/// class Class<T> {
/// T method() {}
/// T getter => null;
/// }
///
/// Class<int> c = ...
/// c.method; // The getter type is `int Function()`.
/// c.getter; // The getter type is `int`.
///
DartType getGetterTypeForMemberTarget(
Member interfaceMember, DartType receiverType) {
Class memberClass = interfaceMember.enclosingClass!;
assert(interfaceMember is Field || interfaceMember is Procedure,
"Unexpected interface member $interfaceMember.");
DartType calleeType = interfaceMember.getterType;
if (memberClass.typeParameters.isNotEmpty) {
receiverType = resolveTypeParameter(receiverType);
if (receiverType is InterfaceType) {
List<DartType> castedTypeArguments = classHierarchy
.getTypeArgumentsAsInstanceOf(receiverType, memberClass)!;
calleeType = Substitution.fromPairs(
memberClass.typeParameters, castedTypeArguments)
.substituteType(calleeType);
}
}
if (!isNonNullableByDefault) {
calleeType = legacyErasure(calleeType);
}
return calleeType;
}
/// Returns the type of [target] when accessed as an invocation on
/// [receiverType].
///
/// If the target is known not to be invokable [unknownFunction] is returned.
///
/// For instance
///
/// class Class<T> {
/// T method() {}
/// T Function() getter1 => null;
/// T getter2 => null;
/// }
///
/// Class<int> c = ...
/// c.method; // The getter type is `int Function()`.
/// c.getter1; // The getter type is `int Function()`.
/// c.getter2; // The getter type is [unknownFunction].
///
FunctionType getFunctionType(
ObjectAccessTarget target, DartType receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
return _getFunctionType(receiverType);
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.invalid:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
return unknownFunction;
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
return _getFunctionType(
getGetterTypeForMemberTarget(target.member!, receiverType));
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Method:
case ProcedureKind.Operator:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
if (!isNonNullableByDefault) {
functionType = legacyErasure(functionType) as FunctionType;
}
return functionType;
case ProcedureKind.Getter:
// TODO(johnniwinther): Handle implicit .call on extension getter.
return _getFunctionType(target.member!.function!.returnType);
case ProcedureKind.Setter:
case ProcedureKind.Factory:
break;
}
}
throw unhandled('$target', 'getFunctionType', -1, null);
}
/// Returns the type of the receiver argument in an access to an extension
/// member on [extension] with the given extension [typeArguments].
DartType getExtensionReceiverType(
Extension extension, List<DartType> typeArguments) {
DartType receiverType = extension.onType;
if (extension.typeParameters.isNotEmpty) {
Substitution substitution =
Substitution.fromPairs(extension.typeParameters, typeArguments);
return substitution.substituteType(receiverType);
}
return receiverType;
}
/// Returns the return type of the invocation of [target] on [receiverType].
// TODO(johnniwinther): Cleanup [getFunctionType], [getReturnType],
// [getIndexKeyType] and [getIndexSetValueType]. We shouldn't need that many.
DartType getReturnType(ObjectAccessTarget target, DartType receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
FunctionType functionType = _getFunctionType(
getGetterTypeForMemberTarget(target.member!, receiverType));
return functionType.returnType;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Operator:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
DartType returnType = functionType.returnType;
if (functionType.typeParameters.isNotEmpty) {
Substitution substitution = Substitution.fromPairs(
functionType.typeParameters,
target.inferredExtensionTypeArguments);
returnType = substitution.substituteType(returnType);
}
if (!isNonNullableByDefault) {
returnType = legacyErasure(returnType);
}
return returnType;
default:
throw unhandled('$target', 'getFunctionType', -1, null);
}
case ObjectAccessTargetKind.never:
return const NeverType.nonNullable();
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
break;
}
return const DynamicType();
}
DartType getPositionalParameterTypeForTarget(
ObjectAccessTarget target, DartType receiverType, int index) {
switch (target.kind) {
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
FunctionType functionType = _getFunctionType(
getGetterTypeForMemberTarget(target.member!, receiverType));
if (functionType.positionalParameters.length > index) {
return functionType.positionalParameters[index];
}
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
FunctionType functionType =
target.member!.function!.computeFunctionType(library.nonNullable);
if (functionType.positionalParameters.length > index + 1) {
DartType keyType = functionType.positionalParameters[index + 1];
if (functionType.typeParameters.isNotEmpty) {
Substitution substitution = Substitution.fromPairs(
functionType.typeParameters,
target.inferredExtensionTypeArguments);
keyType = substitution.substituteType(keyType);
}
if (!isNonNullableByDefault) {
keyType = legacyErasure(keyType);
}
return keyType;
}
break;
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
break;
}
return const DynamicType();
}
/// Returns the type of the 'key' parameter in an [] or []= implementation.
///
/// For instance
///
/// class Class<K, V> {
/// V operator [](K key) => null;
/// void operator []=(K key, V value) {}
/// }
///
/// extension Extension<K, V> on Class<K, V> {
/// V operator [](K key) => null;
/// void operator []=(K key, V value) {}
/// }
///
/// new Class<int, String>()[0]; // The key type is `int`.
/// new Class<int, String>()[0] = 'foo'; // The key type is `int`.
/// Extension<int, String>(null)[0]; // The key type is `int`.
/// Extension<int, String>(null)[0] = 'foo'; // The key type is `int`.
///
DartType getIndexKeyType(ObjectAccessTarget target, DartType receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
FunctionType functionType = _getFunctionType(
getGetterTypeForMemberTarget(target.member!, receiverType));
if (functionType.positionalParameters.length >= 1) {
return functionType.positionalParameters[0];
}
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Operator:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
if (functionType.positionalParameters.length >= 2) {
DartType keyType = functionType.positionalParameters[1];
if (functionType.typeParameters.isNotEmpty) {
Substitution substitution = Substitution.fromPairs(
functionType.typeParameters,
target.inferredExtensionTypeArguments);
keyType = substitution.substituteType(keyType);
}
if (!isNonNullableByDefault) {
keyType = legacyErasure(keyType);
}
return keyType;
}
break;
default:
throw unhandled('$target', 'getFunctionType', -1, null);
}
break;
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
break;
}
return const DynamicType();
}
/// Returns the type of the 'value' parameter in an []= implementation.
///
/// For instance
///
/// class Class<K, V> {
/// void operator []=(K key, V value) {}
/// }
///
/// extension Extension<K, V> on Class<K, V> {
/// void operator []=(K key, V value) {}
/// }
///
/// new Class<int, String>()[0] = 'foo'; // The value type is `String`.
/// Extension<int, String>(null)[0] = 'foo'; // The value type is `String`.
///
DartType getIndexSetValueType(
ObjectAccessTarget target, DartType? receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
FunctionType functionType = _getFunctionType(
getGetterTypeForMemberTarget(target.member!, receiverType!));
if (functionType.positionalParameters.length >= 2) {
return functionType.positionalParameters[1];
}
break;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Operator:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
if (functionType.positionalParameters.length >= 3) {
DartType indexType = functionType.positionalParameters[2];
if (functionType.typeParameters.isNotEmpty) {
Substitution substitution = Substitution.fromPairs(
functionType.typeParameters,
target.inferredExtensionTypeArguments);
indexType = substitution.substituteType(indexType);
}
if (!isNonNullableByDefault) {
indexType = legacyErasure(indexType);
}
return indexType;
}
break;
default:
throw unhandled('$target', 'getFunctionType', -1, null);
}
break;
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
break;
}
return const DynamicType();
}
FunctionType _getFunctionType(DartType calleeType) {
calleeType = resolveTypeParameter(calleeType);
if (calleeType is FunctionType) {
if (!isNonNullableByDefault) {
calleeType = legacyErasure(calleeType);
}
return calleeType as FunctionType;
}
return unknownFunction;
}
FunctionType getFunctionTypeForImplicitCall(DartType calleeType) {
calleeType = resolveTypeParameter(calleeType);
if (calleeType is FunctionType) {
if (!isNonNullableByDefault) {
calleeType = legacyErasure(calleeType);
}
return calleeType as FunctionType;
} else if (calleeType is InterfaceType) {
Member? member =
_getInterfaceMember(calleeType.classNode, callName, false, -1);
if (member != null) {
DartType callType = getGetterTypeForMemberTarget(member, calleeType);
if (callType is FunctionType) {
if (!isNonNullableByDefault) {
callType = legacyErasure(callType);
}
return callType as FunctionType;
}
}
}
return unknownFunction;
}
DartType? getDerivedTypeArgumentOf(DartType type, Class class_) {
if (type is InterfaceType) {
List<DartType>? typeArgumentsAsInstanceOfClass =
classHierarchy.getTypeArgumentsAsInstanceOf(type, class_);
if (typeArgumentsAsInstanceOfClass != null) {
return typeArgumentsAsInstanceOfClass[0];
}
}
return null;
}
/// If the [member] is a forwarding stub, return the target it forwards to.
/// Otherwise return the given [member].
Member getRealTarget(Member member) {
if (member is Procedure && member.isForwardingStub) {
return member.abstractForwardingStubTarget!;
}
return member;
}
DartType getSetterType(ObjectAccessTarget target, DartType receiverType) {
switch (target.kind) {
case ObjectAccessTargetKind.dynamic:
case ObjectAccessTargetKind.never:
case ObjectAccessTargetKind.missing:
case ObjectAccessTargetKind.ambiguous:
return const DynamicType();
case ObjectAccessTargetKind.invalid:
return const InvalidType();
case ObjectAccessTargetKind.instanceMember:
case ObjectAccessTargetKind.objectMember:
case ObjectAccessTargetKind.nullableInstanceMember:
Member interfaceMember = target.member!;
Class memberClass = interfaceMember.enclosingClass!;
DartType setterType;
if (interfaceMember is Procedure) {
assert(interfaceMember.kind == ProcedureKind.Setter);
List<VariableDeclaration> setterParameters =
interfaceMember.function.positionalParameters;
setterType = setterParameters.length > 0
? setterParameters[0].type
: const DynamicType();
} else if (interfaceMember is Field) {
setterType = interfaceMember.type;
} else {
throw unhandled(interfaceMember.runtimeType.toString(),
'getSetterType', -1, null);
}
if (memberClass.typeParameters.isNotEmpty) {
receiverType = resolveTypeParameter(receiverType);
if (receiverType is InterfaceType) {
setterType = Substitution.fromPairs(
memberClass.typeParameters,
classHierarchy.getTypeArgumentsAsInstanceOf(
receiverType, memberClass)!)
.substituteType(setterType);
}
}
if (!isNonNullableByDefault) {
setterType = legacyErasure(setterType);
}
return setterType;
case ObjectAccessTargetKind.extensionMember:
case ObjectAccessTargetKind.nullableExtensionMember:
switch (target.extensionMethodKind) {
case ProcedureKind.Setter:
FunctionType functionType = target.member!.function!
.computeFunctionType(library.nonNullable);
List<TypeParameter> extensionTypeParameters = functionType
.typeParameters
.take(target.inferredExtensionTypeArguments.length)
.toList();
Substitution substitution = Substitution.fromPairs(
extensionTypeParameters, target.inferredExtensionTypeArguments);
DartType setterType = substitution
.substituteType(functionType.positionalParameters[1]);
if (!isNonNullableByDefault) {
setterType = legacyErasure(setterType);
}
return setterType;
case ProcedureKind.Method:
case ProcedureKind.Getter:
case ProcedureKind.Factory:
case ProcedureKind.Operator:
break;
}
// TODO(johnniwinther): Compute the right setter type.
return const DynamicType();
case ObjectAccessTargetKind.callFunction:
case ObjectAccessTargetKind.nullableCallFunction:
break;
}
throw unhandled(target.runtimeType.toString(), 'getSetterType', -1, null);
}
DartType getTypeArgumentOf(DartType type, Class class_) {
if (type is InterfaceType && identical(type.classNode, class_)) {
return type.typeArguments[0];
} else {
return const UnknownType();
}
}
/// Modifies a type as appropriate when inferring a declared variable's type.
@override
DartType inferDeclarationType(DartType initializerType,
{bool forSyntheticVariable: false}) {
if (initializerType is NullType) {
// If the initializer type is Null or bottom, the inferred type is
// dynamic.
// TODO(paulberry): this rule is inherited from analyzer behavior but is
// not spec'ed anywhere.
return const DynamicType();
}
if (forSyntheticVariable) {
return normalizeNullabilityInLibrary(initializerType, library.library);
} else {
return demoteTypeInLibrary(initializerType, library.library);
}
}
void inferSyntheticVariable(VariableDeclarationImpl variable) {
assert(variable.isImplicitlyTyped);
assert(variable.initializer != null);
ExpressionInferenceResult result = inferExpression(
variable.initializer!, const UnknownType(), true,
isVoidAllowed: true);
variable.initializer = result.expression..parent = variable;
DartType inferredType =
inferDeclarationType(result.inferredType, forSyntheticVariable: true);
instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
variable.type = inferredType;
}
Link<NullAwareGuard> inferSyntheticVariableNullAware(
VariableDeclarationImpl variable) {
assert(variable.isImplicitlyTyped);
assert(variable.initializer != null);
ExpressionInferenceResult result = inferNullAwareExpression(
variable.initializer!, const UnknownType(), true,
isVoidAllowed: true);
Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards;
variable.initializer = result.nullAwareAction..parent = variable;
DartType inferredType =
inferDeclarationType(result.inferredType, forSyntheticVariable: true);
instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
variable.type = inferredType;
return nullAwareGuards;
}
NullAwareGuard createNullAwareGuard(VariableDeclaration variable) {
return new NullAwareGuard(variable, variable.fileOffset, this);
}
ExpressionInferenceResult wrapExpressionInferenceResultInProblem(
ExpressionInferenceResult result,
Message message,
int fileOffset,
int length,
{List<LocatedMessage>? context}) {
return createNullAwareExpressionInferenceResult(
result.inferredType,
helper!.wrapInProblem(
result.nullAwareAction, message, fileOffset, length,
context: context),
result.nullAwareGuards);
}
ExpressionInferenceResult createNullAwareExpressionInferenceResult(
DartType inferredType,
Expression expression,
Link<NullAwareGuard>? nullAwareGuards) {
if (nullAwareGuards != null && nullAwareGuards.isNotEmpty) {
return new NullAwareExpressionInferenceResult(
computeNullable(inferredType),
inferredType,
nullAwareGuards,
expression);
} else {
return new ExpressionInferenceResult(inferredType, expression);
}
}
/// Performs type inference on the given [expression].
///
/// [typeContext] is the expected type of the expression, based on surrounding
/// code. [typeNeeded] indicates whether it is necessary to compute the
/// actual type of the expression. If [typeNeeded] is `true`,
/// [ExpressionInferenceResult.inferredType] is the actual type of the
/// expression; otherwise `null`.
///
/// Derived classes should override this method with logic that dispatches on
/// the expression type and calls the appropriate specialized "infer" method.
ExpressionInferenceResult _inferExpression(
Expression expression, DartType typeContext, bool typeNeeded,
{bool isVoidAllowed: false, bool forEffect: false}) {
registerIfUnreachableForTesting(expression);
// `null` should never be used as the type context. An instance of
// `UnknownType` should be used instead.
// ignore: unnecessary_null_comparison
assert(typeContext != null);
// For full (non-top level) inference, we need access to the
// ExpressionGeneratorHelper so that we can perform error recovery.
assert(isTopLevel || helper != null);
// When doing top level inference, we skip subexpressions whose type isn't
// needed so that we don't induce bogus dependencies on fields mentioned in
// those subexpressions.
if (!typeNeeded) {
return new ExpressionInferenceResult(const UnknownType(), expression);
}
InferenceVisitor visitor = new InferenceVisitor(this);
ExpressionInferenceResult result;
if (expression is ExpressionJudgment) {
result = expression.acceptInference(visitor, typeContext);
} else if (expression is InternalExpression) {
result = expression.acceptInference(visitor, typeContext);
} else {
result = expression.accept1(visitor, typeContext);
}
DartType inferredType = result.inferredType;
// ignore: unnecessary_null_comparison
assert(inferredType != null,
"No type inferred for $expression (${expression.runtimeType}).");
if (inferredType is VoidType && !isVoidAllowed) {
if (expression.parent is! ArgumentsImpl) {
helper?.addProblem(
messageVoidExpression, expression.fileOffset, noLength);
}
}
if (coreTypes.isBottom(result.inferredType)) {
flowAnalysis.handleExit();
if (shouldThrowUnsoundnessException &&
// Don't throw on expressions that inherently return the bottom type.
!(result.nullAwareAction is Throw ||
result.nullAwareAction is Rethrow ||
result.nullAwareAction is InvalidExpression)) {
Expression replacement = createLet(
createVariable(result.expression, result.inferredType),
createReachabilityError(expression.fileOffset,
messageNeverValueError, messageNeverValueWarning));
flowAnalysis.forwardExpression(replacement, result.expression);
result =
new ExpressionInferenceResult(result.inferredType, replacement);
}
}
return result;
}
@override
ExpressionInferenceResult inferExpression(
Expression expression, DartType typeContext, bool typeNeeded,
{bool isVoidAllowed: false, bool forEffect: false}) {
ExpressionInferenceResult result = _inferExpression(
expression, typeContext, typeNeeded,
isVoidAllowed: isVoidAllowed, forEffect: forEffect);
return result.stopShorting();
}
ExpressionInferenceResult inferNullAwareExpression(
Expression expression, DartType typeContext, bool typeNeeded,
{bool isVoidAllowed: false, bool forEffect: false}) {
ExpressionInferenceResult result = _inferExpression(
expression, typeContext, typeNeeded,
isVoidAllowed: isVoidAllowed, forEffect: forEffect);
if (isNonNullableByDefault) {
return result;
} else {
return result.stopShorting();
}
}
@override
Expression inferFieldInitializer(
InferenceHelper helper,
DartType context,
Expression initializer,
) {
assert(closureContext == null);
assert(!isTopLevel);
this.helper = helper;
ExpressionInferenceResult initializerResult =
inferExpression(initializer, context, true, isVoidAllowed: true);
initializer = ensureAssignableResult(context, initializerResult,
isVoidAllowed: context is VoidType);
this.helper = null;
return initializer;
}
@override
InferredFunctionBody inferFunctionBody(InferenceHelper helper, int fileOffset,
DartType returnType, AsyncMarker asyncMarker, Statement body) {
// ignore: unnecessary_null_comparison
assert(body != null);
// ignore: unnecessary_null_comparison
assert(closureContext == null);
this.helper = helper;
closureContext = new ClosureContext(this, asyncMarker, returnType, false);
StatementInferenceResult result = inferStatement(body);
if (dataForTesting != null) {
if (!flowAnalysis.isReachable) {
dataForTesting!.flowAnalysisResult.functionBodiesThatDontComplete
.add(body);
}
}
result =
closureContext!.handleImplicitReturn(this, body, result, fileOffset);
DartType? futureValueType = closureContext!.futureValueType;
assert(
!(isNonNullableByDefault &&
asyncMarker == AsyncMarker.Async &&
futureValueType == null),
"No future value type computed.");
closureContext = null;
this.helper = null;
flowAnalysis.finish();
return new InferredFunctionBody(
result.hasChanged ? result.statement : body, futureValueType);
}
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,
bool isImplicitCall: false,
Member? staticTarget,
bool isExtensionMemberInvocation = false}) {
int extensionTypeParameterCount = getExtensionTypeParameterCount(arguments);
if (extensionTypeParameterCount != 0) {
return _inferGenericExtensionMethodInvocation(extensionTypeParameterCount,
typeContext, offset, calleeType, arguments, hoistedExpressions,
isSpecialCasedBinaryOperator: isSpecialCasedBinaryOperator,
isSpecialCasedTernaryOperator: isSpecialCasedTernaryOperator,
receiverType: receiverType,
skipTypeArgumentInference: skipTypeArgumentInference,
isConst: isConst,
isImplicitExtensionMember: isImplicitExtensionMember);
}
return _inferInvocation(
typeContext, offset, calleeType, arguments, hoistedExpressions,
isSpecialCasedBinaryOperator: isSpecialCasedBinaryOperator,
isSpecialCasedTernaryOperator: isSpecialCasedTernaryOperator,
receiverType: receiverType,
skipTypeArgumentInference: skipTypeArgumentInference,
isConst: isConst,
isImplicitExtensionMember: isImplicitExtensionMember,
isImplicitCall: isImplicitCall,
staticTarget: staticTarget,
isExtensionMemberInvocation: isExtensionMemberInvocation);
}
InvocationInferenceResult _inferGenericExtensionMethodInvocation(
int extensionTypeParameterCount,
DartType typeContext,
int offset,
FunctionType calleeType,
Arguments arguments,
List<VariableDeclaration>? hoistedExpressions,
{bool isSpecialCasedBinaryOperator: false,
bool isSpecialCasedTernaryOperator: false,
DartType? receiverType,
bool skipTypeArgumentInference: false,
bool isConst: false,
bool isImplicitExtensionMember: false,
bool isImplicitCall: false,
Member? staticTarget}) {
FunctionType extensionFunctionType = new FunctionType(
[calleeType.positionalParameters.first],
const DynamicType(),
library.nonNullable,
requiredParameterCount: 1,
typeParameters: calleeType.typeParameters
.take(extensionTypeParameterCount)
.toList());
ArgumentsImpl extensionArguments = engine.forest.createArguments(
arguments.fileOffset, [arguments.positional.first],
types: getExplicitExtensionTypeArguments(arguments));
_inferInvocation(const UnknownType(), offset, extensionFunctionType,
extensionArguments, hoistedExpressions,
skipTypeArgumentInference: skipTypeArgumentInference,
receiverType: receiverType,
isImplicitExtensionMember: isImplicitExtensionMember,
isImplicitCall: isImplicitCall,
staticTarget: staticTarget,
isExtensionMemberInvocation: true);
Substitution extensionSubstitution = Substitution.fromPairs(
extensionFunctionType.typeParameters, extensionArguments.types);
List<TypeParameter> targetTypeParameters = const <TypeParameter>[];
if (calleeType.typeParameters.length > extensionTypeParameterCount) {
targetTypeParameters =
calleeType.typeParameters.skip(extensionTypeParameterCount).toList();
}
FunctionType targetFunctionType = new FunctionType(
calleeType.positionalParameters.skip(1).toList(),
calleeType.returnType,
library.nonNullable,
requiredParameterCount: calleeType.requiredParameterCount - 1,
namedParameters: calleeType.namedParameters,
typeParameters: targetTypeParameters);
targetFunctionType = extensionSubstitution
.substituteType(targetFunctionType) as FunctionType;
ArgumentsImpl targetArguments = engine.forest.createArguments(
arguments.fileOffset, arguments.positional.skip(1).toList(),
named: arguments.named, types: getExplicitTypeArguments(arguments));
InvocationInferenceResult result = _inferInvocation(typeContext, offset,
targetFunctionType, targetArguments, hoistedExpressions,
isSpecialCasedBinaryOperator: isSpecialCasedBinaryOperator,
isSpecialCasedTernaryOperator: isSpecialCasedTernaryOperator,
skipTypeArgumentInference: skipTypeArgumentInference,
isConst: isConst,
isImplicitCall: isImplicitCall,
staticTarget: staticTarget);
arguments.positional.clear();
arguments.positional.addAll(extensionArguments.positional);
arguments.positional.addAll(targetArguments.positional);
setParents(arguments.positional, arguments);
// The `targetArguments.named` is the same list as `arguments.named` so
// we just need to ensure that parent relations are realigned.
setParents(arguments.named, arguments);
arguments.types.clear();
arguments.types.addAll(extensionArguments.types);
arguments.types.addAll(targetArguments.types);
return result;
}
/// Performs the type inference steps that are shared by all kinds of
/// invocations (constructors, instance methods, and static methods).
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);
}
FunctionType inferLocalFunction(FunctionNode function, DartType? typeContext,
int fileOffset, DartType? returnContext) {
bool hasImplicitReturnType = false;
if (returnContext == null) {
hasImplicitReturnType = true;
returnContext =
isNonNullableByDefault ? const UnknownType() : const DynamicType();
}
if (!isTopLevel) {
List<VariableDeclaration> positionalParameters =
function.positionalParameters;
for (int i = 0; i < positionalParameters.length; i++) {
VariableDeclaration parameter = positionalParameters[i];
flowAnalysis.declare(parameter, true);
inferMetadataKeepingHelper(parameter, parameter.annotations);
if (parameter.initializer != null) {
ExpressionInferenceResult initializerResult = inferExpression(
parameter.initializer!, parameter.type, !isTopLevel);
parameter.initializer = initializerResult.expression
..parent = parameter;
}
}
for (VariableDeclaration parameter in function.namedParameters) {
flowAnalysis.declare(parameter, true);
inferMetadataKeepingHelper(parameter, parameter.annotations);
ExpressionInferenceResult initializerResult = inferExpression(
parameter.initializer!, parameter.type, !isTopLevel);
parameter.initializer = initializerResult.expression
..parent = parameter;
}
}
// Let `<T0, ..., Tn>` be the set of type parameters of the closure (with
// `n`=0 if there are no type parameters).
List<TypeParameter> typeParameters = function.typeParameters;
// Let `(P0 x0, ..., Pm xm)` be the set of formal parameters of the closure
// (including required, positional optional, and named optional parameters).
// If any type `Pi` is missing, denote it as `_`.
List<VariableDeclaration> formals = function.positionalParameters.toList()
..addAll(function.namedParameters);
// Let `B` denote the closure body. If `B` is an expression function body
// (`=> e`), treat it as equivalent to a block function body containing a
// single `return` statement (`{ return e; }`).
// Attempt to match `K` as a function type compatible with the closure (that
// is, one having n type parameters and a compatible set of formal
// parameters). If there is a successful match, let `<S0, ..., Sn>` be the
// set of matched type parameters and `(Q0, ..., Qm)` be the set of matched
// formal parameter types, and let `N` be the return type.
Substitution substitution;
List<DartType?> formalTypesFromContext =
new List<DartType?>.filled(formals.length, null);
if (typeContext is FunctionType &&
typeContext.typeParameters.length == typeParameters.length) {
for (int i = 0; i < formals.length; i++) {
if (i < function.positionalParameters.length) {
formalTypesFromContext[i] =
getPositionalParameterType(typeContext, i);
} else {
formalTypesFromContext[i] =
getNamedParameterType(typeContext, formals[i].name!);
}
}
returnContext = typeContext.returnType;
// Let `[T/S]` denote the type substitution where each `Si` is replaced
// with the corresponding `Ti`.
Map<TypeParameter, DartType> substitutionMap =
<TypeParameter, DartType>{};
for (int i = 0; i < typeContext.typeParameters.length; i++) {
substitutionMap[typeContext.typeParameters[i]] =
i < typeParameters.length
? new TypeParameterType.forAlphaRenaming(
typeContext.typeParameters[i], typeParameters[i])
: const DynamicType();
}
substitution = Substitution.fromMap(substitutionMap);
} else {
// If the match is not successful because `K` is `_`, let all `Si`, all
// `Qi`, and `N` all be `_`.
// If the match is not successful for any other reason, this will result
// in a type error, so the implementation is free to choose the best
// error recovery path.
substitution = Substitution.empty;
}
// Define `Ri` as follows: if `Pi` is not `_`, let `Ri` be `Pi`.
// Otherwise, if `Qi` is not `_`, let `Ri` be the greatest closure of
// `Qi[T/S]` with respect to `?`. Otherwise, let `Ri` be `dynamic`.
for (int i = 0; i < formals.length; i++) {
VariableDeclarationImpl formal = formals[i] as VariableDeclarationImpl;
if (formal.isImplicitlyTyped) {
DartType inferredType;
if (formalTypesFromContext[i] != null) {
inferredType = computeGreatestClosure2(
substitution.substituteType(formalTypesFromContext[i]!));
if (typeSchemaEnvironment.isSubtypeOf(
inferredType,
const NullType(),
isNonNullableByDefault
? SubtypeCheckMode.withNullabilities
: SubtypeCheckMode.ignoringNullabilities)) {
inferredType = coreTypes.objectRawType(library.nullable);
}
} else {
inferredType = const DynamicType();
}
instrumentation?.record(uriForInstrumentation, formal.fileOffset,
'type', new InstrumentationValueForType(inferredType));
formal.type = demoteTypeInLibrary(inferredType, library.library);
if (dataForTesting != null) {
dataForTesting!.typeInferenceResult.inferredVariableTypes[formal] =
formal.type;
}
}
if (isNonNullableByDefault) {
// If a parameter is a positional or named optional parameter and its
// type is potentially non-nullable, it should have an initializer.
bool isOptionalPositional = function.requiredParameterCount <= i &&
i < function.positionalParameters.length;
bool isOptionalNamed =
i >= function.positionalParameters.length && !formal.isRequired;
if ((isOptionalPositional || isOptionalNamed) &&
formal.type.isPotentiallyNonNullable &&
!formal.hasDeclaredInitializer) {
library.addProblem(
templateOptionalNonNullableWithoutInitializerError.withArguments(
formal.name!, formal.type, isNonNullableByDefault),
formal.fileOffset,
formal.name!.length,
library.importUri);
}
}
}
if (isNonNullableByDefault) {
for (VariableDeclaration parameter in function.namedParameters) {
VariableDeclarationImpl formal = parameter as VariableDeclarationImpl;
// Required named parameters shouldn't have initializers.
if (formal.isRequired && formal.hasDeclaredInitializer) {
library.addProblem(
templateRequiredNamedParameterHasDefaultValueError
.withArguments(formal.name!),
formal.fileOffset,
formal.name!.length,
library.importUri);
}
}
}
// Let `N'` be `N[T/S]`. The [ClosureContext] constructor will adjust
// accordingly if the closure is declared with `async`, `async*`, or
// `sync*`.
if (returnContext is! UnknownType) {
returnContext = substitution.substituteType(returnContext);
}
// Apply type inference to `B` in return context `N’`, with any references
// to `xi` in `B` having type `Pi`. This produces `B’`.
bool needToSetReturnType = hasImplicitReturnType;
ClosureContext? oldClosureContext = this.closureContext;
ClosureContext closureContext = new ClosureContext(
this, function.asyncMarker, returnContext, needToSetReturnType);
this.closureContext = closureContext;
StatementInferenceResult bodyResult = inferStatement(function.body!);
// If the closure is declared with `async*` or `sync*`, let `M` be the
// least upper bound of the types of the `yield` expressions in `B’`, or
// `void` if `B’` contains no `yield` expressions. Otherwise, let `M` be
// the least upper bound of the types of the `return` expressions in `B’`,
// or `void` if `B’` contains no `return` expressions.
if (needToSetReturnType) {
DartType inferredReturnType = closureContext.inferReturnType(this,
hasImplicitReturn: flowAnalysis.isReachable);
// Then the result of inference is `<T0, ..., Tn>(R0 x0, ..., Rn xn) B`
// with type `<T0, ..., Tn>(R0, ..., Rn) -> M’` (with some of the `Ri` and
// `xi` denoted as optional or named parameters, if appropriate).
instrumentation?.record(uriForInstrumentation, fileOffset, 'returnType',
new InstrumentationValueForType(inferredReturnType));
function.returnType = inferredReturnType;
}
bodyResult = closureContext.handleImplicitReturn(
this, function.body!, bodyResult, fileOffset);
function.futureValueType = closureContext.futureValueType;
assert(
!(isNonNullableByDefault &&
function.asyncMarker == AsyncMarker.Async &&
function.futureValueType == null),
"No future value type computed.");
if (bodyResult.hasChanged) {
function.body = bodyResult.statement..parent = function;
}
this.closureContext = oldClosureContext;
return function.computeFunctionType(library.nonNullable);
}
@override
void inferMetadata(
InferenceHelper helper, TreeNode? parent, List<Expression>? annotations) {
if (annotations != null) {
this.helper = helper;
inferMetadataKeepingHelper(parent, annotations);
this.helper = null;
}
}
@override
void inferMetadataKeepingHelper(
TreeNode? parent, List<Expression>? annotations) {
if (annotations != null) {
for (int index = 0; index < annotations.length; index++) {
ExpressionInferenceResult result = inferExpression(
annotations[index], const UnknownType(), !isTopLevel);
annotations[index] = result.expression..parent = parent;
}
}
}
StaticInvocation transformExtensionMethodInvocation(int fileOffset,
ObjectAccessTarget target, Expression receiver, Arguments arguments) {
assert(target.isExtensionMember || target.isNullableExtensionMember);
Procedure procedure = target.member as Procedure;
return engine.forest.createStaticInvocation(
fileOffset,
procedure,
engine.forest.createArgumentsForExtensionMethod(
arguments.fileOffset,
target.inferredExtensionTypeArguments.length,
procedure.function.typeParameters.length -
target.inferredExtensionTypeArguments.length,
receiver,
extensionTypeArguments: target.inferredExtensionTypeArguments,
positionalArguments: arguments.positional,
namedArguments: arguments.named,
typeArguments: arguments.types));
}
ExpressionInferenceResult _inferDynamicInvocation(
int fileOffset,
Link<NullAwareGuard> nullAwareGuards,
Expression receiver,
Name name,
ArgumentsImpl arguments,
DartType typeContext,
List<VariableDeclaration>? hoistedExpressions,
{required bool isImplicitCall}) {
// ignore: unnecessary_null_comparison
assert(isImplicitCall != null);
InvocationInferenceResult result = inferInvocation(
typeContext, fileOffset, unknownFunction, arguments,
hoistedExpressions: hoistedExpressions,
receiverType: const DynamicType(),
isImplicitCall: isImplicitCall);
assert(name != equalsName);
Expression expression = new DynamicInvocation(
DynamicAccessKind.Dynamic, receiver, name, arguments)
..fileOffset = fileOffset;
return createNullAwareExpressionInferenceResult(
result.inferredType, result.applyResult(expression), nullAwareGuards);
}
ExpressionInferenceResult _inferNeverInvocation(
int fileOffset,
Link<NullAwareGuard> nullAwareGuards,
Expression receiver,
NeverType receiverType,
Name name,
ArgumentsImpl arguments,
DartType typeContext,
List<VariableDeclaration>? hoistedExpressions,
{required bool isImplicitCall}) {
// ignore: unnecessary_null_comparison
assert(isImplicitCall != null);
InvocationInferenceResult result = inferInvocation(
typeContext, fileOffset, unknownFunction, arguments,
hoistedExpressions: hoistedExpressions,
receiverType: receiverType,
isImplicitCall: isImplicitCall);
assert(name != equalsName);
Expression expression = new DynamicInvocation(
DynamicAccessKind.Never, receiver, name, arguments)
..fileOffset = fileOffset;
return createNullAwareExpressionInferenceResult(
const NeverType.nonNullable(),
result.applyResult(expression),
nullAwareGuards);
}
ExpressionInferenceResult _inferMissingInvocation(
int fileOffset,
Link<NullAwareGuard> nullAwareGuards,
Expression receiver,
DartType receiverType,
ObjectAccessTarget target,
Name name,
ArgumentsImpl arguments,
DartType typeContext,
List<VariableDeclaration>? hoistedExpressions,
{required bool isExpressionInvocation,