-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix nondeterministic macro output #330
Conversation
error[E0277]: the trait bound `K: std::cmp::Eq` is not satisfied --> mockall_derive/src/collections.rs:27:22 | 25 | impl<K, V> UnorderedMap<K, V> { | - help: consider restricting this bound: `K: std::cmp::Eq` 26 | pub fn new() -> Self { 27 | UnorderedMap(HashMap::new()) | ^^^^^^^^^^^^^^ the trait `std::cmp::Eq` is not implemented for `K` | = note: required by `std::collections::HashMap::<K, V>::new` error[E0277]: the trait bound `K: std::hash::Hash` is not satisfied --> mockall_derive/src/collections.rs:27:22 | 25 | impl<K, V> UnorderedMap<K, V> { | - help: consider restricting this bound: `K: std::hash::Hash` 26 | pub fn new() -> Self { 27 | UnorderedMap(HashMap::new()) | ^^^^^^^^^^^^^^ the trait `std::hash::Hash` is not implemented for `K` | = note: required by `std::collections::HashMap::<K, V>::new`
Interesting find! I promise to review this by the weekend, but I may not have time until then. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the problem is real, your solution seems excessively complicated. What's wrong with just using a deterministic HashMap/HashSet? They're only random because they use a random hasher by default. But they can be made deterministic by constructing one like this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=420f28eeccedee9cd077fff59f831635 .
Also, this bug needs a regression test. I'll work on that.
If you check the "allow edits by maintainers" button, I'll push the test to the PR. |
@asomers, I believe the complexity over your proposed alternative is justified because that's what makes this mistake machine catchable. Replacing all current places that create a randomized hashmap with |
That's what the test is for. Also, would your |
A test can (probabilistically) catch nondeterminism in a handful of automock invocations -- the ones covered by the test. Whereas banning HashMap rules out the entire category of HashMap-related nondeterminism across all possible automock invocations, including ones not well covered by the test suite. I do agree with you that another test would be good, because a test could catch new sources of nondeterminism outside of HashMap. In practice that's less of a concern to me because I've debugged many of these nondeterministic compilation issues and it's been HashMap iteration every time, with the one exception of https://crates.io/crates/const-random which is blatant and intentional.
It does not catch this as far as I can tell. The mechanism here is the same as what is used for deprecations -- it's catching whether your code has named the thing. If the only place the word |
Well, |
I think this isn't how the linter works. (But you could propose an enhancement to clippy. I don't know enough about the linter infrastructure to know how reasonable this would be to implement.)
use std::marker::PhantomData;
#[deprecated]
pub struct Thing;
#[allow(deprecated)]
pub struct Generic<T = Thing>(PhantomData<T>);
fn main() {
// does not mention "Thing", no warning
let _g: Generic = Generic(PhantomData);
}
There is a
I think the reality is there isn't an appropriate collection type in std, so for a solution that involves banning iteration in collection order, you would either need it in this crate or you would need to add a dependency that provides it (which is an option if you are open to it). FWIW it's adding <1% to the Rust code already in this crate. |
@dtolnay I ended up going the "HashMap with BuildHasherDefault" route. But I did make one new change: instead of a single test case for nondeterministic output, I added a Superseded by #333 . |
Summary: The mentioned PR asomers/mockall#330 was superceded by asomers/mockall#333 and is part of 0.11 release Reviewed By: dtolnay Differential Revision: D33881560 fbshipit-source-id: 1676f725739939695e4b7b2b8eb14b08ad27e6c1
Summary: The mentioned PR asomers/mockall#330 was superceded by asomers/mockall#333 and is part of 0.11 release Reviewed By: dtolnay Differential Revision: D33881560 fbshipit-source-id: 1676f725739939695e4b7b2b8eb14b08ad27e6c1
Summary: The mentioned PR asomers/mockall#330 was superceded by asomers/mockall#333 and is part of 0.11 release Reviewed By: dtolnay Differential Revision: D33881560 fbshipit-source-id: 1676f725739939695e4b7b2b8eb14b08ad27e6c1
The automock attribute currently creates nondeterministic output, which breaks in environments that cache crate compilations, and causes unnecessary rebuilds of downstream code when the code shouldn't have changed.
Repro:
Running
cargo expand | rg "for<'., '.>"
this shows mockall sometimes expanding to:and sometimes to:
The source of the nondeterminism is iterating over a randomized HashSet.
This PR fixes the nondeterminism and uses https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type to disallow iteration of hash based collections in the future.