blob: 84269fea839dbeee1dc76262e5111fcce5bd6232 [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:front_end/src/base/instrumentation.dart';
import 'package:front_end/src/fasta/deprecated_problems.dart'
show deprecated_internalProblem;
import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart';
import 'package:front_end/src/fasta/names.dart' show callName;
import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart';
import 'package:front_end/src/fasta/type_inference/type_inference_listener.dart';
import 'package:front_end/src/fasta/type_inference/type_promotion.dart';
import 'package:front_end/src/fasta/type_inference/type_schema.dart';
import 'package:front_end/src/fasta/type_inference/type_schema_elimination.dart';
import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
import 'package:kernel/ast.dart'
show
Arguments,
AsyncMarker,
BottomType,
Class,
DartType,
DynamicType,
Expression,
Field,
FunctionNode,
FunctionType,
Initializer,
InterfaceType,
InvocationExpression,
Member,
MethodInvocation,
Name,
Procedure,
ProcedureKind,
PropertyGet,
PropertySet,
ReturnStatement,
Statement,
SuperMethodInvocation,
SuperPropertyGet,
SuperPropertySet,
TypeParameter,
TypeParameterType,
VariableDeclaration,
VoidType;
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/type_algebra.dart';
bool isOverloadableArithmeticOperator(String name) {
return identical(name, '+') ||
identical(name, '-') ||
identical(name, '*') ||
identical(name, '%');
}
bool _isUserDefinableOperator(String name) {
return identical(name, '<') ||
identical(name, '>') ||
identical(name, '<=') ||
identical(name, '>=') ||
identical(name, '==') ||
identical(name, '-') ||
identical(name, '+') ||
identical(name, '/') ||
identical(name, '~/') ||
identical(name, '*') ||
identical(name, '%') ||
identical(name, '|') ||
identical(name, '^') ||
identical(name, '&') ||
identical(name, '<<') ||
identical(name, '>>') ||
identical(name, '[]=') ||
identical(name, '~');
}
/// Keeps track of information about the innermost function or closure being
/// inferred.
class ClosureContext {
final bool isAsync;
final bool isGenerator;
final DartType returnContext;
DartType _inferredReturnType;
factory ClosureContext(TypeInferrerImpl inferrer, AsyncMarker asyncMarker,
DartType returnContext) {
bool isAsync = asyncMarker == AsyncMarker.Async ||
asyncMarker == AsyncMarker.AsyncStar;
bool isGenerator = asyncMarker == AsyncMarker.SyncStar ||
asyncMarker == AsyncMarker.AsyncStar;
if (isGenerator) {
if (isAsync) {
returnContext = inferrer.getTypeArgumentOf(
returnContext, inferrer.coreTypes.streamClass);
} else {
returnContext = inferrer.getTypeArgumentOf(
returnContext, inferrer.coreTypes.iterableClass);
}
} else if (isAsync) {
returnContext = inferrer.wrapFutureOrType(
inferrer.typeSchemaEnvironment.flattenFutures(returnContext));
}
return new ClosureContext._(isAsync, isGenerator, returnContext);
}
ClosureContext._(this.isAsync, this.isGenerator, this.returnContext);
/// Updates the inferred return type based on the presence of a return
/// statement returning the given [type].
void handleReturn(TypeInferrerImpl inferrer, DartType type) {
if (isGenerator) return;
if (isAsync) {
type = inferrer.typeSchemaEnvironment.flattenFutures(type);
}
_updateInferredReturnType(inferrer, type);
}
void handleYield(TypeInferrerImpl inferrer, bool isYieldStar, DartType type) {
if (!isGenerator) return;
if (isYieldStar) {
type = inferrer.getDerivedTypeArgumentOf(
type,
isAsync
? inferrer.coreTypes.streamClass
: inferrer.coreTypes.iterableClass);
if (type == null) return;
}
_updateInferredReturnType(inferrer, type);
}
DartType inferReturnType(
TypeInferrerImpl inferrer, bool isExpressionFunction) {
DartType inferredReturnType =
inferrer.inferReturnType(_inferredReturnType, isExpressionFunction);
if (!isExpressionFunction &&
returnContext != null &&
!_analyzerSubtypeOf(inferrer, inferredReturnType, returnContext)) {
// For block-bodied functions, if the inferred return type isn't a
// subtype of the context, we use the context. We use analyzer subtyping
// rules here.
// TODO(paulberry): this is inherited from analyzer; it's not part of
// the spec. See also dartbug.com/29606.
inferredReturnType = greatestClosure(inferrer.coreTypes, returnContext);
} else if (isExpressionFunction &&
returnContext != null &&
inferredReturnType is DynamicType) {
// For expression-bodied functions, if the inferred return type is
// `dynamic`, we use the context.
// TODO(paulberry): this is inherited from analyzer; it's not part of the
// spec.
inferredReturnType = greatestClosure(inferrer.coreTypes, returnContext);
}
if (isGenerator) {
if (isAsync) {
inferredReturnType = inferrer.wrapType(
inferredReturnType, inferrer.coreTypes.streamClass);
} else {
inferredReturnType = inferrer.wrapType(
inferredReturnType, inferrer.coreTypes.iterableClass);
}
} else if (isAsync) {
inferredReturnType = inferrer.wrapFutureType(inferredReturnType);
}
return inferredReturnType;
}
void _updateInferredReturnType(TypeInferrerImpl inferrer, DartType type) {
if (_inferredReturnType == null) {
_inferredReturnType = type;
} else {
_inferredReturnType = inferrer.typeSchemaEnvironment
.getLeastUpperBound(_inferredReturnType, type);
}
}
static bool _analyzerSubtypeOf(
TypeInferrerImpl inferrer, DartType subtype, DartType supertype) {
if (supertype is VoidType) {
if (subtype is VoidType) return true;
if (subtype is InterfaceType &&
identical(subtype.classNode, inferrer.coreTypes.nullClass)) {
return true;
}
return false;
}
return inferrer.typeSchemaEnvironment.isSubtypeOf(subtype, supertype);
}
}
/// 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 {
/// Gets the [TypePromoter] that can be used to perform type promotion within
/// this method body or initializer.
TypePromoter get typePromoter;
/// The URI of the code for which type inference is currently being
/// performed--this is used for testing.
String get uri;
/// Performs full type inference on the given field initializer.
void inferFieldInitializer(DartType declaredType, Expression initializer);
/// Performs type inference on the given function body.
void inferFunctionBody(
DartType returnType, AsyncMarker asyncMarker, Statement body);
/// Performs type inference on the given constructor initializer.
void inferInitializer(Initializer initializer);
/// Performs type inference on the given metadata annotations.
void inferMetadata(List<Expression> annotations);
/// Performs type inference on the given function parameter initializer
/// expression.
void inferParameterInitializer(Expression initializer, DartType declaredType);
}
/// Implementation of [TypeInferrer] which doesn't do any type inference.
///
/// This is intended for profiling, to ensure that type inference and type
/// promotion do not slow down compilation too much.
class TypeInferrerDisabled extends TypeInferrer {
@override
final typePromoter = new TypePromoterDisabled();
@override
String get uri => null;
@override
void inferFieldInitializer(DartType declaredType, Expression initializer) {}
@override
void inferFunctionBody(
DartType returnType, AsyncMarker asyncMarker, Statement body) {}
@override
void inferInitializer(Initializer initializer) {}
@override
void inferMetadata(List<Expression> annotations) {}
@override
void inferParameterInitializer(
Expression initializer, DartType declaredType) {}
}
/// Derived class containing generic implementations of [TypeInferrer].
///
/// This class contains as much of the implementation of type inference as
/// possible without knowing the identity of the type parameters. It defers to
/// abstract methods for everything else.
abstract class TypeInferrerImpl extends TypeInferrer {
static final FunctionType _functionReturningDynamic =
new FunctionType(const [], const DynamicType());
final TypeInferenceEngineImpl engine;
@override
final String uri;
/// 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 CoreTypes coreTypes;
final bool strongMode;
final ClassHierarchy classHierarchy;
final Instrumentation instrumentation;
final TypeSchemaEnvironment typeSchemaEnvironment;
final TypeInferenceListener listener;
final InterfaceType thisType;
/// The [AccessorNode] whose type will be type inferred using this
/// [TypeInferrerImpl], or `null` if this [TypeInferrerImpl] will be used to
/// infer types outside the scope of top level type inference.
final AccessorNode accessorNode;
/// Context information for the current closure, or `null` if we are not
/// inside a closure.
ClosureContext closureContext;
/// When performing top level inference, this boolean is set to `false` if we
/// discover that the type of the object is not immediately evident.
///
/// Not used when performing local inference.
bool isImmediatelyEvident = true;
List<AccessorNode> _dryRunDependencies;
TypeInferrerImpl(this.engine, this.uri, this.listener, bool topLevel,
this.thisType, this.accessorNode)
: coreTypes = engine.coreTypes,
strongMode = engine.strongMode,
classHierarchy = engine.classHierarchy,
instrumentation = topLevel ? null : engine.instrumentation,
typeSchemaEnvironment = engine.typeSchemaEnvironment,
isTopLevel = topLevel;
/// Indicates whether we are currently doing a "dry run" in order to collect
/// type inference dependencies.
bool get isDryRun => _dryRunDependencies != null;
/// Gets the type promoter that should be used to promote types during
/// inference.
TypePromoter get typePromoter;
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation using [fileOffset].
Member findInterfaceMember(DartType receiverType, Name name, int fileOffset,
{bool setter: false, bool silent: false}) {
// Our non-strong golden files currently don't include interface
// targets, so we can't store the interface target without causing tests
// to fail. TODO(paulberry): fix this.
if (!strongMode) return null;
receiverType = resolveTypeParameter(receiverType);
if (receiverType is InterfaceType) {
var interfaceMember = classHierarchy
.getInterfaceMember(receiverType.classNode, name, setter: setter);
if (!silent && interfaceMember != null) {
instrumentation?.record(Uri.parse(uri), fileOffset, 'target',
new InstrumentationValueForMember(interfaceMember));
}
return interfaceMember;
}
return null;
}
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation and records it in [methodInvocation].
Member findMethodInvocationMember(
DartType receiverType, InvocationExpression methodInvocation,
{bool silent: false}) {
// TODO(paulberry): could we add getters to InvocationExpression to make
// these is-checks unnecessary?
if (methodInvocation is MethodInvocation) {
var interfaceMember = findInterfaceMember(
receiverType, methodInvocation.name, methodInvocation.fileOffset,
silent: silent);
if (strongMode) {
methodInvocation.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else if (methodInvocation is SuperMethodInvocation) {
var interfaceMember = findInterfaceMember(
receiverType, methodInvocation.name, methodInvocation.fileOffset,
silent: silent);
if (strongMode) {
methodInvocation.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else {
throw deprecated_internalProblem(
'Unexpected invocation type: ${methodInvocation.runtimeType}');
}
}
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation and records it in [propertyGet].
Member findPropertyGetMember(DartType receiverType, Expression propertyGet,
{bool silent: false}) {
// TODO(paulberry): could we add a common base class to PropertyGet and
// SuperPropertyGet to make these is-checks unnecessary?
if (propertyGet is PropertyGet) {
var interfaceMember = findInterfaceMember(
receiverType, propertyGet.name, propertyGet.fileOffset,
silent: silent);
if (strongMode) {
propertyGet.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else if (propertyGet is SuperPropertyGet) {
var interfaceMember = findInterfaceMember(
receiverType, propertyGet.name, propertyGet.fileOffset,
silent: silent);
if (strongMode) {
propertyGet.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else {
throw deprecated_internalProblem(
'Unexpected propertyGet type: ${propertyGet.runtimeType}');
}
}
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation and records it in [propertySet].
Member findPropertySetMember(DartType receiverType, Expression propertySet,
{bool silent: false}) {
if (propertySet is PropertySet) {
var interfaceMember = findInterfaceMember(
receiverType, propertySet.name, propertySet.fileOffset,
setter: true, silent: silent);
if (strongMode) {
propertySet.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else if (propertySet is SuperPropertySet) {
var interfaceMember = findInterfaceMember(
receiverType, propertySet.name, propertySet.fileOffset,
setter: true, silent: silent);
if (strongMode) {
propertySet.interfaceTarget = interfaceMember;
}
return interfaceMember;
} else {
throw deprecated_internalProblem(
'Unexpected propertySet type: ${propertySet.runtimeType}');
}
}
/// Ends a dry run started by [startDryRun] and returns the collected
/// dependencies.
List<AccessorNode> finishDryRun() {
var dryRunDependencies = _dryRunDependencies;
_dryRunDependencies = null;
return dryRunDependencies;
}
FunctionType getCalleeFunctionType(Member interfaceMember,
DartType receiverType, Name methodName, bool followCall) {
var type = getCalleeType(interfaceMember, receiverType, methodName);
if (type is FunctionType) {
return type;
} else if (followCall && type is InterfaceType) {
var member = classHierarchy.getInterfaceMember(type.classNode, callName);
var callType = member?.getterType;
if (callType is FunctionType) {
return callType;
}
}
return _functionReturningDynamic;
}
DartType getCalleeType(
Member interfaceMember, DartType receiverType, Name methodName) {
if (receiverType is InterfaceType) {
if (interfaceMember == null) return const DynamicType();
var memberClass = interfaceMember.enclosingClass;
DartType calleeType;
if (interfaceMember is Procedure) {
if (interfaceMember.kind == ProcedureKind.Getter) {
calleeType = interfaceMember.function.returnType;
} else {
calleeType = interfaceMember.function.functionType;
}
} else if (interfaceMember is Field) {
calleeType = interfaceMember.type;
} else {
calleeType = const DynamicType();
}
if (memberClass.typeParameters.isNotEmpty) {
var castedType =
classHierarchy.getTypeAsInstanceOf(receiverType, memberClass);
calleeType = Substitution
.fromInterfaceType(castedType)
.substituteType(calleeType);
}
return calleeType;
} else if (receiverType is DynamicType) {
return const DynamicType();
} else if (receiverType is FunctionType) {
if (methodName.name == 'call') {
return receiverType;
} else {
// TODO(paulberry): handle the case of invoking .toString() on a
// function type.
return const DynamicType();
}
} else if (receiverType is TypeParameterType) {
// TODO(paulberry): use the bound
return const DynamicType();
} else {
// TODO(paulberry): handle the case of invoking .toString() on a type
// that's none of the above (e.g. `dynamic` or `bottom`)
return const DynamicType();
}
}
DartType getDerivedTypeArgumentOf(DartType type, Class class_) {
if (type is InterfaceType) {
var typeAsInstanceOfClass =
classHierarchy.getTypeAsInstanceOf(type, class_);
if (typeAsInstanceOfClass != null) {
return typeAsInstanceOfClass.typeArguments[0];
}
}
return null;
}
/// Gets the initializer for the given [field], or `null` if there is no
/// initializer.
Expression getFieldInitializer(KernelField field);
DartType getSetterType(Member interfaceMember, DartType receiverType) {
if (receiverType is InterfaceType) {
if (interfaceMember == null) return const DynamicType();
var memberClass = interfaceMember.enclosingClass;
DartType setterType;
if (interfaceMember is Procedure) {
assert(interfaceMember.kind == ProcedureKind.Setter);
var setterParameters = interfaceMember.function.positionalParameters;
setterType = setterParameters.length > 0
? setterParameters[0].type
: const DynamicType();
} else if (interfaceMember is Field) {
setterType = interfaceMember.type;
} else {
setterType = const DynamicType();
}
if (memberClass.typeParameters.isNotEmpty) {
var castedType =
classHierarchy.getTypeAsInstanceOf(receiverType, memberClass);
setterType = Substitution
.fromInterfaceType(castedType)
.substituteType(setterType);
}
return setterType;
} else if (receiverType is TypeParameterType) {
// TODO(paulberry): use the bound
return const DynamicType();
} else {
return const DynamicType();
}
}
DartType getTypeArgumentOf(DartType type, Class class_) {
if (type is InterfaceType && identical(type.classNode, class_)) {
return type.typeArguments[0];
} else {
return null;
}
}
/// Modifies a type as appropriate when inferring a declared variable's type.
DartType inferDeclarationType(DartType initializerType) {
if (initializerType is BottomType ||
(initializerType is InterfaceType &&
initializerType.classNode == coreTypes.nullClass)) {
// 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();
}
return initializerType;
}
/// 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`, the actual type
/// of the expression is returned; otherwise `null` is returned.
///
/// Derived classes should override this method with logic that dispatches on
/// the expression type and calls the appropriate specialized "infer" method.
DartType inferExpression(
Expression expression, DartType typeContext, bool typeNeeded);
@override
void inferFieldInitializer(DartType declaredType, Expression initializer) {
assert(closureContext == null);
inferExpression(initializer, declaredType, false);
}
/// Performs type inference on the given [field]'s initializer expression.
///
/// Derived classes should provide an implementation that calls
/// [inferExpression] for the given [field]'s initializer expression.
DartType inferFieldTopLevel(
KernelField field, DartType type, bool typeNeeded);
@override
void inferFunctionBody(
DartType returnType, AsyncMarker asyncMarker, Statement body) {
assert(closureContext == null);
closureContext = new ClosureContext(this, asyncMarker, returnType);
inferStatement(body);
closureContext = null;
}
/// Performs the type inference steps that are shared by all kinds of
/// invocations (constructors, instance methods, and static methods).
DartType inferInvocation(DartType typeContext, bool typeNeeded, int offset,
FunctionType calleeType, DartType returnType, Arguments arguments,
{bool isOverloadedArithmeticOperator: false,
DartType receiverType,
bool skipTypeArgumentInference: false,
bool forceArgumentInference: false}) {
var calleeTypeParameters = calleeType.typeParameters;
List<DartType> explicitTypeArguments = getExplicitTypeArguments(arguments);
bool inferenceNeeded = !skipTypeArgumentInference &&
explicitTypeArguments == null &&
strongMode &&
calleeTypeParameters.isNotEmpty;
List<DartType> inferredTypes;
Substitution substitution;
List<DartType> formalTypes;
List<DartType> actualTypes;
if (inferenceNeeded) {
inferredTypes = new List<DartType>.filled(
calleeTypeParameters.length, const UnknownType());
typeSchemaEnvironment.inferGenericFunctionOrType(returnType,
calleeTypeParameters, null, null, typeContext, inferredTypes);
substitution =
Substitution.fromPairs(calleeTypeParameters, inferredTypes);
formalTypes = [];
actualTypes = [];
} 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()));
}
// TODO(paulberry): if we are doing top level inference and type arguments
// were omitted, report an error.
if (!isTopLevel ||
isOverloadedArithmeticOperator ||
TypeInferenceEngineImpl.expandedTopLevelInference) {
int i = 0;
_forEachArgument(arguments, (name, expression) {
DartType formalType = name != null
? getNamedParameterType(calleeType, name)
: getPositionalParameterType(calleeType, i++);
DartType inferredFormalType = substitution != null
? substitution.substituteType(formalType)
: formalType;
var expressionType = inferExpression(
expression,
inferredFormalType,
inferenceNeeded ||
isOverloadedArithmeticOperator ||
forceArgumentInference);
if (inferenceNeeded) {
formalTypes.add(formalType);
actualTypes.add(expressionType);
}
if (isOverloadedArithmeticOperator) {
returnType = typeSchemaEnvironment.getTypeOfOverloadedArithmetic(
receiverType, expressionType);
}
});
}
if (inferenceNeeded) {
typeSchemaEnvironment.inferGenericFunctionOrType(
returnType,
calleeTypeParameters,
formalTypes,
actualTypes,
typeContext,
inferredTypes);
substitution =
Substitution.fromPairs(calleeTypeParameters, inferredTypes);
instrumentation?.record(Uri.parse(uri), offset, 'typeArgs',
new InstrumentationValueForTypeArgs(inferredTypes));
arguments.types.clear();
arguments.types.addAll(inferredTypes);
}
DartType inferredType;
if (typeNeeded) {
inferredType = substitution == null
? returnType
: substitution.substituteType(returnType);
}
return inferredType;
}
DartType inferLocalFunction(FunctionNode function, DartType typeContext,
bool typeNeeded, int fileOffset, DartType returnContext) {
bool hasImplicitReturnType = returnContext == null;
if (!isTopLevel) {
for (var parameter in function.positionalParameters) {
if (parameter.initializer != null) {
inferExpression(parameter.initializer, parameter.type, false);
}
}
for (var parameter in function.namedParameters) {
if (parameter.initializer != null) {
inferExpression(parameter.initializer, parameter.type, false);
}
}
}
// 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 (strongMode && typeContext is FunctionType) {
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`.
var substitutionMap = <TypeParameter, DartType>{};
for (int i = 0; i < typeContext.typeParameters.length; i++) {
substitutionMap[typeContext.typeParameters[i]] =
i < typeParameters.length
? new TypeParameterType(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++) {
KernelVariableDeclaration formal = formals[i];
if (KernelVariableDeclaration.isImplicitlyTyped(formal)) {
DartType inferredType;
if (formalTypesFromContext[i] != null) {
inferredType = greatestClosure(coreTypes,
substitution.substituteType(formalTypesFromContext[i]));
} else {
inferredType = const DynamicType();
}
instrumentation?.record(Uri.parse(uri), formal.fileOffset, 'type',
new InstrumentationValueForType(inferredType));
formal.type = inferredType;
}
}
// Let `N'` be `N[T/S]`. The [ClosureContext] constructor will adjust
// accordingly if the closure is declared with `async`, `async*`, or
// `sync*`.
if (returnContext != null) {
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 isExpressionFunction = function.body is ReturnStatement;
bool needToSetReturnType = hasImplicitReturnType && strongMode;
ClosureContext oldClosureContext = this.closureContext;
ClosureContext closureContext =
new ClosureContext(this, function.asyncMarker, returnContext);
this.closureContext = closureContext;
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.
DartType inferredReturnType;
if (needToSetReturnType || typeNeeded) {
inferredReturnType =
closureContext.inferReturnType(this, isExpressionFunction);
}
// 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).
if (needToSetReturnType) {
instrumentation?.record(Uri.parse(uri), fileOffset, 'returnType',
new InstrumentationValueForType(inferredReturnType));
function.returnType = inferredReturnType;
}
this.closureContext = oldClosureContext;
return typeNeeded ? function.functionType : null;
}
@override
void inferMetadata(List<Expression> annotations) {
if (annotations != null) {
for (var annotation in annotations) {
inferExpression(annotation, null, false);
}
}
}
/// Performs the core type inference algorithm for method invocations (this
/// handles both null-aware and non-null-aware method invocations).
DartType inferMethodInvocation(
Expression expression,
Expression receiver,
int fileOffset,
bool isImplicitCall,
DartType typeContext,
bool typeNeeded,
{VariableDeclaration receiverVariable,
MethodInvocation desugaredInvocation,
Member interfaceMember,
Name methodName,
Arguments arguments}) {
typeNeeded =
listener.methodInvocationEnter(expression, typeContext) || typeNeeded;
// First infer the receiver so we can look up the method that was invoked.
var receiverType = inferExpression(receiver, null, true);
if (strongMode) {
receiverVariable?.type = receiverType;
}
bool isOverloadedArithmeticOperator = false;
if (desugaredInvocation != null) {
interfaceMember =
findMethodInvocationMember(receiverType, desugaredInvocation);
methodName = desugaredInvocation.name;
arguments = desugaredInvocation.arguments;
}
if (interfaceMember is Procedure) {
isOverloadedArithmeticOperator = typeSchemaEnvironment
.isOverloadedArithmeticOperatorAndType(interfaceMember, receiverType);
}
var calleeType = getCalleeFunctionType(
interfaceMember, receiverType, methodName, !isImplicitCall);
bool forceArgumentInference = false;
if (isDryRun) {
if (_isUserDefinableOperator(methodName.name)) {
// If this is an overloadable arithmetic operator, then type inference
// might depend on the RHS, so conservatively assume it does.
forceArgumentInference =
isOverloadableArithmeticOperator(methodName.name);
} else {
// If no type arguments were given, then type inference might depend on
// the arguments (because the called method might be generic), so
// conservatively assume it does.
forceArgumentInference = getExplicitTypeArguments(arguments) == null;
}
}
var inferredType = inferInvocation(typeContext, typeNeeded, fileOffset,
calleeType, calleeType.returnType, arguments,
isOverloadedArithmeticOperator: isOverloadedArithmeticOperator,
receiverType: receiverType,
forceArgumentInference: forceArgumentInference);
listener.methodInvocationExit(expression, inferredType);
return inferredType;
}
@override
void inferParameterInitializer(
Expression initializer, DartType declaredType) {
assert(closureContext == null);
inferExpression(initializer, declaredType, false);
}
/// Performs the core type inference algorithm for property gets (this handles
/// both null-aware and non-null-aware property gets).
DartType inferPropertyGet(Expression expression, Expression receiver,
int fileOffset, DartType typeContext, bool typeNeeded,
{VariableDeclaration receiverVariable,
PropertyGet desugaredGet,
Name propertyName}) {
typeNeeded =
listener.propertyGetEnter(expression, typeContext) || typeNeeded;
// First infer the receiver so we can look up the getter that was invoked.
var receiverType = inferExpression(receiver, null, true);
if (strongMode) {
receiverVariable?.type = receiverType;
}
propertyName ??= desugaredGet.name;
Member interfaceMember =
findInterfaceMember(receiverType, propertyName, fileOffset);
if (isTopLevel &&
((interfaceMember is Procedure &&
interfaceMember.kind == ProcedureKind.Getter) ||
interfaceMember is Field)) {
if (TypeInferenceEngineImpl.fullTopLevelInference) {
if (interfaceMember is KernelField) {
var accessorNode = KernelMember.getAccessorNode(interfaceMember);
if (accessorNode != null) {
engine.inferAccessorFused(accessorNode, this.accessorNode);
}
}
} else {
// References to fields and getters can't be relied upon for top level
// inference.
recordNotImmediatelyEvident(fileOffset);
}
}
desugaredGet?.interfaceTarget = interfaceMember;
var inferredType =
getCalleeType(interfaceMember, receiverType, propertyName);
// TODO(paulberry): Infer tear-off type arguments if appropriate.
listener.propertyGetExit(expression, inferredType);
return typeNeeded ? inferredType : null;
}
/// Modifies a type as appropriate when inferring a closure return type.
DartType inferReturnType(DartType returnType, bool isExpressionFunction) {
if (returnType == null) {
// Analyzer infers `Null` if there is no `return` expression; the spec
// says to return `void`. TODO(paulberry): resolve this difference.
return coreTypes.nullClass.rawType;
}
if (isExpressionFunction &&
returnType is InterfaceType &&
identical(returnType.classNode, coreTypes.nullClass)) {
// Analyzer coerces `Null` to `dynamic` in expression functions; the spec
// doesn't say to do this. TODO(paulberry): resolve this difference.
return const DynamicType();
}
return returnType;
}
/// Performs type inference on the given [statement].
///
/// Derived classes should override this method with logic that dispatches on
/// the statement type and calls the appropriate specialized "infer" method.
void inferStatement(Statement statement);
/// Records that the accessor represented by [accessorNode] is a dependency of
/// the accessor for which we are currently doing a dry run of type inference.
///
/// May only be called if a dry run is in progress.
void recordDryRunDependency(AccessorNode accessorNode) {
listener.recordDependency(accessorNode);
_dryRunDependencies.add(accessorNode);
}
void recordNotImmediatelyEvident(int fileOffset) {
assert(isTopLevel);
isImmediatelyEvident = false;
// TODO(paulberry): report an error.
}
/// If the given [type] is a [TypeParameterType], resolve it to its bound.
DartType resolveTypeParameter(DartType type) {
DartType resolveOneStep(DartType type) {
if (type is TypeParameterType) {
return type.bound;
} else {
return null;
}
}
var resolved = resolveOneStep(type);
if (resolved == null) return type;
// Detect circularities using the tortoise-and-hare algorithm.
type = resolved;
DartType hare = resolveOneStep(type);
if (hare == null) return type;
while (true) {
if (identical(type, hare)) {
// We found a circularity. Give up and return `dynamic`.
return const DynamicType();
}
// Hare takes two steps
var step1 = resolveOneStep(hare);
if (step1 == null) return hare;
var step2 = resolveOneStep(step1);
if (step2 == null) return hare;
hare = step2;
// Tortoise takes one step
type = resolveOneStep(type);
}
}
/// Begins a dry run of type inference, in which the goal is to collect the
/// dependencies of a given accessor.
void startDryRun() {
assert(_dryRunDependencies == null);
_dryRunDependencies = <AccessorNode>[];
}
DartType wrapFutureOrType(DartType type) {
if (type is InterfaceType &&
identical(type.classNode, coreTypes.futureOrClass)) {
return type;
}
// TODO(paulberry): If [type] is a subtype of `Future`, should we just
// return it unmodified?
return new InterfaceType(
coreTypes.futureOrClass, <DartType>[type ?? const DynamicType()]);
}
DartType wrapFutureType(DartType type) {
var typeWithoutFutureOr = type ?? const DynamicType();
if (type is InterfaceType &&
identical(type.classNode, coreTypes.futureOrClass)) {
typeWithoutFutureOr = type.typeArguments[0];
}
return new InterfaceType(coreTypes.futureClass,
<DartType>[typeSchemaEnvironment.flattenFutures(typeWithoutFutureOr)]);
}
DartType wrapType(DartType type, Class class_) {
return new InterfaceType(class_, <DartType>[type ?? const DynamicType()]);
}
void _forEachArgument(
Arguments arguments, void callback(String name, Expression expression)) {
for (var expression in arguments.positional) {
callback(null, expression);
}
for (var namedExpression in arguments.named) {
callback(namedExpression.name, namedExpression.value);
}
}
}