Skip to content

Commit

Permalink
Refactor to improve readability (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
yunjhongwu authored Dec 3, 2023
1 parent aa89e54 commit 00e5c4f
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 298 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ name = "named-type"
description = "Named type macro"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
proc-macro = true
Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# named-type
Macros to create strong typed and named values in Rust.
- `NamedType`: creates a strong type with a name.
- `NamedNumeric`: creates a strong type with a name and implements traits for arithmetic operations.
- `default_display`: implements `Display` trait for the type with the default format `TypeName(value)`.
- `NamedType` creates a strong type with a name.
- `NamedNumeric` creates a strong type with a name and implements traits for arithmetic operations.
- `custom_display` suppresses the default `Display` trait implementation and allows users to implement it manually.

## Supported underlying types:
- Both `NamedType` and `NamedNumeric`:
Expand All @@ -23,6 +23,7 @@ struct Tag(String);

let tag = Tag("dev".to_string());
println!("{:?}", tag); // Tag { value: "dev" }
println!("{}", tag); // Tag("dev")
```

#### Strong type:
Expand Down Expand Up @@ -60,15 +61,22 @@ assert!(x < y);
assert!(y >= x);
```

#### Named type with `default_display`:
#### Named type with `custom_display`:

```rust
use named_type::NamedNumeric;

#[derive(NamedNumeric)]
#[default_display]
struct Second(i32);
#[custom_display]
struct Mile(i32);

impl Display for Mile {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Mile({:.2})", &self.0)
}
}

println!("{}", Second(std::f64::consts::E)); // "Mile(2.72)"
println!("{:?}", Second(std::f64::consts::E)); // "Second { value: 2.718281828459045 }"

println!("{}", Second(2)); // "Second(2)"
println!("{:?}", Second(2)); // "Second { value: 2 }"
```
59 changes: 59 additions & 0 deletions src/detail/arithmetic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_arithmetic(name: &syn::Ident) -> TokenStream {
quote! {
impl std::ops::Add for #name {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
Self(self.value() + rhs.value())
}
}

impl std::ops::AddAssign for #name {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.value()
}
}

impl std::ops::Sub for #name {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.value() - rhs.value())
}
}

impl std::ops::SubAssign for #name {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.value()
}
}

impl std::ops::Mul for #name {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.value() * rhs.value())
}
}

impl std::ops::MulAssign for #name {
fn mul_assign(&mut self, rhs: Self) {
self.0 *= rhs.value()
}
}

impl std::ops::Div for #name {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self(self.value() / rhs.value())
}
}

impl std::ops::DivAssign for #name {
fn div_assign(&mut self, rhs: Self) {
self.0 /= rhs.value()
}
}
}
}
20 changes: 20 additions & 0 deletions src/detail/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_basic(name: &syn::Ident) -> TokenStream {
quote! {
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct(stringify!(#name))
.field("value", &self.0)
.finish()
}
}

impl std::cmp::PartialEq for #name {
fn eq(&self, rhs: &Self) -> bool {
self.value() == rhs.value()
}
}
}
}
31 changes: 31 additions & 0 deletions src/detail/basic_primitive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_basic_primitive(name: &syn::Ident, value_type: &syn::Ident) -> TokenStream {
quote! {
impl #name {
pub fn new(value : #value_type) -> Self {
Self(value)
}

pub fn value(&self) -> #value_type {
self.0
}
}

impl Copy for #name {}

impl Clone for #name {
fn clone(&self) -> Self {
*self
}
}

#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
impl std::cmp::PartialOrd for #name {
fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
self.value().partial_cmp(&rhs.value())
}
}
}
}
28 changes: 28 additions & 0 deletions src/detail/basic_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_basic_string(name: &syn::Ident) -> TokenStream {
quote! {
impl #name {
pub fn new(value : &str) -> Self {
Self(String::from(value))
}

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

impl Clone for #name {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

impl std::cmp::PartialOrd for #name {
fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
Some(self.value().cmp(&rhs.value()))
}
}
}
}
20 changes: 20 additions & 0 deletions src/detail/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

pub(crate) fn implement_display(name: &syn::Ident) -> TokenStream {
quote! {
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}({})", stringify!(#name), &self.0)
}
}
}
}

pub(crate) fn custom_display(input: &DeriveInput) -> bool {
input
.attrs
.iter()
.any(|attr| attr.path().is_ident("custom_display"))
}
20 changes: 20 additions & 0 deletions src/detail/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_hash(name: &syn::Ident) -> TokenStream {
quote! {
impl std::cmp::Eq for #name {}

impl std::cmp::Ord for #name {
fn cmp(&self, rhs: &Self) -> std::cmp::Ordering {
self.value().cmp(&rhs.value())
}
}

impl std::hash::Hash for #name {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value().hash(state);
}
}
}
}
16 changes: 16 additions & 0 deletions src/detail/min_max.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_min_max(name: &syn::Ident, value_type: &syn::Ident) -> TokenStream {
quote! {
impl #name {
fn min() -> Self {
Self(#value_type::MIN)
}

fn max() -> Self {
Self(#value_type::MAX)
}
}
}
}
23 changes: 23 additions & 0 deletions src/detail/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
mod arithmetic;
mod basic;
mod basic_primitive;
mod basic_string;
mod display;
mod hash;
mod min_max;
mod nan;
mod negate;
mod not;
mod underlying_type;

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 display::{custom_display, implement_display};
pub(crate) use hash::implement_hash;
pub(crate) use min_max::implement_min_max;
pub(crate) use nan::implement_nan;
pub(crate) use negate::implement_negate;
pub(crate) use not::implement_not;
pub(crate) use underlying_type::{get_type, get_type_ident, UnderlyingType};
12 changes: 12 additions & 0 deletions src/detail/nan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_nan(name: &syn::Ident, value_type: &syn::Ident) -> TokenStream {
quote! {
impl #name {
fn nan() -> Self {
Self(#value_type::NAN)
}
}
}
}
14 changes: 14 additions & 0 deletions src/detail/negate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_negate(name: &syn::Ident) -> TokenStream {
quote! {
impl std::ops::Neg for #name {
type Output = Self;

fn neg(self) -> Self::Output {
Self(-self.value())
}
}
}
}
14 changes: 14 additions & 0 deletions src/detail/not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn implement_not(name: &syn::Ident) -> TokenStream {
quote! {
impl std::ops::Not for #name {
type Output = Self;

fn not(self) -> Self::Output {
#name(!self.value())
}
}
}
}
33 changes: 33 additions & 0 deletions src/detail/underlying_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use syn::{Data, DeriveInput, Type};

#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
pub(crate) enum UnderlyingType {
Int,
Float,
UInt,
Bool,
Char,
String,
}

pub(crate) fn get_type_ident(input: &DeriveInput) -> &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 &path.path.segments.last().unwrap().ident;
}
}
panic!("Unsupported input")
}

pub(crate) fn get_type(value_type: &syn::Ident) -> UnderlyingType {
match value_type.to_string().as_str() {
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" => UnderlyingType::Int,
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" => UnderlyingType::UInt,
"f32" | "f64" => UnderlyingType::Float,
"bool" => UnderlyingType::Bool,
"char" => UnderlyingType::Char,
"String" => UnderlyingType::String,
_ => panic!("Unsupported type"),
}
}
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
mod detail;
mod named_type;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

use crate::named_type::expand_named_type;

#[proc_macro_derive(NamedType, attributes(default_display))]
#[proc_macro_derive(NamedType, attributes(custom_display))]
pub fn named_type(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_named_type(input, false).into()
}

#[proc_macro_derive(NamedNumeric, attributes(default_display))]
#[proc_macro_derive(NamedNumeric, attributes(custom_display))]
pub fn named_numeric(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_named_type(input, true).into()
Expand Down
Loading

0 comments on commit 00e5c4f

Please sign in to comment.