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

Add checked operations for integer based newtypes #213

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
18 changes: 17 additions & 1 deletion nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::common::{
gen::{
tests::gen_test_should_have_valid_default_value, traits::GeneratedTraits, GenerateNewtype,
},
models::{ConstFn, ErrorTypePath, Guard, TypeName, TypedCustomFunction},
models::{CheckedOps, ConstFn, ErrorTypePath, Guard, TypeName, TypedCustomFunction},
};

use self::error::gen_validation_error_type;
Expand Down Expand Up @@ -133,6 +133,22 @@ impl GenerateNewtype for AnyNewtype {
)
}

fn gen_ops(
_type_name: &TypeName,
_generics: &Generics,
_guard: &Guard<Self::Sanitizer, Self::Validator>,
checked_ops: CheckedOps,
) -> Result<TokenStream, syn::Error> {
match checked_ops {
CheckedOps::Off => Ok(TokenStream::default()),
CheckedOps::On => {
let span = proc_macro2::Span::call_site();
let msg = "Numeric operations can only be implemented for integer types";
Err(syn::Error::new(span, msg))
}
}
}

fn gen_tests(
type_name: &TypeName,
generics: &Generics,
Expand Down
2 changes: 2 additions & 0 deletions nutype_macros/src/any/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn parse_attributes(
const_fn,
default,
derive_traits,
checked_ops,
} = attrs;
let raw_guard = AnyRawGuard {
sanitizers,
Expand All @@ -41,6 +42,7 @@ pub fn parse_attributes(
guard,
default,
derive_traits,
checked_ops,
})
}

Expand Down
14 changes: 13 additions & 1 deletion nutype_macros/src/common/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::collections::HashSet;
use self::traits::GeneratedTraits;

use super::models::{
ConstFn, CustomFunction, ErrorTypePath, GenerateParams, Guard, NewUnchecked,
CheckedOps, ConstFn, CustomFunction, ErrorTypePath, GenerateParams, Guard, NewUnchecked,
ParseErrorTypeName, TypeName, TypeTrait,
};
use crate::common::{
Expand Down Expand Up @@ -249,6 +249,13 @@ pub trait GenerateNewtype {
guard: &Guard<Self::Sanitizer, Self::Validator>,
) -> Result<GeneratedTraits, syn::Error>;

fn gen_ops(
type_name: &TypeName,
generics: &Generics,
guard: &Guard<Self::Sanitizer, Self::Validator>,
checked_ops: CheckedOps,
) -> Result<TokenStream, syn::Error>;

fn gen_try_new(
type_name: &TypeName,
generics: &Generics,
Expand Down Expand Up @@ -400,6 +407,7 @@ pub trait GenerateNewtype {
maybe_default_value,
inner_type,
generics,
checked_ops,
} = params;

let module_name = gen_module_name_for_type(&type_name);
Expand Down Expand Up @@ -459,6 +467,8 @@ pub trait GenerateNewtype {
&guard,
)?;

let checked_ops = Self::gen_ops(&type_name, &generics, &guard, checked_ops)?;

Ok(quote!(
#[doc(hidden)]
#[allow(non_snake_case, reason = "we keep original structure name which is probably CamelCase")]
Expand All @@ -472,6 +482,8 @@ pub trait GenerateNewtype {
#implementation
#implement_traits

#checked_ops

#[cfg(test)]
mod tests {
use super::*;
Expand Down
101 changes: 101 additions & 0 deletions nutype_macros/src/common/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,104 @@ pub fn gen_impl_trait_default(
)
}
}

/// Generate implementation of checked numeric operations for integer types.
pub fn gen_impl_checked_ops(
type_name: &TypeName,
generics: &Generics,
maybe_error_type_name: Option<&ErrorTypePath>,
) -> TokenStream {
let generics_without_bounds = strip_trait_bounds_on_generics(generics);

if let Some(_error_type_name) = maybe_error_type_name {
// The case with validation
quote! {
impl #type_name #generics_without_bounds {
#[inline]
pub fn checked_add(&self, rhs: &Self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_add(rhs.0)?).ok()
}

#[inline]
pub fn checked_div(&self, rhs: &Self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_div(rhs.0)?).ok()
}

#[inline]
pub fn checked_mul(&self, rhs: &Self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_mul(rhs.0)?).ok()
}

#[inline]
pub fn checked_neg(&self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_neg()?).ok()
}

#[inline]
pub fn checked_rem(&self, rhs: &Self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_rem(rhs.0)?).ok()
}

#[inline]
pub fn checked_shl(&self, rhs: u32) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_shl(rhs)?).ok()
}

#[inline]
pub fn checked_shr(&self, rhs: u32) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_shr(rhs)?).ok()
}

#[inline]
pub fn checked_sub(&self, rhs: &Self) -> ::core::option::Option<Self> {
Self::try_new(self.0.checked_sub(rhs.0)?).ok()
}
}
}
} else {
// The case without validation
quote! {
impl #type_name #generics_without_bounds {
#[inline]
pub fn checked_add(&self, rhs: &Self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_add(rhs.0)?))
}

#[inline]
pub fn checked_div(&self, rhs: &Self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_div(rhs.0)?))
}

#[inline]
pub fn checked_mul(&self, rhs: &Self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_mul(rhs.0)?))
}

#[inline]
pub fn checked_neg(&self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_neg()?))
}

#[inline]
pub fn checked_rem(&self, rhs: &Self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_rem(rhs.0)?))
}

#[inline]
pub fn checked_shl(&self, rhs: u32) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_shl(rhs)?))
}

#[inline]
pub fn checked_shr(&self, rhs: u32) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_shr(rhs)?))
}

#[inline]
pub fn checked_sub(&self, rhs: &Self) -> ::core::option::Option<Self> {
Some(Self::new(self.0.checked_sub(rhs.0)?))
}
}
}
}
}
16 changes: 16 additions & 0 deletions nutype_macros/src/common/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ pub struct Attributes<G, DT> {
/// `new_unchecked` flag
pub new_unchecked: NewUnchecked,

/// `checked_ops` flag
pub checked_ops: CheckedOps,

/// `const_fn` flag
pub const_fn: ConstFn,

Expand Down Expand Up @@ -390,6 +393,16 @@ pub enum NewUnchecked {
On,
}

/// The flag indicating generation of checked operations,
/// as defined in `num_traits::ops::checked` module.
#[derive(Debug, Clone, Copy, Default)]
pub enum CheckedOps {
#[default]
Off,

On,
}

/// The flag that indicates the functions must be generated with `const` keyword.
#[derive(Debug, Clone, Copy, Default)]
pub enum ConstFn {
Expand Down Expand Up @@ -420,6 +433,7 @@ pub struct GenerateParams<IT, Trait, Guard> {
pub guard: Guard,
pub new_unchecked: NewUnchecked,
pub const_fn: ConstFn,
pub checked_ops: CheckedOps,
pub maybe_default_value: Option<syn::Expr>,
}

Expand Down Expand Up @@ -466,6 +480,7 @@ pub trait Newtype {
const_fn,
default: maybe_default_value,
derive_traits,
checked_ops,
} = Self::parse_attributes(attrs, &type_name)?;
let traits = Self::validate(&guard, derive_traits)?;
let generated_output = Self::generate(GenerateParams {
Expand All @@ -477,6 +492,7 @@ pub trait Newtype {
guard,
new_unchecked,
const_fn,
checked_ops,
maybe_default_value,
inner_type,
})?;
Expand Down
9 changes: 8 additions & 1 deletion nutype_macros/src/common/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use syn::{
use crate::common::models::SpannedDeriveTrait;

use super::models::{
ConstFn, CustomFunction, ErrorTypePath, NewUnchecked, TypedCustomFunction, ValueOrExpr,
CheckedOps, ConstFn, CustomFunction, ErrorTypePath, NewUnchecked, TypedCustomFunction,
ValueOrExpr,
};

pub fn is_doc_attribute(attribute: &syn::Attribute) -> bool {
Expand Down Expand Up @@ -75,6 +76,9 @@ pub struct ParseableAttributes<Sanitizer, Validator> {

/// Parsed from `derive(...)` attribute
pub derive_traits: Vec<SpannedDeriveTrait>,

/// Parsed from `checked_ops` attribute
pub checked_ops: CheckedOps,
}

enum ValidateAttr<Validator: Parse + Kinded> {
Expand Down Expand Up @@ -230,6 +234,7 @@ impl<Sanitizer, Validator> Default for ParseableAttributes<Sanitizer, Validator>
const_fn: ConstFn::NoConst,
default: None,
derive_traits: vec![],
checked_ops: CheckedOps::Off,
}
}
}
Expand Down Expand Up @@ -306,6 +311,8 @@ where
return Err(syn::Error::new(ident.span(), msg));
}
}
} else if ident == "checked_ops" {
attrs.checked_ops = CheckedOps::On;
} else {
let msg = format!("Unknown attribute `{ident}`");
return Err(syn::Error::new(ident.span(), msg));
Expand Down
18 changes: 17 additions & 1 deletion nutype_macros/src/float/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
traits::GeneratedTraits,
GenerateNewtype,
},
models::{ConstFn, ErrorTypePath, Guard, TypeName},
models::{CheckedOps, ConstFn, ErrorTypePath, Guard, TypeName},
},
float::models::FloatInnerType,
};
Expand Down Expand Up @@ -153,6 +153,22 @@ where
)
}

fn gen_ops(
_type_name: &TypeName,
_generics: &Generics,
_guard: &Guard<Self::Sanitizer, Self::Validator>,
checked_ops: CheckedOps,
) -> Result<TokenStream, syn::Error> {
match checked_ops {
CheckedOps::Off => Ok(TokenStream::default()),
CheckedOps::On => {
let span = proc_macro2::Span::call_site();
let msg = "Numeric operations can only be implemented for integer types";
Err(syn::Error::new(span, msg))
}
}
}

fn gen_tests(
type_name: &TypeName,
generics: &Generics,
Expand Down
2 changes: 2 additions & 0 deletions nutype_macros/src/float/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ where
const_fn,
default,
derive_traits,
checked_ops,
} = attrs;
let raw_guard = FloatRawGuard {
sanitizers,
Expand All @@ -54,6 +55,7 @@ where
guard,
default,
derive_traits,
checked_ops,
})
}

Expand Down
20 changes: 18 additions & 2 deletions nutype_macros/src/integer/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use crate::common::{
gen_test_should_have_consistent_lower_and_upper_boundaries,
gen_test_should_have_valid_default_value,
},
traits::GeneratedTraits,
traits::{gen_impl_checked_ops, GeneratedTraits},
GenerateNewtype,
},
models::{ConstFn, ErrorTypePath, Guard, TypeName},
models::{CheckedOps, ConstFn, ErrorTypePath, Guard, TypeName},
};

impl<T> GenerateNewtype for IntegerNewtype<T>
Expand Down Expand Up @@ -145,6 +145,22 @@ where
)
}

fn gen_ops(
type_name: &TypeName,
generics: &Generics,
guard: &Guard<Self::Sanitizer, Self::Validator>,
checked_ops: CheckedOps,
) -> Result<TokenStream, syn::Error> {
let checked_ops = match checked_ops {
CheckedOps::Off => TokenStream::default(),
CheckedOps::On => {
let maybe_error_type_name = guard.maybe_error_type_path();
gen_impl_checked_ops(type_name, generics, maybe_error_type_name)
}
};
Ok(checked_ops)
}

fn gen_tests(
type_name: &TypeName,
generics: &Generics,
Expand Down
2 changes: 2 additions & 0 deletions nutype_macros/src/integer/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ where
const_fn,
default,
derive_traits,
checked_ops,
} = attrs;
let raw_guard = IntegerRawGuard {
sanitizers,
Expand All @@ -54,6 +55,7 @@ where
guard,
default,
derive_traits,
checked_ops,
})
}

Expand Down
Loading