diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart index c22782c0703..acd3621e338 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart @@ -23,6 +23,19 @@ class AssignedVariablePatternResult 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 extends PatternResult { diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart index b0ebdcfef87..8ce22d6b9da 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart @@ -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 or FutureOr? 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. + 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. + + // 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. /// diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart index c34b4c5a30a..4022583f75f 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer_operations.dart @@ -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( diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index 6e33b2cbaea..ca9991c09df 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -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 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 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(); + + // - 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 or FutureOr then + // flatten(T) ≜ S. + // - If T derives a future type Future? or FutureOr? 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]); diff --git a/pkg/_fe_analyzer_shared/test/mini_types.dart b/pkg/_fe_analyzer_shared/test/mini_types.dart index 717504d51b3..02529bb0f5e 100644 --- a/pkg/_fe_analyzer_shared/test/mini_types.dart +++ b/pkg/_fe_analyzer_shared/test/mini_types.dart @@ -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 for some U, then T derives the future type Future. + 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 for some U, then T derives the future type + // FutureOr. + 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 _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; diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart index ae5b2adf494..b92390ead78 100644 --- a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart +++ b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart @@ -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', () { + h.run([ + await_( + expr('int').checkSchema('FutureOr'), + ).inTypeSchema('FutureOr'), + ]); + }); + + test('Schema is FutureOr?', () { + h.run([ + await_( + expr('int').checkSchema('FutureOr?'), + ).inTypeSchema('FutureOr?'), + ]); + }); + + 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'), + ).inTypeSchema('int'), + ]); + }); + }); + + group('Upward inference:', () { + test('Operand has type Future', () { + h.run([await_(expr('Future')).checkType('int')]); + }); + + test('Operand has type FutureOr', () { + h.run([await_(expr('FutureOr')).checkType('int')]); + }); + + test('Operand has type Future?', () { + h.run([await_(expr('Future?')).checkType('int?')]); + }); + + test('Operand has type FutureOr?', () { + h.run([await_(expr('FutureOr?')).checkType('int?')]); + }); + + test('Operand has other type', () { + h.run([await_(expr('int')).checkType('int')]); + }); + }); + }); + group('cascade:', () { group('IR:', () { test('not null-aware', () { diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart index 5a3859edf12..e716e9ffad6 100644 --- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart +++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart @@ -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); diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 41d5d9127f6..e0a4a42ee18 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -2146,12 +2146,15 @@ class ResolverVisitor extends ThrowingAstVisitor }) { 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(), + resolver: this, ); - popRewrite(); - typeAnalyzer.visitAwaitExpression(node); _insertImplicitCallReference( insertGenericFunctionInstantiation(node, contextType: contextType), contextType: contextType, @@ -4541,15 +4544,6 @@ class ResolverVisitor extends ThrowingAstVisitor } } - /// Creates a union of `T | Future`, 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. diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart index f43af10610d..4dcc46df9ec 100644 --- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart +++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart @@ -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:
The static type of a boolean literal is /// bool.
void visitBooleanLiteral(covariant BooleanLiteralImpl node) { diff --git a/pkg/front_end/lib/src/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/type_inference/inference_visitor.dart index 5710cb57048..69c1029f863 100644 --- a/pkg/front_end/lib/src/type_inference/inference_visitor.dart +++ b/pkg/front_end/lib/src/type_inference/inference_visitor.dart @@ -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, diff --git a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart index d34d3ff8c1f..9326d319082 100644 --- a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart +++ b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart @@ -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( diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index 3545e6e5e97..6188f8cd237 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt @@ -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