Skip to content

Commit

Permalink
NestedStrongType with custom_underlying (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
yunjhongwu authored Feb 4, 2024
1 parent 72e6c45 commit 36ae82c
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 96 deletions.
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

0 comments on commit 36ae82c

Please sign in to comment.