Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- **[FIX]** Fix misleading error for value-type mismatches in validators (see [#241](https://github.com/greyblake/nutype/issues/241)).
- **[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)).
- **[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.
- **[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.
- **[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.

### v0.7.0 - 2026-04-25
Expand Down
6 changes: 3 additions & 3 deletions nutype_macros/src/any/generate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod error;
mod traits;

use std::collections::HashSet;
use alloc::collections::BTreeSet;

use proc_macro2::TokenStream;
use quote::quote;
Expand Down Expand Up @@ -122,7 +122,7 @@ impl GenerateNewtype for AnyNewtype {
type_name: &TypeName,
generics: &Generics,
inner_type: &Self::InnerType,
traits: HashSet<Self::TypedTrait>,
traits: BTreeSet<Self::TypedTrait>,
unsafe_traits: &[SpannedDeriveUnsafeTrait],
maybe_default_value: Option<syn::Expr>,
guard: &AnyGuard,
Expand All @@ -148,7 +148,7 @@ impl GenerateNewtype for AnyNewtype {
_inner_type: &Self::InnerType,
maybe_default_value: &Option<syn::Expr>,
guard: &Guard<Self::Sanitizer, Self::Validator>,
_traits: &HashSet<Self::TypedTrait>,
_traits: &BTreeSet<Self::TypedTrait>,
) -> TokenStream {
let test_valid_default_value = gen_test_should_have_valid_default_value(
type_name,
Expand Down
4 changes: 2 additions & 2 deletions nutype_macros/src/any/generate/traits/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pub mod arbitrary;
pub mod into_iter;

use alloc::collections::BTreeSet;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use std::collections::HashSet;

use crate::{
any::models::{AnyDeriveTrait, AnyGuard, AnyInnerType},
Expand Down Expand Up @@ -128,7 +128,7 @@ pub fn gen_traits(
type_name: &TypeName,
generics: &syn::Generics,
inner_type: &AnyInnerType,
traits: HashSet<AnyDeriveTrait>,
traits: BTreeSet<AnyDeriveTrait>,
unsafe_traits: &[SpannedDeriveUnsafeTrait],
maybe_default_value: Option<syn::Expr>,
guard: &AnyGuard,
Expand Down
4 changes: 3 additions & 1 deletion nutype_macros/src/any/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub enum AnyValidator {

pub type SpannedAnyValidator = SpannedItem<AnyValidator>;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
// Note that the order in which the variants are declared here is the order
// in which traits are derived and implemented in the generated code.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum AnyDeriveTrait {
// Standard
Debug,
Expand Down
8 changes: 4 additions & 4 deletions nutype_macros/src/common/generate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pub mod parse_error;
pub mod tests;
pub mod traits;

use alloc::collections::BTreeSet;
use core::hash::Hash;
use std::collections::HashSet;

use self::traits::GeneratedTraits;

Expand Down Expand Up @@ -220,7 +220,7 @@ pub trait GenerateNewtype {
type_name: &TypeName,
generics: &Generics,
inner_type: &Self::InnerType,
traits: HashSet<Self::TypedTrait>,
traits: BTreeSet<Self::TypedTrait>,
unsafe_traits: &[SpannedDeriveUnsafeTrait],
maybe_default_value: Option<syn::Expr>,
guard: &Guard<Self::Sanitizer, Self::Validator>,
Expand Down Expand Up @@ -539,7 +539,7 @@ pub trait GenerateNewtype {
inner_type: &Self::InnerType,
maybe_default_value: &Option<syn::Expr>,
guard: &Guard<Self::Sanitizer, Self::Validator>,
traits: &HashSet<Self::TypedTrait>,
traits: &BTreeSet<Self::TypedTrait>,
) -> TokenStream;
}

Expand All @@ -548,7 +548,7 @@ pub trait GenerateNewtype {
/// because nutype weaves the custom functions into its own generated impls.
fn validate_serde_customization<TypedTrait: TypeTrait>(
serde_customization: &SerdeCustomization,
traits: &HashSet<TypedTrait>,
traits: &BTreeSet<TypedTrait>,
conditional_derives: &[ConditionalDeriveGroup<TypedTrait>],
) -> Result<(), syn::Error> {
let has_serialize = traits.iter().any(|t| t.is_serde_serialize())
Expand Down
9 changes: 4 additions & 5 deletions nutype_macros/src/common/generate/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use core::hash::Hash;
use std::collections::HashSet;
use alloc::collections::BTreeSet;

use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
Expand Down Expand Up @@ -47,7 +46,7 @@ pub struct GeneratableTraits<TransparentTrait, IrregularTrait> {
}

pub fn split_into_generatable_traits<InputTrait, TransparentTrait, IrregularTrait>(
input_traits: HashSet<InputTrait>,
input_traits: BTreeSet<InputTrait>,
) -> GeneratableTraits<TransparentTrait, IrregularTrait>
where
GeneratableTrait<TransparentTrait, IrregularTrait>: From<InputTrait>,
Expand Down Expand Up @@ -109,7 +108,7 @@ pub fn process_conditional_derives<InputTrait, TransparentTrait, IrregularTrait>
gen_impl_traits: impl Fn(Vec<IrregularTrait>) -> Result<TokenStream, syn::Error>,
) -> Result<ConditionalTraits, syn::Error>
where
InputTrait: Eq + Hash + Clone,
InputTrait: Ord + Clone,
TransparentTrait: ToTokens,
IrregularTrait: HasGeneratedParseError,
GeneratableTrait<TransparentTrait, IrregularTrait>: From<InputTrait>,
Expand All @@ -121,7 +120,7 @@ where
for group in conditional_derives {
let pred = &group.predicate;

let cond_traits: HashSet<InputTrait> = group.typed_traits.iter().cloned().collect();
let cond_traits: BTreeSet<InputTrait> = group.typed_traits.iter().cloned().collect();
let GeneratableTraits {
transparent_traits: cond_transparent,
irregular_traits: cond_irregular,
Expand Down
6 changes: 3 additions & 3 deletions nutype_macros/src/common/models.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
mod error_type_path;

use alloc::collections::BTreeSet;
use core::{fmt::Debug, ops::Add};
use kinded::Kinded;
use std::collections::HashSet;
use syn::Generics;

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

/// Typed traits from `cfg_attr(...)` entries, grouped by predicate.
pub conditional: Vec<ValidatedCfgAttrDerives<TypedTrait>>,
Expand Down Expand Up @@ -696,7 +696,7 @@ impl ToTokens for ConstructorVisibility {
pub struct GenerateParams<IT, Trait, Guard> {
pub inner_type: IT,
pub doc_attrs: Vec<Attribute>,
pub traits: HashSet<Trait>,
pub traits: BTreeSet<Trait>,
pub unsafe_traits: Vec<SpannedDeriveUnsafeTrait>,
pub vis: syn::Visibility,
pub type_name: TypeName,
Expand Down
2 changes: 1 addition & 1 deletion nutype_macros/src/common/parse/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
};

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

let input_span = input.span();
let DeriveInput {
Expand Down
6 changes: 3 additions & 3 deletions nutype_macros/src/common/validate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::hash::Hash;
use alloc::collections::BTreeSet;
use kinded::Kinded;
use proc_macro2::Span;
use std::collections::HashSet;
Expand Down Expand Up @@ -282,7 +282,7 @@ pub fn validate_all_derive_traits<TypedTrait>(
convert: impl Fn(DeriveTrait, bool, Span) -> Result<TypedTrait, syn::Error>,
) -> Result<ValidatedDerives<TypedTrait>, syn::Error>
where
TypedTrait: Eq + Hash + TypeTrait,
TypedTrait: Ord + TypeTrait,
{
// 0. Check for unconditional-vs-conditional duplicates
check_cfg_attr_no_duplicates(&derive_traits, cfg_attr_entries)?;
Expand All @@ -302,7 +302,7 @@ where
let unconditional = derive_traits
.iter()
.map(|st| convert(st.item, has_validation, st.span))
.collect::<Result<HashSet<_>, _>>()?;
.collect::<Result<BTreeSet<_>, _>>()?;

// 4. Convert conditional traits (same conversion, per entry)
let conditional = cfg_attr_entries
Expand Down
6 changes: 3 additions & 3 deletions nutype_macros/src/decimal/generate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub mod traits;

use std::collections::HashSet;
use alloc::collections::BTreeSet;

use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
Expand Down Expand Up @@ -70,7 +70,7 @@ where
type_name: &TypeName,
generics: &Generics,
inner_type: &Self::InnerType,
traits: HashSet<Self::TypedTrait>,
traits: BTreeSet<Self::TypedTrait>,
unsafe_traits: &[SpannedDeriveUnsafeTrait],
maybe_default_value: Option<syn::Expr>,
guard: &DecimalGuard<T>,
Expand All @@ -96,7 +96,7 @@ where
_inner_type: &Self::InnerType,
maybe_default_value: &Option<syn::Expr>,
guard: &Guard<Self::Sanitizer, Self::Validator>,
_traits: &HashSet<Self::TypedTrait>,
_traits: &BTreeSet<Self::TypedTrait>,
) -> TokenStream {
let test_lower_vs_upper = guard.standard_validators().and_then(|validators| {
gen_test_should_have_consistent_lower_and_upper_boundaries(type_name, validators)
Expand Down
4 changes: 2 additions & 2 deletions nutype_macros/src/decimal/generate/traits/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod arbitrary;

use std::collections::HashSet;
use alloc::collections::BTreeSet;

use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
Expand Down Expand Up @@ -28,7 +28,7 @@ pub fn gen_traits<T: ToTokens>(
type_name: &TypeName,
generics: &Generics,
inner_type: &DecimalInnerType,
traits: HashSet<DecimalDeriveTrait>,
traits: BTreeSet<DecimalDeriveTrait>,
unsafe_traits: &[SpannedDeriveUnsafeTrait],
maybe_default_value: Option<syn::Expr>,
guard: &DecimalGuard<T>,
Expand Down
5 changes: 4 additions & 1 deletion nutype_macros/src/decimal/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ pub type SpannedDecimalValidator<T> = SpannedItem<DecimalValidator<T>>;
// * no `ValuableValuable` (`rust_decimal::Decimal` does not implement `Valuable`),
// * `ArbitraryArbitrary` is supported (requires the user to enable
// `rust_decimal/rust-fuzz`).
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
//
// Note that the order in which the variants are declared here is the order
// in which traits are derived and implemented in the generated code.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum DecimalDeriveTrait {
// Standard
Debug,
Expand Down
Loading