enum-kinds-0.5.1/.cargo_vcs_info.json0000644000000001120000000000100131030ustar { "git": { "sha1": "ef239d81fb5088227b7f459ca8cc5e0ce8ad1621" } } enum-kinds-0.5.1/Cargo.toml0000644000000024540000000000100111140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "enum-kinds" version = "0.5.1" authors = ["Samuel Laurén "] description = "Generate enums with matching variants but without any of the associated data." homepage = "https://github.com/Soft/enum-kinds" readme = "README.md" keywords = ["macro", "enum", "derive", "proc-macro", "deriving"] license = "MIT" repository = "https://github.com/Soft/enum-kinds" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.28" [dependencies.quote] version = "1.0.9" [dependencies.syn] version = "1.0.74" [dev-dependencies.serde] version = "1.0.127" [dev-dependencies.serde_derive] version = "1.0.127" [dev-dependencies.serde_json] version = "1.0.66" [features] default = [] no-stdlib = [] [badges.travis-ci] repository = "Soft/enum-kinds" enum-kinds-0.5.1/Cargo.toml.orig000064400000000000000000000012600072674642500146170ustar 00000000000000[package] name = "enum-kinds" version = "0.5.1" authors = ["Samuel Laurén "] homepage = "https://github.com/Soft/enum-kinds" repository = "https://github.com/Soft/enum-kinds" description = "Generate enums with matching variants but without any of the associated data." readme = "README.md" license = "MIT" keywords = ["macro", "enum", "derive", "proc-macro", "deriving"] edition = "2018" [lib] proc-macro = true [dependencies] syn = "1.0.74" quote = "1.0.9" proc-macro2 = "1.0.28" [dev-dependencies] serde = "1.0.127" serde_derive = "1.0.127" serde_json = "1.0.66" [features] default = [] no-stdlib = [] [badges] travis-ci = { repository = "Soft/enum-kinds" } enum-kinds-0.5.1/README.md000064400000000000000000000046420072674642500132160ustar 00000000000000# enum-kinds [![Build Status](https://api.travis-ci.org/Soft/enum-kinds.svg?branch=master)](https://travis-ci.org/Soft/enum-kinds) [![Latest Version](https://img.shields.io/crates/v/enum-kinds.svg)](https://crates.io/crates/enum-kinds) [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/enum-kinds) Custom derive for generating enums with matching variants but without any of the data. In other words, `enum-kinds` automatically generates enums that have the same set of variants as the original enum, but with all the embedded data stripped away (that is, all the variants of the newly-generated enum are unit variants). Additionally, `enum-kinds` implements `From` trait for going from the original enum to the unit variant version. The crate is compatible with stable Rust releases. This crate replaces earlier `enum_kinds_macros` and `enum_kinds_traits` crates. # Example ```rust,ignore #[macro_use] extern crate enum_kinds; #[derive(EnumKind)] #[enum_kind(SomeEnumKind)] enum SomeEnum { First(String, u32), Second(char), Third } #[test] fn test_enum_kind() { let first = SomeEnum::First("Example".to_owned(), 32); assert_eq!(SomeEnumKind::from(&first), SomeEnumKind::First); } ``` The `#[derive(EnumKind)]` attribute automatically creates another `enum` named `SomeEnumKind` that contains matching unit variant for each of the variants in `SomeEnum`. # Additional Attributes for Generated Enums By default, derived kind enums implement `Debug`, `Clone`, `Copy`, `PartialEq` and `Eq` traits. Additional attributes can be attached to the generated `enum` by including them to the `enum_kind` attribute: `#[enum_kind(NAME, derive(SomeTrait), derive(AnotherTrait))]`. For example, to implement [Serde's](https://serde.rs) Serialize and Deserialize traits: ``` rust,ignore #[macro_use] extern crate enum_kinds; #[macro_use] extern crate serde_derive; extern crate serde; #[derive(EnumKind)] #[enum_kind(AdditionalDerivesKind, derive(Serialize, Deserialize))] enum AdditionalDerives { Variant(String, u32), Another(String) } ``` # no_std support `enum-kinds` can be used without the standard library by enabling `no-stdlib` feature. # Issues If you encounter any problems using the crate, please report them at [the issue tracker](https://github.com/Soft/enum-kinds/issues). # License The crate is available under the terms of [MIT license](https://opensource.org/licenses/MIT). enum-kinds-0.5.1/src/lib.rs000075500000000000000000000126470072674642500136510ustar 00000000000000#![doc = include_str!("../README.md")] extern crate proc_macro; extern crate proc_macro2; extern crate quote; #[macro_use] extern crate syn; use proc_macro2::TokenStream; use quote::quote; use std::collections::HashSet; use syn::punctuated::Punctuated; use syn::{ Data, DataEnum, DeriveInput, Fields, GenericParam, Lifetime, LifetimeDef, Meta, MetaList, MetaNameValue, NestedMeta, Path, }; #[proc_macro_derive(EnumKind, attributes(enum_kind))] pub fn enum_kind(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).expect("#[derive(EnumKind)] failed to parse input"); let (name, traits) = get_enum_specification(&ast); let enum_ = create_kind_enum(&ast, &name, traits); let impl_ = create_impl(&ast, &name); let code = quote! { #enum_ #impl_ }; proc_macro::TokenStream::from(code) } fn find_attribute( definition: &DeriveInput, name: &str, ) -> Option> { for attr in definition.attrs.iter() { match attr.parse_meta() { Ok(Meta::List(MetaList { ref path, ref nested, .. })) if path.is_ident(name) => return Some(nested.clone()), _ => continue, } } None } fn get_enum_specification(definition: &DeriveInput) -> (Path, Vec) { let params = find_attribute(definition, "enum_kind") .expect("#[derive(EnumKind)] requires an associated enum_kind attribute to be specified"); let mut iter = params.iter(); if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = iter.next() { return (path.to_owned(), iter.cloned().collect()); } else { panic!("#[enum_kind(NAME)] attribute requires NAME to be specified"); } } fn has_docs(traits: &[NestedMeta]) -> bool { traits.iter().any(|attr| { if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, .. })) = attr { path.is_ident("doc") } else { false } }) } fn create_kind_enum( definition: &DeriveInput, kind_ident: &Path, traits: Vec, ) -> TokenStream { let variant_idents = match &definition.data { &Data::Enum(DataEnum { ref variants, .. }) => variants.iter().map(|ref v| v.ident.clone()), _ => { panic!("#[derive(EnumKind)] is only allowed for enums"); } }; let visibility = &definition.vis; let docs_attr = if !has_docs(traits.as_ref()) { quote! {#[allow(missing_docs)]} } else { quote! {} }; let code = quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] #docs_attr #( #[#traits] )* #visibility enum #kind_ident { #(#variant_idents),* } }; TokenStream::from(code) } fn is_uninhabited_enum(definition: &DeriveInput) -> bool { if let Data::Enum(ref data) = definition.data { return data.variants.len() == 0; } return false; } fn create_impl(definition: &DeriveInput, kind_ident: &Path) -> TokenStream { let (_, ty_generics, where_clause) = definition.generics.split_for_impl(); let ident = &definition.ident; let arms = match &definition.data { &Data::Enum(DataEnum { ref variants, .. }) => variants.iter().map(|ref v| { let variant = &v.ident; match v.fields { Fields::Unit => quote! { &#ident::#variant => #kind_ident::#variant, }, Fields::Unnamed(_) => quote! { &#ident::#variant(..) => #kind_ident::#variant, }, Fields::Named(_) => quote! { &#ident::#variant{..} => #kind_ident::#variant, }, } }), _ => { panic!("#[derive(EnumKind)] is only allowed for enums"); } }; let trait_: Path = if cfg!(feature = "no-stdlib") { parse_quote!(::core::convert::From) } else { parse_quote!(::std::convert::From) }; let mut counter: u32 = 1; let used: HashSet = definition .generics .lifetimes() .map(|ld| ld.lifetime.clone()) .collect(); let a = loop { let lifetime: Lifetime = syn::parse_str(&format!("'__enum_kinds{}", counter)).unwrap(); if !used.contains(&lifetime) { break LifetimeDef::new(lifetime); } counter += 1; }; let mut generics = definition.generics.clone(); generics.params.insert(0, GenericParam::Lifetime(a.clone())); let (impl_generics, _, _) = generics.split_for_impl(); let impl_ = if is_uninhabited_enum(definition) { quote! { unreachable!(); } } else { quote! { match _value { #(#arms)* } } }; let tokens = quote! { #[automatically_derived] #[allow(unused_attributes)] impl #impl_generics #trait_<&#a #ident#ty_generics> for #kind_ident #where_clause { fn from(_value: &#a #ident#ty_generics) -> Self { #impl_ } } #[automatically_derived] #[allow(unused_attributes)] impl #impl_generics #trait_<#ident#ty_generics> for #kind_ident #where_clause { fn from(value: #ident#ty_generics) -> Self { #kind_ident::from(&value) } } }; TokenStream::from(tokens) } enum-kinds-0.5.1/tests/kinds.rs000064400000000000000000000054610072674642500145570ustar 00000000000000#[macro_use] extern crate enum_kinds; use std::fmt::Debug; #[macro_use] extern crate serde_derive; extern crate serde; extern crate serde_json; #[derive(EnumKind)] #[enum_kind(UnnamedEnumKind)] #[allow(dead_code)] enum UnnamedEnum { First(String, u32), Second(char), Third, } #[derive(EnumKind)] #[enum_kind(NamedEnumKind)] #[allow(dead_code)] enum NamedEnum { Foo { foo: String, bar: u32 }, Bar { zap: char }, } #[derive(EnumKind)] #[enum_kind(WithLifetimeKind)] #[allow(dead_code)] enum WithLifetime<'a> { First(&'a str), } #[derive(EnumKind)] #[enum_kind(WithWhereClauseKind)] #[allow(dead_code)] enum WithWhereClause<'b, T> where T: Debug, T: 'b, T: ?Sized, { First { value: &'b T }, } #[derive(EnumKind)] #[enum_kind(WithCollisionKind)] #[allow(dead_code)] enum WithCollision<'__enum_kinds1> { First(&'__enum_kinds1 str), } #[derive(EnumKind)] #[enum_kind(UninhabitedEnumKind)] #[allow(dead_code)] enum UninhabitedEnum {} #[derive(EnumKind)] #[enum_kind(WithExtraTraitsKind, derive(Serialize, Deserialize))] #[allow(dead_code)] enum WithExtraTraits { First(u32, u32), Second(String), } #[derive(EnumKind)] #[enum_kind(WithExtraTraitsMultipleKind, derive(Serialize), derive(Deserialize))] #[allow(dead_code)] enum WithExtraTraitsMultiple { First(u32, u32), Second(String), } mod forbids_missing_docs { #![forbid(missing_docs)] #[derive(EnumKind)] #[enum_kind(WithDocumentationKind, doc = "a documented kind enum")] #[allow(dead_code)] enum WithDocumentation { First(u32, u32), Second(String), } } #[test] fn test_unnamed() { let first = UnnamedEnum::First("Example".to_owned(), 32); assert_eq!(UnnamedEnumKind::from(first), UnnamedEnumKind::First); } #[test] fn test_named() { let foo = NamedEnum::Foo { foo: "Example".to_owned(), bar: 32, }; assert_eq!(NamedEnumKind::from(&foo), NamedEnumKind::Foo); } #[test] fn test_with_lifetimes() { let first = WithLifetime::First("hello"); assert_eq!(WithLifetimeKind::from(&first), WithLifetimeKind::First); } #[test] fn test_with_where_clause() { let first = WithWhereClause::First { value: "hello" }; assert_eq!( WithWhereClauseKind::from(&first), WithWhereClauseKind::First ); } #[test] fn test_with_collision() { let first = WithCollision::First("hello"); assert_eq!(WithCollisionKind::from(&first), WithCollisionKind::First); } #[test] fn test_with_extra_traits() { let first = WithExtraTraits::First(20, 30); let kind: WithExtraTraitsKind = first.into(); serde_json::to_string(&kind).unwrap(); } #[test] fn test_with_extra_traits_multiple() { let first = WithExtraTraitsMultiple::First(20, 30); let kind: WithExtraTraitsMultipleKind = first.into(); serde_json::to_string(&kind).unwrap(); }