Skip to content

Commit 0f79e72

Browse files
authored
Merge pull request #135 from greyblake/generic-newtype
Support for generic types
2 parents b334d20 + 2cf0376 commit 0f79e72

File tree

19 files changed

+355
-85
lines changed

19 files changed

+355
-85
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
### v0.x.x - 2024-xx-xx
2+
3+
* Support newtypes with generics
4+
5+
16
### v0.4.2 - 2024-04-07
27

38
* Support `no_std` ( the dependency needs to be declared as `nutype = { default-features = false }` )

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ members = [
1919
"examples/serde_complex",
2020
"examples/string_bounded_len",
2121
"examples/string_regex_email",
22-
"examples/string_arbitrary",
22+
"examples/string_arbitrary", "examples/any_generics",
2323
]

dummy/src/main.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use nutype::nutype;
2+
use std::borrow::Cow;
23

3-
#[nutype(
4-
validate(predicate = |v| v),
5-
derive(Default),
6-
default = true
7-
)]
8-
pub struct TestData(bool);
4+
#[nutype(derive(Into))]
5+
struct Clarabelle<'a>(Cow<'a, str>);
96

10-
fn main() {}
7+
fn main() {
8+
// let clarabelle = Clarabelle::new(Cow::Borrowed("Clarabelle"));
9+
// assert_eq!(clarabelle.to_string(), "Clarabelle");
10+
}

examples/any_generics/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "any_generics"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
nutype = { path = "../../nutype" }

examples/any_generics/src/main.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use nutype::nutype;
2+
use std::borrow::Cow;
3+
4+
#[nutype(
5+
validate(predicate = |vec| !vec.is_empty()),
6+
derive(Debug),
7+
)]
8+
struct NotEmpty<T>(Vec<T>);
9+
10+
#[nutype(derive(
11+
Debug,
12+
Display,
13+
Clone,
14+
PartialEq,
15+
Eq,
16+
PartialOrd,
17+
Ord,
18+
Hash,
19+
Into,
20+
From,
21+
Deref,
22+
Borrow,
23+
// TODO
24+
// AsRef,
25+
// FromStr,
26+
// TryFrom,
27+
// Default,
28+
// Serialize,
29+
// Deserialize,
30+
// Arbitrary,
31+
))]
32+
struct Clarabelle<'b>(Cow<'b, str>);
33+
34+
fn main() {
35+
{
36+
let v = NotEmpty::new(vec![1, 2, 3]).unwrap();
37+
assert_eq!(v.into_inner(), vec![1, 2, 3]);
38+
}
39+
{
40+
let err = NotEmpty::<i32>::new(vec![]).unwrap_err();
41+
assert_eq!(err, NotEmptyError::PredicateViolated);
42+
}
43+
44+
{
45+
let muu = Clarabelle::new(Cow::Borrowed("Muu"));
46+
assert_eq!(muu.to_string(), "Muu");
47+
}
48+
}

nutype_macros/src/any/gen/mod.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::collections::HashSet;
55

66
use proc_macro2::TokenStream;
77
use quote::quote;
8-
use syn::parse_quote;
8+
use syn::{parse_quote, Generics};
99

1010
use crate::common::{
1111
gen::{
@@ -53,7 +53,7 @@ impl GenerateNewtype for AnyNewtype {
5353
.collect();
5454

5555
quote!(
56-
fn sanitize(mut value: #inner_type) -> #inner_type {
56+
fn __sanitize__(mut value: #inner_type) -> #inner_type {
5757
#transformations
5858
value
5959
}
@@ -72,7 +72,7 @@ impl GenerateNewtype for AnyNewtype {
7272
.map(|validator| match validator {
7373
AnyValidator::Predicate(predicate) => {
7474
let inner_type_ref: syn::Type = parse_quote!(
75-
&'a #inner_type
75+
&'nutype_a #inner_type
7676
);
7777
let typed_predicate: TypedCustomFunction = predicate
7878
.clone()
@@ -88,7 +88,20 @@ impl GenerateNewtype for AnyNewtype {
8888
.collect();
8989

9090
quote!(
91-
fn validate<'a>(val: &'a #inner_type) -> ::core::result::Result<(), #error_name> {
91+
// NOTE 1: we're using a unique lifetime name `nutype_a` in a hope that it will not clash
92+
// with any other lifetimes in the user's code.
93+
//
94+
// NOTE 2:
95+
// When inner type is Cow<'a, str>, the generated code will look like this (with 2
96+
// lifetimes):
97+
//
98+
// fn __validate__<'nutype_a>(val: &'nutype_a Cow<'a, str>)
99+
//
100+
// Clippy does not like passing a reference to a Cow. So we need to ignore the `clippy::ptr_arg` warning.
101+
// Since this code is generic which is used for different inner types (not only Cow), we cannot easily fix it to make
102+
// clippy happy.
103+
#[allow(clippy::ptr_arg)]
104+
fn __validate__<'nutype_a>(val: &'nutype_a #inner_type) -> ::core::result::Result<(), #error_name> {
92105
#validations
93106
Ok(())
94107
}
@@ -104,6 +117,7 @@ impl GenerateNewtype for AnyNewtype {
104117

105118
fn gen_traits(
106119
type_name: &TypeName,
120+
generics: &Generics,
107121
inner_type: &Self::InnerType,
108122
maybe_error_type_name: Option<ErrorTypeName>,
109123
traits: HashSet<Self::TypedTrait>,
@@ -112,6 +126,7 @@ impl GenerateNewtype for AnyNewtype {
112126
) -> Result<GeneratedTraits, syn::Error> {
113127
gen_traits(
114128
type_name,
129+
generics,
115130
inner_type,
116131
maybe_error_type_name,
117132
traits,

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ enum AnyIrregularTrait {
107107

108108
pub fn gen_traits(
109109
type_name: &TypeName,
110+
generics: &syn::Generics,
110111
inner_type: &AnyInnerType,
111112
maybe_error_type_name: Option<ErrorTypeName>,
112113
traits: HashSet<AnyDeriveTrait>,
@@ -126,6 +127,7 @@ pub fn gen_traits(
126127

127128
let implement_traits = gen_implemented_traits(
128129
type_name,
130+
generics,
129131
inner_type,
130132
maybe_error_type_name,
131133
irregular_traits,
@@ -141,6 +143,7 @@ pub fn gen_traits(
141143

142144
fn gen_implemented_traits(
143145
type_name: &TypeName,
146+
generics: &syn::Generics,
144147
inner_type: &AnyInnerType,
145148
maybe_error_type_name: Option<ErrorTypeName>,
146149
impl_traits: Vec<AnyIrregularTrait>,
@@ -151,16 +154,16 @@ fn gen_implemented_traits(
151154
.iter()
152155
.map(|t| match t {
153156
AnyIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)),
154-
AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)),
155-
AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type.clone())),
156-
AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)),
157-
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)),
158-
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)),
157+
AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)),
158+
AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type.clone())),
159+
AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name, generics)),
160+
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
161+
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)),
159162
AnyIrregularTrait::FromStr => Ok(
160163
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
161164
),
162165
AnyIrregularTrait::TryFrom => Ok(
163-
gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())
166+
gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref())
164167
),
165168
AnyIrregularTrait::Default => match maybe_default_value {
166169
Some(ref default_value) => {

nutype_macros/src/common/gen/mod.rs

+36-24
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::common::{
2020
};
2121
use proc_macro2::{Punct, Spacing, TokenStream, TokenTree};
2222
use quote::{format_ident, quote, ToTokens};
23-
use syn::Visibility;
23+
use syn::{Generics, Visibility};
2424

2525
/// Inject an inner type into a closure, so compiler does not complain if the token stream matchers
2626
/// the expected closure pattern.
@@ -133,9 +133,13 @@ pub fn gen_reimports(
133133
}
134134
}
135135

136-
pub fn gen_impl_into_inner(type_name: &TypeName, inner_type: impl ToTokens) -> TokenStream {
136+
pub fn gen_impl_into_inner(
137+
type_name: &TypeName,
138+
generics: &Generics,
139+
inner_type: impl ToTokens,
140+
) -> TokenStream {
137141
quote! {
138-
impl #type_name {
142+
impl #generics #type_name #generics {
139143
#[inline]
140144
pub fn into_inner(self) -> #inner_type {
141145
self.0
@@ -178,6 +182,7 @@ pub trait GenerateNewtype {
178182

179183
fn gen_traits(
180184
type_name: &TypeName,
185+
generics: &Generics,
181186
inner_type: &Self::InnerType,
182187
maybe_error_type_name: Option<ErrorTypeName>,
183188
traits: HashSet<Self::TypedTrait>,
@@ -187,14 +192,15 @@ pub trait GenerateNewtype {
187192

188193
fn gen_new_with_validation(
189194
type_name: &TypeName,
195+
generics: &Generics,
190196
inner_type: &Self::InnerType,
191197
sanitizers: &[Self::Sanitizer],
192198
validators: &[Self::Validator],
193199
) -> TokenStream {
194-
let sanitize = Self::gen_fn_sanitize(inner_type, sanitizers);
200+
let fn_sanitize = Self::gen_fn_sanitize(inner_type, sanitizers);
195201
let validation_error = Self::gen_validation_error_type(type_name, validators);
196202
let error_type_name = gen_error_type_name(type_name);
197-
let validate = Self::gen_fn_validate(inner_type, type_name, validators);
203+
let fn_validate = Self::gen_fn_validate(inner_type, type_name, validators);
198204

199205
let (input_type, convert_raw_value_if_necessary) = if Self::NEW_CONVERT_INTO_INNER_TYPE {
200206
(
@@ -208,29 +214,30 @@ pub trait GenerateNewtype {
208214
quote!(
209215
#validation_error
210216

211-
impl #type_name {
217+
impl #generics #type_name #generics {
212218
pub fn new(raw_value: #input_type) -> ::core::result::Result<Self, #error_type_name> {
213-
// Keep sanitize() and validate() within new() so they do not overlap with outer
214-
// scope imported with `use super::*`.
215-
#sanitize
216-
#validate
217-
218219
#convert_raw_value_if_necessary
219220

220-
let sanitized_value: #inner_type = sanitize(raw_value);
221-
validate(&sanitized_value)?;
221+
let sanitized_value: #inner_type = Self::__sanitize__(raw_value);
222+
Self::__validate__(&sanitized_value)?;
222223
Ok(#type_name(sanitized_value))
223224
}
225+
226+
// Definite associated private functions __sanitize__() and __validate__() with underscores so they do not overlap with outer
227+
// scope imported with `use super::*`.
228+
#fn_sanitize
229+
#fn_validate
224230
}
225231
)
226232
}
227233

228234
fn gen_new_without_validation(
229235
type_name: &TypeName,
236+
generics: &Generics,
230237
inner_type: &Self::InnerType,
231238
sanitizers: &[Self::Sanitizer],
232239
) -> TokenStream {
233-
let sanitize = Self::gen_fn_sanitize(inner_type, sanitizers);
240+
let fn_sanitize = Self::gen_fn_sanitize(inner_type, sanitizers);
234241

235242
let (input_type, convert_raw_value_if_necessary) = if Self::NEW_CONVERT_INTO_INNER_TYPE {
236243
(
@@ -242,34 +249,37 @@ pub trait GenerateNewtype {
242249
};
243250

244251
quote!(
245-
impl #type_name {
252+
impl #generics #type_name #generics {
246253
pub fn new(raw_value: #input_type) -> Self {
247-
#sanitize
248-
249254
#convert_raw_value_if_necessary
250-
251-
Self(sanitize(raw_value))
255+
Self(Self::__sanitize__(raw_value))
252256
}
257+
// Definite associated private function __sanitize__() with underscores so they do not overlap with outer
258+
// scope imported with `use super::*`.
259+
#fn_sanitize
253260
}
254261
)
255262
}
256263

257264
fn gen_implementation(
258265
type_name: &TypeName,
266+
generics: &Generics,
259267
inner_type: &Self::InnerType,
260268
guard: &Guard<Self::Sanitizer, Self::Validator>,
261269
new_unchecked: NewUnchecked,
262270
) -> TokenStream {
263271
let impl_new = match guard {
264272
Guard::WithoutValidation { sanitizers } => {
265-
Self::gen_new_without_validation(type_name, inner_type, sanitizers)
273+
Self::gen_new_without_validation(type_name, generics, inner_type, sanitizers)
266274
}
267275
Guard::WithValidation {
268276
sanitizers,
269277
validators,
270-
} => Self::gen_new_with_validation(type_name, inner_type, sanitizers, validators),
278+
} => Self::gen_new_with_validation(
279+
type_name, generics, inner_type, sanitizers, validators,
280+
),
271281
};
272-
let impl_into_inner = gen_impl_into_inner(type_name, inner_type);
282+
let impl_into_inner = gen_impl_into_inner(type_name, generics, inner_type);
273283
let impl_new_unchecked = gen_new_unchecked(type_name, inner_type, new_unchecked);
274284

275285
quote! {
@@ -296,11 +306,12 @@ pub trait GenerateNewtype {
296306
new_unchecked,
297307
maybe_default_value,
298308
inner_type,
309+
generics,
299310
} = params;
300311

301312
let module_name = gen_module_name_for_type(&type_name);
302313
let implementation =
303-
Self::gen_implementation(&type_name, &inner_type, &guard, new_unchecked);
314+
Self::gen_implementation(&type_name, &generics, &inner_type, &guard, new_unchecked);
304315

305316
let maybe_error_type_name: Option<ErrorTypeName> = match guard {
306317
Guard::WithoutValidation { .. } => None,
@@ -335,6 +346,7 @@ pub trait GenerateNewtype {
335346
implement_traits,
336347
} = Self::gen_traits(
337348
&type_name,
349+
&generics,
338350
&inner_type,
339351
maybe_error_type_name,
340352
traits,
@@ -349,7 +361,7 @@ pub trait GenerateNewtype {
349361

350362
#(#doc_attrs)*
351363
#derive_transparent_traits
352-
pub struct #type_name(#inner_type);
364+
pub struct #type_name #generics(#inner_type);
353365

354366
#implementation
355367
#implement_traits

0 commit comments

Comments
 (0)