Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 5e360c1

Browse files
authored
Merge pull request #349 from tgross35/better-typing
Migrate types from a macro to a trait
2 parents 0d810ed + 7c41830 commit 5e360c1

File tree

14 files changed

+747
-380
lines changed

14 files changed

+747
-380
lines changed

crates/libm-macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ publish = false
88
proc-macro = true
99

1010
[dependencies]
11+
heck = "0.5.0"
1112
proc-macro2 = "1.0.88"
1213
quote = "1.0.37"
1314
syn = { version = "2.0.79", features = ["full", "extra-traits", "visit-mut"] }

crates/libm-macros/src/enums.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use heck::ToUpperCamelCase;
2+
use proc_macro2 as pm2;
3+
use proc_macro2::{Ident, Span};
4+
use quote::quote;
5+
use syn::spanned::Spanned;
6+
use syn::{Fields, ItemEnum, Variant};
7+
8+
use crate::{ALL_FUNCTIONS_FLAT, base_name};
9+
10+
/// Implement `#[function_enum]`, see documentation in `lib.rs`.
11+
pub fn function_enum(
12+
mut item: ItemEnum,
13+
attributes: pm2::TokenStream,
14+
) -> syn::Result<pm2::TokenStream> {
15+
expect_empty_enum(&item)?;
16+
let attr_span = attributes.span();
17+
let mut attr = attributes.into_iter();
18+
19+
// Attribute should be the identifier of the `BaseName` enum.
20+
let Some(tt) = attr.next() else {
21+
return Err(syn::Error::new(attr_span, "expected one attribute"));
22+
};
23+
24+
let pm2::TokenTree::Ident(base_enum) = tt else {
25+
return Err(syn::Error::new(tt.span(), "expected an identifier"));
26+
};
27+
28+
if let Some(tt) = attr.next() {
29+
return Err(syn::Error::new(tt.span(), "unexpected token after identifier"));
30+
}
31+
32+
let enum_name = &item.ident;
33+
let mut as_str_arms = Vec::new();
34+
let mut base_arms = Vec::new();
35+
36+
for func in ALL_FUNCTIONS_FLAT.iter() {
37+
let fn_name = func.name;
38+
let ident = Ident::new(&fn_name.to_upper_camel_case(), Span::call_site());
39+
let bname_ident = Ident::new(&base_name(fn_name).to_upper_camel_case(), Span::call_site());
40+
41+
// Match arm for `fn as_str(self)` matcher
42+
as_str_arms.push(quote! { Self::#ident => #fn_name });
43+
44+
// Match arm for `fn base_name(self)` matcher
45+
base_arms.push(quote! { Self::#ident => #base_enum::#bname_ident });
46+
47+
let variant =
48+
Variant { attrs: Vec::new(), ident, fields: Fields::Unit, discriminant: None };
49+
50+
item.variants.push(variant);
51+
}
52+
53+
let res = quote! {
54+
// Instantiate the enum
55+
#item
56+
57+
impl #enum_name {
58+
/// The stringified version of this function name.
59+
const fn as_str(self) -> &'static str {
60+
match self {
61+
#( #as_str_arms , )*
62+
}
63+
}
64+
65+
/// The base name enum for this function.
66+
const fn base_name(self) -> #base_enum {
67+
match self {
68+
#( #base_arms, )*
69+
}
70+
}
71+
}
72+
};
73+
74+
Ok(res)
75+
}
76+
77+
/// Implement `#[base_name_enum]`, see documentation in `lib.rs`.
78+
pub fn base_name_enum(
79+
mut item: ItemEnum,
80+
attributes: pm2::TokenStream,
81+
) -> syn::Result<pm2::TokenStream> {
82+
expect_empty_enum(&item)?;
83+
if !attributes.is_empty() {
84+
let sp = attributes.span();
85+
return Err(syn::Error::new(sp.span(), "no attributes expected"));
86+
}
87+
88+
let mut base_names: Vec<_> =
89+
ALL_FUNCTIONS_FLAT.iter().map(|func| base_name(func.name)).collect();
90+
base_names.sort_unstable();
91+
base_names.dedup();
92+
93+
let item_name = &item.ident;
94+
let mut as_str_arms = Vec::new();
95+
96+
for base_name in base_names {
97+
let ident = Ident::new(&base_name.to_upper_camel_case(), Span::call_site());
98+
99+
// Match arm for `fn as_str(self)` matcher
100+
as_str_arms.push(quote! { Self::#ident => #base_name });
101+
102+
let variant =
103+
Variant { attrs: Vec::new(), ident, fields: Fields::Unit, discriminant: None };
104+
105+
item.variants.push(variant);
106+
}
107+
108+
let res = quote! {
109+
// Instantiate the enum
110+
#item
111+
112+
impl #item_name {
113+
/// The stringified version of this base name.
114+
const fn as_str(self) -> &'static str {
115+
match self {
116+
#( #as_str_arms ),*
117+
}
118+
}
119+
}
120+
};
121+
122+
Ok(res)
123+
}
124+
125+
/// Verify that an enum is empty, otherwise return an error
126+
fn expect_empty_enum(item: &ItemEnum) -> syn::Result<()> {
127+
if !item.variants.is_empty() {
128+
Err(syn::Error::new(item.variants.span(), "expected an empty enum"))
129+
} else {
130+
Ok(())
131+
}
132+
}

0 commit comments

Comments
 (0)