Rollup merge of #156970 - qaijuang:async_closure_coverage, r=Zalathar

coverage: Use original HIR info for synthetic by-move coroutine bodies

> This is a copy of #156952(because my git was acting weird)

Synthetic by-move coroutine bodies created for async closures don't have useful HIR of their own. Coverage was falling back to the parent async closure for HIR info, which means the executed `AsyncFnOnce` body could inherit hole spans from the wrong body and report the user-written closure body as uncovered.

This PR uses the synthetic body's coroutine type to recover the original coroutine body def-id, then extracts HIR info from that body instead. That keeps the coverage spans tied to the body the synthetic MIR was cloned from.

Fixes rust-lang/rust#151135.

r? Zalathar
This commit is contained in:
Guillaume Gomez
2026-05-27 20:45:09 +02:00
committed by GitHub
4 changed files with 138 additions and 3 deletions
@@ -1,7 +1,7 @@
use rustc_hir as hir;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
@@ -24,9 +24,16 @@ pub(crate) fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> E
// FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back
// to HIR for it.
// HACK: For synthetic MIR bodies (async closures), use the def id of the HIR body.
// Synthetic by-move coroutine bodies don't have useful HIR of their own.
// Use the original coroutine body instead. These synthetic bodies are
// created with a coroutine type, so we can inspect that type as-is.
if tcx.is_synthetic_mir(def_id) {
return extract_hir_info(tcx, tcx.local_parent(def_id));
let effective_def_id =
match *tcx.type_of(def_id).instantiate_identity().skip_normalization().kind() {
ty::Coroutine(coroutine_def_id, _) => coroutine_def_id.expect_local(),
_ => tcx.local_parent(def_id),
};
return extract_hir_info(tcx, effective_def_id);
}
let hir_node = tcx.hir_node_by_def_id(def_id);
+72
View File
@@ -0,0 +1,72 @@
Function name: async_closure2::call_once::<async_closure2::main::{closure#0}>
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0c, 01, 00, 2a]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 12, 1) to (start + 0, 42)
Highest counter ID seen: c0
Function name: async_closure2::call_once::<async_closure2::main::{closure#0}>::{closure#0}
Raw bytes (21): 0x[01, 01, 01, 05, 09, 03, 01, 0c, 2b, 00, 2c, 01, 01, 05, 00, 0e, 02, 01, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 1
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
Number of file 0 mappings: 3
- Code(Counter(0)) at (prev + 12, 43) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 14)
- Code(Expression(0, Sub)) at (prev + 1, 1) to (start + 0, 2)
= (c1 - c2)
Highest counter ID seen: c0
Function name: async_closure2::main
Raw bytes (54): 0x[01, 01, 00, 0a, 01, 10, 01, 00, 0e, 01, 01, 09, 00, 16, 01, 04, 05, 00, 17, 01, 00, 18, 00, 21, 01, 00, 22, 00, 2f, 01, 01, 05, 00, 0f, 01, 00, 10, 00, 15, 01, 00, 16, 00, 1a, 01, 00, 1b, 00, 2b, 05, 01, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 10
- Code(Counter(0)) at (prev + 16, 1) to (start + 0, 14)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 22)
- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 23)
- Code(Counter(0)) at (prev + 0, 24) to (start + 0, 33)
- Code(Counter(0)) at (prev + 0, 34) to (start + 0, 47)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 15)
- Code(Counter(0)) at (prev + 0, 16) to (start + 0, 21)
- Code(Counter(0)) at (prev + 0, 22) to (start + 0, 26)
- Code(Counter(0)) at (prev + 0, 27) to (start + 0, 43)
- Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2)
Highest counter ID seen: c1
Function name: async_closure2::main::{closure#0}
Raw bytes (44): 0x[01, 01, 00, 08, 01, 11, 22, 00, 23, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 09, 00, 0e, 01, 00, 0f, 00, 18, 01, 00, 1c, 00, 2c, 01, 01, 05, 00, 06]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 8
- Code(Counter(0)) at (prev + 17, 34) to (start + 0, 35)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14)
- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24)
- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 14)
- Code(Counter(0)) at (prev + 0, 15) to (start + 0, 24)
- Code(Counter(0)) at (prev + 0, 28) to (start + 0, 44)
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 6)
Highest counter ID seen: c0
Function name: async_closure2::main::{closure#0}::{closure#0}::<_> (unused)
Raw bytes (44): 0x[01, 01, 00, 08, 00, 11, 22, 00, 23, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 09, 00, 0e, 00, 00, 0f, 00, 18, 00, 00, 1c, 00, 2c, 00, 01, 05, 00, 06]
Number of files: 1
- file 0 => $DIR/async_closure2.rs
Number of expressions: 0
Number of file 0 mappings: 8
- Code(Zero) at (prev + 17, 34) to (start + 0, 35)
- Code(Zero) at (prev + 1, 9) to (start + 0, 14)
- Code(Zero) at (prev + 0, 15) to (start + 0, 24)
- Code(Zero) at (prev + 0, 28) to (start + 0, 44)
- Code(Zero) at (prev + 1, 9) to (start + 0, 14)
- Code(Zero) at (prev + 0, 15) to (start + 0, 24)
- Code(Zero) at (prev + 0, 28) to (start + 0, 44)
- Code(Zero) at (prev + 1, 5) to (start + 0, 6)
Highest counter ID seen: (none)
+33
View File
@@ -0,0 +1,33 @@
LL| |// Regression test for <https://github.com/rust-lang/rust/issues/151135>.
LL| |
LL| |//@ edition: 2021
LL| |
LL| |//@ aux-build: executor.rs
LL| |extern crate executor;
LL| |
LL| |use std::sync::atomic::{AtomicUsize, Ordering};
LL| |
LL| |static STEPS: AtomicUsize = AtomicUsize::new(0);
LL| |
LL| 1|async fn call_once(f: impl AsyncFnOnce()) {
LL| 1| f().await;
LL| 1|}
LL| |
LL| 1|pub fn main() {
LL| 1| let async_closure = async || {
LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
LL| 1| };
------------------
| Unexecuted instantiation: async_closure2::main::{closure#0}::{closure#0}::<_>
------------------
| async_closure2::main::{closure#0}:
| LL| 1| let async_closure = async || {
| LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
| LL| 1| STEPS.fetch_add(1, Ordering::SeqCst);
| LL| 1| };
------------------
LL| 1| executor::block_on(call_once(async_closure));
LL| 1| assert_eq!(STEPS.load(Ordering::SeqCst), 2);
LL| 1|}
+23
View File
@@ -0,0 +1,23 @@
// Regression test for <https://github.com/rust-lang/rust/issues/151135>.
//@ edition: 2021
//@ aux-build: executor.rs
extern crate executor;
use std::sync::atomic::{AtomicUsize, Ordering};
static STEPS: AtomicUsize = AtomicUsize::new(0);
async fn call_once(f: impl AsyncFnOnce()) {
f().await;
}
pub fn main() {
let async_closure = async || {
STEPS.fetch_add(1, Ordering::SeqCst);
STEPS.fetch_add(1, Ordering::SeqCst);
};
executor::block_on(call_once(async_closure));
assert_eq!(STEPS.load(Ordering::SeqCst), 2);
}