Skip to content
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 code generation #333

Merged
merged 2 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ task:
lint_script:
- rustup component add clippy
- cargo clippy $CARGO_ARGS --all-targets --workspace -- -D warnings
reproducibility_script:
- env RUSTFLAGS="--cfg reprocheck" cargo check $CARGO_ARGS --all-targets
minver_script:
- cargo update -Zminimal-versions
- cargo test $CARGO_ARGS
Expand Down
15 changes: 15 additions & 0 deletions mockall/tests/automock_multiple_lifetime_parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// vim: tw=80
//! Methods with multiple generic lifetime parameters should produce their
//! generated code deterministically.
//!
//! This test is designed to work with "--cfg reprocheck"

#![deny(warnings)]
#![allow(clippy::needless_lifetimes)]

use mockall::*;

#[automock]
trait Foo {
fn foo<'a, 'b, 'c, 'd, 'e, 'f>(&self, x: &'a &'b &'c &'d &'e &'f i32);
}
35 changes: 31 additions & 4 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use cfg_if::cfg_if;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, format_ident, quote};
use std::{
collections::{HashMap, HashSet},
env,
hash::BuildHasherDefault
};
use syn::{
*,
Expand All @@ -34,6 +34,10 @@ use crate::mock_item::MockItem;
use crate::mock_item_struct::MockItemStruct;
use crate::mockable_item::MockableItem;

// Define deterministic aliases for these common types.
type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<std::collections::hash_map::DefaultHasher>>;
type HashSet<K> = std::collections::HashSet<K, BuildHasherDefault<std::collections::hash_map::DefaultHasher>>;

cfg_if! {
// proc-macro2's Span::unstable method requires the nightly feature, and it
// doesn't work in test mode.
Expand Down Expand Up @@ -126,7 +130,7 @@ fn deanonymize(literal_type: &mut Type) {
fn declosurefy(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
(Generics, Vec<FnArg>, Vec<TokenStream>)
{
let mut hm = HashMap::new();
let mut hm = HashMap::default();

let mut save_fn_types = |ident: &Ident, tpb: &TypeParamBound| {
if let TypeParamBound::Trait(tb) = tpb {
Expand Down Expand Up @@ -1033,7 +1037,7 @@ fn mock_it<M: Into<MockableItem>>(inputs: M) -> TokenStream
ts
}

fn do_mock(input: TokenStream) -> TokenStream
fn do_mock_once(input: TokenStream) -> TokenStream
{
let item: MockableStruct = match syn::parse2(input) {
Ok(mock) => mock,
Expand All @@ -1044,6 +1048,18 @@ fn do_mock(input: TokenStream) -> TokenStream
mock_it(item)
}

fn do_mock(input: TokenStream) -> TokenStream
{
cfg_if! {
if #[cfg(reprocheck)] {
let ts_a = do_mock_once(input.clone());
let ts_b = do_mock_once(input.clone());
assert_eq!(ts_a.to_string(), ts_b.to_string());
}
}
do_mock_once(input)
}

#[proc_macro]
pub fn mock(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
do_mock(input.into()).into()
Expand All @@ -1058,7 +1074,7 @@ pub fn automock(attrs: proc_macro::TokenStream, input: proc_macro::TokenStream)
do_automock(attrs, input).into()
}

fn do_automock(attrs: TokenStream, input: TokenStream) -> TokenStream {
fn do_automock_once(attrs: TokenStream, input: TokenStream) -> TokenStream {
let mut output = input.clone();
let attrs: Attrs = match parse2(attrs) {
Ok(a) => a,
Expand All @@ -1076,6 +1092,17 @@ fn do_automock(attrs: TokenStream, input: TokenStream) -> TokenStream {
output
}

fn do_automock(attrs: TokenStream, input: TokenStream) -> TokenStream {
cfg_if! {
if #[cfg(reprocheck)] {
let ts_a = do_automock_once(attrs.clone(), input.clone());
let ts_b = do_automock_once(attrs.clone(), input.clone());
assert_eq!(ts_a.to_string(), ts_b.to_string());
}
}
do_automock_once(attrs, input)
}

#[cfg(test)]
mod t {
use super::*;
Expand Down