├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── README.tpl ├── rust-toolchain ├── src ├── analyze.rs ├── attrs.rs ├── impls │ ├── as_ref.rs │ ├── deref.rs │ ├── deref_mut.rs │ ├── deriveable │ │ ├── clone.rs │ │ ├── copy.rs │ │ ├── debug.rs │ │ ├── default.rs │ │ ├── deserialize.rs │ │ ├── eq.rs │ │ ├── hash.rs │ │ ├── mod.rs │ │ ├── ord.rs │ │ ├── partial_eq.rs │ │ ├── partial_ord.rs │ │ └── serialize.rs │ ├── display.rs │ ├── from.rs │ ├── from_str.rs │ ├── mod.rs │ ├── number.rs │ ├── string.rs │ └── value.rs ├── info.rs └── lib.rs └── tests ├── cases ├── fail-enum.rs ├── fail-enum.stderr ├── fail-generics.rs ├── fail-generics.stderr ├── fail-named-field.rs ├── fail-named-field.stderr ├── fail-two-fields.rs ├── fail-two-fields.stderr ├── fail-zero-fields.rs ├── fail-zero-fields.stderr ├── pass-char.rs ├── pass-display.rs ├── pass-floats.rs ├── pass-int.rs ├── pass-lint.rs ├── pass-serde.rs ├── pass-skip.rs └── pass-string.rs ├── progress.rs └── synonym.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose --all-features 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | # 15 | #already existing elements are commented out 16 | 17 | /target 18 | #**/*.rs.bk 19 | #Cargo.lock 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.6 (2024-09-21) 4 | 5 | * [#10](https://github.com/synek317/synonym/pull/10) Fix `missing_docs` warnings by simply adding `#[allow]` to generated methods (author: Mike Tsao) 6 | 7 | ## 0.1.5 (2024-07-01) 8 | 9 | * Fix warnings when `with_serde` feature is not enabled 10 | 11 | ## 0.1.4 (2024-07-01) 12 | 13 | * Add `Box` and `&'static str` synonyms 14 | * Add `NonZero*` synonyms 15 | * Add `.value()` method 16 | * Add `From` for `Box` synonyms 17 | * Optimize `FromStr` for `Box` synonyms 18 | * Reorganize the documentation 19 | 20 | ## 0.1.3 (2024-06-05) 21 | 22 | * Fix `missing_docs` warning on `as_str` 23 | 24 | ## 0.1.2 (2023-05-30) 25 | 26 | * [#2](https://github.com/synek317/synonym/pull/2) Fix `as_str` for String synonyms (author: Mike Tsao) 27 | * Reduce dependencies 28 | 29 | ## 0.1.1 (2023-11-22) 30 | 31 | * Update to Rust 1.74 32 | 33 | ## 0.1.0 (2023-09-02) 34 | 35 | * Update to Rust 1.72 36 | * Add documentation 37 | 38 | ## 0.0.5 (2020-06-14) 39 | 40 | * Add serde support 41 | 42 | ## 0.0.4 (2020-06-13) 43 | 44 | * Impl `as_str` for String synonyms 45 | 46 | ## 0.0.3 (2020-03-11) 47 | 48 | * Add Display 49 | 50 | ## 0.0.2 (2020-01-21) 51 | 52 | * Add number ops 53 | 54 | ## 0.0.1 (2020-01-20) 55 | 56 | * Initial release -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synonym" 3 | version = "0.1.6" 4 | authors = ["Marcin Sas-Szymanski "] 5 | license-file = "LICENSE" 6 | description = "Customizable derive macro to create newtypes. It peeks into the underlying type to choose which traits should be implemented." 7 | homepage = "https://github.com/synek317/synonym" 8 | documentation = "https://docs.rs/synonym" 9 | repository = "https://github.com/synek317/synonym" 10 | categories = ["development-tools", "rust-patterns"] 11 | keywords = ["newtype", "newtypes", "synonym", "alias"] 12 | edition = "2018" 13 | 14 | [badges] 15 | travis-ci = { repository = "synek317/synonym", branch = "master" } 16 | maintenance = { status = "actively-developed" } 17 | 18 | [lib] 19 | doctest = false 20 | proc-macro = true 21 | path = "src/lib.rs" 22 | 23 | [[test]] 24 | name = "tests" 25 | path = "tests/progress.rs" 26 | 27 | [dev-dependencies] 28 | trybuild = "1.0" 29 | serde = { version = "1.0", features = [ "derive" ] } 30 | serde_json = "1.0" 31 | 32 | [dependencies] 33 | syn = { version = "2.0", default-features = false } 34 | quote = "1.0" 35 | proc-macro2 = "1.0" 36 | darling = "0.20.3" 37 | 38 | [features] 39 | with_serde = [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 synek317 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/synonym.svg)](https://crates.io/crates/synonym) 2 | [![Docs.rs](https://docs.rs/synonym/badge.svg)](https://docs.rs/synonym) 3 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rust-lang/docs.rs/master/LICENSE) 4 | [![Build Status](https://travis-ci.org/synek317/synonym.svg?branch=master)](https://travis-ci.org/synek317/synonym) 5 | ![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) 6 | 7 | # Synonym 8 | 9 | ## Overview 10 | 11 | The `synonym` library is a Rust crate designed to simplify the creation of newtypes. It provides a customizable `#[derive(Synonym)]` macro that automatically implements various traits based on the underlying type of your newtype. This saves you from the boilerplate code usually required when defining newtypes. 12 | 13 | ## Usage 14 | 15 | To use `synonym`, add it to your Cargo.toml: 16 | 17 | ```toml 18 | [dependencies] 19 | synonym = "0.1.5" 20 | ``` 21 | 22 | ### Basic example 23 | 24 | Import the `Synonym` trait into your Rust file: 25 | 26 | ```rust 27 | use synonym::Synonym; 28 | ``` 29 | 30 | Then, define your newtype and annotate it with `#[derive(Synonym)]:` 31 | ```rust 32 | #[derive(Synonym)] 33 | pub struct MyInt(i32); 34 | ``` 35 | 36 | ### Customization with Attributes 37 | You can customize which traits are implemented or skipped using the `#[synonym(skip(...))]` and `#[synonym(force(...))]` attributes: 38 | ```rust 39 | #[derive(Synonym)] 40 | #[synonym(skip(Eq, PartialEq))] 41 | pub struct MyString(String); 42 | ``` 43 | 44 | Supported `skip` and `force` values are listed in the *Trait implementation table* below. 45 | 46 | ## Generated code 47 | When you use `#[derive(Synonym)]`, the library generates implementations for various traits. Here's a simplified example for a newtype `MyInt(i32)`: 48 | ```rust 49 | impl Eq for MyInt {} 50 | impl PartialEq for MyInt { 51 | fn eq(&self, other: &Self) -> bool { 52 | self.0 == other.0 53 | } 54 | } 55 | // ... and so on for other traits 56 | ``` 57 | 58 | ## Trait implementation table 59 | 60 | Custom methods 61 | 62 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 63 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 64 | | .as_str() | String | | | | v | v | v | v | 65 | | .value() [2] | Value | v | v | v | v | v | v | v | 66 | 67 | 68 | Conversion 69 | 70 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 71 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 72 | | AsRef | AsRef | v | v | v | v | v | v | v | 73 | | Borrow | String | | | | v | v | v | | 74 | | From<&'a str> | String | | | | v | v [4] | | | 75 | | From | String | | | | | v | | | 76 | | Deref [3] | Deref | | | | | | | | 77 | | DerefMut [3] | DerefMut | | | | | | | | 78 | | From | From | v | v | v | v | v | v | v | 79 | | FromStr | FromStr | v | v | v | v | v | | v | 80 | 81 | Fundamental traits 82 | 83 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 84 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 85 | | Clone | Clone | v | v | v | v | v | v | v | 86 | | Copy | Copy | v | v | v | | v | v | 87 | | Debug | Debug | v | v | v | v | v | v | v | 88 | | Default | Default | v | | v | v | v | v | v | 89 | | Display [5] | Display | v | v | v | v | v | v | v | 90 | | Hash | Hash | v | v | | v | v | v | v | 91 | 92 | Comparison 93 | 94 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 95 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 96 | | PartialOrd | PartialOrd | v | v | v | v | v | v | v | 97 | | Ord | Ord | v | v | | v | v | v | v | 98 | | PartialEq | PartialEq | v | v | v | v | v | v | v | 99 | | Eq | Eq | v | v | | v | v | v | v | 100 | 101 | Serde [6] 102 | 103 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 104 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 105 | | Serialize | Serialize | v | v | v | v | v | | v | 106 | | Deserialize | Deserialize | v | v | v | v | v | | v | 107 | 108 | Maths [7] 109 | 110 | | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 111 | |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 112 | | Add=Self | Number | v | | v | | | | 113 | | AddAssign | Number | v | | v | | | | 114 | | Sub=Self | Number | v | | v | | | | 115 | | SubAssign | Number | v | | v | | | | 116 | | Mul=Self | Number | v | | v | | | | 117 | | MulAssign | Number | v | | v | | | | 118 | | Div=Self | Number | v | | v | | | | 119 | | DivAssign | Number | v | | v | | | | 120 | 121 | 122 | [1] Integers are: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize` 123 | [2] .value() returns `Inner` for `Copy` types and `&Inner` for non-`Copy` types 124 | [3] `Deref` and `DerefMut` are never implemented unless they are forced with `#[synonym(force(deref,deref_mut))]` 125 | [4] In constrast to other strings, `FromStr` for `Box` synonyms uses `Inner::From<&'str>` instead of `Inner::FromStr` since there is no `FromStr` implementation for `Box` 126 | [5] Display implementation can be configured, see below 127 | [6] Only provided when feature `with_serde` is enabled 128 | [7] This is subject to change 129 | 130 | ## Fine-tuning 131 | 132 | ### Display 133 | To specify how the Display trait should be implemented, you can use the `#[synonym(display = "...")]` attribute. Here are the available options: 134 | 135 | * `Opaque`: Formats the output as TypeName(Value). 136 | * `Transparent`: Directly uses the inner type's Display implementation. 137 | * `UpperCase`: Converts the inner value to uppercase before displaying. 138 | * `LowerCase`: Converts the inner value to lowercase before displaying. 139 | * `OpaqueUpperCase`: Formats the output as TypeName(VALUE) where VALUE is uppercase. 140 | * `OpaqueLowerCase`: Formats the output as TypeName(value) where value is lowercase. 141 | * `Custom string`: Allows for a custom format string 142 | 143 | #### Examples 144 | ```rust 145 | #[derive(Synonym)] 146 | #[synonym(display = "UpperCase")] 147 | struct CountryName(String); 148 | 149 | #[derive(Synonym)] 150 | #[synonym(display = "::<> {} <>::")] 151 | struct Turbo(String); 152 | ``` 153 | 154 | ### Serde Support 155 | 156 | To enable Serde support for serialization and deserialization, you'll need to enable the `with_serde` feature flag in your `Cargo.toml`: 157 | 158 | ```toml 159 | [dependencies] 160 | synonym = { version = "0.1.5", features = ["with_serde"] } 161 | ``` 162 | 163 | With this feature enabled, the `Serialize` and `Deserialize` traits will be automatically implemented for your type. 164 | 165 | --- 166 | This documentation was generated with the assistance of ChatGPT-4 by OpenAI. 167 | 168 | ## License 169 | 170 | Licensed under of MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 171 | 172 | ### Contribution 173 | 174 | All contributions and comments are more than welcome! Don't be afraid to open an issue or PR whenever you find a bug or have an idea to improve this crate. 175 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/synonym.svg)](https://crates.io/crates/synonym) 2 | [![Docs.rs](https://docs.rs/synonym/badge.svg)](https://docs.rs/synonym) 3 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rust-lang/docs.rs/master/LICENSE) 4 | {{badges}} 5 | 6 | # Synonym 7 | 8 | {{readme}} 9 | 10 | ## License 11 | 12 | Licensed under of MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 13 | 14 | ### Contribution 15 | 16 | All contributions and comments are more than welcome! Don't be afraid to open an issue or PR whenever you find a bug or have an idea to improve this crate. 17 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.74.0 2 | -------------------------------------------------------------------------------- /src/analyze.rs: -------------------------------------------------------------------------------- 1 | use crate::attrs::Attrs; 2 | use crate::info::{Info, Kind}; 3 | use darling::FromDeriveInput; 4 | use quote::ToTokens; 5 | use syn::{Data, DeriveInput, Fields}; 6 | 7 | pub fn analyze(input: &DeriveInput) -> Option { 8 | if has_generics(input) { 9 | return None; 10 | } 11 | 12 | let mut info = Info { 13 | name: input.ident.clone(), 14 | kind: Kind::Other, 15 | typ: syn::Type::Never(syn::TypeNever { 16 | bang_token: syn::token::Not::default(), 17 | }), 18 | attrs: Attrs::from_derive_input(input).unwrap(), 19 | }; 20 | 21 | match &input.data { 22 | Data::Struct(data) => match &data.fields { 23 | Fields::Unnamed(fields) => { 24 | if fields.unnamed.len() != 1 { 25 | return None; 26 | } 27 | 28 | info.typ = fields.unnamed.first().unwrap().ty.clone(); 29 | if let syn::Type::Group(g) = info.typ { 30 | info.typ = *g.elem; 31 | } 32 | 33 | info.kind = match info 34 | .typ 35 | .to_token_stream() 36 | .to_string() 37 | .replace("std :: string :: ", "") 38 | .replace("std :: num :: ", "") 39 | .replace("std :: primitive :: ", "") 40 | .replace("core :: primitive :: ", "") 41 | .as_str() 42 | { 43 | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" 44 | | "i64" | "i128" | "isize" => Kind::Integer, 45 | "NonZeroU8" | "NonZeroU16" | "NonZeroU32" | "NonZeroU64" | "NonZeroU128" 46 | | "NonZeroUsize" | "NonZeroI8" | "NonZeroI16" | "NonZeroI32" | "NonZeroI64" 47 | | "NonZeroI128" | "NonZeroIsize" => Kind::NonZeroInteger, 48 | "f32" | "f64" => Kind::Float, 49 | "String" => Kind::String, 50 | "Box < str >" => Kind::BoxStr, 51 | "& 'static str" => Kind::StaticStr, 52 | "char" => Kind::Char, 53 | _ => Kind::Other, 54 | }; 55 | } 56 | _ => return None, 57 | }, 58 | _ => return None, 59 | } 60 | 61 | Some(info) 62 | } 63 | 64 | fn has_generics(input: &DeriveInput) -> bool { 65 | input.generics.lt_token.is_some() 66 | } 67 | -------------------------------------------------------------------------------- /src/attrs.rs: -------------------------------------------------------------------------------- 1 | use darling::{FromDeriveInput, FromMeta}; 2 | 3 | #[derive(Default, Debug, FromDeriveInput)] 4 | #[darling(default, attributes(synonym))] 5 | pub struct Attrs { 6 | pub skip: ImplList, 7 | pub force: ImplList, 8 | #[darling(rename = "caseinsensitive")] 9 | pub case_insensitive: bool, // TODO 10 | #[darling(rename = "display", map = "parse_display_kind")] 11 | pub display: DisplayKind, 12 | } 13 | 14 | #[derive(Default, Debug, FromMeta)] 15 | #[darling(default)] 16 | pub struct ImplList { 17 | #[darling(rename = "Eq")] 18 | pub eq: bool, 19 | #[darling(rename = "PartialEq")] 20 | pub partial_eq: bool, 21 | #[darling(rename = "Ord")] 22 | pub ord: bool, 23 | #[darling(rename = "PartialOrd")] 24 | pub partial_ord: bool, 25 | #[darling(rename = "Clone")] 26 | pub clone: bool, 27 | #[darling(rename = "Copy")] 28 | pub copy: bool, 29 | #[darling(rename = "Hash")] 30 | pub hash: bool, 31 | #[darling(rename = "Default")] 32 | pub default: bool, 33 | #[darling(rename = "Debug")] 34 | pub debug: bool, 35 | #[darling(rename = "Display")] 36 | pub display: bool, 37 | #[darling(rename = "FromStr")] 38 | pub from_str: bool, 39 | #[darling(rename = "AsRef")] 40 | pub as_ref: bool, 41 | #[darling(rename = "Deref")] 42 | pub deref: bool, 43 | #[darling(rename = "DerefMut")] 44 | pub deref_mut: bool, 45 | #[darling(rename = "From")] 46 | pub from: bool, 47 | #[darling(rename = "String")] 48 | pub string: bool, 49 | #[darling(rename = "Number")] 50 | pub number: bool, 51 | #[darling(rename = "Serialize")] 52 | pub serialize: bool, 53 | #[darling(rename = "Deserialize")] 54 | pub deserialize: bool, 55 | #[darling(rename = "Value")] 56 | pub value: bool, 57 | } 58 | 59 | #[derive(Debug, FromMeta, Default)] 60 | pub enum DisplayKind { 61 | Opaque, 62 | #[default] 63 | Transparent, 64 | UpperCase, 65 | LowerCase, 66 | OpaqueUpperCase, 67 | OpaqueLowerCase, 68 | Custom(String), 69 | } 70 | 71 | fn parse_display_kind(s: String) -> DisplayKind { 72 | s.parse().unwrap() 73 | } 74 | 75 | impl core::str::FromStr for DisplayKind { 76 | type Err = core::convert::Infallible; 77 | 78 | fn from_str(s: &str) -> Result { 79 | match s.to_lowercase().as_str() { 80 | "opaque" => Ok(DisplayKind::Opaque), 81 | "transparent" => Ok(DisplayKind::Transparent), 82 | "uppercase" => Ok(DisplayKind::UpperCase), 83 | "lowercase" => Ok(DisplayKind::LowerCase), 84 | "opaquelowercase" | "lowercaseopaque" => Ok(DisplayKind::OpaqueLowerCase), 85 | "opaqueuppercase" | "uppercaseopaque" => Ok(DisplayKind::OpaqueUpperCase), 86 | _ => Ok(DisplayKind::Custom(s.to_string())), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/impls/as_ref.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_as_ref(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_as_ref(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | quote! { 13 | impl ::core::convert::AsRef<#typ> for #name { 14 | fn as_ref(&self) -> &#typ { 15 | &self.0 16 | } 17 | } 18 | } 19 | } 20 | 21 | pub fn is_as_ref(info: &Info) -> bool { 22 | if info.attrs.skip.as_ref { 23 | return false; 24 | } 25 | 26 | true 27 | } 28 | -------------------------------------------------------------------------------- /src/impls/deref.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_deref(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_deref(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | quote! { 13 | impl ::core::ops::Deref for #name { 14 | type Target = #typ; 15 | 16 | #[inline] 17 | fn deref(&self) -> &#typ { 18 | &self.0 19 | } 20 | } 21 | } 22 | } 23 | 24 | pub fn is_deref(info: &Info) -> bool { 25 | if info.attrs.force.deref || info.attrs.force.deref_mut { 26 | return true; 27 | } 28 | 29 | false 30 | } 31 | -------------------------------------------------------------------------------- /src/impls/deref_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_deref_mut(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_deref_mut(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | quote! { 13 | impl ::core::ops::DerefMut for #name { 14 | #[inline] 15 | fn deref_mut(&mut self) -> &mut #typ { 16 | &mut self.0 17 | } 18 | } 19 | } 20 | } 21 | 22 | pub fn is_deref_mut(info: &Info) -> bool { 23 | if info.attrs.force.deref_mut { 24 | return true; 25 | } 26 | 27 | false 28 | } 29 | -------------------------------------------------------------------------------- /src/impls/deriveable/clone.rs: -------------------------------------------------------------------------------- 1 | use crate::{info::Info, is_copy}; 2 | use quote::quote; 3 | 4 | pub fn impl_clone(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_clone(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | if is_copy(info) { 12 | quote! { 13 | impl ::core::clone::Clone for #name { 14 | fn clone(&self) -> Self { 15 | *self 16 | } 17 | } 18 | } 19 | } else { 20 | quote! { 21 | impl ::core::clone::Clone for #name { 22 | fn clone(&self) -> Self { 23 | Self(self.0.clone()) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | pub fn is_clone(info: &Info) -> bool { 31 | if info.attrs.force.clone { 32 | return true; 33 | } 34 | if info.attrs.skip.clone { 35 | return false; 36 | } 37 | 38 | info.kind.is_clone() 39 | } 40 | -------------------------------------------------------------------------------- /src/impls/deriveable/copy.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_copy(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_copy(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::marker::Copy for #name {} 13 | } 14 | } 15 | 16 | pub fn is_copy(info: &Info) -> bool { 17 | if info.attrs.force.copy { 18 | return true; 19 | } 20 | if info.attrs.skip.copy { 21 | return false; 22 | } 23 | 24 | info.kind.is_copy() 25 | } 26 | -------------------------------------------------------------------------------- /src/impls/deriveable/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_debug(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_debug(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let name_str = name.to_string(); 11 | 12 | quote! { 13 | impl ::core::fmt::Debug for #name { 14 | fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 15 | f.debug_tuple(#name_str) 16 | .field(&self.0) 17 | .finish() 18 | } 19 | } 20 | } 21 | } 22 | 23 | pub fn is_debug(info: &Info) -> bool { 24 | if info.attrs.force.debug { 25 | return true; 26 | } 27 | if info.attrs.skip.debug { 28 | return false; 29 | } 30 | 31 | info.kind.is_debug() 32 | } 33 | -------------------------------------------------------------------------------- /src/impls/deriveable/default.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_default(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_default(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::default::Default for #name { 13 | fn default() -> Self { 14 | Self(::core::default::Default::default()) 15 | } 16 | } 17 | } 18 | } 19 | 20 | pub fn is_default(info: &Info) -> bool { 21 | if info.attrs.force.default { 22 | return true; 23 | } 24 | if info.attrs.skip.default { 25 | return false; 26 | } 27 | 28 | info.kind.is_default() 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/deriveable/deserialize.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_deserialize(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_deserialize(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | quote! { 13 | impl<'de> ::serde::Deserialize<'de> for #name { 14 | fn deserialize(deserializer: D) -> Result 15 | where 16 | D: ::serde::Deserializer<'de>, 17 | { 18 | <#typ as ::serde::Deserialize<'de>>::deserialize(deserializer).map(#name) 19 | } 20 | } 21 | } 22 | } 23 | 24 | #[cfg(any(test, feature = "with_serde"))] 25 | pub fn is_deserialize(info: &Info) -> bool { 26 | if info.attrs.force.serialize { 27 | return true; 28 | } 29 | if info.attrs.skip.serialize { 30 | return false; 31 | } 32 | 33 | info.kind.is_deserialize() 34 | } 35 | 36 | #[cfg(not(any(test, feature = "with_serde")))] 37 | pub fn is_deserialize(_: &Info) -> bool { 38 | false 39 | } 40 | -------------------------------------------------------------------------------- /src/impls/deriveable/eq.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_eq(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_eq(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::cmp::Eq for #name {} 13 | } 14 | } 15 | 16 | pub fn is_eq(info: &Info) -> bool { 17 | if info.attrs.force.eq { 18 | return true; 19 | } 20 | if info.attrs.skip.eq || info.attrs.skip.partial_eq { 21 | return false; 22 | } 23 | 24 | info.kind.is_eq() 25 | } 26 | -------------------------------------------------------------------------------- /src/impls/deriveable/hash.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_hash(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_hash(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::hash::Hash for #name { 13 | fn hash(&self, state: &mut H) { 14 | ::core::hash::Hash::hash::(&self.0, state) 15 | } 16 | } 17 | } 18 | } 19 | 20 | pub fn is_hash(info: &Info) -> bool { 21 | if info.attrs.force.hash { 22 | return true; 23 | } 24 | if info.attrs.skip.hash { 25 | return false; 26 | } 27 | 28 | info.kind.is_hash() 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/deriveable/mod.rs: -------------------------------------------------------------------------------- 1 | mod clone; 2 | mod copy; 3 | mod debug; 4 | mod default; 5 | mod deserialize; 6 | mod eq; 7 | mod hash; 8 | mod ord; 9 | mod partial_eq; 10 | mod partial_ord; 11 | mod serialize; 12 | 13 | pub use self::clone::*; 14 | pub use self::copy::*; 15 | pub use self::debug::*; 16 | pub use self::default::*; 17 | pub use self::deserialize::*; 18 | pub use self::eq::*; 19 | pub use self::hash::*; 20 | pub use self::ord::*; 21 | pub use self::partial_eq::*; 22 | pub use self::partial_ord::*; 23 | pub use self::serialize::*; 24 | -------------------------------------------------------------------------------- /src/impls/deriveable/ord.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_ord(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_ord(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::cmp::Ord for #name { 13 | fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { 14 | ::core::cmp::Ord::cmp(&self.0, &other.0) 15 | } 16 | } 17 | } 18 | } 19 | 20 | pub fn is_ord(info: &Info) -> bool { 21 | if info.attrs.force.ord { 22 | return true; 23 | } 24 | if info.attrs.skip.ord || info.attrs.skip.partial_ord { 25 | return false; 26 | } 27 | 28 | info.kind.is_ord() 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/deriveable/partial_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_partial_eq(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_partial_eq(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::cmp::PartialEq for #name { 13 | fn eq(&self, other: &Self) -> bool { 14 | ::core::cmp::PartialEq::eq(&self.0, &other.0) 15 | } 16 | } 17 | } 18 | } 19 | 20 | pub fn is_partial_eq(info: &Info) -> bool { 21 | if info.attrs.force.partial_eq || info.attrs.force.eq { 22 | return true; 23 | } 24 | if info.attrs.skip.partial_eq { 25 | return false; 26 | } 27 | 28 | info.kind.is_partial_eq() 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/deriveable/partial_ord.rs: -------------------------------------------------------------------------------- 1 | use crate::{impls::is_ord, info::Info}; 2 | use quote::quote; 3 | 4 | pub fn impl_partial_ord(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_partial_ord(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | if is_ord(info) { 12 | quote! { 13 | impl ::core::cmp::PartialOrd for #name { 14 | fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { 15 | Some(self.cmp(other)) 16 | } 17 | } 18 | } 19 | } else { 20 | quote! { 21 | impl ::core::cmp::PartialOrd for #name { 22 | fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { 23 | ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | pub fn is_partial_ord(info: &Info) -> bool { 31 | if info.attrs.force.partial_ord || info.attrs.force.ord { 32 | return true; 33 | } 34 | if info.attrs.skip.partial_ord { 35 | return false; 36 | } 37 | 38 | info.kind.is_partial_ord() 39 | } 40 | -------------------------------------------------------------------------------- /src/impls/deriveable/serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_serialize(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_serialize(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::serde::Serialize for #name { 13 | fn serialize(&self, serializer: S) -> Result 14 | where 15 | S: ::serde::Serializer, 16 | { 17 | self.0.serialize(serializer) 18 | } 19 | } 20 | } 21 | } 22 | 23 | #[cfg(any(test, feature = "with_serde"))] 24 | pub fn is_serialize(info: &Info) -> bool { 25 | if info.attrs.force.serialize { 26 | return true; 27 | } 28 | if info.attrs.skip.serialize { 29 | return false; 30 | } 31 | 32 | info.kind.is_serialize() 33 | } 34 | 35 | #[cfg(not(any(test, feature = "with_serde")))] 36 | pub fn is_serialize(_: &Info) -> bool { 37 | false 38 | } 39 | -------------------------------------------------------------------------------- /src/impls/display.rs: -------------------------------------------------------------------------------- 1 | use crate::attrs::DisplayKind; 2 | use crate::info::Info; 3 | use quote::quote; 4 | 5 | pub fn impl_display(info: &Info) -> proc_macro2::TokenStream { 6 | if !is_display(info) { 7 | return quote! {}; 8 | } 9 | 10 | let name = &info.name; 11 | let name_s = name.to_string(); 12 | 13 | let fmt_impl = match info.attrs.display { 14 | DisplayKind::Opaque => quote! { 15 | ::core::write!(f, "{}({})", #name_s, self.0) 16 | }, 17 | DisplayKind::Transparent => quote! { 18 | ::core::fmt::Display::fmt(&self.0, f) 19 | }, 20 | DisplayKind::UpperCase => quote! { 21 | ::core::fmt::Display::fmt(&self.0.to_uppercase(), f) 22 | }, 23 | DisplayKind::LowerCase => quote! { 24 | ::core::fmt::Display::fmt(&self.0.to_lowercase(), f) 25 | }, 26 | DisplayKind::OpaqueUpperCase => quote! { 27 | ::core::write!(f, "{}({})", #name_s, self.0.to_uppercase()) 28 | }, 29 | DisplayKind::OpaqueLowerCase => quote! { 30 | ::core::write!(f, "{}({})", #name_s, self.0.to_lowercase()) 31 | }, 32 | DisplayKind::Custom(ref fmt) => quote! { 33 | ::core::write!(f, #fmt, self.0) 34 | }, 35 | }; 36 | 37 | quote! { 38 | impl ::core::fmt::Display for #name { 39 | #[inline] 40 | fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 41 | #fmt_impl 42 | } 43 | } 44 | } 45 | } 46 | 47 | pub fn is_display(info: &Info) -> bool { 48 | if info.attrs.force.display { 49 | return true; 50 | } 51 | if info.attrs.skip.display { 52 | return false; 53 | } 54 | 55 | info.kind.is_display() 56 | } 57 | -------------------------------------------------------------------------------- /src/impls/from.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_from(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_from(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | quote! { 13 | impl ::core::convert::From<#typ> for #name { 14 | fn from(t: #typ) -> Self { 15 | Self(::core::convert::From::from(t)) 16 | } 17 | } 18 | 19 | impl ::core::convert::From<#name> for #typ { 20 | fn from(t: #name) -> Self { 21 | t.0 22 | } 23 | } 24 | } 25 | } 26 | 27 | pub fn is_from(info: &Info) -> bool { 28 | if info.attrs.skip.from { 29 | return false; 30 | } 31 | 32 | true 33 | } 34 | -------------------------------------------------------------------------------- /src/impls/from_str.rs: -------------------------------------------------------------------------------- 1 | use crate::info::{Info, Kind}; 2 | use quote::quote; 3 | 4 | pub fn impl_from_str(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_from_str(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | if info.kind == Kind::BoxStr { 13 | quote! { 14 | impl ::core::str::FromStr for #name { 15 | type Err = ::core::convert::Infallible; 16 | 17 | fn from_str(s: &str) -> Result { 18 | Ok(Self(s.into())) 19 | } 20 | } 21 | } 22 | } else { 23 | quote! { 24 | impl ::core::str::FromStr for #name { 25 | type Err = <#typ as ::core::str::FromStr>::Err; 26 | 27 | fn from_str(s: &str) -> Result { 28 | ::core::str::FromStr::from_str(s).map(Self) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | pub fn is_from_str(info: &Info) -> bool { 36 | if info.attrs.force.from_str { 37 | return true; 38 | } 39 | if info.attrs.skip.from_str { 40 | return false; 41 | } 42 | 43 | info.kind.is_from_str() 44 | } 45 | -------------------------------------------------------------------------------- /src/impls/mod.rs: -------------------------------------------------------------------------------- 1 | mod as_ref; 2 | mod deref; 3 | mod deref_mut; 4 | mod deriveable; 5 | mod display; 6 | mod from; 7 | mod from_str; 8 | mod number; 9 | mod string; 10 | mod value; 11 | 12 | pub use self::as_ref::*; 13 | pub use self::deref::*; 14 | pub use self::deref_mut::*; 15 | pub use self::deriveable::*; 16 | pub use self::display::*; 17 | pub use self::from::*; 18 | pub use self::from_str::*; 19 | pub use self::number::*; 20 | pub use self::string::*; 21 | pub use self::value::*; 22 | -------------------------------------------------------------------------------- /src/impls/number.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_number(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_number(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | quote! { 12 | impl ::core::ops::Add<#name> for #name { 13 | type Output = Self; 14 | 15 | fn add(self, other: Self) -> Self::Output { 16 | Self(self.0 + other.0) 17 | } 18 | } 19 | 20 | impl ::core::ops::AddAssign for #name { 21 | fn add_assign(&mut self, other: Self) { 22 | self.0 += other.0; 23 | } 24 | } 25 | 26 | impl ::core::ops::Sub<#name> for #name { 27 | type Output = Self; 28 | 29 | fn sub(self, other: Self) -> Self::Output { 30 | Self(self.0 - other.0) 31 | } 32 | } 33 | 34 | impl ::core::ops::SubAssign for #name { 35 | fn sub_assign(&mut self, other: Self) { 36 | self.0 -= other.0; 37 | } 38 | } 39 | 40 | impl ::core::ops::Mul<#name> for #name { 41 | type Output = Self; 42 | 43 | fn mul(self, other: Self) -> Self::Output { 44 | Self(self.0 * other.0) 45 | } 46 | } 47 | 48 | impl ::core::ops::MulAssign for #name { 49 | fn mul_assign(&mut self, other: Self) { 50 | self.0 *= other.0; 51 | } 52 | } 53 | 54 | impl ::core::ops::Div<#name> for #name { 55 | type Output = Self; 56 | 57 | fn div(self, other: Self) -> Self::Output { 58 | Self(self.0 / other.0) 59 | } 60 | } 61 | 62 | impl ::core::ops::DivAssign for #name { 63 | fn div_assign(&mut self, other: Self) { 64 | self.0 /= other.0; 65 | } 66 | } 67 | } 68 | } 69 | 70 | pub fn is_number(info: &Info) -> bool { 71 | if info.attrs.force.number { 72 | return true; 73 | } 74 | if info.attrs.skip.number { 75 | return false; 76 | } 77 | 78 | info.kind.is_number() 79 | } 80 | -------------------------------------------------------------------------------- /src/impls/string.rs: -------------------------------------------------------------------------------- 1 | use crate::info::{Info, Kind}; 2 | use quote::quote; 3 | 4 | pub fn impl_string(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_string(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | 11 | let mut tokens = quote! { 12 | impl ::core::borrow::Borrow for #name { 13 | fn borrow(&self) -> &str { 14 | &self.0 15 | } 16 | } 17 | 18 | impl #name { 19 | #[allow(missing_docs)] 20 | pub fn as_str(&self) -> &str { 21 | &self.0 22 | } 23 | } 24 | }; 25 | 26 | if info.kind != Kind::StaticStr { 27 | tokens.extend(quote! { 28 | impl<'a> ::core::convert::From<&'a str> for #name { 29 | fn from(s: &'a str) -> Self { 30 | Self(::core::convert::From::from(s)) 31 | } 32 | } 33 | }); 34 | } 35 | 36 | if info.kind == Kind::BoxStr { 37 | tokens.extend(quote! { 38 | impl ::core::convert::From for #name { 39 | fn from(s: String) -> Self { 40 | Self(s.into_boxed_str()) 41 | } 42 | } 43 | }); 44 | } 45 | 46 | tokens 47 | } 48 | 49 | pub fn is_string(info: &Info) -> bool { 50 | if info.attrs.force.string { 51 | return true; 52 | } 53 | if info.attrs.skip.string { 54 | return false; 55 | } 56 | 57 | info.kind.is_string() 58 | } 59 | -------------------------------------------------------------------------------- /src/impls/value.rs: -------------------------------------------------------------------------------- 1 | use crate::info::Info; 2 | use quote::quote; 3 | 4 | pub fn impl_value(info: &Info) -> proc_macro2::TokenStream { 5 | if !is_value(info) { 6 | return quote! {}; 7 | } 8 | 9 | let name = &info.name; 10 | let typ = &info.typ; 11 | 12 | if info.kind.is_copy() || info.attrs.force.copy { 13 | quote! { 14 | #[allow(missing_docs)] 15 | impl #name { 16 | pub fn value(&self) -> #typ { 17 | self.0 18 | } 19 | } 20 | } 21 | } else { 22 | quote! { 23 | #[allow(missing_docs)] 24 | impl #name { 25 | pub fn value(&self) -> &#typ { 26 | &self.0 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | pub fn is_value(info: &Info) -> bool { 34 | if info.attrs.skip.value { 35 | return false; 36 | } 37 | 38 | true 39 | } 40 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use crate::attrs::Attrs; 2 | use syn::{Ident, Type}; 3 | 4 | #[derive(Debug)] 5 | pub struct Info { 6 | pub name: Ident, 7 | pub kind: Kind, 8 | pub typ: Type, 9 | pub attrs: Attrs, 10 | } 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | pub enum Kind { 14 | Integer, 15 | NonZeroInteger, 16 | Float, 17 | String, 18 | BoxStr, 19 | StaticStr, 20 | Char, 21 | Other, 22 | } 23 | 24 | impl Kind { 25 | pub fn is_clone(&self) -> bool { 26 | match self { 27 | Kind::Integer 28 | | Kind::NonZeroInteger 29 | | Kind::Float 30 | | Kind::String 31 | | Kind::BoxStr 32 | | Kind::StaticStr 33 | | Kind::Char => true, 34 | Kind::Other => false, 35 | } 36 | } 37 | 38 | pub fn is_copy(&self) -> bool { 39 | match self { 40 | Kind::Integer | Kind::NonZeroInteger | Kind::Float | Kind::Char | Kind::StaticStr => { 41 | true 42 | } 43 | Kind::String | Kind::BoxStr | Kind::Other => false, 44 | } 45 | } 46 | 47 | pub fn is_debug(&self) -> bool { 48 | match self { 49 | Kind::Integer 50 | | Kind::NonZeroInteger 51 | | Kind::Float 52 | | Kind::String 53 | | Kind::BoxStr 54 | | Kind::StaticStr 55 | | Kind::Char => true, 56 | Kind::Other => false, 57 | } 58 | } 59 | 60 | pub fn is_default(&self) -> bool { 61 | match self { 62 | Kind::Integer 63 | | Kind::Float 64 | | Kind::String 65 | | Kind::BoxStr 66 | | Kind::StaticStr 67 | | Kind::Char => true, 68 | Kind::NonZeroInteger | Kind::Other => false, 69 | } 70 | } 71 | 72 | #[cfg(any(test, feature = "with_serde"))] 73 | pub fn is_deserialize(&self) -> bool { 74 | match self { 75 | Kind::Integer 76 | | Kind::NonZeroInteger 77 | | Kind::Float 78 | | Kind::String 79 | | Kind::BoxStr 80 | | Kind::Char => true, 81 | Kind::Other | Kind::StaticStr => false, 82 | } 83 | } 84 | 85 | pub fn is_eq(&self) -> bool { 86 | match self { 87 | Kind::Integer 88 | | Kind::NonZeroInteger 89 | | Kind::String 90 | | Kind::BoxStr 91 | | Kind::StaticStr 92 | | Kind::Char => true, 93 | Kind::Float | Kind::Other => false, 94 | } 95 | } 96 | 97 | pub fn is_hash(&self) -> bool { 98 | match self { 99 | Kind::Integer 100 | | Kind::NonZeroInteger 101 | | Kind::String 102 | | Kind::BoxStr 103 | | Kind::StaticStr 104 | | Kind::Char => true, 105 | Kind::Float | Kind::Other => false, 106 | } 107 | } 108 | 109 | pub fn is_ord(&self) -> bool { 110 | match self { 111 | Kind::Integer 112 | | Kind::NonZeroInteger 113 | | Kind::String 114 | | Kind::BoxStr 115 | | Kind::StaticStr 116 | | Kind::Char => true, 117 | Kind::Float | Kind::Other => false, 118 | } 119 | } 120 | 121 | pub fn is_partial_eq(&self) -> bool { 122 | match self { 123 | Kind::Integer 124 | | Kind::NonZeroInteger 125 | | Kind::Float 126 | | Kind::String 127 | | Kind::BoxStr 128 | | Kind::StaticStr 129 | | Kind::Char => true, 130 | Kind::Other => false, 131 | } 132 | } 133 | 134 | pub fn is_partial_ord(&self) -> bool { 135 | match self { 136 | Kind::Integer 137 | | Kind::NonZeroInteger 138 | | Kind::Float 139 | | Kind::String 140 | | Kind::BoxStr 141 | | Kind::StaticStr 142 | | Kind::Char => true, 143 | Kind::Other => false, 144 | } 145 | } 146 | 147 | #[cfg(any(test, feature = "with_serde"))] 148 | pub fn is_serialize(&self) -> bool { 149 | match self { 150 | Kind::Integer 151 | | Kind::NonZeroInteger 152 | | Kind::Float 153 | | Kind::String 154 | | Kind::BoxStr 155 | | Kind::Char => true, 156 | Kind::Other | Kind::StaticStr => false, 157 | } 158 | } 159 | 160 | pub fn is_display(&self) -> bool { 161 | match self { 162 | Kind::Integer 163 | | Kind::NonZeroInteger 164 | | Kind::Float 165 | | Kind::String 166 | | Kind::BoxStr 167 | | Kind::StaticStr 168 | | Kind::Char => true, 169 | Kind::Other => false, 170 | } 171 | } 172 | 173 | pub fn is_from_str(&self) -> bool { 174 | match self { 175 | Kind::Integer 176 | | Kind::NonZeroInteger 177 | | Kind::Float 178 | | Kind::String 179 | | Kind::BoxStr 180 | | Kind::Char => true, 181 | Kind::Other | Kind::StaticStr => false, 182 | } 183 | } 184 | 185 | pub fn is_number(&self) -> bool { 186 | match self { 187 | Kind::Integer | Kind::Float => true, 188 | Kind::NonZeroInteger 189 | | Kind::String 190 | | Kind::BoxStr 191 | | Kind::StaticStr 192 | | Kind::Char 193 | | Kind::Other => false, 194 | } 195 | } 196 | 197 | pub fn is_string(&self) -> bool { 198 | match self { 199 | Kind::String | Kind::BoxStr | Kind::StaticStr => true, 200 | Kind::Integer | Kind::NonZeroInteger | Kind::Float | Kind::Char | Kind::Other => false, 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Overview 2 | //! 3 | //! The `synonym` library is a Rust crate designed to simplify the creation of newtypes. It provides a customizable `#[derive(Synonym)]` macro that automatically implements various traits based on the underlying type of your newtype. This saves you from the boilerplate code usually required when defining newtypes. 4 | //! 5 | //! # Usage 6 | //! 7 | //! To use `synonym`, add it to your Cargo.toml: 8 | //! 9 | //! ```toml 10 | //! [dependencies] 11 | //! synonym = "0.1.5" 12 | //! ``` 13 | //! 14 | //! ## Basic example 15 | //! 16 | //! Import the `Synonym` trait into your Rust file: 17 | //! 18 | //! ```rust 19 | //! use synonym::Synonym; 20 | //! ``` 21 | //! 22 | //! Then, define your newtype and annotate it with `#[derive(Synonym)]:` 23 | //! ```rust 24 | //! #[derive(Synonym)] 25 | //! pub struct MyInt(i32); 26 | //! ``` 27 | //! 28 | //! ## Customization with Attributes 29 | //! You can customize which traits are implemented or skipped using the `#[synonym(skip(...))]` and `#[synonym(force(...))]` attributes: 30 | //! ```rust 31 | //! #[derive(Synonym)] 32 | //! #[synonym(skip(Eq, PartialEq))] 33 | //! pub struct MyString(String); 34 | //! ``` 35 | //! 36 | //! Supported `skip` and `force` values are listed in the *Trait implementation table* below. 37 | //! 38 | //! # Generated code 39 | //! When you use `#[derive(Synonym)]`, the library generates implementations for various traits. Here's a simplified example for a newtype `MyInt(i32)`: 40 | //! ```rust 41 | //! impl Eq for MyInt {} 42 | //! impl PartialEq for MyInt { 43 | //! fn eq(&self, other: &Self) -> bool { 44 | //! self.0 == other.0 45 | //! } 46 | //! } 47 | //! // ... and so on for other traits 48 | //! ``` 49 | //! 50 | //! # Trait implementation table 51 | //! 52 | //! Custom methods 53 | //! 54 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 55 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 56 | //! | .as_str() | String | | | | v | v | v | v | 57 | //! | .value() [2] | Value | v | v | v | v | v | v | v | 58 | //! 59 | //! 60 | //! Conversion 61 | //! 62 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 63 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 64 | //! | AsRef | AsRef | v | v | v | v | v | v | v | 65 | //! | Borrow | String | | | | v | v | v | | 66 | //! | From<&'a str> | String | | | | v | v [4] | | | 67 | //! | From | String | | | | | v | | | 68 | //! | Deref [3] | Deref | | | | | | | | 69 | //! | DerefMut [3] | DerefMut | | | | | | | | 70 | //! | From | From | v | v | v | v | v | v | v | 71 | //! | FromStr | FromStr | v | v | v | v | v | | v | 72 | //! 73 | //! Fundamental traits 74 | //! 75 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 76 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 77 | //! | Clone | Clone | v | v | v | v | v | v | v | 78 | //! | Copy | Copy | v | v | v | | v | v | 79 | //! | Debug | Debug | v | v | v | v | v | v | v | 80 | //! | Default | Default | v | | v | v | v | v | v | 81 | //! | Display [5] | Display | v | v | v | v | v | v | v | 82 | //! | Hash | Hash | v | v | | v | v | v | v | 83 | //! 84 | //! Comparison 85 | //! 86 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 87 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 88 | //! | PartialOrd | PartialOrd | v | v | v | v | v | v | v | 89 | //! | Ord | Ord | v | v | | v | v | v | v | 90 | //! | PartialEq | PartialEq | v | v | v | v | v | v | v | 91 | //! | Eq | Eq | v | v | | v | v | v | v | 92 | //! 93 | //! Serde [6] 94 | //! 95 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 96 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 97 | //! | Serialize | Serialize | v | v | v | v | v | | v | 98 | //! | Deserialize | Deserialize | v | v | v | v | v | | v | 99 | //! 100 | //! Maths [7] 101 | //! 102 | //! | | skip/force | `Integer` [1] | `NonZero*` | `Float` | `String` | `Box` | `&'static str` | `char` | 103 | //! |------------------|-------------|---------------|------------|---------|----------|------------|----------------|--------| 104 | //! | Add=Self | Number | v | | v | | | | 105 | //! | AddAssign | Number | v | | v | | | | 106 | //! | Sub=Self | Number | v | | v | | | | 107 | //! | SubAssign | Number | v | | v | | | | 108 | //! | Mul=Self | Number | v | | v | | | | 109 | //! | MulAssign | Number | v | | v | | | | 110 | //! | Div=Self | Number | v | | v | | | | 111 | //! | DivAssign | Number | v | | v | | | | 112 | //! 113 | //! 114 | //! [1] Integers are: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize` 115 | //! [2] .value() returns `Inner` for `Copy` types and `&Inner` for non-`Copy` types 116 | //! [3] `Deref` and `DerefMut` are never implemented unless they are forced with `#[synonym(force(Deref,DerefMut))]` 117 | //! [4] In constrast to other strings, `FromStr` for `Box` synonyms uses `Inner::From<&'str>` instead of `Inner::FromStr` since there is no `FromStr` implementation for `Box` 118 | //! [5] Display implementation can be configured, see below 119 | //! [6] Only provided when feature `with_serde` is enabled 120 | //! [7] This is subject to change 121 | //! 122 | //! # Fine-tuning 123 | //! 124 | //! ## Display 125 | //! To specify how the Display trait should be implemented, you can use the `#[synonym(display = "...")]` attribute. Here are the available options: 126 | //! 127 | //! * `Opaque`: Formats the output as TypeName(Value). 128 | //! * `Transparent`: Directly uses the inner type's Display implementation. 129 | //! * `UpperCase`: Converts the inner value to uppercase before displaying. 130 | //! * `LowerCase`: Converts the inner value to lowercase before displaying. 131 | //! * `OpaqueUpperCase`: Formats the output as TypeName(VALUE) where VALUE is uppercase. 132 | //! * `OpaqueLowerCase`: Formats the output as TypeName(value) where value is lowercase. 133 | //! * `Custom string`: Allows for a custom format string 134 | //! 135 | //! ### Examples 136 | //! ```rust 137 | //! #[derive(Synonym)] 138 | //! #[synonym(display = "UpperCase")] 139 | //! struct CountryName(String); 140 | //! 141 | //! #[derive(Synonym)] 142 | //! #[synonym(display = "::<> {} <>::")] 143 | //! struct Turbo(String); 144 | //! ``` 145 | //! 146 | //! ## Serde Support 147 | //! 148 | //! To enable Serde support for serialization and deserialization, you'll need to enable the `with_serde` feature flag in your `Cargo.toml`: 149 | //! 150 | //! ```toml 151 | //! [dependencies] 152 | //! synonym = { version = "0.1.5", features = ["with_serde"] } 153 | //! ``` 154 | //! 155 | //! With this feature enabled, the `Serialize` and `Deserialize` traits will be automatically implemented for your type. 156 | //! 157 | //! --- 158 | //! This documentation was generated with the assistance of ChatGPT-4 by OpenAI. 159 | 160 | extern crate proc_macro; 161 | 162 | mod analyze; 163 | mod attrs; 164 | mod impls; 165 | mod info; 166 | 167 | use crate::proc_macro::TokenStream; 168 | use crate::{analyze::analyze, impls::*}; 169 | use quote::quote; 170 | use quote::TokenStreamExt; 171 | use syn::{parse_macro_input, DeriveInput}; 172 | 173 | #[proc_macro_derive(Synonym, attributes(synonym))] 174 | pub fn synonym_derive(input: TokenStream) -> TokenStream { 175 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); 176 | let info = analyze(&input).expect("Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported"); 177 | let mut expanded = quote! {}; 178 | 179 | expanded.append_all(&[ 180 | impl_eq(&info), 181 | impl_partial_eq(&info), 182 | impl_ord(&info), 183 | impl_partial_ord(&info), 184 | impl_clone(&info), 185 | impl_copy(&info), 186 | impl_hash(&info), 187 | impl_default(&info), 188 | impl_debug(&info), 189 | impl_as_ref(&info), 190 | impl_deref(&info), 191 | impl_deref_mut(&info), 192 | impl_from(&info), 193 | impl_from_str(&info), 194 | impl_display(&info), 195 | impl_string(&info), 196 | impl_number(&info), 197 | impl_serialize(&info), 198 | impl_deserialize(&info), 199 | impl_value(&info), 200 | ]); 201 | 202 | TokenStream::from(expanded) 203 | } 204 | -------------------------------------------------------------------------------- /tests/cases/fail-enum.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | pub enum Foo {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/cases/fail-enum.stderr: -------------------------------------------------------------------------------- 1 | error: proc-macro derive panicked 2 | --> $DIR/fail-enum.rs:3:10 3 | | 4 | 3 | #[derive(Synonym)] 5 | | ^^^^^^^ 6 | | 7 | = help: message: Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported 8 | -------------------------------------------------------------------------------- /tests/cases/fail-generics.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | pub struct Foo { 5 | _foo: std::marker::PhantomData 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/cases/fail-generics.stderr: -------------------------------------------------------------------------------- 1 | error: proc-macro derive panicked 2 | --> $DIR/fail-generics.rs:3:10 3 | | 4 | 3 | #[derive(Synonym)] 5 | | ^^^^^^^ 6 | | 7 | = help: message: Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported 8 | -------------------------------------------------------------------------------- /tests/cases/fail-named-field.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | #[allow(dead_code)] 5 | pub struct Foo { _bar: u8 } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/cases/fail-named-field.stderr: -------------------------------------------------------------------------------- 1 | error: proc-macro derive panicked 2 | --> $DIR/fail-named-field.rs:3:10 3 | | 4 | 3 | #[derive(Synonym)] 5 | | ^^^^^^^ 6 | | 7 | = help: message: Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported 8 | -------------------------------------------------------------------------------- /tests/cases/fail-two-fields.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | pub struct Foo(u8, u8); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/cases/fail-two-fields.stderr: -------------------------------------------------------------------------------- 1 | error: proc-macro derive panicked 2 | --> $DIR/fail-two-fields.rs:3:10 3 | | 4 | 3 | #[derive(Synonym)] 5 | | ^^^^^^^ 6 | | 7 | = help: message: Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported 8 | -------------------------------------------------------------------------------- /tests/cases/fail-zero-fields.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | pub struct Foo; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/cases/fail-zero-fields.stderr: -------------------------------------------------------------------------------- 1 | error: proc-macro derive panicked 2 | --> $DIR/fail-zero-fields.rs:3:10 3 | | 4 | 3 | #[derive(Synonym)] 5 | | ^^^^^^^ 6 | | 7 | = help: message: Synonym supports only tuple structs with single unnamed field. Enums, unions and generic type parameters are not supported 8 | -------------------------------------------------------------------------------- /tests/cases/pass-char.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | struct Foo(char); 5 | 6 | fn main() { 7 | check_partial_eq(Foo('x')); 8 | check_eq(Foo('x')); 9 | check_partial_ord(Foo('x')); 10 | check_ord(Foo('x')); 11 | check_clone(Foo('x')); 12 | check_copy(Foo('x')); 13 | check_hash(Foo('x')); 14 | check_default(Foo('x')); 15 | check_debug(Foo('x')); 16 | check_display(Foo('x')); 17 | check_as_ref(Foo('x')); 18 | check_from(Foo('x')); 19 | check_from_inner('x'); 20 | check_from_str(Foo('x')); 21 | check_value(Foo('x').value()); 22 | } 23 | 24 | fn check_partial_eq(_: impl PartialEq) {} 25 | fn check_eq(_: impl Eq) {} 26 | fn check_partial_ord(_: impl PartialOrd) {} 27 | fn check_ord(_: impl Ord) {} 28 | fn check_clone(_: impl Clone) {} 29 | fn check_copy(_: impl Copy) {} 30 | fn check_hash(_: impl core::hash::Hash) {} 31 | fn check_default(_: impl Default) {} 32 | fn check_debug(_: impl core::fmt::Debug) {} 33 | fn check_display(_: impl core::fmt::Display) {} 34 | fn check_as_ref(_: impl AsRef) {} 35 | fn check_from(_: impl From) {} 36 | fn check_from_inner(_: impl From) {} 37 | fn check_from_str(_: impl core::str::FromStr) {} 38 | fn check_value(_: char) {} 39 | -------------------------------------------------------------------------------- /tests/cases/pass-display.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym)] 4 | struct TransparentString(String); 5 | 6 | #[derive(Synonym)] 7 | #[synonym(display = "LowerCase")] 8 | struct LowerCaseString(String); 9 | 10 | #[derive(Synonym)] 11 | #[synonym(display = "UpperCase")] 12 | struct UpperCaseString(String); 13 | 14 | #[derive(Synonym)] 15 | #[synonym(display = "opaque")] 16 | struct OpaqueString(String); 17 | 18 | #[derive(Synonym)] 19 | #[synonym(display = "OpaqueLowerCase")] 20 | struct OpaqueLowerCaseString(String); 21 | 22 | #[derive(Synonym)] 23 | #[synonym(display = "OpaqueUpperCase")] 24 | struct OpaqueUpperCaseString(String); 25 | 26 | #[derive(Synonym)] 27 | #[synonym(display = "CustomFormat - {}")] 28 | struct CustomString(String); 29 | 30 | macro_rules! assert { 31 | ($expected:expr, $actual:expr) => ( 32 | if $actual != $expected { 33 | panic!("Assertion failed! Expected: {}, got: {}", $expected, $actual) 34 | } 35 | ) 36 | } 37 | 38 | fn main() { 39 | let transparent = TransparentString::from("transparent"); 40 | let lower_case = LowerCaseString::from("Lower cAse"); 41 | let upper_case = UpperCaseString::from("Upper Case"); 42 | let opaque = OpaqueString::from("opaque"); 43 | let opaque_lower_case = OpaqueLowerCaseString::from("Opaque Lower case"); 44 | let opaque_upper_case = OpaqueUpperCaseString::from("opaque Upper Case"); 45 | let custom = CustomString::from("custom"); 46 | 47 | assert!("transparent", transparent.to_string()); 48 | assert!("lower case", lower_case.to_string()); 49 | assert!("UPPER CASE", upper_case.to_string()); 50 | assert!("OpaqueString(opaque)", opaque.to_string()); 51 | assert!("OpaqueLowerCaseString(opaque lower case)", opaque_lower_case.to_string()); 52 | assert!("OpaqueUpperCaseString(OPAQUE UPPER CASE)", opaque_upper_case.to_string()); 53 | assert!("CustomFormat - custom", custom.to_string()); 54 | } 55 | -------------------------------------------------------------------------------- /tests/cases/pass-floats.rs: -------------------------------------------------------------------------------- 1 | macro_rules! check { 2 | ($t:ty, $v:expr) => { 3 | { 4 | mod dummy { 5 | use synonym::Synonym; 6 | 7 | #[derive(Synonym)] 8 | pub struct Foo(pub $t); 9 | } 10 | 11 | use dummy::Foo; 12 | 13 | fn check_as_ref(_: impl AsRef<$t>) {} 14 | fn check_from(_: impl From<$t>) {} 15 | fn check_from_inner(_: impl From) {} 16 | fn check_value(_: $t) {} 17 | 18 | check_partial_eq(Foo($v)); 19 | check_partial_ord(Foo($v)); 20 | check_clone(Foo($v)); 21 | check_copy(Foo($v)); 22 | check_default(Foo($v)); 23 | check_debug(Foo($v)); 24 | check_display(Foo($v)); 25 | check_as_ref(Foo($v)); 26 | check_from(Foo($v)); 27 | check_from_inner($v); 28 | check_from_str(Foo($v)); 29 | check_value(Foo($v).value()); 30 | check_add(Foo($v)); 31 | check_sub(Foo($v)); 32 | check_mul(Foo($v)); 33 | check_div(Foo($v)); 34 | check_add_assign(Foo($v)); 35 | check_sub_assign(Foo($v)); 36 | check_mul_assign(Foo($v)); 37 | check_div_assign(Foo($v)); 38 | } 39 | } 40 | } 41 | 42 | fn main() { 43 | check!(f32, 1f32); 44 | check!(f64, 1f64); 45 | } 46 | 47 | fn check_partial_eq(_: impl PartialEq) {} 48 | fn check_partial_ord(_: impl PartialOrd) {} 49 | fn check_clone(_: impl Clone) {} 50 | fn check_copy(_: impl Copy) {} 51 | fn check_default(_: impl Default) {} 52 | fn check_debug(_: impl core::fmt::Debug) {} 53 | fn check_display(_: impl core::fmt::Display) {} 54 | fn check_from_str(_: impl core::str::FromStr) {} 55 | fn check_add>(_: T) {} 56 | fn check_sub>(_: T) {} 57 | fn check_mul>(_: T) {} 58 | fn check_div>(_: T) {} 59 | fn check_add_assign(_: impl core::ops::AddAssign) {} 60 | fn check_sub_assign(_: impl core::ops::SubAssign) {} 61 | fn check_mul_assign(_: impl core::ops::MulAssign) {} 62 | fn check_div_assign(_: impl core::ops::DivAssign) {} 63 | -------------------------------------------------------------------------------- /tests/cases/pass-int.rs: -------------------------------------------------------------------------------- 1 | macro_rules! check { 2 | ($t:ty, $v:expr) => { 3 | mod dummy { 4 | use synonym::Synonym; 5 | 6 | #[derive(Synonym)] 7 | pub struct Foo(pub $t); 8 | } 9 | 10 | use dummy::Foo; 11 | 12 | fn check_as_ref(_: impl AsRef<$t>) {} 13 | fn check_from(_: impl From<$t>) {} 14 | fn check_from_inner(_: impl From) {} 15 | fn check_value(_: $t) {} 16 | 17 | check_partial_eq(Foo($v)); 18 | check_eq(Foo($v)); 19 | check_partial_ord(Foo($v)); 20 | check_ord(Foo($v)); 21 | check_clone(Foo($v)); 22 | check_copy(Foo($v)); 23 | check_hash(Foo($v)); 24 | check_debug(Foo($v)); 25 | check_display(Foo($v)); 26 | check_as_ref(Foo($v)); 27 | check_from(Foo($v)); 28 | check_from_inner($v); 29 | check_from_str(Foo($v)); 30 | check_value(Foo($v).value()); 31 | }; 32 | (builtin; $t:ty, $v:expr) => { 33 | { 34 | check!($t, $v); 35 | check_default(Foo($v)); 36 | check_add(Foo($v)); 37 | check_sub(Foo($v)); 38 | check_mul(Foo($v)); 39 | check_div(Foo($v)); 40 | check_add_assign(Foo($v)); 41 | check_sub_assign(Foo($v)); 42 | check_mul_assign(Foo($v)); 43 | check_div_assign(Foo($v)); 44 | } 45 | }; 46 | (nonzero; $t:ty, $v:expr) => { 47 | { 48 | check!($t, $v); 49 | } 50 | } 51 | } 52 | 53 | fn main() { 54 | check!(builtin; u8, 1u8); 55 | check!(builtin; u16, 1u16); 56 | check!(builtin; u32, 1u32); 57 | check!(builtin; u64, 1u64); 58 | check!(builtin; u128, 1u128); 59 | check!(builtin; usize, 1usize); 60 | 61 | check!(builtin; i8, 1i8); 62 | check!(builtin; i16, 1i16); 63 | check!(builtin; i32, 1i32); 64 | check!(builtin; i64, 1i64); 65 | check!(builtin; i128, 1i128); 66 | check!(builtin; isize, 1isize); 67 | 68 | check!(nonzero; std::num::NonZeroU8, std::num::NonZeroU8::new(1).unwrap()); 69 | check!(nonzero; std::num::NonZeroU16, std::num::NonZeroU16::new(1).unwrap()); 70 | check!(nonzero; std::num::NonZeroU32, std::num::NonZeroU32::new(1).unwrap()); 71 | check!(nonzero; std::num::NonZeroU64, std::num::NonZeroU64::new(1).unwrap()); 72 | check!(nonzero; std::num::NonZeroU128, std::num::NonZeroU128::new(1).unwrap()); 73 | check!(nonzero; std::num::NonZeroUsize, std::num::NonZeroUsize::new(1).unwrap()); 74 | 75 | check!(nonzero; std::num::NonZeroI8, std::num::NonZeroI8::new(1).unwrap()); 76 | check!(nonzero; std::num::NonZeroI16, std::num::NonZeroI16::new(1).unwrap()); 77 | check!(nonzero; std::num::NonZeroI32, std::num::NonZeroI32::new(1).unwrap()); 78 | check!(nonzero; std::num::NonZeroI64, std::num::NonZeroI64::new(1).unwrap()); 79 | check!(nonzero; std::num::NonZeroI128, std::num::NonZeroI128::new(1).unwrap()); 80 | check!(nonzero; std::num::NonZeroIsize, std::num::NonZeroIsize::new(1).unwrap()); 81 | } 82 | 83 | fn check_partial_eq(_: impl PartialEq) {} 84 | fn check_eq(_: impl Eq) {} 85 | fn check_partial_ord(_: impl PartialOrd) {} 86 | fn check_ord(_: impl Ord) {} 87 | fn check_clone(_: impl Clone) {} 88 | fn check_copy(_: impl Copy) {} 89 | fn check_hash(_: impl core::hash::Hash) {} 90 | fn check_default(_: impl Default) {} 91 | fn check_debug(_: impl core::fmt::Debug) {} 92 | fn check_display(_: impl core::fmt::Display) {} 93 | fn check_from_str(_: impl core::str::FromStr) {} 94 | fn check_add>(_: T) {} 95 | fn check_sub>(_: T) {} 96 | fn check_mul>(_: T) {} 97 | fn check_div>(_: T) {} 98 | fn check_add_assign(_: impl core::ops::AddAssign) {} 99 | fn check_sub_assign(_: impl core::ops::SubAssign) {} 100 | fn check_mul_assign(_: impl core::ops::MulAssign) {} 101 | fn check_div_assign(_: impl core::ops::DivAssign) {} -------------------------------------------------------------------------------- /tests/cases/pass-lint.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | /// Verifies that derived code is compatible with `#[deny(missing_docs)]`. See 4 | /// https://github.com/synek317/synonym/pull/10 for further context. 5 | 6 | // Custom methods (all are implemented for char/String) 7 | 8 | /// Copy struct that triggers synonym custom methods 9 | #[deny(missing_docs)] 10 | #[derive(Synonym)] 11 | pub struct CustomMethodCopyTest(char); 12 | 13 | /// Non-Copy struct that triggers synonym custom methods 14 | #[deny(missing_docs)] 15 | #[derive(Synonym)] 16 | pub struct CustomMethodNonCopyTest(String); 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /tests/cases/pass-serde.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "with_serde"))] 2 | fn main() { 3 | panic!("This test requires the 'with_serde' feature to be enabled. Run with `cargo test --features with_serde` or `cargo test --all-features`"); 4 | } 5 | 6 | #[cfg(feature = "with_serde")] 7 | fn main() { 8 | use synonym::Synonym; 9 | use serde::{Serialize, Deserialize}; 10 | 11 | macro_rules! check { 12 | ($t:ty, $v:expr, $json:expr) => { 13 | { 14 | #[derive(Synonym)] 15 | struct Foo($t); 16 | 17 | let foo = Foo($v); 18 | let json = serde_json::to_string(&foo).unwrap(); 19 | let deserialized = serde_json::from_str(&json).unwrap(); 20 | 21 | assert_eq!(foo, deserialized); 22 | assert_eq!(json, $json); 23 | } 24 | } 25 | } 26 | 27 | check!(u8, 1u8, "1"); 28 | check!(u16, 2u16, "2"); 29 | check!(u32, 3u32, "3"); 30 | check!(u64, 4u64, "4"); 31 | check!(u128, 5u128, "5"); 32 | check!(usize, 6usize, "6"); 33 | check!(usize, 7usize, "7"); 34 | check!(String, "Foo".to_string(), r#""Foo""#); 35 | check!(Box, "Foo".to_string().into_boxed_str(), r#""Foo""#); 36 | check!(char, 'X', r#""X""#); 37 | 38 | #[derive(Synonym)] 39 | struct Baz(u32); 40 | 41 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 42 | struct Foo { 43 | bar: Baz 44 | } 45 | 46 | let foo = Foo { bar: Baz(42) }; 47 | let json = serde_json::to_string(&foo).unwrap(); 48 | let deserialized = serde_json::from_str(&json).unwrap(); 49 | 50 | assert_eq!(foo, deserialized); 51 | assert_eq!(json, r#"{"bar":42}"#); 52 | } 53 | -------------------------------------------------------------------------------- /tests/cases/pass-skip.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | #[derive(Synonym, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Default, Debug)] 4 | #[synonym(skip(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Default, Debug, AsRef, From, FromStr, Display))] 5 | struct FooU32(u32); 6 | 7 | impl core::convert::AsRef for FooU32 { 8 | fn as_ref(&self) -> &u32 { 9 | unimplemented!() 10 | } 11 | } 12 | 13 | impl core::convert::From for FooU32 { 14 | fn from(_t: u32) -> Self { 15 | unimplemented!() 16 | } 17 | } 18 | 19 | impl core::convert::From for u32 { 20 | fn from(_t: FooU32) -> Self { 21 | unimplemented!() 22 | } 23 | } 24 | 25 | impl ::core::str::FromStr for FooU32 { 26 | type Err = ::Err; 27 | 28 | fn from_str(_s: &str) -> Result { 29 | unimplemented!() 30 | } 31 | } 32 | 33 | impl ::core::fmt::Display for FooU32 { 34 | fn fmt(&self, _f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 35 | unimplemented!() 36 | } 37 | } 38 | 39 | #[derive(Synonym)] 40 | #[synonym(skip(String))] 41 | struct FooString(String); 42 | 43 | impl ::core::borrow::Borrow for FooString { 44 | fn borrow(&self) -> &str { 45 | unimplemented!() 46 | } 47 | } 48 | 49 | impl<'a> ::core::convert::From<&'a str> for FooString { 50 | fn from(_s: &'a str) -> Self { 51 | unimplemented!() 52 | } 53 | } 54 | 55 | fn main() { 56 | let _ = FooU32(42); 57 | } 58 | -------------------------------------------------------------------------------- /tests/cases/pass-string.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | 3 | // define Foo in a module to make sure methods defined directly for Foo (e.g. as_str) are accessible 4 | 5 | macro_rules! check { 6 | ($t:ty, $v:expr) => { 7 | mod dummy { 8 | use synonym::Synonym; 9 | 10 | #[derive(Synonym)] 11 | pub struct Foo(pub $t); 12 | } 13 | 14 | use dummy::Foo; 15 | 16 | fn check_partial_eq(_: impl PartialEq) {} 17 | fn check_eq(_: impl Eq) {} 18 | fn check_partial_ord(_: impl PartialOrd) {} 19 | fn check_ord(_: impl Ord) {} 20 | fn check_clone(_: impl Clone) {} 21 | fn check_hash(_: impl core::hash::Hash) {} 22 | fn check_default(_: impl Default) {} 23 | fn check_debug(_: impl core::fmt::Debug) {} 24 | fn check_display(_: impl core::fmt::Display) {} 25 | fn check_as_ref(_: impl AsRef<$t>) {} 26 | fn check_from(_: impl From<$t>) {} 27 | fn check_from_inner(_: impl From) {} 28 | fn check_as_str(_: &str) {} 29 | fn check_from_string(_: impl From) {} 30 | 31 | check_partial_eq(Foo($v)); 32 | check_eq(Foo($v)); 33 | check_partial_ord(Foo($v)); 34 | check_ord(Foo($v)); 35 | check_clone(Foo($v)); 36 | check_hash(Foo($v)); 37 | check_default(Foo($v)); 38 | check_debug(Foo($v)); 39 | check_display(Foo($v)); 40 | check_as_ref(Foo($v)); 41 | check_from(Foo($v)); 42 | check_from_inner($v); 43 | check_as_str(Foo($v).as_str()); 44 | check_as_str(Foo($v).borrow()); 45 | }; 46 | (non-static str; $t:ty, $v:expr) => { 47 | { 48 | check!($t, $v); 49 | fn check_from_str(_: impl core::str::FromStr) {} 50 | fn check_value(_: &$t) {} 51 | 52 | check_from_str(Foo($v)); 53 | check_value(Foo($v).value()); 54 | check_from_string(Foo($v)); 55 | } 56 | }; 57 | (static str; $t:ty, $v:expr) => { 58 | { 59 | check!($t, $v); 60 | fn check_copy(_: impl core::marker::Copy) {} 61 | fn check_value(_: $t) {} 62 | 63 | check_copy(Foo($v)); 64 | check_value(Foo($v).value()); 65 | } 66 | } 67 | } 68 | 69 | fn main() { 70 | check!(non-static str; String, "x".to_string()); 71 | check!(non-static str; Box, "x".to_string().into_boxed_str()); 72 | check!(static str; &'static str, "x"); 73 | } -------------------------------------------------------------------------------- /tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | 5 | t.pass("tests/cases/pass-*.rs"); 6 | t.compile_fail("tests/cases/fail-*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/synonym.rs: -------------------------------------------------------------------------------- 1 | use synonym::Synonym; 2 | 3 | // see https://rust-lang.github.io/rust-clippy/master/index.html#/incorrect_clone_impl_on_copy_type 4 | // this test only ensures that cargo clippy doesn't show a warning 5 | #[test] 6 | fn clone_derived_() { 7 | #[derive(Synonym)] 8 | struct Foo(u32); 9 | } 10 | --------------------------------------------------------------------------------