Skip to content

Commit 14032a6

Browse files
johnniwinthercommit-bot@chromium.org
authored andcommitted
[cfe] Handle conditional await in CFE
This converts `return e` in async methods in opt-in libraries into return let v = e in v is Future<FTV> ? await v ; v where FTV is the future value type of the enclosing function. Closes #44395 Closes #44396 Closes #44397 Closes #44399 TEST=existing tests Change-Id: I59687039bfe4a97ffdaa55ac0f193ca4fb208f44 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175310 Commit-Queue: Johnni Winther <[email protected]> Reviewed-by: Erik Ernst <[email protected]>
1 parent 97a4280 commit 14032a6

File tree

76 files changed

+3477
-428
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+3477
-428
lines changed

pkg/front_end/lib/src/fasta/type_inference/closure_context.dart

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -546,30 +546,75 @@ class _AsyncClosureContext implements ClosureContext {
546546
statement.expression.fileOffset,
547547
noLength)
548548
..parent = statement;
549-
} else if (flattenedExpressionType is! VoidType &&
550-
!inferrer.typeSchemaEnvironment
551-
.performNullabilityAwareSubtypeCheck(
552-
flattenedExpressionType, futureValueType)
553-
.isSubtypeWhenUsingNullabilities()) {
554-
// It is a compile-time error if s is `return e;`, flatten(S) is not
555-
// void, S is not assignable to T_v, and flatten(S) is not a subtype
556-
// of T_v.
557-
statement.expression = inferrer.ensureAssignable(
558-
futureValueType, expressionType, statement.expression,
559-
fileOffset: statement.expression.fileOffset,
560-
runtimeCheckedType:
561-
inferrer.computeGreatestClosure2(_returnContext),
562-
declaredContextType: returnType,
563-
isVoidAllowed: false,
564-
errorTemplate: templateInvalidReturnAsync,
565-
nullabilityErrorTemplate: templateInvalidReturnAsyncNullability,
566-
nullabilityPartErrorTemplate:
567-
templateInvalidReturnAsyncPartNullability,
568-
nullabilityNullErrorTemplate:
569-
templateInvalidReturnAsyncNullabilityNull,
570-
nullabilityNullTypeErrorTemplate:
571-
templateInvalidReturnAsyncNullabilityNullType)
572-
..parent = statement;
549+
} else {
550+
DartType futureOrType =
551+
inferrer.computeGreatestClosure2(_returnContext);
552+
if (flattenedExpressionType is! VoidType &&
553+
!inferrer.typeSchemaEnvironment
554+
.performNullabilityAwareSubtypeCheck(
555+
flattenedExpressionType, futureValueType)
556+
.isSubtypeWhenUsingNullabilities()) {
557+
// It is a compile-time error if s is `return e;`, flatten(S) is not
558+
// void, S is not assignable to T_v, and flatten(S) is not a subtype
559+
// of T_v.
560+
statement.expression = inferrer.ensureAssignable(
561+
futureValueType, expressionType, statement.expression,
562+
fileOffset: statement.expression.fileOffset,
563+
runtimeCheckedType: futureOrType,
564+
declaredContextType: returnType,
565+
isVoidAllowed: false,
566+
errorTemplate: templateInvalidReturnAsync,
567+
nullabilityErrorTemplate: templateInvalidReturnAsyncNullability,
568+
nullabilityPartErrorTemplate:
569+
templateInvalidReturnAsyncPartNullability,
570+
nullabilityNullErrorTemplate:
571+
templateInvalidReturnAsyncNullabilityNull,
572+
nullabilityNullTypeErrorTemplate:
573+
templateInvalidReturnAsyncNullabilityNullType)
574+
..parent = statement;
575+
}
576+
// For `return e`:
577+
// When `f` is an asynchronous non-generator with future value type
578+
// T_v, evaluation proceeds as follows:
579+
//
580+
// The expression `e` is evaluated to an object `o`.
581+
// If the run-time type of `o` is a subtype of `Future<T_v>`,
582+
// let `v` be a fresh variable bound to `o` and
583+
// evaluate `await v` to an object `r`;
584+
// otherwise let `r` be `o`.
585+
// A dynamic error occurs unless the dynamic type of `r`
586+
// is a subtype of the actual value of T_v.
587+
// Then the return statement `s` completes returning `r`.
588+
DartType futureType = new InterfaceType(
589+
inferrer.coreTypes.futureClass,
590+
Nullability.nonNullable,
591+
[futureValueType]);
592+
VariableDeclaration variable;
593+
Expression isOperand;
594+
Expression awaitOperand;
595+
Expression resultExpression;
596+
if (isPureExpression(statement.expression)) {
597+
isOperand = clonePureExpression(statement.expression);
598+
awaitOperand = clonePureExpression(statement.expression);
599+
resultExpression = statement.expression;
600+
} else {
601+
variable = createVariable(statement.expression, expressionType);
602+
isOperand = createVariableGet(variable);
603+
awaitOperand = createVariableGet(variable);
604+
resultExpression = createVariableGet(variable);
605+
}
606+
Expression replacement = new ConditionalExpression(
607+
new IsExpression(isOperand, futureType)
608+
..fileOffset = statement.fileOffset,
609+
new AwaitExpression(awaitOperand)
610+
..fileOffset = statement.fileOffset,
611+
resultExpression,
612+
futureOrType)
613+
..fileOffset = statement.fileOffset;
614+
if (variable != null) {
615+
replacement = createLet(variable, replacement);
616+
}
617+
statement.expression = replacement..parent = statement;
573618
}
574619
}
575620
} else {

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,16 +2514,13 @@ class TypeInferrerImpl implements TypeInferrer {
25142514
// `void` if `B’` contains no `yield` expressions. Otherwise, let `M` be
25152515
// the least upper bound of the types of the `return` expressions in `B’`,
25162516
// or `void` if `B’` contains no `return` expressions.
2517-
DartType inferredReturnType;
25182517
if (needToSetReturnType) {
2519-
inferredReturnType = closureContext.inferReturnType(this,
2518+
DartType inferredReturnType = closureContext.inferReturnType(this,
25202519
hasImplicitReturn: flowAnalysis.isReachable);
2521-
}
25222520

2523-
// Then the result of inference is `<T0, ..., Tn>(R0 x0, ..., Rn xn) B` with
2524-
// type `<T0, ..., Tn>(R0, ..., Rn) -> M’` (with some of the `Ri` and `xi`
2525-
// denoted as optional or named parameters, if appropriate).
2526-
if (needToSetReturnType) {
2521+
// Then the result of inference is `<T0, ..., Tn>(R0 x0, ..., Rn xn) B`
2522+
// with type `<T0, ..., Tn>(R0, ..., Rn) -> M'` (with some of the `Ri` and
2523+
// `xi` denoted as optional or named parameters, if appropriate).
25272524
instrumentation?.record(uriForInstrumentation, fileOffset, 'returnType',
25282525
new InstrumentationValueForType(inferredReturnType));
25292526
function.returnType = inferredReturnType;

pkg/front_end/test/spell_checking_list_code.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ printer
868868
printf
869869
println
870870
proc
871+
proceeds
871872
producers
872873
product
873874
progresses

pkg/front_end/test/spell_checking_list_tests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ ugly
737737
unassignment
738738
unawaited
739739
unbreak
740+
uncaught
740741
unconverted
741742
uncover
742743
uncovers

pkg/front_end/test/text_representation/data/expressions.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ exprEmptyTypedMap() => <int, String>{};
382382
exprMap() => {0: "foo", 1: "bar"};
383383

384384
/*member: exprAwait:await o*/
385-
exprAwait(o) async => await o;
385+
exprAwait(o) async {
386+
await o;
387+
}
386388

387389
/*member: exprLoadLibrary:prefix.loadLibrary()*/
388390
exprLoadLibrary() => prefix.loadLibrary();

pkg/front_end/test/text_representation/text_representation_test.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,16 @@ class TextRepresentationDataExtractor extends CfeDataExtractor<String> {
156156

157157
@override
158158
String computeMemberValue(Id id, Member node) {
159-
if (node.name.text == 'stmtVariableDeclarationMulti') {
160-
print(node);
161-
}
162159
if (node.name.text.startsWith(expressionMarker)) {
163160
if (node is Procedure) {
164161
Statement body = node.function.body;
165162
if (body is ReturnStatement) {
166163
return body.expression.toText(strategy);
164+
} else if (body is Block &&
165+
body.statements.isNotEmpty &&
166+
body.statements.first is ExpressionStatement) {
167+
ExpressionStatement statement = body.statements.first;
168+
return statement.expression.toText(strategy);
167169
}
168170
} else if (node is Field && node.initializer != null) {
169171
return node.initializer.toText(strategy);

pkg/front_end/testcases/late_lowering/later.dart.strong.expect

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,28 @@ static method hest() → dynamic async {
134134
await for (core::String s in asy::Stream::fromIterable<core::String>(<core::String>["hest"])) {
135135
core::print(s);
136136
}
137-
return "hest";
137+
return let final core::String #t8 = "hest" in #t8 is asy::Future<dynamic> ?{FutureOr<dynamic>} await #t8 : #t8;
138138
}
139139
static method fisk() → dynamic async {
140140
lowered core::String? #s1;
141141
function #s1#get() → core::String
142-
return let final core::String? #t8 = #s1 in #t8.==(null) ?{core::String} #s1 = invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:40:20: Error: `await` expressions are not supported in late local initializers.
142+
return let final core::String? #t9 = #s1 in #t9.==(null) ?{core::String} #s1 = invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:40:20: Error: `await` expressions are not supported in late local initializers.
143143
late String s1 = await hest(); // Error.
144-
^^^^^" as{TypeError,ForDynamic,ForNonNullableByDefault} core::String : #t8{core::String};
145-
function #s1#set(core::String #t9) → dynamic
146-
return #s1 = #t9;
144+
^^^^^" as{TypeError,ForDynamic,ForNonNullableByDefault} core::String : #t9{core::String};
145+
function #s1#set(core::String #t10) → dynamic
146+
return #s1 = #t10;
147147
lowered core::String? #s2;
148148
function #s2#get() → core::String
149-
return let final core::String? #t10 = #s2 in #t10.==(null) ?{core::String} #s2 = "${#C1}${invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:41:30: Error: `await` expressions are not supported in late local initializers.
149+
return let final core::String? #t11 = #s2 in #t11.==(null) ?{core::String} #s2 = "${#C1}${invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:41:30: Error: `await` expressions are not supported in late local initializers.
150150
late String s2 = '\${fisk}\${await hest()}\${fisk}'; // Error.
151-
^^^^^"}${#C1}" : #t10{core::String};
152-
function #s2#set(core::String #t11) → dynamic
153-
return #s2 = #t11;
151+
^^^^^"}${#C1}" : #t11{core::String};
152+
function #s2#set(core::String #t12) → dynamic
153+
return #s2 = #t12;
154154
lowered core::Function? #f;
155155
function #f#get() → core::Function
156-
return let final core::Function? #t12 = #f in #t12.==(null) ?{core::Function} #f = () → asy::Future<dynamic> async => await self::hest() : #t12{core::Function};
157-
function #f#set(core::Function #t13) → dynamic
158-
return #f = #t13;
156+
return let final core::Function? #t13 = #f in #t13.==(null) ?{core::Function} #f = () → asy::Future<dynamic> async => let final dynamic #t14 = await self::hest() in #t14 is asy::Future<dynamic> ?{FutureOr<dynamic>} await #t14 : #t14 : #t13{core::Function};
157+
function #f#set(core::Function #t15) → dynamic
158+
return #f = #t15;
159159
}
160160
static method main() → dynamic {}
161161

pkg/front_end/testcases/late_lowering/later.dart.strong.transformed.expect

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ static method hest() → dynamic /* originally async */ {
148148
dynamic :saved_try_context_var1;
149149
dynamic :exception0;
150150
dynamic :stack_trace0;
151+
FutureOr<dynamic>:async_temporary_0;
152+
FutureOr<dynamic>:async_temporary_1;
151153
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
152154
try {
153155
#L1:
@@ -175,7 +177,15 @@ static method hest() → dynamic /* originally async */ {
175177
:result;
176178
}
177179
}
178-
:return_value = "hest";
180+
final core::String #t11 = "hest";
181+
if(#t11 is asy::Future<dynamic>) {
182+
[yield] let dynamic #t12 = asy::_awaitHelper(#t11, :async_op_then, :async_op_error, :async_op) in null;
183+
:async_temporary_1 = _in::unsafeCast<core::String>(:result);
184+
}
185+
else {
186+
:async_temporary_1 = #t11;
187+
}
188+
:return_value = :async_temporary_1;
179189
break #L1;
180190
}
181191
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);
@@ -204,21 +214,21 @@ static method fisk() → dynamic /* originally async */ {
204214
{
205215
lowered core::String? #s1;
206216
function #s1#get() → core::String
207-
return let final core::String? #t11 = #s1 in #t11.==(null) ?{core::String} #s1 = invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:40:20: Error: `await` expressions are not supported in late local initializers.
217+
return let final core::String? #t13 = #s1 in #t13.==(null) ?{core::String} #s1 = invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:40:20: Error: `await` expressions are not supported in late local initializers.
208218
late String s1 = await hest(); // Error.
209-
^^^^^" : #t11{core::String};
210-
function #s1#set(core::String #t12) → dynamic
211-
return #s1 = #t12;
219+
^^^^^" : #t13{core::String};
220+
function #s1#set(core::String #t14) → dynamic
221+
return #s1 = #t14;
212222
lowered core::String? #s2;
213223
function #s2#get() → core::String
214-
return let final core::String? #t13 = #s2 in #t13.==(null) ?{core::String} #s2 = "${#C1}${invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:41:30: Error: `await` expressions are not supported in late local initializers.
224+
return let final core::String? #t15 = #s2 in #t15.==(null) ?{core::String} #s2 = "${#C1}${invalid-expression "pkg/front_end/testcases/late_lowering/later.dart:41:30: Error: `await` expressions are not supported in late local initializers.
215225
late String s2 = '\${fisk}\${await hest()}\${fisk}'; // Error.
216-
^^^^^"}${#C1}" : #t13{core::String};
217-
function #s2#set(core::String #t14) → dynamic
218-
return #s2 = #t14;
226+
^^^^^"}${#C1}" : #t15{core::String};
227+
function #s2#set(core::String #t16) → dynamic
228+
return #s2 = #t16;
219229
lowered core::Function? #f;
220230
function #f#get() → core::Function
221-
return let final core::Function? #t15 = #f in #t15.==(null) ?{core::Function} #f = () → asy::Future<dynamic> /* originally async */ {
231+
return let final core::Function? #t17 = #f in #t17.==(null) ?{core::Function} #f = () → asy::Future<dynamic> /* originally async */ {
222232
final asy::_Future<dynamic> :async_future = new asy::_Future::•<dynamic>();
223233
core::bool* :is_sync = false;
224234
FutureOr<dynamic>? :return_value;
@@ -227,12 +237,21 @@ static method fisk() → dynamic /* originally async */ {
227237
core::int :await_jump_var = 0;
228238
dynamic :await_ctx_var;
229239
dynamic :saved_try_context_var0;
240+
FutureOr<dynamic>:async_temporary_0;
230241
function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
231242
try {
232243
#L4:
233244
{
234-
[yield] let dynamic #t16 = asy::_awaitHelper(self::hest(), :async_op_then, :async_op_error, :async_op) in null;
235-
:return_value = :result;
245+
[yield] let dynamic #t18 = asy::_awaitHelper(self::hest(), :async_op_then, :async_op_error, :async_op) in null;
246+
final dynamic #t19 = :result;
247+
if(#t19 is asy::Future<dynamic>) {
248+
[yield] let dynamic #t20 = asy::_awaitHelper(#t19, :async_op_then, :async_op_error, :async_op) in null;
249+
:async_temporary_0 = :result;
250+
}
251+
else {
252+
:async_temporary_0 = #t19;
253+
}
254+
:return_value = :async_temporary_0;
236255
break #L4;
237256
}
238257
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);
@@ -246,9 +265,9 @@ static method fisk() → dynamic /* originally async */ {
246265
:async_op.call();
247266
:is_sync = true;
248267
return :async_future;
249-
} : #t15{core::Function};
250-
function #f#set(core::Function #t17) → dynamic
251-
return #f = #t17;
268+
} : #t17{core::Function};
269+
function #f#set(core::Function #t21) → dynamic
270+
return #f = #t21;
252271
}
253272
asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync);
254273
return;
@@ -270,4 +289,4 @@ constants {
270289

271290
Extra constant evaluation status:
272291
Evaluated: VariableGet @ org-dartlang-testcase:///later.dart:46:18 -> IntConstant(42)
273-
Extra constant evaluation: evaluated: 207, effectively constant: 1
292+
Extra constant evaluation: evaluated: 234, effectively constant: 1

pkg/front_end/testcases/late_lowering/later.dart.weak.expect

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ static method hest() → dynamic async {
154154
await for (core::String s in asy::Stream::fromIterable<core::String>(<core::String>["hest"])) {
155155
core::print(s);
156156
}
157-
return "hest";
157+
return let final core::String #t8 = "hest" in #t8 is asy::Future<dynamic> ?{FutureOr<dynamic>} await #t8 : #t8;
158158
}
159159
static method fisk() → dynamic async {
160160
lowered core::String? #s1;
@@ -168,9 +168,9 @@ static method fisk() → dynamic async {
168168
}
169169
return #s1{core::String};
170170
}
171-
function #s1#set(core::String #t8) → dynamic {
171+
function #s1#set(core::String #t9) → dynamic {
172172
#s1#isSet = true;
173-
return #s1 = #t8;
173+
return #s1 = #t9;
174174
}
175175
lowered core::String? #s2;
176176
lowered core::bool #s2#isSet = false;
@@ -183,22 +183,22 @@ static method fisk() → dynamic async {
183183
}
184184
return #s2{core::String};
185185
}
186-
function #s2#set(core::String #t9) → dynamic {
186+
function #s2#set(core::String #t10) → dynamic {
187187
#s2#isSet = true;
188-
return #s2 = #t9;
188+
return #s2 = #t10;
189189
}
190190
lowered core::Function? #f;
191191
lowered core::bool #f#isSet = false;
192192
function #f#get() → core::Function {
193193
if(!#f#isSet) {
194-
#f = () → asy::Future<dynamic> async => await self::hest();
194+
#f = () → asy::Future<dynamic> async => let final dynamic #t11 = await self::hest() in #t11 is asy::Future<dynamic> ?{FutureOr<dynamic>} await #t11 : #t11;
195195
#f#isSet = true;
196196
}
197197
return #f{core::Function};
198198
}
199-
function #f#set(core::Function #t10) → dynamic {
199+
function #f#set(core::Function #t12) → dynamic {
200200
#f#isSet = true;
201-
return #f = #t10;
201+
return #f = #t12;
202202
}
203203
}
204204
static method main() → dynamic {}

0 commit comments

Comments
 (0)