inkwell_internals/
enum.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3use syn::fold::Fold;
4use syn::parse::{Error, Parse, ParseStream, Result};
5use syn::parse_quote;
6use syn::spanned::Spanned;
7use syn::{Arm, PatPath, Path};
8use syn::{Attribute, Ident, Variant};
9
10/// Used to track an enum variant and its corresponding mappings (LLVM <-> Rust),
11/// as well as attributes
12struct EnumVariant {
13    llvm_variant: Ident,
14    rust_variant: Ident,
15    attrs: Vec<Attribute>,
16}
17
18impl EnumVariant {
19    fn new(variant: &Variant) -> Self {
20        let rust_variant = variant.ident.clone();
21        let llvm_variant = Ident::new(&format!("LLVM{}", rust_variant), variant.span());
22        let mut attrs = variant.attrs.clone();
23        attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
24        Self {
25            llvm_variant,
26            rust_variant,
27            attrs,
28        }
29    }
30
31    fn with_name(variant: &Variant, mut llvm_variant: Ident) -> Self {
32        let rust_variant = variant.ident.clone();
33        llvm_variant.set_span(rust_variant.span());
34        let mut attrs = variant.attrs.clone();
35        attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
36        Self {
37            llvm_variant,
38            rust_variant,
39            attrs,
40        }
41    }
42}
43
44/// Used when constructing the variants of an enum declaration.
45#[derive(Default)]
46struct EnumVariants {
47    variants: Vec<EnumVariant>,
48    error: Option<Error>,
49}
50
51impl EnumVariants {
52    #[inline]
53    fn len(&self) -> usize {
54        self.variants.len()
55    }
56
57    #[inline]
58    fn iter(&self) -> core::slice::Iter<'_, EnumVariant> {
59        self.variants.iter()
60    }
61
62    #[inline]
63    fn has_error(&self) -> bool {
64        self.error.is_some()
65    }
66
67    #[inline]
68    fn set_error(&mut self, err: &str, span: Span) {
69        self.error = Some(Error::new(span, err));
70    }
71
72    fn into_error(self) -> Error {
73        self.error.unwrap()
74    }
75}
76
77impl Fold for EnumVariants {
78    fn fold_variant(&mut self, mut variant: Variant) -> Variant {
79        use syn::Meta;
80
81        if self.has_error() {
82            return variant;
83        }
84
85        // Check for llvm_variant
86        if let Some(attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("llvm_variant")) {
87            // Extract attribute meta
88            if let Meta::List(meta) = &attr.meta {
89                // We should only have one element
90
91                if let Ok(Meta::Path(name)) = meta.parse_args() {
92                    self.variants
93                        .push(EnumVariant::with_name(&variant, name.get_ident().unwrap().clone()));
94                    // Strip the llvm_variant attribute from the final AST
95                    variant.attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
96                    return variant;
97                }
98            }
99
100            // If at any point we fall through to here, it is the same basic issue, invalid format
101            self.set_error("expected #[llvm_variant(VARIANT_NAME)]", attr.span());
102            return variant;
103        }
104
105        self.variants.push(EnumVariant::new(&variant));
106        variant
107    }
108}
109
110/// Used to parse an enum declaration decorated with `#[llvm_enum(..)]`
111pub struct LLVMEnumType {
112    name: Ident,
113    decl: syn::ItemEnum,
114    variants: EnumVariants,
115}
116
117impl Parse for LLVMEnumType {
118    fn parse(input: ParseStream) -> Result<Self> {
119        // Parse enum declaration
120        let decl = input.parse::<syn::ItemEnum>()?;
121        let name = decl.ident.clone();
122
123        // Fold over variants and expand llvm_versions
124        let decl = crate::cfg::VersionFolder::fold_any(Fold::fold_item_enum, decl)?;
125
126        let mut variants = EnumVariants::default();
127        let decl = variants.fold_item_enum(decl);
128        if variants.has_error() {
129            return Err(variants.into_error());
130        }
131
132        Ok(Self { name, decl, variants })
133    }
134}
135
136pub fn llvm_enum(llvm_ty: Path, llvm_enum_type: LLVMEnumType) -> TokenStream {
137    // Construct match arms for LLVM -> Rust enum conversion
138    let mut from_arms = Vec::with_capacity(llvm_enum_type.variants.len());
139    for variant in llvm_enum_type.variants.iter() {
140        let src_variant = variant.llvm_variant.clone();
141        // Filter out doc comments or else rustc will warn about docs on match arms in newer versions.
142        let src_attrs: Vec<_> = variant
143            .attrs
144            .iter()
145            .filter(|&attr| !attr.meta.path().is_ident("doc"))
146            .collect();
147        let src_ty = llvm_ty.clone();
148        let dst_variant = variant.rust_variant.clone();
149        let dst_ty = llvm_enum_type.name.clone();
150
151        let pat = PatPath {
152            attrs: Vec::new(),
153            qself: None,
154            path: parse_quote!(#src_ty::#src_variant),
155        };
156
157        let arm: Arm = parse_quote! {
158            #(#src_attrs)*
159            #pat => { #dst_ty::#dst_variant }
160        };
161        from_arms.push(arm);
162    }
163
164    // Construct match arms for Rust -> LLVM enum conversion
165    let mut to_arms = Vec::with_capacity(llvm_enum_type.variants.len());
166    for variant in llvm_enum_type.variants.iter() {
167        let src_variant = variant.rust_variant.clone();
168        // Filter out doc comments or else rustc will warn about docs on match arms in newer versions.
169        let src_attrs: Vec<_> = variant
170            .attrs
171            .iter()
172            .filter(|&attr| !attr.meta.path().is_ident("doc"))
173            .collect();
174        let src_ty = llvm_enum_type.name.clone();
175        let dst_variant = variant.llvm_variant.clone();
176        let dst_ty = llvm_ty.clone();
177
178        let pat = PatPath {
179            attrs: Vec::new(),
180            qself: None,
181            path: parse_quote!(#src_ty::#src_variant),
182        };
183
184        let arm: Arm = parse_quote! {
185            #(#src_attrs)*
186            #pat => { #dst_ty::#dst_variant }
187        };
188        to_arms.push(arm);
189    }
190
191    let enum_ty = llvm_enum_type.name.clone();
192    let enum_decl = llvm_enum_type.decl;
193
194    quote! {
195        #enum_decl
196
197        impl #enum_ty {
198            fn new(src: #llvm_ty) -> Self {
199                match src {
200                    #(#from_arms)*
201                }
202            }
203        }
204        impl From<#llvm_ty> for #enum_ty {
205            fn from(src: #llvm_ty) -> Self {
206                Self::new(src)
207            }
208        }
209        impl Into<#llvm_ty> for #enum_ty {
210            fn into(self) -> #llvm_ty {
211                match self {
212                    #(#to_arms),*
213                }
214            }
215        }
216    }
217}