Skip to content

Conversation

@jlizen
Copy link
Member

@jlizen jlizen commented Sep 20, 2025

Closes: #7637

Motivation

#7307 added a biased mode to join! and try_join!. However, it did so in a way that leaked the const COUNT: u32 into the outer scope of the macro expansion. This resulted in it shadowing any caller-specified const COUNTs in the futures passed into the macro call - which either caused a compiler failure in case of type mismatch, or the caller provided constant's value being discarded.

This change fixes this by refactoring to keep the macro expansion's declaration of const COUNT within its poll_fn body. This restores the original scoping prior to the biased feature.

Solution

This was a bit fiddly since we want to use a const generic with our Rotator helper to avoid increasing the memory footprint of the *join future, but the rotator is instantiated in the outer scope. At the same time, we don't want to just use an obscure variable name to reduce the chance of collision.

@robinhundt had a nice suggestion to instead use a helper trait with an associated type, along with some marker structs per rotator type. This allows us to signal whether we are in biased mode or not in our tt muncher expansions, but defer directly naming the const COUNT generic due to the additional indirection layer. We thus can pass in the literal $($total:tt) expression as our generic in our terminal macro expansion and no longer need the const COUNT declaration in outer scope.

This preserves the reduced memory footprint while also sidestepping hygiene issues. It technically does result in us calculating the 0 + 1 + 2... expression an additional time (at the rotator declaration and also at the const COUNT declaration inside poll_fn scope), though I would hope that the compiler will optimize that away. Anyway it's a cheap, one-time cost on initialization and less important than memory footprint.

The main tradeoff is that this is a bit more complicated that the original implementation. Though, I find that it is similarly readable compared to the macro impl code... none of this stuff is nearly as ugly as the tt muncher itself ^^. And then, this is not an implementation detail that concerns the end user anyway.

Testing

I structured this PR as two atomic commits.

The first commit demonstrates the regression with tests that pass by asserting that the macro hygiene issue results in the macro expansion shadowing the user-provided generic.

The second commit implements the solution described above, and flips the tests over to asserting that the user-provided value is respected.

@jlizen
Copy link
Member Author

jlizen commented Sep 20, 2025

cc @ADD-SP whenever you have a moment, thanks!

@ADD-SP ADD-SP added C-bug Category: This is a bug. A-tokio Area: The main tokio crate M-macros Module: macros in the main Tokio crate labels Sep 21, 2025
Copy link
Member

@ADD-SP ADD-SP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@ADD-SP ADD-SP changed the title fix(macros/(try_)join): don't leak const COUNT in macro expansion macros: fix the hygiene issue of join! and try_join! Sep 21, 2025
@ADD-SP ADD-SP merged commit eb99e47 into tokio-rs:master Sep 21, 2025
97 checks passed
@jlizen
Copy link
Member Author

jlizen commented Sep 21, 2025

Appreciate the eyes!

@Darksonn Darksonn mentioned this pull request Oct 14, 2025
ADD-SP pushed a commit that referenced this pull request Oct 14, 2025
@ADD-SP ADD-SP mentioned this pull request Oct 14, 2025
ADD-SP pushed a commit that referenced this pull request Oct 14, 2025
ADD-SP pushed a commit that referenced this pull request Oct 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tokio Area: The main tokio crate C-bug Category: This is a bug. M-macros Module: macros in the main Tokio crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

COUNT constant in join*! macros can collide with user's constant used in futures

2 participants