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

NestedStrongType with custom_underlying #53

Merged
merged 1 commit into from
Feb 4, 2024
Merged
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
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ println!("{}", timestamp); // Timestamp(1701620628123456789)
- Conditionally, based on the underlying data type, traits like `Copy`, `Eq`, `Ord`, `Hash` may also be implemented. For primitive data types like `i32` or `bool`, these additional traits will be automatically included.
- Numeric types, both integer and floating-point, also implement constants `MIN`, `MAX`, and `ZERO`. Additionally, for floating-point types, `NAN` is implemented.

- **Attributes:** Adding the following attributes to `#[strong_type(...)]` allows for additional features:
- `auto_operators`: Automatically implements relevant arithmetic (for numeric types) or logical (for boolean types) operators.
- `custom_display`: Allows users to manually implement the `Display` trait, providing an alternative to the default display format.
- **Attributes:**
- Adding the following attributes to `#[strong_type(...)]` allows for additional features:
- `auto_operators`: Automatically implements relevant arithmetic (for numeric types) or logical (for boolean types) operators.
- `custom_display`: Allows users to manually implement the `Display` trait, providing an alternative to the default display format.
- Specifying the corresponding primitive types via `#[custom_underlying(...)]` for nested strong types.

## Installation
Add `strong-type` to your `Cargo.toml`:
Expand All @@ -36,6 +38,7 @@ strong-type = "0.7"
- Boolean type: `bool`
- `char`
- `String`
- Strong types of the above types

## Examples
#### Creating a named strong type:
Expand Down Expand Up @@ -142,3 +145,21 @@ impl Display for Second {
println!("{}", Second::new(std::f64::consts::E)); // "Second(2.72)"
println!("{:?}", Second::new(std::f64::consts::E)); // "Second { value: 2.718281828459045 }"
```

#### Nested strong types:

```rust
#[derive(StrongType)]
#[strong_type(auto_operators)]
struct Dollar(i32);

#[derive(StrongType)]
#[strong_type(auto_operators)]
#[custom_underlying(i32)]
struct Cash(Dollar);

#[derive(StrongType)]
#[strong_type(auto_operators)]
#[custom_underlying(i32)]
struct Coin(Cash);
```
26 changes: 26 additions & 0 deletions strong-type-derive/src/detail/basic_primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,29 @@ pub(crate) fn implement_basic_primitive(name: &syn::Ident, value_type: &syn::Ide
}
}
}

pub(crate) fn implement_primitive_accessor(
name: &syn::Ident,
primitive_type: &syn::Ident,
) -> TokenStream {
quote! {
impl #name {
pub fn primitive(&self) -> #primitive_type {
self.value()
}
}
}
}

pub(crate) fn implement_primitive_accessor_derived(
name: &syn::Ident,
primitive_type: &syn::Ident,
) -> TokenStream {
quote! {
impl #name {
pub fn primitive(&self) -> #primitive_type {
self.0.primitive()
}
}
}
}
37 changes: 31 additions & 6 deletions strong-type-derive/src/detail/basic_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ use quote::quote;

pub(crate) fn implement_basic_string(name: &syn::Ident) -> TokenStream {
quote! {
impl #name {
pub fn value(&self) -> &str {
self.0.as_str()
}
}

impl Clone for #name {
fn clone(&self) -> Self {
Self(self.0.clone())
Expand All @@ -22,3 +16,34 @@ pub(crate) fn implement_basic_string(name: &syn::Ident) -> TokenStream {
}
}
}

pub(crate) fn implement_primitive_str_accessor(name: &syn::Ident) -> TokenStream {
quote! {
impl #name {
pub fn value(&self) -> &str {
self.0.as_str()
}

pub fn primitive(&self) -> &str {
self.value()
}
}
}
}

pub(crate) fn implement_primitive_str_accessor_derived(
name: &syn::Ident,
value_type: &syn::Ident,
) -> TokenStream {
quote! {
impl #name {
pub fn value(&self) -> &#value_type {
&self.0
}

pub fn primitive(&self) -> &str {
self.0.primitive()
}
}
}
}
13 changes: 13 additions & 0 deletions strong-type-derive/src/detail/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@ pub(crate) fn implement_constants(name: &syn::Ident, value_type: &syn::Ident) ->
}
}
}
pub(crate) fn implement_constants_derived(
name: &syn::Ident,
value_type: &syn::Ident,
) -> TokenStream {
quote! {
impl #name {
pub const MIN: Self = Self(#value_type::MIN);
pub const MAX: Self = Self(#value_type::MAX);
pub const ZERO: Self = Self(#value_type::ZERO);
pub const ONE: Self = Self(#value_type::ONE);
}
}
}
15 changes: 10 additions & 5 deletions strong-type-derive/src/detail/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ mod display;
mod hash;
mod nan;
mod negate;
mod underlying_type;
mod underlying_type_utils;
mod utils;

pub(crate) use arithmetic::implement_arithmetic;
pub(crate) use basic::implement_basic;
pub(crate) use basic_primitive::implement_basic_primitive;
pub(crate) use basic_string::implement_basic_string;
pub(crate) use basic_primitive::{
implement_basic_primitive, implement_primitive_accessor, implement_primitive_accessor_derived,
};
pub(crate) use basic_string::{
implement_basic_string, implement_primitive_str_accessor,
implement_primitive_str_accessor_derived,
};
pub(crate) use bit_ops::implement_bit_shift;
pub(crate) use bool_ops::implement_bool_ops;
pub(crate) use constants::implement_constants;
pub(crate) use constants::{implement_constants, implement_constants_derived};
pub(crate) use display::implement_display;
pub(crate) use hash::implement_hash;
pub(crate) use nan::implement_nan;
pub(crate) use negate::implement_negate;
pub(crate) use underlying_type::{get_type_group, get_type_ident, UnderlyingTypeGroup};
pub(crate) use underlying_type_utils::{get_type, TypeInfo, UnderlyingType, ValueTypeGroup};
pub(crate) use utils::{get_attributes, is_struct_valid, StrongTypeAttributes};
55 changes: 0 additions & 55 deletions strong-type-derive/src/detail/underlying_type.rs

This file was deleted.

104 changes: 104 additions & 0 deletions strong-type-derive/src/detail/underlying_type_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use syn::{Data, DeriveInput, Type};

pub(crate) enum UnderlyingType {
Primitive,
Derived,
}
pub(crate) enum ValueTypeGroup {
Int(UnderlyingType),
Float(UnderlyingType),
UInt(UnderlyingType),
Bool(UnderlyingType),
Char(UnderlyingType),
String(UnderlyingType),
}

pub(crate) struct TypeInfo {
pub primitive_type: syn::Ident,
pub value_type: syn::Ident,
pub type_group: ValueTypeGroup,
}

fn get_type_ident(input: &DeriveInput) -> Option<syn::Ident> {
if let Data::Struct(ref data_struct) = input.data {
if let Type::Path(ref path) = &data_struct.fields.iter().next().unwrap().ty {
return Some(path.path.segments.last().unwrap().ident.clone());
}
}
None
}

fn get_primitive_from_custom_underlying(input: &DeriveInput) -> Option<syn::Ident> {
for attr in input.attrs.iter() {
if attr.path().is_ident("custom_underlying") {
let mut primitive = None;
attr.parse_nested_meta(|meta| match meta.path.get_ident() {
Some(ident) => {
primitive = Some(ident.clone());
Ok(())
}
None => Err(meta.error("Unsupported attribute")),
})
.ok()?;
return primitive;
}
}

None
}

pub(crate) fn get_type(input: &DeriveInput) -> TypeInfo {
if let Some(value_type) = get_type_ident(input) {
match get_primitive_from_custom_underlying(input) {
Some(primitive_type) => TypeInfo {
primitive_type: primitive_type.clone(),
value_type: value_type.clone(),
type_group: get_type_group(&primitive_type, UnderlyingType::Derived),
},
None => TypeInfo {
primitive_type: value_type.clone(),
value_type: value_type.clone(),
type_group: get_type_group(&value_type, UnderlyingType::Primitive),
},
}
} else {
panic!("Unsupported input")
}
}

pub(crate) fn get_type_group(
value_type: &syn::Ident,
underlying_type: UnderlyingType,
) -> ValueTypeGroup {
if value_type == "i8"
|| value_type == "i16"
|| value_type == "i32"
|| value_type == "i64"
|| value_type == "i128"
|| value_type == "isize"
{
return ValueTypeGroup::Int(underlying_type);
}
if value_type == "u8"
|| value_type == "u16"
|| value_type == "u32"
|| value_type == "u64"
|| value_type == "u128"
|| value_type == "usize"
{
return ValueTypeGroup::UInt(underlying_type);
}
if value_type == "f32" || value_type == "f64" {
return ValueTypeGroup::Float(underlying_type);
}
if value_type == "bool" {
return ValueTypeGroup::Bool(underlying_type);
}
if value_type == "char" {
return ValueTypeGroup::Char(underlying_type);
}
if value_type == "String" {
return ValueTypeGroup::String(underlying_type);
}
panic!("Unsupported type: {}", value_type);
}
2 changes: 1 addition & 1 deletion strong-type-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{parse_macro_input, DeriveInput};

use crate::strong_type::expand_strong_type;

#[proc_macro_derive(StrongType, attributes(strong_type))]
#[proc_macro_derive(StrongType, attributes(strong_type, custom_underlying))]
pub fn strong_type(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_strong_type(input).into()
Expand Down
Loading
Loading