Skip to main content

zyn_core/ext/
item.rs

1//! Extension trait for `syn::Item` inspection.
2//!
3//! [`ItemExt`] adds variant predicates, conversions, and common field
4//! accessors to `syn::Item`.
5//!
6//! # Examples
7//!
8//! ```ignore
9//! use zyn::ext::ItemExt;
10//!
11//! if item.is_struct() {
12//!     let s = item.as_struct().unwrap();
13//! }
14//!
15//! let attrs = item.attrs();
16//! let ident = item.ident();
17//! ```
18
19use syn::Item;
20
21/// Extension methods for `syn::Item`.
22///
23/// Provides variant predicates (`is_struct`, `is_enum`, `is_fn`, etc.),
24/// conversions (`as_struct`, `as_enum`, `as_fn`, etc.), and common field
25/// accessors (`attrs`, `ident`, `generics`, `vis`) that work across
26/// all applicable variants.
27///
28/// # Examples
29///
30/// ```ignore
31/// use zyn::ext::ItemExt;
32///
33/// assert!(item.is_struct());
34/// let attrs = item.attrs();
35/// let ident = item.ident().unwrap();
36/// ```
37pub trait ItemExt {
38    /// Returns `true` if this is `Item::Struct`.
39    fn is_struct(&self) -> bool;
40    /// Returns `true` if this is `Item::Enum`.
41    fn is_enum(&self) -> bool;
42    /// Returns `true` if this is `Item::Union`.
43    fn is_union(&self) -> bool;
44    /// Returns `true` if this is `Item::Fn`.
45    fn is_fn(&self) -> bool;
46    /// Returns `true` if this is `Item::Trait`.
47    fn is_trait(&self) -> bool;
48    /// Returns `true` if this is `Item::Impl`.
49    fn is_impl(&self) -> bool;
50    /// Returns `true` if this is `Item::Mod`.
51    fn is_mod(&self) -> bool;
52    /// Returns `true` if this is `Item::Type`.
53    fn is_type(&self) -> bool;
54    /// Returns `true` if this is `Item::Const`.
55    fn is_const(&self) -> bool;
56    /// Returns `true` if this is `Item::Static`.
57    fn is_static(&self) -> bool;
58    /// Returns `true` if this is `Item::Use`.
59    fn is_use(&self) -> bool;
60    /// Returns the inner `syn::ItemStruct` if this is a struct.
61    fn as_struct(&self) -> Option<&syn::ItemStruct>;
62    /// Returns the inner `syn::ItemEnum` if this is an enum.
63    fn as_enum(&self) -> Option<&syn::ItemEnum>;
64    /// Returns the inner `syn::ItemUnion` if this is a union.
65    fn as_union(&self) -> Option<&syn::ItemUnion>;
66    /// Returns the inner `syn::ItemFn` if this is a function.
67    fn as_fn(&self) -> Option<&syn::ItemFn>;
68    /// Returns the inner `syn::ItemTrait` if this is a trait.
69    fn as_trait(&self) -> Option<&syn::ItemTrait>;
70    /// Returns the inner `syn::ItemImpl` if this is an impl block.
71    fn as_impl(&self) -> Option<&syn::ItemImpl>;
72    /// Returns the inner `syn::ItemMod` if this is a module.
73    fn as_mod(&self) -> Option<&syn::ItemMod>;
74    /// Returns the inner `syn::ItemType` if this is a type alias.
75    fn as_type(&self) -> Option<&syn::ItemType>;
76    /// Returns the inner `syn::ItemConst` if this is a constant.
77    fn as_const(&self) -> Option<&syn::ItemConst>;
78    /// Returns the inner `syn::ItemStatic` if this is a static.
79    fn as_static(&self) -> Option<&syn::ItemStatic>;
80    /// Returns the inner `syn::ItemUse` if this is a use declaration.
81    fn as_use(&self) -> Option<&syn::ItemUse>;
82    /// Returns the attributes for this item. All variants have attributes.
83    fn attrs(&self) -> &[syn::Attribute];
84    /// Returns the identifier if this variant has one.
85    /// Returns `None` for `Impl`, `Use`, `ForeignMod`, and `Verbatim`.
86    fn ident(&self) -> Option<&syn::Ident>;
87    /// Returns the generics if this variant has them.
88    /// Returns `None` for variants without generic parameters.
89    fn generics(&self) -> Option<&syn::Generics>;
90    /// Returns the visibility if this variant has one.
91    /// Returns `None` for `Impl`, `ForeignMod`, and `Verbatim`.
92    fn vis(&self) -> Option<&syn::Visibility>;
93    /// Returns the span of this item.
94    fn span(&self) -> proc_macro2::Span;
95}
96
97impl ItemExt for Item {
98    fn is_struct(&self) -> bool {
99        matches!(self, Self::Struct(_))
100    }
101
102    fn is_enum(&self) -> bool {
103        matches!(self, Self::Enum(_))
104    }
105
106    fn is_union(&self) -> bool {
107        matches!(self, Self::Union(_))
108    }
109
110    fn is_fn(&self) -> bool {
111        matches!(self, Self::Fn(_))
112    }
113
114    fn is_trait(&self) -> bool {
115        matches!(self, Self::Trait(_))
116    }
117
118    fn is_impl(&self) -> bool {
119        matches!(self, Self::Impl(_))
120    }
121
122    fn is_mod(&self) -> bool {
123        matches!(self, Self::Mod(_))
124    }
125
126    fn is_type(&self) -> bool {
127        matches!(self, Self::Type(_))
128    }
129
130    fn is_const(&self) -> bool {
131        matches!(self, Self::Const(_))
132    }
133
134    fn is_static(&self) -> bool {
135        matches!(self, Self::Static(_))
136    }
137
138    fn is_use(&self) -> bool {
139        matches!(self, Self::Use(_))
140    }
141
142    fn as_struct(&self) -> Option<&syn::ItemStruct> {
143        match self {
144            Self::Struct(v) => Some(v),
145            _ => None,
146        }
147    }
148
149    fn as_enum(&self) -> Option<&syn::ItemEnum> {
150        match self {
151            Self::Enum(v) => Some(v),
152            _ => None,
153        }
154    }
155
156    fn as_union(&self) -> Option<&syn::ItemUnion> {
157        match self {
158            Self::Union(v) => Some(v),
159            _ => None,
160        }
161    }
162
163    fn as_fn(&self) -> Option<&syn::ItemFn> {
164        match self {
165            Self::Fn(v) => Some(v),
166            _ => None,
167        }
168    }
169
170    fn as_trait(&self) -> Option<&syn::ItemTrait> {
171        match self {
172            Self::Trait(v) => Some(v),
173            _ => None,
174        }
175    }
176
177    fn as_impl(&self) -> Option<&syn::ItemImpl> {
178        match self {
179            Self::Impl(v) => Some(v),
180            _ => None,
181        }
182    }
183
184    fn as_mod(&self) -> Option<&syn::ItemMod> {
185        match self {
186            Self::Mod(v) => Some(v),
187            _ => None,
188        }
189    }
190
191    fn as_type(&self) -> Option<&syn::ItemType> {
192        match self {
193            Self::Type(v) => Some(v),
194            _ => None,
195        }
196    }
197
198    fn as_const(&self) -> Option<&syn::ItemConst> {
199        match self {
200            Self::Const(v) => Some(v),
201            _ => None,
202        }
203    }
204
205    fn as_static(&self) -> Option<&syn::ItemStatic> {
206        match self {
207            Self::Static(v) => Some(v),
208            _ => None,
209        }
210    }
211
212    fn as_use(&self) -> Option<&syn::ItemUse> {
213        match self {
214            Self::Use(v) => Some(v),
215            _ => None,
216        }
217    }
218
219    fn attrs(&self) -> &[syn::Attribute] {
220        match self {
221            Self::Const(v) => &v.attrs,
222            Self::Enum(v) => &v.attrs,
223            Self::ExternCrate(v) => &v.attrs,
224            Self::Fn(v) => &v.attrs,
225            Self::ForeignMod(v) => &v.attrs,
226            Self::Impl(v) => &v.attrs,
227            Self::Mod(v) => &v.attrs,
228            Self::Static(v) => &v.attrs,
229            Self::Struct(v) => &v.attrs,
230            Self::Trait(v) => &v.attrs,
231            Self::Type(v) => &v.attrs,
232            Self::Union(v) => &v.attrs,
233            Self::Use(v) => &v.attrs,
234            _ => &[],
235        }
236    }
237
238    fn ident(&self) -> Option<&syn::Ident> {
239        match self {
240            Self::Const(v) => Some(&v.ident),
241            Self::Enum(v) => Some(&v.ident),
242            Self::ExternCrate(v) => Some(&v.ident),
243            Self::Fn(v) => Some(&v.sig.ident),
244            Self::Mod(v) => Some(&v.ident),
245            Self::Static(v) => Some(&v.ident),
246            Self::Struct(v) => Some(&v.ident),
247            Self::Trait(v) => Some(&v.ident),
248            Self::Type(v) => Some(&v.ident),
249            Self::Union(v) => Some(&v.ident),
250            _ => None,
251        }
252    }
253
254    fn generics(&self) -> Option<&syn::Generics> {
255        match self {
256            Self::Enum(v) => Some(&v.generics),
257            Self::Fn(v) => Some(&v.sig.generics),
258            Self::Impl(v) => Some(&v.generics),
259            Self::Struct(v) => Some(&v.generics),
260            Self::Trait(v) => Some(&v.generics),
261            Self::Type(v) => Some(&v.generics),
262            Self::Union(v) => Some(&v.generics),
263            _ => None,
264        }
265    }
266
267    fn vis(&self) -> Option<&syn::Visibility> {
268        match self {
269            Self::Const(v) => Some(&v.vis),
270            Self::Enum(v) => Some(&v.vis),
271            Self::ExternCrate(v) => Some(&v.vis),
272            Self::Fn(v) => Some(&v.vis),
273            Self::Mod(v) => Some(&v.vis),
274            Self::Static(v) => Some(&v.vis),
275            Self::Struct(v) => Some(&v.vis),
276            Self::Trait(v) => Some(&v.vis),
277            Self::Type(v) => Some(&v.vis),
278            Self::Union(v) => Some(&v.vis),
279            Self::Use(v) => Some(&v.vis),
280            _ => None,
281        }
282    }
283
284    fn span(&self) -> proc_macro2::Span {
285        use syn::spanned::Spanned;
286        Spanned::span(self)
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    fn item_from(input: &str) -> Item {
295        syn::parse_str(input).unwrap()
296    }
297
298    mod predicates {
299        use super::*;
300
301        #[test]
302        fn struct_variant() {
303            let item = item_from("struct Foo;");
304            assert!(item.is_struct());
305            assert!(!item.is_enum());
306            assert!(!item.is_fn());
307        }
308
309        #[test]
310        fn enum_variant() {
311            let item = item_from("enum Foo { A, B }");
312            assert!(item.is_enum());
313            assert!(!item.is_struct());
314        }
315
316        #[test]
317        fn fn_variant() {
318            let item = item_from("fn foo() {}");
319            assert!(item.is_fn());
320            assert!(!item.is_struct());
321        }
322
323        #[test]
324        fn impl_variant() {
325            let item = item_from("impl Foo {}");
326            assert!(item.is_impl());
327        }
328
329        #[test]
330        fn trait_variant() {
331            let item = item_from("trait Foo {}");
332            assert!(item.is_trait());
333        }
334
335        #[test]
336        fn mod_variant() {
337            let item = item_from("mod foo {}");
338            assert!(item.is_mod());
339        }
340
341        #[test]
342        fn type_variant() {
343            let item = item_from("type Foo = Bar;");
344            assert!(item.is_type());
345        }
346
347        #[test]
348        fn const_variant() {
349            let item = item_from("const X: i32 = 0;");
350            assert!(item.is_const());
351        }
352
353        #[test]
354        fn static_variant() {
355            let item = item_from("static X: i32 = 0;");
356            assert!(item.is_static());
357        }
358
359        #[test]
360        fn use_variant() {
361            let item = item_from("use std::fmt;");
362            assert!(item.is_use());
363        }
364    }
365
366    mod conversions {
367        use super::*;
368
369        #[test]
370        fn as_struct_some() {
371            let item = item_from("struct Foo;");
372            assert!(item.as_struct().is_some());
373        }
374
375        #[test]
376        fn as_struct_none() {
377            let item = item_from("enum Foo { A }");
378            assert!(item.as_struct().is_none());
379        }
380
381        #[test]
382        fn as_enum_some() {
383            let item = item_from("enum Foo { A }");
384            assert!(item.as_enum().is_some());
385        }
386
387        #[test]
388        fn as_fn_some() {
389            let item = item_from("fn foo() {}");
390            assert!(item.as_fn().is_some());
391        }
392
393        #[test]
394        fn as_impl_some() {
395            let item = item_from("impl Foo {}");
396            assert!(item.as_impl().is_some());
397        }
398    }
399
400    mod accessors {
401        use super::*;
402
403        #[test]
404        fn attrs_on_struct() {
405            let item = item_from("#[derive(Clone)] struct Foo;");
406            assert_eq!(item.attrs().len(), 1);
407        }
408
409        #[test]
410        fn attrs_on_fn() {
411            let item = item_from("#[inline] fn foo() {}");
412            assert_eq!(item.attrs().len(), 1);
413        }
414
415        #[test]
416        fn ident_on_struct() {
417            let item = item_from("struct Foo;");
418            assert_eq!(item.ident().unwrap().to_string(), "Foo");
419        }
420
421        #[test]
422        fn ident_on_fn() {
423            let item = item_from("fn bar() {}");
424            assert_eq!(item.ident().unwrap().to_string(), "bar");
425        }
426
427        #[test]
428        fn ident_on_impl_is_none() {
429            let item = item_from("impl Foo {}");
430            assert!(item.ident().is_none());
431        }
432
433        #[test]
434        fn generics_on_struct() {
435            let item = item_from("struct Foo<T> { x: T }");
436            assert!(item.generics().is_some());
437        }
438
439        #[test]
440        fn generics_on_const_is_none() {
441            let item = item_from("const X: i32 = 0;");
442            assert!(item.generics().is_none());
443        }
444
445        #[test]
446        fn vis_on_pub_struct() {
447            let item = item_from("pub struct Foo;");
448            let vis = item.vis().unwrap();
449            assert!(matches!(vis, syn::Visibility::Public(_)));
450        }
451
452        #[test]
453        fn vis_on_impl_is_none() {
454            let item = item_from("impl Foo {}");
455            assert!(item.vis().is_none());
456        }
457    }
458}