macros: fix the hygiene issue of join! and try_join! (#7766)

Signed-off-by: ADD-SP <[email protected]>
This commit is contained in:
Qi
2025-12-08 18:39:29 +08:00
committed by GitHub
parent b5054e1dff
commit 97d06ae1a6
7 changed files with 227 additions and 23 deletions
+42
View File
@@ -0,0 +1,42 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
+60
View File
@@ -0,0 +1,60 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_join.rs:11:34
|
11 | let _ = tokio::join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_join.rs:14:34
|
14 | let _ = tokio::join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
--> tests/fail/macros_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
+42
View File
@@ -0,0 +1,42 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::try_join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::try_join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::try_join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::try_join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::try_join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::try_join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
@@ -0,0 +1,60 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_try_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_try_join.rs:11:38
|
11 | let _ = tokio::try_join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_try_join.rs:14:38
|
14 | let _ = tokio::try_join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_try_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
--> tests/fail/macros_try_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_try_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
+6
View File
@@ -18,6 +18,12 @@ fn compile_fail_full() {
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_dead_code.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_try_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_type_mismatch.rs");
+8 -11
View File
@@ -129,16 +129,13 @@ doc! {macro_rules! join {
$( ( $($skip:tt)* ) $e:expr, )*
}) => {{
use $crate::macros::support::{maybe_done, poll_fn, Future, Pin, RotatorSelect};
use $crate::macros::support::Poll::{Ready, Pending};
// Safety: nothing must be moved out of `futures`. This is to satisfy
// the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a tuple
// and the standard library provides no way to pin-project to the fields
// of a tuple.
let mut futures = ( $( maybe_done($e), )* );
let mut futures = ( $( $crate::macros::support::maybe_done($e), )* );
// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them. This
@@ -149,9 +146,9 @@ doc! {macro_rules! join {
// Each time the future created by poll_fn is polled, if not using biased mode,
// a different future is polled first to ensure every future passed to join!
// can make progress even if one of the futures consumes the whole budget.
let mut rotator = <$rotator_select as RotatorSelect>::Rotator::<{$($total)*}>::default();
let mut rotator = <$rotator_select as $crate::macros::support::RotatorSelect>::Rotator::<{$($total)*}>::default();
poll_fn(move |cx| {
$crate::macros::support::poll_fn(move |cx| {
const COUNT: u32 = $($total)*;
let mut is_pending = false;
@@ -176,10 +173,10 @@ doc! {macro_rules! join {
// Safety: future is stored on the stack above
// and never moved.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
// Try polling
if fut.poll(cx).is_pending() {
if $crate::macros::support::Future::poll(fut.as_mut(), cx).is_pending() {
is_pending = true;
}
} else {
@@ -190,15 +187,15 @@ doc! {macro_rules! join {
}
if is_pending {
Pending
$crate::macros::support::Poll::Pending
} else {
Ready(($({
$crate::macros::support::Poll::Ready(($({
// Extract the future for this branch from the tuple.
let ( $($skip,)* fut, .. ) = &mut futures;
// Safety: future is stored on the stack above
// and never moved.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
fut.take_output().expect("expected completed future")
},)*))
+9 -12
View File
@@ -179,16 +179,13 @@ doc! {macro_rules! try_join {
$( ( $($skip:tt)* ) $e:expr, )*
}) => {{
use $crate::macros::support::{maybe_done, poll_fn, Future, Pin, RotatorSelect};
use $crate::macros::support::Poll::{Ready, Pending};
// Safety: nothing must be moved out of `futures`. This is to satisfy
// the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a tuple
// and the standard library provides no way to pin-project to the fields
// of a tuple.
let mut futures = ( $( maybe_done($e), )* );
let mut futures = ( $( $crate::macros::support::maybe_done($e), )* );
// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them. This
@@ -199,9 +196,9 @@ doc! {macro_rules! try_join {
// Each time the future created by poll_fn is polled, if not using biased mode,
// a different future is polled first to ensure every future passed to try_join!
// can make progress even if one of the futures consumes the whole budget.
let mut rotator = <$rotator_select as RotatorSelect>::Rotator::<{$($total)*}>::default();
let mut rotator = <$rotator_select as $crate::macros::support::RotatorSelect>::Rotator::<{$($total)*}>::default();
poll_fn(move |cx| {
$crate::macros::support::poll_fn(move |cx| {
const COUNT: u32 = $($total)*;
let mut is_pending = false;
@@ -226,13 +223,13 @@ doc! {macro_rules! try_join {
// Safety: future is stored on the stack above
// and never moved.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
// Try polling
if fut.as_mut().poll(cx).is_pending() {
if $crate::macros::support::Future::poll(fut.as_mut(), cx).is_pending() {
is_pending = true;
} else if fut.as_mut().output_mut().expect("expected completed future").is_err() {
return Ready(Err(fut.take_output().expect("expected completed future").err().unwrap()))
return $crate::macros::support::Poll::Ready(Err(fut.take_output().expect("expected completed future").err().unwrap()))
}
} else {
// Future skipped, one less future to skip in the next iteration
@@ -242,15 +239,15 @@ doc! {macro_rules! try_join {
}
if is_pending {
Pending
$crate::macros::support::Poll::Pending
} else {
Ready(Ok(($({
$crate::macros::support::Poll::Ready(Ok(($({
// Extract the future for this branch from the tuple.
let ( $($skip,)* fut, .. ) = &mut futures;
// Safety: future is stored on the stack above
// and never moved.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
fut
.take_output()