Skip to content

Commit f18efa4

Browse files
committed
Auto merge of #157309 - cjgillot:coroutine-hir-desugar, r=oli-obk
Desugar async blocks in HIR instead of MIR Implements MCP rust-lang/compiler-team#997 Based on #157166 In the current implementation, `gen`/`async`/`async gen` blocks and closures have type `Coroutine(..)` and `CoroutineClosure(..)`. Those types implement `Iterator`, `Future` or `AsyncIterator` depending on the initial desugaring. This creates a lot of complexity: - trait solvers must check which kind of coroutine each time; - MIR StateTransform needs to fixup types depending on the coroutine kind. I propose to change the desugaring for coroutines to: - `gen { .. }` becomes `CoroutineIterator::from_coroutine(#[coroutine] { .. })`; - `async { .. }` becomes `CoroutineFuture::from_coroutine(#[coroutine] { .. })`; - `async gen { .. }` becomes `CoroutineAsyncIterator::from_coroutine(#[coroutine] { .. })`. This way, all coroutines implement `std::ops::Coroutine` and `core` is responsible for translating this to user-friendly traits. All the complexity is pushed to error-reporting code, which is not soundness-critical. Coroutine closures are a little more complex, as we need to keep the `CoroutineClosure` type for borrow-checking. Main design point: I create two methods on `TyCtxt` that are meant to do the back-and-forth between wrapped and unwrapped coroutines. `coroutine_desugared_type` wraps a coroutine inside the adapter struct. `try_unwrap_desugared_coroutine` unwraps it. r? @oli-obk cc @lcnr @RalfJung cc @estebank as I modify quite a lot of diagnostic code Fixes #149748
2 parents 1a833e1 + 07eacfb commit f18efa4

218 files changed

Lines changed: 4038 additions & 4165 deletions

File tree

Some content is hidden

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

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ use rustc_ast::node_id::NodeMap;
66
use rustc_ast::*;
77
use rustc_data_structures::stack::ensure_sufficient_stack;
88
use rustc_errors::msg;
9-
use rustc_hir as hir;
109
use rustc_hir::def::{DefKind, Res};
11-
use rustc_hir::{HirId, Target, find_attr};
10+
use rustc_hir::{self as hir, HirId, LangItem, Target, find_attr};
1211
use rustc_middle::span_bug;
1312
use rustc_middle::ty::TyCtxt;
1413
use rustc_session::diagnostics::report_lit_error;
@@ -358,9 +357,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
358357
GenBlockKind::Gen => hir::CoroutineDesugaring::Gen,
359358
GenBlockKind::AsyncGen => hir::CoroutineDesugaring::AsyncGen,
360359
};
361-
self.make_desugared_coroutine_expr(
360+
return self.make_desugared_coroutine_expr(
362361
*capture_clause,
363362
e.id,
363+
expr_hir_id,
364364
None,
365365
*decl_span,
366366
e.span,
@@ -374,7 +374,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
374374
expr
375375
})
376376
},
377-
)
377+
);
378378
}
379379
ExprKind::Block(blk, opt_label) => {
380380
// Different from loops, label of block resolves to block id rather than
@@ -816,26 +816,29 @@ impl<'hir> LoweringContext<'_, 'hir> {
816816
&mut self,
817817
capture_clause: CaptureBy,
818818
closure_node_id: NodeId,
819+
closure_hir_id: HirId,
819820
return_ty: Option<hir::FnRetTy<'hir>>,
820821
fn_decl_span: Span,
821822
span: Span,
822823
desugaring_kind: hir::CoroutineDesugaring,
823824
coroutine_source: hir::CoroutineSource,
824825
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
825-
) -> hir::ExprKind<'hir> {
826+
) -> hir::Expr<'hir> {
826827
let closure_def_id = self.local_def_id(closure_node_id);
827828
let coroutine_kind = hir::CoroutineKind::Desugared(desugaring_kind, coroutine_source);
828829

830+
let span = self.lower_span(span);
831+
let unstable_span = self.mark_span_with_reason(
832+
DesugaringKind::Async,
833+
span,
834+
Some(Arc::clone(&self.allow_gen_future)),
835+
);
836+
829837
// The `async` desugaring takes a resume argument and maintains a `task_context`,
830838
// whereas a generator does not.
831839
let (inputs, params, task_context): (&[_], &[_], _) = match desugaring_kind {
832840
hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::AsyncGen => {
833841
// Resume argument type: `ResumeTy`
834-
let unstable_span = self.mark_span_with_reason(
835-
DesugaringKind::Async,
836-
self.lower_span(span),
837-
Some(Arc::clone(&self.allow_gen_future)),
838-
);
839842
let resume_ty =
840843
self.make_lang_item_qpath(hir::LangItem::ResumeTy, unstable_span, None);
841844
let input_ty = hir::Ty {
@@ -851,21 +854,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
851854
Ident::with_dummy_span(sym::_task_context),
852855
hir::BindingMode::MUT,
853856
);
854-
let param = hir::Param {
855-
hir_id: self.next_id(),
856-
pat,
857-
ty_span: self.lower_span(span),
858-
span: self.lower_span(span),
859-
};
857+
let param = hir::Param { hir_id: self.next_id(), pat, ty_span: span, span };
860858
let params = arena_vec![self; param];
861859

862860
(inputs, params, Some(task_context_hid))
863861
}
864862
hir::CoroutineDesugaring::Gen => (&[], &[], None),
865863
};
866864

867-
let output =
868-
return_ty.unwrap_or_else(|| hir::FnRetTy::DefaultReturn(self.lower_span(span)));
865+
let output = return_ty.unwrap_or_else(|| hir::FnRetTy::DefaultReturn(span));
869866

870867
let fn_decl = self.arena.alloc(hir::FnDecl {
871868
inputs,
@@ -887,7 +884,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
887884
});
888885

889886
// `static |<_task_context?>| -> <return_ty> { <body> }`:
890-
hir::ExprKind::Closure(self.arena.alloc(hir::Closure {
887+
let coroutine_closure = hir::ExprKind::Closure(self.arena.alloc(hir::Closure {
891888
def_id: closure_def_id,
892889
binder: hir::ClosureBinder::Default,
893890
capture_clause: self.lower_capture_clause(capture_clause),
@@ -899,7 +896,19 @@ impl<'hir> LoweringContext<'_, 'hir> {
899896
kind: hir::ClosureKind::Coroutine(coroutine_kind),
900897
constness: hir::Constness::NotConst,
901898
explicit_captures: &[],
902-
}))
899+
}));
900+
let coroutine_closure = hir::Expr { hir_id: closure_hir_id, kind: coroutine_closure, span };
901+
902+
let from_coroutine = match desugaring_kind {
903+
hir::CoroutineDesugaring::Async => LangItem::FutureFromCoroutine,
904+
hir::CoroutineDesugaring::AsyncGen => LangItem::AsyncIteratorFromCoroutine,
905+
hir::CoroutineDesugaring::Gen => LangItem::IterFromCoroutine,
906+
};
907+
self.expr_call_lang_item_fn_mut(
908+
unstable_span,
909+
from_coroutine,
910+
arena_vec![self; coroutine_closure],
911+
)
903912
}
904913

905914
/// Forwards a possible `#[track_caller]` annotation from `outer_hir_id` to
@@ -952,19 +961,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
952961
/// }
953962
/// ```
954963
fn lower_expr_await(&mut self, await_kw_span: Span, expr: &Expr) -> hir::ExprKind<'hir> {
964+
let full_span = expr.span.to(await_kw_span);
955965
let expr = self.arena.alloc(self.lower_expr_mut(expr));
956-
self.make_lowered_await(await_kw_span, expr, FutureKind::Future)
966+
self.make_lowered_await(await_kw_span, full_span, expr, FutureKind::Future)
957967
}
958968

959969
/// Takes an expr that has already been lowered and generates a desugared await loop around it
960970
fn make_lowered_await(
961971
&mut self,
962972
await_kw_span: Span,
973+
// Pass the span separately, as `expr.span` may be a desugaring.
974+
full_span: Span,
963975
expr: &'hir hir::Expr<'hir>,
964976
await_kind: FutureKind,
965977
) -> hir::ExprKind<'hir> {
966-
let full_span = expr.span.to(await_kw_span);
967-
968978
let is_async_gen = match self.coroutine_kind {
969979
Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => false,
970980
Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _)) => true,
@@ -975,19 +985,19 @@ impl<'hir> LoweringContext<'_, 'hir> {
975985
// is not accidentally orphaned.
976986
let stmt_id = self.next_id();
977987
let expr_err = self.expr(
978-
expr.span,
988+
full_span,
979989
hir::ExprKind::Err(self.dcx().emit_err(AwaitOnlyInAsyncFnAndBlocks {
980990
await_kw_span,
981991
item_span: self.current_item,
982992
})),
983993
);
984994
return hir::ExprKind::Block(
985995
self.block_all(
986-
expr.span,
996+
full_span,
987997
arena_vec![self; hir::Stmt {
988998
hir_id: stmt_id,
989999
kind: hir::StmtKind::Semi(expr),
990-
span: expr.span,
1000+
span: full_span,
9911001
}],
9921002
Some(self.arena.alloc(expr_err)),
9931003
),
@@ -1646,18 +1656,54 @@ impl<'hir> LoweringContext<'_, 'hir> {
16461656
.emit();
16471657
}
16481658

1649-
let is_async_gen = match self.coroutine_kind {
1650-
Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _)) => false,
1651-
Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _)) => true,
1652-
Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => {
1653-
// Lower to a block `{ EXPR; <error> }` so that the awaited expr
1654-
// is not accidentally orphaned.
1659+
let Some(coroutine_kind) = self.coroutine_kind else {
1660+
let suggestion = self.current_item.map(|s| s.shrink_to_lo());
1661+
self.dcx().emit_err(YieldInClosure { span, suggestion });
1662+
self.coroutine_kind = Some(hir::CoroutineKind::Coroutine(Movability::Movable));
1663+
return hir::ExprKind::Yield(yielded, hir::YieldSource::Yield);
1664+
};
1665+
1666+
match coroutine_kind {
1667+
// Raw and Gen coroutines, nothing to do.
1668+
hir::CoroutineKind::Coroutine(_)
1669+
| hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _) => {
1670+
hir::ExprKind::Yield(yielded, hir::YieldSource::Yield)
1671+
}
1672+
// `yield $expr` is transformed into `task_context = yield async_gen_ready($expr)`.
1673+
// This ensures that we store our resumed `ResumeContext` correctly, and also that
1674+
// the apparent value of the `yield` expression is `()`.
1675+
hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _) => {
1676+
let desugar_span = self.mark_span_with_reason(
1677+
DesugaringKind::Async,
1678+
span,
1679+
Some(Arc::clone(&self.allow_async_gen)),
1680+
);
1681+
let wrapped_yielded = self.expr_call_lang_item_fn(
1682+
desugar_span,
1683+
hir::LangItem::AsyncGenReady,
1684+
std::slice::from_ref(yielded),
1685+
);
1686+
let yield_expr = self.arena.alloc(
1687+
self.expr(span, hir::ExprKind::Yield(wrapped_yielded, hir::YieldSource::Yield)),
1688+
);
1689+
1690+
let Some(task_context_hid) = self.task_context else {
1691+
unreachable!("use of `await` outside of an async context.");
1692+
};
1693+
let task_context_ident = Ident::with_dummy_span(sym::_task_context);
1694+
let lhs = self.expr_ident(desugar_span, task_context_ident, task_context_hid);
1695+
1696+
hir::ExprKind::Assign(lhs, yield_expr, self.lower_span(span))
1697+
}
1698+
// Lower to a block `{ EXPR; <error> }` so that the awaited expr
1699+
// is not accidentally orphaned.
1700+
hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) => {
16551701
let stmt_id = self.next_id();
16561702
let expr_err = self.expr(
16571703
yielded.span,
16581704
hir::ExprKind::Err(self.dcx().emit_err(AsyncCoroutinesNotSupported { span })),
16591705
);
1660-
return hir::ExprKind::Block(
1706+
hir::ExprKind::Block(
16611707
self.block_all(
16621708
yielded.span,
16631709
arena_vec![self; hir::Stmt {
@@ -1668,45 +1714,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
16681714
Some(self.arena.alloc(expr_err)),
16691715
),
16701716
None,
1671-
);
1672-
}
1673-
Some(hir::CoroutineKind::Coroutine(_)) => false,
1674-
None => {
1675-
let suggestion = self.current_item.map(|s| s.shrink_to_lo());
1676-
self.dcx().emit_err(YieldInClosure { span, suggestion });
1677-
self.coroutine_kind = Some(hir::CoroutineKind::Coroutine(Movability::Movable));
1678-
1679-
false
1717+
)
16801718
}
1681-
};
1682-
1683-
if is_async_gen {
1684-
// `yield $expr` is transformed into `task_context = yield async_gen_ready($expr)`.
1685-
// This ensures that we store our resumed `ResumeContext` correctly, and also that
1686-
// the apparent value of the `yield` expression is `()`.
1687-
let desugar_span = self.mark_span_with_reason(
1688-
DesugaringKind::Async,
1689-
span,
1690-
Some(Arc::clone(&self.allow_async_gen)),
1691-
);
1692-
let wrapped_yielded = self.expr_call_lang_item_fn(
1693-
desugar_span,
1694-
hir::LangItem::AsyncGenReady,
1695-
std::slice::from_ref(yielded),
1696-
);
1697-
let yield_expr = self.arena.alloc(
1698-
self.expr(span, hir::ExprKind::Yield(wrapped_yielded, hir::YieldSource::Yield)),
1699-
);
1700-
1701-
let Some(task_context_hid) = self.task_context else {
1702-
unreachable!("use of `await` outside of an async context.");
1703-
};
1704-
let task_context_ident = Ident::with_dummy_span(sym::_task_context);
1705-
let lhs = self.expr_ident(desugar_span, task_context_ident, task_context_hid);
1706-
1707-
hir::ExprKind::Assign(lhs, yield_expr, self.lower_span(span))
1708-
} else {
1709-
hir::ExprKind::Yield(yielded, hir::YieldSource::Yield)
17101719
}
17111720
}
17121721

@@ -1801,7 +1810,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
18011810
));
18021811
// `unsafe { ... }`
18031812
let iter = self.arena.alloc(self.expr_unsafe(head_span, iter));
1804-
let kind = self.make_lowered_await(head_span, iter, FutureKind::AsyncIterator);
1813+
let kind = self.make_lowered_await(
1814+
head_span,
1815+
head_span,
1816+
iter,
1817+
FutureKind::AsyncIterator,
1818+
);
18051819
self.arena.alloc(hir::Expr { hir_id: self.next_id(), kind, span: head_span })
18061820
}
18071821
};

compiler/rustc_ast_lowering/src/expr/closure.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -329,18 +329,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
329329
// Transform `async |x: u8| -> X { ... }` into
330330
// `|x: u8| || -> X { ... }`.
331331
let body_id = this.lower_body(|this| {
332-
let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| {
333-
this.lower_coroutine_body_with_moved_arguments(
334-
&inner_decl,
335-
|this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)),
336-
fn_decl_span,
337-
body.span,
338-
coroutine_kind,
339-
hir::CoroutineSource::Closure,
340-
)
341-
});
332+
let ((parameters, expr, coroutine_hir_id), _) =
333+
this.with_move_expr_bindings(None, |this| {
334+
this.lower_coroutine_body_with_moved_arguments(
335+
&inner_decl,
336+
|this| {
337+
this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body))
338+
},
339+
fn_decl_span,
340+
body.span,
341+
coroutine_kind,
342+
hir::CoroutineSource::Closure,
343+
)
344+
});
342345

343-
this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id);
346+
this.maybe_forward_track_caller(body.span, closure_hir_id, coroutine_hir_id);
344347

345348
(parameters, expr)
346349
});

compiler/rustc_ast_lowering/src/item.rs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,18 +1444,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
14441444
};
14451445
// FIXME(contracts): Support contracts on async fn.
14461446
self.lower_body(|this| {
1447-
let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments(
1448-
decl,
1449-
|this| this.lower_block_expr(body),
1450-
fn_decl_span,
1451-
body.span,
1452-
coroutine_kind,
1453-
hir::CoroutineSource::Fn,
1454-
);
1447+
let (parameters, expr, coroutine_hir_id) = this
1448+
.lower_coroutine_body_with_moved_arguments(
1449+
decl,
1450+
|this| this.lower_block_expr(body),
1451+
fn_decl_span,
1452+
body.span,
1453+
coroutine_kind,
1454+
hir::CoroutineSource::Fn,
1455+
);
14551456

14561457
// FIXME(async_fn_track_caller): Can this be moved above?
1457-
let hir_id = expr.hir_id;
1458-
this.maybe_forward_track_caller(body.span, fn_id, hir_id);
1458+
this.maybe_forward_track_caller(body.span, fn_id, coroutine_hir_id);
14591459

14601460
(parameters, expr)
14611461
})
@@ -1473,7 +1473,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
14731473
body_span: Span,
14741474
coroutine_kind: CoroutineKind,
14751475
coroutine_source: hir::CoroutineSource,
1476-
) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>) {
1476+
) -> (&'hir [hir::Param<'hir>], hir::Expr<'hir>, HirId) {
14771477
let mut parameters: Vec<hir::Param<'_>> = Vec::new();
14781478
let mut statements: Vec<hir::Stmt<'_>> = Vec::new();
14791479

@@ -1637,6 +1637,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
16371637
CoroutineKind::AsyncGen { .. } => hir::CoroutineDesugaring::AsyncGen,
16381638
};
16391639
let closure_id = coroutine_kind.closure_id();
1640+
let closure_hir_id = self.lower_node_id(closure_id);
16401641

16411642
let coroutine_expr = self.make_desugared_coroutine_expr(
16421643
// The default capture mode here is by-ref. Later on during upvar analysis,
@@ -1645,6 +1646,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
16451646
// all async closures would default to `FnOnce` as their calling mode.
16461647
CaptureBy::Ref,
16471648
closure_id,
1649+
closure_hir_id,
16481650
None,
16491651
fn_decl_span,
16501652
body_span,
@@ -1653,13 +1655,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
16531655
mkbody,
16541656
);
16551657

1656-
let expr = hir::Expr {
1657-
hir_id: self.lower_node_id(closure_id),
1658-
kind: coroutine_expr,
1659-
span: self.lower_span(body_span),
1660-
};
1661-
1662-
(self.arena.alloc_from_iter(parameters), expr)
1658+
(self.arena.alloc_from_iter(parameters), coroutine_expr, closure_hir_id)
16631659
}
16641660

16651661
fn lower_method_sig(

0 commit comments

Comments
 (0)