blob: e043a3ff83bd3601971286c1a7f3b5a436bbbbf2 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
class BodyInferenceContext {
static const _key = 'BodyInferenceContext';
final TypeSystemImpl _typeSystem;
final bool isAsynchronous;
final bool isGenerator;
/// The imposed return type, from the typing context.
/// Might be `null` if an empty typing context.
final DartType? imposedType;
/// The context type, computed from [imposedType].
/// Might be `null` if an empty typing context.
final DartType? contextType;
/// Types of all `return` or `yield` statements in the body.
final List<DartType> _returnTypes = [];
factory BodyInferenceContext({
required TypeSystemImpl typeSystem,
required FunctionBody node,
required DartType? imposedType,
}) {
var contextType = _contextTypeForImposed(typeSystem, node, imposedType);
var bodyContext = BodyInferenceContext._(
typeSystem: typeSystem,
isAsynchronous: node.isAsynchronous,
isGenerator: node.isGenerator,
imposedType: imposedType,
contextType: contextType,
);
node.setProperty(_key, bodyContext);
return bodyContext;
}
BodyInferenceContext._({
required TypeSystemImpl typeSystem,
required this.isAsynchronous,
required this.isGenerator,
required this.imposedType,
required this.contextType,
}) : _typeSystem = typeSystem;
bool get isSynchronous => !isAsynchronous;
TypeProvider get _typeProvider => _typeSystem.typeProvider;
void addReturnExpression(Expression? expression) {
if (expression == null) {
_returnTypes.add(_typeProvider.nullType);
} else {
var type = expression.typeOrThrow;
if (isAsynchronous) {
type = _typeSystem.flatten(type);
}
_returnTypes.add(type);
}
}
void addYield(YieldStatement node) {
var expressionType = node.expression.typeOrThrow;
if (node.star == null) {
_returnTypes.add(expressionType);
return;
}
if (isGenerator) {
var requiredClass = isAsynchronous
? _typeProvider.streamElement
: _typeProvider.iterableElement;
var type = _argumentOf(expressionType, requiredClass);
if (type != null) {
_returnTypes.add(type);
}
}
}
DartType computeInferredReturnType({
required bool endOfBlockIsReachable,
}) {
var actualReturnedType = _computeActualReturnedType(
endOfBlockIsReachable: endOfBlockIsReachable,
);
var clampedReturnedType = _clampToContextType(actualReturnedType);
if (isGenerator) {
if (isAsynchronous) {
return _typeProvider.streamType(clampedReturnedType);
} else {
return _typeProvider.iterableType(clampedReturnedType);
}
} else {
if (isAsynchronous) {
return _typeProvider.futureType(
_typeSystem.flatten(clampedReturnedType),
);
} else {
return clampedReturnedType;
}
}
}
/// Let `T` be the **actual returned type** of a function literal.
DartType _clampToContextType(DartType T) {
// Let `R` be the greatest closure of the typing context `K`.
var R = contextType;
if (R == null) {
return T;
}
// If `R` is `void`, or the function literal is marked `async` and `R` is
// `FutureOr<void>`, let `S` be `void`.
if (_typeSystem.isNonNullableByDefault) {
if (R.isVoid ||
isAsynchronous &&
R is InterfaceType &&
R.isDartAsyncFutureOr &&
R.typeArguments[0].isVoid) {
return VoidTypeImpl.instance;
}
}
// Otherwise, if `T <: R` then let `S` be `T`.
if (_typeSystem.isSubtypeOf(T, R)) {
return T;
}
// Otherwise, let `S` be `R`.
return _typeSystem.nonNullifyLegacy(R);
}
DartType _computeActualReturnedType({
required bool endOfBlockIsReachable,
}) {
if (isGenerator) {
if (_returnTypes.isEmpty) {
return DynamicTypeImpl.instance;
}
return _returnTypes.reduce(_typeSystem.getLeastUpperBound);
}
var initialType = endOfBlockIsReachable
? _typeProvider.nullType
: _typeProvider.neverType;
return _returnTypes.fold(initialType, _typeSystem.getLeastUpperBound);
}
static BodyInferenceContext? of(FunctionBody node) {
return node.getProperty(_key);
}
static DartType? _argumentOf(DartType type, ClassElement element) {
var elementType = type.asInstanceOf(element);
if (elementType != null) {
return elementType.typeArguments[0];
}
return null;
}
static DartType? _contextTypeForImposed(
TypeSystemImpl typeSystem,
FunctionBody node,
DartType? imposedType,
) {
if (imposedType == null) {
return null;
}
// If the function expression is neither `async` nor a generator, then the
// context type is the imposed return type.
if (!node.isAsynchronous && !node.isGenerator) {
return imposedType;
}
// If the function expression is declared `async*` and the imposed return
// type is of the form `Stream<S>` for some `S`, then the context type
// is `S`.
if (node.isGenerator && node.isAsynchronous) {
var elementType = _argumentOf(
imposedType,
typeSystem.typeProvider.streamElement,
);
if (elementType != null) {
return elementType;
}
}
// If the function expression is declared `sync*` and the imposed return
// type is of the form `Iterable<S>` for some `S`, then the context type
// is `S`.
if (node.isGenerator && node.isSynchronous) {
var elementType = _argumentOf(
imposedType,
typeSystem.typeProvider.iterableElement,
);
if (elementType != null) {
return elementType;
}
}
// Otherwise the context type is `FutureOr<futureValueTypeSchema(S)>`,
// where `S` is the imposed return type.
return typeSystem.typeProvider.futureOrType(
typeSystem.futureValueType(imposedType),
);
}
}