macros: fix the hygiene issue of join! and try_join!
#7638
Merged
+83
−19
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Closes: #7637
Motivation
#7307 added a
biasedmode tojoin!andtry_join!. However, it did so in a way that leaked theconst COUNT: u32into the outer scope of the macro expansion. This resulted in it shadowing any caller-specifiedconst 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 COUNTwithin itspoll_fnbody. This restores the original scoping prior to thebiasedfeature.Solution
This was a bit fiddly since we want to use a const generic with our
Rotatorhelper to avoid increasing the memory footprint of the*joinfuture, 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 COUNTgeneric 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 theconst COUNTdeclaration 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 theconst COUNTdeclaration insidepoll_fnscope), 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.