diff --git a/nutype_macros/src/any/gen/mod.rs b/nutype_macros/src/any/gen/mod.rs index 91fd2fd..4ae855f 100644 --- a/nutype_macros/src/any/gen/mod.rs +++ b/nutype_macros/src/any/gen/mod.rs @@ -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; @@ -133,6 +133,22 @@ impl GenerateNewtype for AnyNewtype { ) } + fn gen_ops( + _type_name: &TypeName, + _generics: &Generics, + _guard: &Guard, + checked_ops: CheckedOps, + ) -> Result { + 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, diff --git a/nutype_macros/src/any/parse.rs b/nutype_macros/src/any/parse.rs index 28b21b7..5a4f387 100644 --- a/nutype_macros/src/any/parse.rs +++ b/nutype_macros/src/any/parse.rs @@ -29,6 +29,7 @@ pub fn parse_attributes( const_fn, default, derive_traits, + checked_ops, } = attrs; let raw_guard = AnyRawGuard { sanitizers, @@ -41,6 +42,7 @@ pub fn parse_attributes( guard, default, derive_traits, + checked_ops, }) } diff --git a/nutype_macros/src/common/gen/mod.rs b/nutype_macros/src/common/gen/mod.rs index c5accd7..e17e685 100644 --- a/nutype_macros/src/common/gen/mod.rs +++ b/nutype_macros/src/common/gen/mod.rs @@ -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::{ @@ -249,6 +249,13 @@ pub trait GenerateNewtype { guard: &Guard, ) -> Result; + fn gen_ops( + type_name: &TypeName, + generics: &Generics, + guard: &Guard, + checked_ops: CheckedOps, + ) -> Result; + fn gen_try_new( type_name: &TypeName, generics: &Generics, @@ -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); @@ -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")] @@ -472,6 +482,8 @@ pub trait GenerateNewtype { #implementation #implement_traits + #checked_ops + #[cfg(test)] mod tests { use super::*; diff --git a/nutype_macros/src/common/gen/traits.rs b/nutype_macros/src/common/gen/traits.rs index f6d3bbd..c76fb3e 100644 --- a/nutype_macros/src/common/gen/traits.rs +++ b/nutype_macros/src/common/gen/traits.rs @@ -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::try_new(self.0.checked_add(rhs.0)?).ok() + } + + #[inline] + pub fn checked_div(&self, rhs: &Self) -> ::core::option::Option { + Self::try_new(self.0.checked_div(rhs.0)?).ok() + } + + #[inline] + pub fn checked_mul(&self, rhs: &Self) -> ::core::option::Option { + Self::try_new(self.0.checked_mul(rhs.0)?).ok() + } + + #[inline] + pub fn checked_neg(&self) -> ::core::option::Option { + Self::try_new(self.0.checked_neg()?).ok() + } + + #[inline] + pub fn checked_rem(&self, rhs: &Self) -> ::core::option::Option { + Self::try_new(self.0.checked_rem(rhs.0)?).ok() + } + + #[inline] + pub fn checked_shl(&self, rhs: u32) -> ::core::option::Option { + Self::try_new(self.0.checked_shl(rhs)?).ok() + } + + #[inline] + pub fn checked_shr(&self, rhs: u32) -> ::core::option::Option { + Self::try_new(self.0.checked_shr(rhs)?).ok() + } + + #[inline] + pub fn checked_sub(&self, rhs: &Self) -> ::core::option::Option { + 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 { + Some(Self::new(self.0.checked_add(rhs.0)?)) + } + + #[inline] + pub fn checked_div(&self, rhs: &Self) -> ::core::option::Option { + Some(Self::new(self.0.checked_div(rhs.0)?)) + } + + #[inline] + pub fn checked_mul(&self, rhs: &Self) -> ::core::option::Option { + Some(Self::new(self.0.checked_mul(rhs.0)?)) + } + + #[inline] + pub fn checked_neg(&self) -> ::core::option::Option { + Some(Self::new(self.0.checked_neg()?)) + } + + #[inline] + pub fn checked_rem(&self, rhs: &Self) -> ::core::option::Option { + Some(Self::new(self.0.checked_rem(rhs.0)?)) + } + + #[inline] + pub fn checked_shl(&self, rhs: u32) -> ::core::option::Option { + Some(Self::new(self.0.checked_shl(rhs)?)) + } + + #[inline] + pub fn checked_shr(&self, rhs: u32) -> ::core::option::Option { + Some(Self::new(self.0.checked_shr(rhs)?)) + } + + #[inline] + pub fn checked_sub(&self, rhs: &Self) -> ::core::option::Option { + Some(Self::new(self.0.checked_sub(rhs.0)?)) + } + } + } + } +} diff --git a/nutype_macros/src/common/models.rs b/nutype_macros/src/common/models.rs index 892bcbe..a3eaf07 100644 --- a/nutype_macros/src/common/models.rs +++ b/nutype_macros/src/common/models.rs @@ -259,6 +259,9 @@ pub struct Attributes { /// `new_unchecked` flag pub new_unchecked: NewUnchecked, + /// `checked_ops` flag + pub checked_ops: CheckedOps, + /// `const_fn` flag pub const_fn: ConstFn, @@ -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 { @@ -420,6 +433,7 @@ pub struct GenerateParams { pub guard: Guard, pub new_unchecked: NewUnchecked, pub const_fn: ConstFn, + pub checked_ops: CheckedOps, pub maybe_default_value: Option, } @@ -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 { @@ -477,6 +492,7 @@ pub trait Newtype { guard, new_unchecked, const_fn, + checked_ops, maybe_default_value, inner_type, })?; diff --git a/nutype_macros/src/common/parse/mod.rs b/nutype_macros/src/common/parse/mod.rs index d7132f5..c8b523f 100644 --- a/nutype_macros/src/common/parse/mod.rs +++ b/nutype_macros/src/common/parse/mod.rs @@ -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 { @@ -75,6 +76,9 @@ pub struct ParseableAttributes { /// Parsed from `derive(...)` attribute pub derive_traits: Vec, + + /// Parsed from `checked_ops` attribute + pub checked_ops: CheckedOps, } enum ValidateAttr { @@ -230,6 +234,7 @@ impl Default for ParseableAttributes const_fn: ConstFn::NoConst, default: None, derive_traits: vec![], + checked_ops: CheckedOps::Off, } } } @@ -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)); diff --git a/nutype_macros/src/float/gen/mod.rs b/nutype_macros/src/float/gen/mod.rs index c536ad6..6c04a61 100644 --- a/nutype_macros/src/float/gen/mod.rs +++ b/nutype_macros/src/float/gen/mod.rs @@ -22,7 +22,7 @@ use crate::{ traits::GeneratedTraits, GenerateNewtype, }, - models::{ConstFn, ErrorTypePath, Guard, TypeName}, + models::{CheckedOps, ConstFn, ErrorTypePath, Guard, TypeName}, }, float::models::FloatInnerType, }; @@ -153,6 +153,22 @@ where ) } + fn gen_ops( + _type_name: &TypeName, + _generics: &Generics, + _guard: &Guard, + checked_ops: CheckedOps, + ) -> Result { + 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, diff --git a/nutype_macros/src/float/parse.rs b/nutype_macros/src/float/parse.rs index 1923f35..0110e91 100644 --- a/nutype_macros/src/float/parse.rs +++ b/nutype_macros/src/float/parse.rs @@ -42,6 +42,7 @@ where const_fn, default, derive_traits, + checked_ops, } = attrs; let raw_guard = FloatRawGuard { sanitizers, @@ -54,6 +55,7 @@ where guard, default, derive_traits, + checked_ops, }) } diff --git a/nutype_macros/src/integer/gen/mod.rs b/nutype_macros/src/integer/gen/mod.rs index 5e81c16..d5b9068 100644 --- a/nutype_macros/src/integer/gen/mod.rs +++ b/nutype_macros/src/integer/gen/mod.rs @@ -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 GenerateNewtype for IntegerNewtype @@ -145,6 +145,22 @@ where ) } + fn gen_ops( + type_name: &TypeName, + generics: &Generics, + guard: &Guard, + checked_ops: CheckedOps, + ) -> Result { + 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, diff --git a/nutype_macros/src/integer/parse.rs b/nutype_macros/src/integer/parse.rs index 377fa15..f6673dc 100644 --- a/nutype_macros/src/integer/parse.rs +++ b/nutype_macros/src/integer/parse.rs @@ -42,6 +42,7 @@ where const_fn, default, derive_traits, + checked_ops, } = attrs; let raw_guard = IntegerRawGuard { sanitizers, @@ -54,6 +55,7 @@ where guard, default, derive_traits, + checked_ops, }) } diff --git a/nutype_macros/src/string/gen/mod.rs b/nutype_macros/src/string/gen/mod.rs index 31769a5..989ca06 100644 --- a/nutype_macros/src/string/gen/mod.rs +++ b/nutype_macros/src/string/gen/mod.rs @@ -14,7 +14,7 @@ use crate::{ tests::gen_test_should_have_valid_default_value, traits::GeneratedTraits, GenerateNewtype, }, - models::{ConstFn, ErrorTypePath, Guard, TypeName}, + models::{CheckedOps, ConstFn, ErrorTypePath, Guard, TypeName}, }, string::models::{RegexDef, StringInnerType, StringSanitizer, StringValidator}, }; @@ -184,6 +184,22 @@ impl GenerateNewtype for StringNewtype { gen_traits(type_name, generics, traits, maybe_default_value, guard) } + fn gen_ops( + _type_name: &TypeName, + _generics: &Generics, + _guard: &Guard, + checked_ops: CheckedOps, + ) -> Result { + 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, diff --git a/nutype_macros/src/string/parse.rs b/nutype_macros/src/string/parse.rs index ebdcf0e..eed0157 100644 --- a/nutype_macros/src/string/parse.rs +++ b/nutype_macros/src/string/parse.rs @@ -38,6 +38,7 @@ pub fn parse_attributes( const_fn, default, derive_traits, + checked_ops, } = attrs; let raw_guard = StringRawGuard { sanitizers, @@ -50,6 +51,7 @@ pub fn parse_attributes( guard, default, derive_traits, + checked_ops, }) } diff --git a/test_suite/tests/integer.rs b/test_suite/tests/integer.rs index 404b32a..8e09d25 100644 --- a/test_suite/tests/integer.rs +++ b/test_suite/tests/integer.rs @@ -677,6 +677,249 @@ mod traits { assert_eq!(age.to_string(), "35"); } + #[test] + fn test_trait_checked_ops_without_validation() { + #[nutype(checked_ops)] + pub struct Offset(i64); + + { + let lhs = Offset::new(-100); + let rhs = Offset::new(100); + let result = lhs.checked_add(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 0); + } + + { + let lhs = Offset::new(i64::MAX); + let rhs = Offset::new(i64::MAX); + let result = lhs.checked_add(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(100); + let rhs = Offset::new(100); + let result = lhs.checked_div(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 1); + } + + { + let lhs = Offset::new(i64::MAX); + let rhs = Offset::new(0); + let result = lhs.checked_div(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(100); + let rhs = Offset::new(100); + let result = lhs.checked_mul(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 10_000); + } + + { + let lhs = Offset::new(i64::MAX); + let rhs = Offset::new(i64::MAX); + let result = lhs.checked_mul(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(100); + let result = lhs.checked_neg(); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), -100); + } + + { + let lhs = Offset::new(i64::MIN); + let result = lhs.checked_neg(); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(100); + let rhs = Offset::new(80); + let result = lhs.checked_rem(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 20); + } + + { + let lhs = Offset::new(i64::MAX); + let rhs = Offset::new(0); + let result = lhs.checked_rem(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(1); + let result = lhs.checked_shl(1); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 2); + } + + { + let lhs = Offset::new(1); + let result = lhs.checked_shl(i64::BITS + 1); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(2); + let result = lhs.checked_shr(1); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 1); + } + + { + let lhs = Offset::new(1); + let result = lhs.checked_shr(i64::BITS + 1); + assert!(result.is_none()); + } + + { + let lhs = Offset::new(100); + let rhs = Offset::new(100); + let result = lhs.checked_sub(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 0); + } + + { + let lhs = Offset::new(i64::MIN); + let rhs = Offset::new(i64::MAX); + let result = lhs.checked_sub(&rhs); + assert!(result.is_none()); + } + } + + #[test] + fn test_trait_checked_ops_with_validation() { + #[nutype(checked_ops, validate(greater_or_equal = -100, less_or_equal = 100))] + pub struct Offset(i64); + + { + let lhs = Offset::try_new(-100).unwrap(); + let rhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_add(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 0); + } + + { + let lhs = Offset::try_new(-100).unwrap(); + let rhs = Offset::try_new(-100).unwrap(); + let result = lhs.checked_add(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_div(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 1); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(0).unwrap(); + let result = lhs.checked_div(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(10).unwrap(); + let rhs = Offset::try_new(10).unwrap(); + let result = lhs.checked_mul(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 100); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_mul(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_neg(); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), -100); + } + + { + #[nutype(checked_ops, validate(greater_or_equal = -100, less_or_equal = 0))] + pub struct Offset(i64); + + let lhs = Offset::try_new(-100).unwrap(); + let result = lhs.checked_neg(); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(80).unwrap(); + let result = lhs.checked_rem(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 20); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(0).unwrap(); + let result = lhs.checked_rem(&rhs); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(1).unwrap(); + let result = lhs.checked_shl(1); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 2); + } + + { + let lhs = Offset::try_new(1).unwrap(); + let result = lhs.checked_shl(i64::BITS); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(2).unwrap(); + let result = lhs.checked_shr(1); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 1); + } + + { + let lhs = Offset::try_new(1).unwrap(); + let result = lhs.checked_shr(i64::BITS + 1); + assert!(result.is_none()); + } + + { + let lhs = Offset::try_new(100).unwrap(); + let rhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_sub(&rhs); + assert!(result.is_some()); + assert_eq!(result.unwrap().into_inner(), 0); + } + + { + let lhs = Offset::try_new(-100).unwrap(); + let rhs = Offset::try_new(100).unwrap(); + let result = lhs.checked_sub(&rhs); + assert!(result.is_none()); + } + } + #[cfg(feature = "serde")] mod serialization { use super::*; diff --git a/test_suite/tests/ui/any/attributes/unsupported_attribute.rs b/test_suite/tests/ui/any/attributes/unsupported_attribute.rs new file mode 100644 index 0000000..e8faff0 --- /dev/null +++ b/test_suite/tests/ui/any/attributes/unsupported_attribute.rs @@ -0,0 +1,8 @@ +use nutype::nutype; + +pub struct Inner(String); + +#[nutype(checked_ops)] +pub struct Name(Inner); + +fn main () {} diff --git a/test_suite/tests/ui/any/attributes/unsupported_attribute.stderr b/test_suite/tests/ui/any/attributes/unsupported_attribute.stderr new file mode 100644 index 0000000..a738d71 --- /dev/null +++ b/test_suite/tests/ui/any/attributes/unsupported_attribute.stderr @@ -0,0 +1,7 @@ +error: Numeric operations can only be implemented for integer types + --> tests/ui/any/attributes/unsupported_attribute.rs:5:1 + | +5 | #[nutype(checked_ops)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `nutype` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test_suite/tests/ui/float/attributes/unsupported_attribute.rs b/test_suite/tests/ui/float/attributes/unsupported_attribute.rs new file mode 100644 index 0000000..781a7eb --- /dev/null +++ b/test_suite/tests/ui/float/attributes/unsupported_attribute.rs @@ -0,0 +1,6 @@ +use nutype::nutype; + +#[nutype(checked_ops)] +pub struct Name(f32); + +fn main () {} diff --git a/test_suite/tests/ui/float/attributes/unsupported_attribute.stderr b/test_suite/tests/ui/float/attributes/unsupported_attribute.stderr new file mode 100644 index 0000000..4cdd17c --- /dev/null +++ b/test_suite/tests/ui/float/attributes/unsupported_attribute.stderr @@ -0,0 +1,7 @@ +error: Numeric operations can only be implemented for integer types + --> tests/ui/float/attributes/unsupported_attribute.rs:3:1 + | +3 | #[nutype(checked_ops)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `nutype` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/test_suite/tests/ui/string/attributes/unsupported_attribute.rs b/test_suite/tests/ui/string/attributes/unsupported_attribute.rs new file mode 100644 index 0000000..389a750 --- /dev/null +++ b/test_suite/tests/ui/string/attributes/unsupported_attribute.rs @@ -0,0 +1,6 @@ +use nutype::nutype; + +#[nutype(checked_ops)] +pub struct Name(String); + +fn main () {} diff --git a/test_suite/tests/ui/string/attributes/unsupported_attribute.stderr b/test_suite/tests/ui/string/attributes/unsupported_attribute.stderr new file mode 100644 index 0000000..6a1ad15 --- /dev/null +++ b/test_suite/tests/ui/string/attributes/unsupported_attribute.stderr @@ -0,0 +1,7 @@ +error: Numeric operations can only be implemented for integer types + --> tests/ui/string/attributes/unsupported_attribute.rs:3:1 + | +3 | #[nutype(checked_ops)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `nutype` (in Nightly builds, run with -Z macro-backtrace for more info)