Skip to content

Commit 2d4f6a1

Browse files
committed
feat: add Variant trait and derive macro
0 parents  commit 2d4f6a1

File tree

6 files changed

+108
-0
lines changed

6 files changed

+108
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
/Cargo.lock

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "tagname"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
[dependencies]
8+
tagname_derive = { path = "tagname_derive" }
9+
10+
[workspace]
11+
members = ["tagname_derive"]

src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub use tagname_derive::Variant;
2+
3+
pub trait Variant {
4+
fn tag_name(&self) -> &'static str;
5+
}

tagname_derive/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "tagname_derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
syn = "1.0"
13+
quote = "1.0"
14+
proc-macro2 = "1.0.47"
15+

tagname_derive/src/lib.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::{Ident, Variant};
4+
5+
#[proc_macro_derive(Variant)]
6+
pub fn variant_derive(input: TokenStream) -> TokenStream {
7+
let ast = syn::parse(input).unwrap();
8+
9+
// Build the trait implementation
10+
impl_variant_derive_macro(ast)
11+
}
12+
13+
fn impl_variant_derive_macro(ast: syn::DeriveInput) -> TokenStream {
14+
let name = &ast.ident;
15+
let variants: Vec<Variant> = match ast.data {
16+
syn::Data::Enum(enum_data) => enum_data
17+
.variants
18+
.into_pairs()
19+
.into_iter()
20+
.map(|pair| pair.into_value())
21+
.collect(),
22+
_ => panic!("cannot derive Variant for non-enum types"),
23+
};
24+
25+
let variants_with_fields = variants.iter().cloned().filter(|v| !v.fields.is_empty());
26+
let variants_without_fields = variants.iter().cloned().filter(|v| v.fields.is_empty());
27+
let with_field_tags: Vec<Ident> = variants_with_fields.into_iter().map(|v| v.ident).collect();
28+
let no_field_tags: Vec<Ident> = variants_without_fields
29+
.into_iter()
30+
.map(|v| v.ident)
31+
.collect();
32+
33+
let gen = quote! {
34+
impl Variant for #name {
35+
fn tag_name(&self) -> &'static str {
36+
match self {
37+
#(
38+
#name::#with_field_tags(_) => stringify!(#with_field_tags)
39+
),*,
40+
#(
41+
#name::#no_field_tags => stringify!(#no_field_tags)
42+
),*
43+
}
44+
}
45+
}
46+
};
47+
gen.into()
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
#[test]
53+
fn it_works() {
54+
let result = 2 + 2;
55+
assert_eq!(result, 4);
56+
}
57+
}

tests/variant.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use tagname::Variant;
2+
3+
#[derive(Variant)]
4+
enum MyVariant {
5+
Yes,
6+
No,
7+
Maybe(usize),
8+
}
9+
10+
#[test]
11+
fn return_correct_tag_names() {
12+
let v1 = MyVariant::Yes;
13+
let v2 = MyVariant::No;
14+
let v3 = MyVariant::Maybe(1);
15+
assert_eq!(v1.tag_name(), "Yes");
16+
assert_eq!(v2.tag_name(), "No");
17+
assert_eq!(v3.tag_name(), "Maybe");
18+
}

0 commit comments

Comments
 (0)