Skip to content

Commit fd55136

Browse files
authored
Merge pull request #257 from sietseringers/reproducibility
Switch from HashSet to BTreeSet for reproducibility
2 parents 2ff468e + 881fc39 commit fd55136

23 files changed

Lines changed: 263 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- **[FIX]** Fix misleading error for value-type mismatches in validators (see [#241](https://github.com/greyblake/nutype/issues/241)).
99
- **[FIX]** Improve rust-analyzer resilience: when `#[nutype(...)]` arguments fail to parse (e.g. while still being typed), emit a best-effort type skeleton alongside the error so the newtype stays resolvable and downstream completions keep working (see [#178](https://github.com/greyblake/nutype/issues/178)).
1010
- **[FIX]** Correct the validation error `Display` message for float newtypes: `less` now reads "The value must be less than ..." and `less_or_equal` reads "The value must be less or equal to ..." (the two were previously swapped). Integer and decimal were already correct.
11+
- **[FIX]** Macro expansion is now reproducible: traits were collected into a `HashSet`, whose iteration order varies between expansions, so the order of derives and generated impls in the emitted code was nondeterministic. Traits are now kept in a `BTreeSet` and emitted in a stable order.
1112
- **[INTERNAL]** Consolidate the integer, float and decimal backends onto a shared numeric code-generation and validation layer (`common/generate/numeric.rs` and shared helpers in `common/validate.rs`), removing a large amount of duplicated code. No change to generated code or public API.
1213
- **[IMPROVEMENT]** Mark the generated `new`, `try_new` and `new_unchecked` constructors with `#[inline]` so they can be inlined across crate boundaries, matching the existing `into_inner` (see [#237](https://github.com/greyblake/nutype/issues/237)).
1314

nutype_macros/src/any/generate/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod error;
22
mod traits;
33

4-
use std::collections::HashSet;
4+
use alloc::collections::BTreeSet;
55

66
use proc_macro2::TokenStream;
77
use quote::quote;
@@ -122,7 +122,7 @@ impl GenerateNewtype for AnyNewtype {
122122
type_name: &TypeName,
123123
generics: &Generics,
124124
inner_type: &Self::InnerType,
125-
traits: HashSet<Self::TypedTrait>,
125+
traits: BTreeSet<Self::TypedTrait>,
126126
unsafe_traits: &[SpannedDeriveUnsafeTrait],
127127
maybe_default_value: Option<syn::Expr>,
128128
guard: &AnyGuard,
@@ -148,7 +148,7 @@ impl GenerateNewtype for AnyNewtype {
148148
_inner_type: &Self::InnerType,
149149
maybe_default_value: &Option<syn::Expr>,
150150
guard: &Guard<Self::Sanitizer, Self::Validator>,
151-
_traits: &HashSet<Self::TypedTrait>,
151+
_traits: &BTreeSet<Self::TypedTrait>,
152152
) -> TokenStream {
153153
let test_valid_default_value = gen_test_should_have_valid_default_value(
154154
type_name,

nutype_macros/src/any/generate/traits/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
pub mod arbitrary;
22
pub mod into_iter;
33

4+
use alloc::collections::BTreeSet;
45
use proc_macro2::TokenStream;
56
use quote::{ToTokens, quote};
6-
use std::collections::HashSet;
77

88
use crate::{
99
any::models::{AnyDeriveTrait, AnyGuard, AnyInnerType},
@@ -128,7 +128,7 @@ pub fn gen_traits(
128128
type_name: &TypeName,
129129
generics: &syn::Generics,
130130
inner_type: &AnyInnerType,
131-
traits: HashSet<AnyDeriveTrait>,
131+
traits: BTreeSet<AnyDeriveTrait>,
132132
unsafe_traits: &[SpannedDeriveUnsafeTrait],
133133
maybe_default_value: Option<syn::Expr>,
134134
guard: &AnyGuard,

nutype_macros/src/any/models.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ pub enum AnyValidator {
2424

2525
pub type SpannedAnyValidator = SpannedItem<AnyValidator>;
2626

27-
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
27+
// Note that the order in which the variants are declared here is the order
28+
// in which traits are derived and implemented in the generated code.
29+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
2830
pub enum AnyDeriveTrait {
2931
// Standard
3032
Debug,

nutype_macros/src/common/generate/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ pub mod parse_error;
66
pub mod tests;
77
pub mod traits;
88

9+
use alloc::collections::BTreeSet;
910
use core::hash::Hash;
10-
use std::collections::HashSet;
1111

1212
use self::traits::GeneratedTraits;
1313

@@ -220,7 +220,7 @@ pub trait GenerateNewtype {
220220
type_name: &TypeName,
221221
generics: &Generics,
222222
inner_type: &Self::InnerType,
223-
traits: HashSet<Self::TypedTrait>,
223+
traits: BTreeSet<Self::TypedTrait>,
224224
unsafe_traits: &[SpannedDeriveUnsafeTrait],
225225
maybe_default_value: Option<syn::Expr>,
226226
guard: &Guard<Self::Sanitizer, Self::Validator>,
@@ -541,7 +541,7 @@ pub trait GenerateNewtype {
541541
inner_type: &Self::InnerType,
542542
maybe_default_value: &Option<syn::Expr>,
543543
guard: &Guard<Self::Sanitizer, Self::Validator>,
544-
traits: &HashSet<Self::TypedTrait>,
544+
traits: &BTreeSet<Self::TypedTrait>,
545545
) -> TokenStream;
546546
}
547547

@@ -550,7 +550,7 @@ pub trait GenerateNewtype {
550550
/// because nutype weaves the custom functions into its own generated impls.
551551
fn validate_serde_customization<TypedTrait: TypeTrait>(
552552
serde_customization: &SerdeCustomization,
553-
traits: &HashSet<TypedTrait>,
553+
traits: &BTreeSet<TypedTrait>,
554554
conditional_derives: &[ConditionalDeriveGroup<TypedTrait>],
555555
) -> Result<(), syn::Error> {
556556
let has_serialize = traits.iter().any(|t| t.is_serde_serialize())

nutype_macros/src/common/generate/traits.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use core::hash::Hash;
2-
use std::collections::HashSet;
1+
use alloc::collections::BTreeSet;
32

43
use proc_macro2::TokenStream;
54
use quote::{ToTokens, quote};
@@ -47,7 +46,7 @@ pub struct GeneratableTraits<TransparentTrait, IrregularTrait> {
4746
}
4847

4948
pub fn split_into_generatable_traits<InputTrait, TransparentTrait, IrregularTrait>(
50-
input_traits: HashSet<InputTrait>,
49+
input_traits: BTreeSet<InputTrait>,
5150
) -> GeneratableTraits<TransparentTrait, IrregularTrait>
5251
where
5352
GeneratableTrait<TransparentTrait, IrregularTrait>: From<InputTrait>,
@@ -109,7 +108,7 @@ pub fn process_conditional_derives<InputTrait, TransparentTrait, IrregularTrait>
109108
gen_impl_traits: impl Fn(Vec<IrregularTrait>) -> Result<TokenStream, syn::Error>,
110109
) -> Result<ConditionalTraits, syn::Error>
111110
where
112-
InputTrait: Eq + Hash + Clone,
111+
InputTrait: Ord + Clone,
113112
TransparentTrait: ToTokens,
114113
IrregularTrait: HasGeneratedParseError,
115114
GeneratableTrait<TransparentTrait, IrregularTrait>: From<InputTrait>,
@@ -121,7 +120,7 @@ where
121120
for group in conditional_derives {
122121
let pred = &group.predicate;
123122

124-
let cond_traits: HashSet<InputTrait> = group.typed_traits.iter().cloned().collect();
123+
let cond_traits: BTreeSet<InputTrait> = group.typed_traits.iter().cloned().collect();
125124
let GeneratableTraits {
126125
transparent_traits: cond_transparent,
127126
irregular_traits: cond_irregular,

nutype_macros/src/common/models.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
mod error_type_path;
22

3+
use alloc::collections::BTreeSet;
34
use core::{fmt::Debug, ops::Add};
45
use kinded::Kinded;
5-
use std::collections::HashSet;
66
use syn::Generics;
77

88
use proc_macro2::{Span, TokenStream};
@@ -493,7 +493,7 @@ pub struct CfgAttrEntry {
493493
/// and conditional derive entries.
494494
pub struct ValidatedDerives<TypedTrait> {
495495
/// Typed traits from unconditional `derive(...)`.
496-
pub unconditional: HashSet<TypedTrait>,
496+
pub unconditional: BTreeSet<TypedTrait>,
497497

498498
/// Typed traits from `cfg_attr(...)` entries, grouped by predicate.
499499
pub conditional: Vec<ValidatedCfgAttrDerives<TypedTrait>>,
@@ -696,7 +696,7 @@ impl ToTokens for ConstructorVisibility {
696696
pub struct GenerateParams<IT, Trait, Guard> {
697697
pub inner_type: IT,
698698
pub doc_attrs: Vec<Attribute>,
699-
pub traits: HashSet<Trait>,
699+
pub traits: BTreeSet<Trait>,
700700
pub unsafe_traits: Vec<SpannedDeriveUnsafeTrait>,
701701
pub vis: syn::Visibility,
702702
pub type_name: TypeName,

nutype_macros/src/common/parse/meta.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
};
1515

1616
pub fn parse_meta(token_stream: TokenStream) -> Result<Meta, syn::Error> {
17-
let input: DeriveInput = syn::parse(token_stream.into())?;
17+
let input: DeriveInput = syn::parse2(token_stream)?;
1818

1919
let input_span = input.span();
2020
let DeriveInput {

nutype_macros/src/common/validate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use core::hash::Hash;
1+
use alloc::collections::BTreeSet;
22
use kinded::Kinded;
33
use proc_macro2::Span;
44
use std::collections::HashSet;
@@ -282,7 +282,7 @@ pub fn validate_all_derive_traits<TypedTrait>(
282282
convert: impl Fn(DeriveTrait, bool, Span) -> Result<TypedTrait, syn::Error>,
283283
) -> Result<ValidatedDerives<TypedTrait>, syn::Error>
284284
where
285-
TypedTrait: Eq + Hash + TypeTrait,
285+
TypedTrait: Ord + TypeTrait,
286286
{
287287
// 0. Check for unconditional-vs-conditional duplicates
288288
check_cfg_attr_no_duplicates(&derive_traits, cfg_attr_entries)?;
@@ -302,7 +302,7 @@ where
302302
let unconditional = derive_traits
303303
.iter()
304304
.map(|st| convert(st.item, has_validation, st.span))
305-
.collect::<Result<HashSet<_>, _>>()?;
305+
.collect::<Result<BTreeSet<_>, _>>()?;
306306

307307
// 4. Convert conditional traits (same conversion, per entry)
308308
let conditional = cfg_attr_entries

nutype_macros/src/decimal/generate/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pub mod traits;
22

3-
use std::collections::HashSet;
3+
use alloc::collections::BTreeSet;
44

55
use proc_macro2::TokenStream;
66
use quote::{ToTokens, quote};
@@ -70,7 +70,7 @@ where
7070
type_name: &TypeName,
7171
generics: &Generics,
7272
inner_type: &Self::InnerType,
73-
traits: HashSet<Self::TypedTrait>,
73+
traits: BTreeSet<Self::TypedTrait>,
7474
unsafe_traits: &[SpannedDeriveUnsafeTrait],
7575
maybe_default_value: Option<syn::Expr>,
7676
guard: &DecimalGuard<T>,
@@ -96,7 +96,7 @@ where
9696
_inner_type: &Self::InnerType,
9797
maybe_default_value: &Option<syn::Expr>,
9898
guard: &Guard<Self::Sanitizer, Self::Validator>,
99-
_traits: &HashSet<Self::TypedTrait>,
99+
_traits: &BTreeSet<Self::TypedTrait>,
100100
) -> TokenStream {
101101
let test_lower_vs_upper = guard.standard_validators().and_then(|validators| {
102102
gen_test_should_have_consistent_lower_and_upper_boundaries(type_name, validators)

0 commit comments

Comments
 (0)