[_fe_analyzer_shared] Move analysis of await expressions to shared logic.

Moves the bulk of the business logic for type analyzing await
expressions from the analyzer and front_end codebases into the shared
`TypeAnalyzer` class. There is no functional change.

This paves the way for fixing
https://github.com/dart-lang/sdk/issues/62889 (Unsound type promotion
in inner async/generator functions), which will require the type
analysis of await expressions to be integrated more closely with flow
analysis. Sharing the type analysis logic will avoid the need to do
that integration twice.

Change-Id: I9f8770ce8127a960b746a89ee3e370ed6a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/489480
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry
2026-03-23 08:29:55 -07:00
committed by Commit Queue
parent 0b5a913de8
commit 1b1c5d55dc
12 changed files with 383 additions and 40 deletions
@@ -23,6 +23,19 @@ class AssignedVariablePatternResult<Error> extends PatternResult {
});
}
/// Result for analyzing an await expression in
/// [TypeAnalyzer.analyzeSwitchExpression].
class AwaitExpressionResult extends ExpressionTypeAnalysisResult {
/// The static type of the operand.
final SharedTypeView operandType;
AwaitExpressionResult({
required super.type,
super.flowAnalysisInfo,
required this.operandType,
});
}
/// Result for analyzing a constant pattern in
/// [TypeAnalyzer.analyzeConstantPattern].
class ConstantPatternResult<Error> extends PatternResult {
@@ -380,6 +380,65 @@ mixin TypeAnalyzer<
flow.promotedType(variable) ?? operations.variableType(variable),
);
/// Analyzes an expression of the form `await operand`.
///
/// Returns an [AwaitExpressionResult] containing the static type of the await
/// expression and its operand.
///
/// Stack effect: pushes the operand expression.
AwaitExpressionResult analyzeAwaitExpression(
Expression operand,
SharedTypeSchemaView schema,
) {
// Stack: ()
// (Note: comments pulled from
// https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md)
// Expression inference of an await expression await e_1, in context K,
// produces an elaborated expression m with static type T, where m and T are
// determined as follows:
SharedTypeSchemaView k = schema;
// Define K_1 as follows:
// - If K is FutureOr<S> or FutureOr<S>? for some type schema S, then let
// K_1 be K.
// - Otherwise, if K is dynamic, then let K_1 be FutureOr<_>.
// - Otherwise, let K_1 be FutureOr<K>.
assert(
schema is! SharedDynamicTypeSchemaView,
'Caller should convert dynamic context to _',
);
SharedTypeSchemaView k1 = operations.matchTypeSchemaFutureOr(k) != null
? k
: operations.futureOrTypeSchema(k);
// Let m_1 be the result of performing expression inference on e_1, in
// context K_1.
ExpressionTypeAnalysisResult m1 = analyzeExpression(
operand,
k1,
isVoidAllowed: false,
);
// Stack: (operand)
// Let T_1 be the static type of m_1.
SharedTypeView t1 = m1.type;
// If T_1 is incompatible with await (as defined in the extension types
// specification), then there is a compile-time error.
// (Currently this error is detected by the analyzer and front_end clients,
// not by shared code. TODO(paulberry): share this logic.)
// Let T_2 be flatten(T_1).
SharedTypeView t2 = operations.flatten(t1);
// Let m_2 be @AWAIT_WITH_TYPE_CHECK(m_1), with static type Future<T_2>.
// Let T be T_2, and let m be `await m_2`.
return new AwaitExpressionResult(type: t2, operandType: t1);
}
/// Analyzes a cast pattern. [innerPattern] is the sub-pattern] and
/// [requiredType] is the type to cast to.
///
@@ -138,6 +138,57 @@ abstract interface class TypeAnalyzerOperations<
required bool inferenceUsingBoundsIsEnabled,
});
/// Computes `flatten` of [type].
///
/// See the `Function Expressions` section of
/// https://storage.googleapis.com/dart-specification/DartLangSpecDraft.pdf.
SharedTypeView flatten(SharedTypeView type);
/// Returns the type `FutureOr` with omitted nullability and type argument
/// [argumentTypeSchema].
///
/// The concrete classes implementing [TypeAnalyzerOperations] should mix in
/// [TypeAnalyzerOperationsMixin] and implement [futureOrTypeInternal] to
/// receive a concrete implementation of [futureOrType] instead of
/// implementing [futureOrType] directly.
SharedTypeView futureOrType(SharedTypeView argumentTypeSchema);
/// [futureOrTypeInternal] should be implemented by concrete classes
/// implementing [TypeAnalyzerOperations]. The implementations of
/// [futureOrType] and [futureOrTypeSchema] are provided by mixing in
/// [TypeAnalyzerOperationsMixin], which defines [futureOrType] and
/// [futureOrTypeSchema] in terms of [futureOrTypeInternal].
///
/// The main purpose of this method is to avoid code duplication in the
/// concrete classes implementing [TypeAnalyzerOperations], so they can
/// implement only one member, in this case [futureOrTypeInternal], and
/// receive the implementation of both [futureOrType] and [futureOrTypeSchema]
/// from the mixin.
///
/// The auxiliary purpose of [futureOrTypeInternal] is to facilitate the
/// development of the shared code at early stages. Sometimes the sharing of
/// the code starts by unifying the implementations of some concrete members
/// in the Analyzer and the CFE by bringing them in a form that looks
/// syntactically very similar in both tools, and then continues by
/// abstracting the two concrete members and using the shared abstracted one
/// instead of the two concrete methods existing previously. During the early
/// stages of unifying the two concrete members it can be beneficial to use
/// [futureOrTypeInternal] instead of the tool-specific ways of constructing a
/// future type, for the sake of uniformity, and to simplify the abstraction
/// step too.
SharedType futureOrTypeInternal(covariant SharedType typeStructure);
/// Returns the type schema `FutureOr` with omitted nullability and type
/// argument [argumentTypeSchema].
///
/// The concrete classes implementing [TypeAnalyzerOperations] should mix in
/// [TypeAnalyzerOperationsMixin] and implement [futureOrTypeInternal] to
/// receive a concrete implementation of [futureOrTypeSchema] instead of
/// implementing [futureOrTypeSchema] directly.
SharedTypeSchemaView futureOrTypeSchema(
SharedTypeSchemaView argumentTypeSchema,
);
/// Returns the type `Future` with omitted nullability and type argument
/// [argumentType].
///
@@ -1066,6 +1117,22 @@ mixin TypeAnalyzerOperationsMixin<
return inferredTypes;
}
@override
SharedTypeView futureOrType(SharedTypeView argumentType) {
return new SharedTypeView(
futureOrTypeInternal(argumentType.unwrapTypeView()),
);
}
@override
SharedTypeSchemaView futureOrTypeSchema(
SharedTypeSchemaView argumentTypeSchema,
) {
return new SharedTypeSchemaView(
futureOrTypeInternal(argumentTypeSchema.unwrapTypeSchemaView()),
);
}
@override
SharedTypeView futureType(SharedTypeView argumentType) {
return new SharedTypeView(
@@ -58,6 +58,14 @@ Statement assert_(ProtoExpression condition, [ProtoExpression? message]) {
);
}
Expression await_(ProtoExpression operand) {
var location = computeLocation();
return new AwaitExpression._(
operand.asExpression(location: location),
location: location,
);
}
Statement block(List<ProtoStatement> statements) =>
new Block._(statements, location: computeLocation());
@@ -716,6 +724,32 @@ class Assert extends Statement {
}
}
class AwaitExpression extends Expression {
final Expression operand;
AwaitExpression._(this.operand, {required super.location});
@override
void preVisit(PreVisitor visitor) {
operand.preVisit(visitor);
}
@override
String toString() => 'await $operand';
@override
ExpressionTypeAnalysisResult visit(Harness h, SharedTypeSchemaView schema) {
var result = h.typeAnalyzer.analyzeAwaitExpression(operand, schema);
h.irBuilder.apply(
'awaitExpr',
[Kind.expression],
Kind.expression,
location: location,
);
return result;
}
}
class Block extends Statement {
final List<Statement> statements;
@@ -3261,6 +3295,54 @@ class MiniAstOperations
);
}
@override
SharedTypeView flatten(SharedTypeView type) {
// (Note: comments below are pulled from the definition of the "flatten"
// function in the "Function Expressions" section of the language spec.)
// We define the auxiliary function flatten(T) as follows, using the first
// applicable case:
var t = type.unwrapTypeView<Type>();
// - If T is X & S for some type variable X and type S then
if (t case TypeParameterType(:var typeParameter, promotion: var s?)) {
// - If S derives a future type U then flatten(T) ≜ flatten(U).
if (_typeSystem.derivedFutureType(s) case var u?) {
return flatten(u.wrapSharedTypeView());
}
// - otherwise, flatten(T) ≜ flatten(X)
return flatten(TypeParameterType(typeParameter).wrapSharedTypeView());
}
// - If T derives a future type Future<S> or FutureOr<S> then
// flatten(T) ≜ S.
// - If T derives a future type Future<S>? or FutureOr<S>? then
// flatten(T) ≜ S?.
if (_typeSystem.derivedFutureType(t) case var f?) {
var s = switch (f) {
PrimaryType(nameInfo: TypeNameInfo(name: 'Future'), args: [var s]) ||
FutureOrType(typeArgument: var s) => s,
_ => fail(
'Derived future type should always be Future<...> or FutureOr<...>',
),
};
if (f.isQuestionType) {
return s.asQuestionType(true).wrapSharedTypeView();
} else {
return s.wrapSharedTypeView();
}
}
// - Otherwise, flatten(T) ≜ T.
return t.wrapSharedTypeView();
}
@override
Type futureOrTypeInternal(Type argumentType) {
return FutureOrType(argumentType);
}
@override
Type futureTypeInternal(Type argumentType) {
return PrimaryType(TypeRegistry.future, args: [argumentType]);
+54 -6
View File
@@ -11,6 +11,7 @@ import 'dart:core' hide Type;
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
/// Surrounds [s] with parentheses if [condition] is `true`, otherwise returns
/// [s] unchanged.
@@ -1246,6 +1247,50 @@ class TypeSystem {
_superInterfaceTemplates[className] = template;
}
/// If [t] derives a future type `F` (as defined in the "Function Expressions"
/// section of the language spec), returns `F`. Otherwise returns `null`.
Type? derivedFutureType(Type t) {
// (Note: comments below are pulled from the definition of "derives a future
// type" in the "Function Expressions" section of the language spec.)
// We say that a type T derives a future type F in the following cases,
// using the first applicable case:
// - If T is a type which is introduced by a class, mixin, or enum
// declaration, and if T or a direct or indirect superinterface of T is
// Future<U> for some U, then T derives the future type Future<U>.
if (t case PrimaryType(isInterfaceType: true, isQuestionType: false)) {
for (var f in [t, ..._getSuperInterfaces(t)]) {
if (f case PrimaryType(nameInfo: TypeNameInfo(name: 'Future'))) {
return f;
}
}
}
// - If T is the type FutureOr<U> for some U, then T derives the future type
// FutureOr<U>.
if (t is FutureOrType) {
return t;
}
// - If T is S? for some S, and S derives the future type F, then T derives
// the future type F?.
if (t.isQuestionType) {
if (derivedFutureType(t.asQuestionType(false)) case var f?) {
return f.asQuestionType(true);
}
}
// - If T is a type variable with bound B, and B derives the future type F,
// then T derives the future type F.
if (t case TypeParameterType(bound: var b)) {
if (derivedFutureType(b) case var f?) {
return f;
}
}
return null;
}
Type factor(Type t, Type s) {
// If T <: S then Never
if (isSubtype(t, s)) return NeverType.instance;
@@ -1551,12 +1596,7 @@ class TypeSystem {
// Super-Interface: T0 is an interface type with super-interfaces S0,...Sn
bool isSuperInterfaceSubtype() {
if (t0 is! PrimaryType) return false;
var superInterfaceTemplate = _superInterfaceTemplates[t0.name];
if (superInterfaceTemplate == null) {
assert(false, 'Superinterfaces for $t0 not known');
return false;
}
var superInterfaces = superInterfaceTemplate(t0.args);
var superInterfaces = _getSuperInterfaces(t0);
// - and Si <: T1 for some i
for (var superInterface in superInterfaces) {
@@ -1728,6 +1768,14 @@ class TypeSystem {
return false;
}
List<Type> _getSuperInterfaces(PrimaryType t) {
var superInterfaceTemplate = _superInterfaceTemplates[t.name];
if (superInterfaceTemplate == null) {
fail('Superinterfaces for $t not known');
}
return superInterfaceTemplate(t.args);
}
bool _isTop(Type t) {
if (t is PrimaryType) {
return t is DynamicType || t is InvalidType || t is VoidType;
@@ -294,6 +294,75 @@ main() {
});
group('Expressions:', () {
group('await:', () {
group('AwaitExpressionResult:', () {
test('operandType', () {
h.run([
await_(expr('int')).checkExpressionTypeAnalysisResult((result) {
result as AwaitExpressionResult;
expect(result.operandType.toString(), 'int');
}),
]);
});
});
group('Downward inference:', () {
test('Schema is FutureOr<S>', () {
h.run([
await_(
expr('int').checkSchema('FutureOr<int>'),
).inTypeSchema('FutureOr<int>'),
]);
});
test('Schema is FutureOr<S>?', () {
h.run([
await_(
expr('int').checkSchema('FutureOr<int>?'),
).inTypeSchema('FutureOr<int>?'),
]);
});
test('Schema is dynamic', () {
h.run([
await_(
expr('int').checkSchema('FutureOr<_>'),
).inTypeSchema('dynamic'),
]);
});
test('Schema is other type', () {
h.run([
await_(
expr('int').checkSchema('FutureOr<int>'),
).inTypeSchema('int'),
]);
});
});
group('Upward inference:', () {
test('Operand has type Future<S>', () {
h.run([await_(expr('Future<int>')).checkType('int')]);
});
test('Operand has type FutureOr<S>', () {
h.run([await_(expr('FutureOr<int>')).checkType('int')]);
});
test('Operand has type Future<S>?', () {
h.run([await_(expr('Future<int>?')).checkType('int?')]);
});
test('Operand has type FutureOr<S>?', () {
h.run([await_(expr('FutureOr<int>?')).checkType('int?')]);
});
test('Operand has other type', () {
h.run([await_(expr('int')).checkType('int')]);
});
});
});
group('cascade:', () {
group('IR:', () {
test('not null-aware', () {
@@ -566,6 +566,16 @@ class TypeSystemOperations
);
}
@override
SharedTypeView flatten(SharedTypeView type) {
return typeSystem.flatten(type.unwrapTypeView()).wrapSharedTypeView();
}
@override
SharedType futureOrTypeInternal(TypeImpl argumentType) {
return typeSystem.typeProvider.futureOrType(argumentType);
}
@override
TypeImpl futureTypeInternal(TypeImpl argumentType) {
return typeSystem.typeProvider.futureType(argumentType);
+7 -13
View File
@@ -2146,12 +2146,15 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(
var analysisResult = analyzeAwaitExpression(
node.expression,
SharedTypeSchemaView(_createFutureOr(contextType)),
contextType.wrapSharedTypeSchemaView(),
);
node.expression = popRewrite()!;
node.recordStaticType(
analysisResult.type.unwrapTypeView<TypeImpl>(),
resolver: this,
);
popRewrite();
typeAnalyzer.visitAwaitExpression(node);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
@@ -4541,15 +4544,6 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
}
}
/// Creates a union of `T | Future<T>`, unless `T` is already a
/// future-union, in which case it simply returns `T`.
TypeImpl _createFutureOr(TypeImpl type) {
if (type.isDartAsyncFutureOr) {
return type;
}
return typeProvider.futureOrType(type);
}
/// Helper function used to print information to the console in debug mode.
/// This method returns `true` so that it can be conveniently called inside of
/// an `assert` statement.
@@ -58,16 +58,6 @@ class StaticTypeAnalyzer {
node.recordStaticType(_getType(node.type), resolver: _resolver);
}
/// The Dart Language Specification, 16.29 (Await Expressions):
///
/// The static type of [the expression "await e"] is flatten(T) where T is
/// the static type of e.
void visitAwaitExpression(covariant AwaitExpressionImpl node) {
var resultType = node.expression.typeOrThrow;
resultType = _typeSystem.flatten(resultType);
node.recordStaticType(resultType, resolver: _resolver);
}
/// The Dart Language Specification, 12.4: <blockquote>The static type of a boolean literal is
/// bool.</blockquote>
void visitBooleanLiteral(covariant BooleanLiteralImpl node) {
@@ -1213,17 +1213,16 @@ class InferenceVisitorImpl extends InferenceVisitorBase
if (typeContext is DynamicType) {
typeContext = const UnknownType();
}
typeContext = wrapFutureOrType(typeContext);
ExpressionInferenceResult operandResult = inferExpression(
AwaitExpressionResult analysisResult = analyzeAwaitExpression(
node.operand,
typeContext,
isVoidAllowed: false,
typeContext.wrapSharedTypeSchemaView(),
);
DartType operandType = operandResult.inferredType;
DartType flattenType = typeSchemaEnvironment.flatten(operandType);
Expression operandRewrite = popRewrite() as Expression;
DartType operandType = analysisResult.operandType.unwrapTypeView();
DartType flattenType = analysisResult.type.unwrapTypeView();
if (_isIncompatibleWithAwait(operandType)) {
Expression wrapped = operandResult.expression;
node.operand = problemReporting.wrapInProblem(
Expression wrapped = operandRewrite;
operandRewrite = problemReporting.wrapInProblem(
compilerContext: compilerContext,
expression: wrapped,
message: diag.awaitOfExtensionTypeNotFuture,
@@ -1231,10 +1230,9 @@ class InferenceVisitorImpl extends InferenceVisitorBase
fileOffset: wrapped.fileOffset,
length: 1,
);
wrapped.parent = node.operand;
} else {
node.operand = operandResult.expression..parent = node;
wrapped.parent = operandRewrite;
}
node.operand = operandRewrite..parent = node;
DartType runtimeCheckType = new InterfaceType(
coreTypes.futureClass,
Nullability.nonNullable,
@@ -587,6 +587,11 @@ class OperationsCfe
);
}
@override
SharedTypeView flatten(SharedTypeView type) {
return new SharedTypeView(typeEnvironment.flatten(type.unwrapTypeView()));
}
@override
bool isAlwaysExhaustiveType(SharedTypeView type) {
return computeIsAlwaysExhaustiveType(
@@ -993,6 +998,11 @@ class OperationsCfe
}
}
@override
FutureOrType futureOrTypeInternal(DartType argumentType) {
return new FutureOrType(argumentType, Nullability.nonNullable);
}
@override
InterfaceType futureTypeInternal(DartType argumentType) {
return new InterfaceType(
@@ -549,6 +549,7 @@ downloaded
downloading
dq
dquote
draft
drawn
ds
dsdk
@@ -579,6 +580,7 @@ efficiently
ei
eight
eighth
elaborated
elected
elem
eliminating
@@ -781,6 +783,7 @@ glyph
gn
gobble
goldens
googleapis
googlesource
goto
gotos