Skip to content

Commit 7c423f7

Browse files
authored
Merge pull request #149 from greyblake/generics-derive-from-str
Start implementing support of derive(FromStr) for generic newtypes
2 parents 91afd23 + 16e9fe6 commit 7c423f7

File tree

12 files changed

+180
-59
lines changed

12 files changed

+180
-59
lines changed

Cargo.lock

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

dummy/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ once_cell = "*"
1515
lazy_static = "*"
1616
ron = "0.8.1"
1717
arbitrary = "1.3.2"
18+
num = "0.4.3"

dummy/src/main.rs

+4-19
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
11
use nutype::nutype;
2-
use std::cmp::Ord;
32

43
#[nutype(
5-
sanitize(with = |mut v| { v.sort(); v }),
6-
validate(predicate = |vec| !vec.is_empty()),
7-
derive(Debug, Deserialize, Serialize),
4+
validate(predicate = |n| n.is_even()),
5+
derive(Debug, FromStr),
86
)]
9-
struct SortedNotEmptyVec<T: Ord>(Vec<T>);
7+
struct Even<T: ::num::Integer>(T);
108

11-
fn main() {
12-
{
13-
// Not empty vec is fine
14-
let json = "[3, 1, 5, 2]";
15-
let sv = serde_json::from_str::<SortedNotEmptyVec<i32>>(json).unwrap();
16-
assert_eq!(sv.into_inner(), vec![1, 2, 3, 5]);
17-
}
18-
{
19-
// Empty vec is not allowed
20-
let json = "[]";
21-
let result = serde_json::from_str::<SortedNotEmptyVec<i32>>(json);
22-
assert!(result.is_err());
23-
}
24-
}
9+
fn main() {}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ fn gen_implemented_traits(
160160
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
161161
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)),
162162
AnyIrregularTrait::FromStr => Ok(
163-
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
163+
gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref())
164164
),
165165
AnyIrregularTrait::TryFrom => Ok(
166166
gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref())

nutype_macros/src/common/gen/parse_error.rs

+31-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use cfg_if::cfg_if;
22
use proc_macro2::TokenStream;
33
use quote::{format_ident, quote};
4+
use syn::Generics;
45

5-
use crate::common::models::{ErrorTypeName, InnerType, ParseErrorTypeName, TypeName};
6+
use crate::common::{
7+
gen::{add_bound_to_all_type_params, strip_trait_bounds_on_generics},
8+
models::{ErrorTypeName, InnerType, ParseErrorTypeName, TypeName},
9+
};
610

711
/// Generate a name for the error which is used for FromStr trait implementation.
812
pub fn gen_parse_error_name(type_name: &TypeName) -> ParseErrorTypeName {
@@ -13,26 +17,33 @@ pub fn gen_parse_error_name(type_name: &TypeName) -> ParseErrorTypeName {
1317
/// Generate an error which is used for FromStr trait implementation of non-string types (e.g.
1418
/// floats or integers)
1519
pub fn gen_def_parse_error(
16-
inner_type: impl Into<InnerType>,
1720
type_name: &TypeName,
21+
generics: &Generics,
22+
inner_type: impl Into<InnerType>,
1823
maybe_error_type_name: Option<&ErrorTypeName>,
1924
parse_error_type_name: &ParseErrorTypeName,
2025
) -> TokenStream {
2126
let inner_type: InnerType = inner_type.into();
2227
let type_name_str = type_name.to_string();
2328

29+
let generics_without_bounds = strip_trait_bounds_on_generics(generics);
30+
let generics_with_fromstr_bound = add_bound_to_all_type_params(
31+
&generics_without_bounds,
32+
syn::parse_quote!(::core::str::FromStr<Err: ::core::fmt::Debug>),
33+
);
34+
2435
let definition = if let Some(error_type_name) = maybe_error_type_name {
2536
quote! {
26-
#[derive(Debug)]
27-
pub enum #parse_error_type_name {
28-
Parse(<#inner_type as ::core::str::FromStr>::Err),
29-
Validate(#error_type_name),
30-
}
37+
#[derive(Debug)] // #[derive(Debug)]
38+
pub enum #parse_error_type_name #generics_with_fromstr_bound { // pub enum ParseErrorFoo<T: ::core::str::FromStr<Err: ::core::fmt::Debug>> {
39+
Parse(<#inner_type as ::core::str::FromStr>::Err), // Parse(<Foo as ::core::str::FromStr>::Err),
40+
Validate(#error_type_name), // Validate(ErrorFoo),
41+
} // }
3142

32-
impl ::core::fmt::Display for #parse_error_type_name {
43+
impl #generics_with_fromstr_bound ::core::fmt::Display for #parse_error_type_name #generics_without_bounds {
3344
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3445
match self {
35-
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
46+
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {:?}", #type_name_str, err),
3647
#parse_error_type_name::Validate(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
3748
}
3849

@@ -41,15 +52,15 @@ pub fn gen_def_parse_error(
4152
}
4253
} else {
4354
quote! {
44-
#[derive(Debug)]
45-
pub enum #parse_error_type_name {
46-
Parse(<#inner_type as ::core::str::FromStr>::Err),
47-
}
55+
#[derive(Debug)] // #[derive(Debug)
56+
pub enum #parse_error_type_name #generics_with_fromstr_bound { // pub enum ParseErrorFoo<T: ::core::str::FromStr<Err: ::core::fmt::Debug>> {
57+
Parse(<#inner_type as ::core::str::FromStr>::Err), // Parse(<Foo as ::core::str::FromStr>::Err),
58+
} // }
4859

49-
impl ::core::fmt::Display for #parse_error_type_name {
60+
impl #generics_with_fromstr_bound ::core::fmt::Display for #parse_error_type_name #generics_without_bounds {
5061
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
5162
match self {
52-
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {}", #type_name_str, err),
63+
#parse_error_type_name::Parse(err) => write!(f, "Failed to parse {}: {:?}", #type_name_str, err),
5364
}
5465
}
5566
}
@@ -58,8 +69,12 @@ pub fn gen_def_parse_error(
5869

5970
cfg_if! {
6071
if #[cfg(feature = "std")] {
72+
let generics_with_fromstr_and_debug_bounds = add_bound_to_all_type_params(
73+
&generics_with_fromstr_bound,
74+
syn::parse_quote!(::core::fmt::Debug),
75+
);
6176
let impl_std_error = quote! {
62-
impl ::std::error::Error for #parse_error_type_name {
77+
impl #generics_with_fromstr_and_debug_bounds ::std::error::Error for #parse_error_type_name #generics_without_bounds {
6378
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
6479
None
6580
}

nutype_macros/src/common/gen/traits.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -203,25 +203,33 @@ pub fn gen_impl_trait_try_from(
203203
/// Generate implementation of FromStr trait for non-string types (e.g. integers or floats).
204204
pub fn gen_impl_trait_from_str(
205205
type_name: &TypeName,
206+
generics: &Generics,
206207
inner_type: impl Into<InnerType>,
207208
maybe_error_type_name: Option<&ErrorTypeName>,
208209
) -> TokenStream {
209210
let inner_type: InnerType = inner_type.into();
210211
let parse_error_type_name = gen_parse_error_name(type_name);
211212
let def_parse_error = gen_def_parse_error(
212-
inner_type.clone(),
213213
type_name,
214+
generics,
215+
inner_type.clone(),
214216
maybe_error_type_name,
215217
&parse_error_type_name,
216218
);
217219

220+
let generics_without_bounds = strip_trait_bounds_on_generics(generics);
221+
let generics_with_fromstr_bound = add_bound_to_all_type_params(
222+
generics,
223+
syn::parse_quote!(::core::str::FromStr<Err: ::core::fmt::Debug>),
224+
);
225+
218226
if let Some(_error_type_name) = maybe_error_type_name {
219227
// The case with validation
220228
quote! {
221229
#def_parse_error
222230

223-
impl ::core::str::FromStr for #type_name {
224-
type Err = #parse_error_type_name;
231+
impl #generics_with_fromstr_bound ::core::str::FromStr for #type_name #generics_without_bounds {
232+
type Err = #parse_error_type_name #generics_without_bounds;
225233

226234
fn from_str(raw_string: &str) -> ::core::result::Result<Self, Self::Err> {
227235
let raw_value: #inner_type = raw_string.parse().map_err(#parse_error_type_name::Parse)?;
@@ -234,8 +242,8 @@ pub fn gen_impl_trait_from_str(
234242
quote! {
235243
#def_parse_error
236244

237-
impl ::core::str::FromStr for #type_name {
238-
type Err = #parse_error_type_name;
245+
impl #generics_with_fromstr_bound ::core::str::FromStr for #type_name #generics_without_bounds {
246+
type Err = #parse_error_type_name #generics_without_bounds;
239247

240248
fn from_str(raw_string: &str) -> ::core::result::Result<Self, Self::Err> {
241249
let value: #inner_type = raw_string.parse().map_err(#parse_error_type_name::Parse)?;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ fn gen_implemented_traits<T: ToTokens>(
172172
FloatIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, generics, inner_type)),
173173
FloatIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
174174
FloatIrregularTrait::FromStr => {
175-
Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref()))
175+
Ok(gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref()))
176176
}
177177
FloatIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)),
178178
FloatIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)),

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ fn gen_implemented_traits<T: ToTokens>(
192192
IntegerIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, generics, inner_type)),
193193
IntegerIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)),
194194
IntegerIrregularTrait::FromStr => {
195-
Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref()))
195+
Ok(gen_impl_trait_from_str(type_name, generics, inner_type, maybe_error_type_name.as_ref()))
196196
}
197197
IntegerIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)),
198198
IntegerIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)),

test_suite/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ arbitrary = "1.3.0"
1919
arbtest = "0.2.0"
2020
ron = "0.8.1"
2121
rmp-serde = "1.1.2"
22+
num = "0.4.3"
2223

2324
[features]
2425
serde = ["nutype/serde", "dep:serde", "dep:serde_json"]

0 commit comments

Comments
 (0)