]> git.feebdaed.xyz Git - 0xmirror/tokio.git/commitdiff
macros: fix the hygiene issue of `join!` and `try_join!` (#7766)
authorQi <qiqi.zhang@konghq.com>
Mon, 8 Dec 2025 10:39:29 +0000 (18:39 +0800)
committerGitHub <noreply@github.com>
Mon, 8 Dec 2025 10:39:29 +0000 (18:39 +0800)
Signed-off-by: ADD-SP <qiqi.zhang@konghq.com>
tests-build/tests/fail/macros_join.rs [new file with mode: 0644]
tests-build/tests/fail/macros_join.stderr [new file with mode: 0644]
tests-build/tests/fail/macros_try_join.rs [new file with mode: 0644]
tests-build/tests/fail/macros_try_join.stderr [new file with mode: 0644]
tests-build/tests/macros.rs
tokio/src/macros/join.rs
tokio/src/macros/try_join.rs

diff --git a/tests-build/tests/fail/macros_join.rs b/tests-build/tests/fail/macros_join.rs
new file mode 100644 (file)
index 0000000..7301149
--- /dev/null
@@ -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!());
+    });
+}
diff --git a/tests-build/tests/fail/macros_join.stderr b/tests-build/tests/fail/macros_join.stderr
new file mode 100644 (file)
index 0000000..45ce61e
--- /dev/null
@@ -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;
+   |
diff --git a/tests-build/tests/fail/macros_try_join.rs b/tests-build/tests/fail/macros_try_join.rs
new file mode 100644 (file)
index 0000000..cf7f79f
--- /dev/null
@@ -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!());
+    });
+}
diff --git a/tests-build/tests/fail/macros_try_join.stderr b/tests-build/tests/fail/macros_try_join.stderr
new file mode 100644 (file)
index 0000000..ac3ae6e
--- /dev/null
@@ -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;
+   |
index 8de08125d304c59e2da778c778361cac6c036539..7e7e6943f956c49894c828d3977a21f1fd225a0c 100644 (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");
 
index 1a62a979fb3c8e4cc5eeffb5d6f0c2097f8a9686..bcf7ca27bdbf152929a50e603d20893aef85d2b6 100644 (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")
                 },)*))
index 04f4825cdc0283859f22a4055f58204fb0fce20d..9d5d7d75c0df26609e76bcf0c4ec5437e9fee0ae 100644 (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()