1 | //! Field parsing. |
2 | |
3 | use std::fmt::{self, Display, Formatter}; |
4 | |
5 | use proc_macro2::{Span, TokenStream}; |
6 | use quote::{format_ident, IdentFragment, ToTokens}; |
7 | use syn::{ext::IdentExt, Attribute, FieldsNamed, FieldsUnnamed, Ident, Index, Result, Type}; |
8 | |
9 | use crate::{DeriveWhere, FieldAttr, Skip, Trait}; |
10 | |
11 | /// Struct, union, struct variant or tuple variant field. |
12 | #[cfg_attr (test, derive(Debug))] |
13 | pub struct Field<'a> { |
14 | /// Attributes. |
15 | pub attr: FieldAttr, |
16 | /// [`struct@Ident`] or [`Index`] for this field. |
17 | pub member: Member<'a>, |
18 | /// [`struct@Ident`] used as a Temporary variable for destructuring `self`. |
19 | pub self_ident: Ident, |
20 | /// [`struct@Ident`] used as a Temporary variable for destructuring `other`. |
21 | pub other_ident: Ident, |
22 | /// [`Type`] used for asserting traits on fields for [`Eq`]. |
23 | pub type_: &'a Type, |
24 | } |
25 | |
26 | /// Borrowed version of [`syn::Member`], to avoid unnecessary allocations. |
27 | #[cfg_attr (test, derive(Debug))] |
28 | pub enum Member<'a> { |
29 | /// Named field. |
30 | Named(&'a Ident), |
31 | /// Unnamed field. |
32 | Unnamed(Index), |
33 | } |
34 | |
35 | impl IdentFragment for Member<'_> { |
36 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
37 | Display::fmt(&self, f) |
38 | } |
39 | } |
40 | |
41 | impl ToTokens for Member<'_> { |
42 | fn to_tokens(&self, tokens: &mut TokenStream) { |
43 | match self { |
44 | Member::Named(ident: &&Ident) => ident.to_tokens(tokens), |
45 | Member::Unnamed(index: &Index) => index.to_tokens(tokens), |
46 | } |
47 | } |
48 | } |
49 | |
50 | impl Display for Member<'_> { |
51 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
52 | match self { |
53 | Member::Named(ident: &&Ident) => write!(f, " {}" , ident.unraw()), |
54 | Member::Unnamed(index: &Index) => write!(f, " {}" , index.index), |
55 | } |
56 | } |
57 | } |
58 | |
59 | impl<'a> Field<'a> { |
60 | /// Create [`Field`]s from [`syn::FieldsNamed`]. |
61 | pub fn from_named( |
62 | derive_wheres: &[DeriveWhere], |
63 | skip_inner: &Skip, |
64 | fields: &'a FieldsNamed, |
65 | ) -> Result<Vec<Self>> { |
66 | fields |
67 | .named |
68 | .iter() |
69 | .map(|field| { |
70 | Field::from_field( |
71 | derive_wheres, |
72 | skip_inner, |
73 | &field.attrs, |
74 | Member::Named(field.ident.as_ref().expect("unexpected unnamed field" )), |
75 | &field.ty, |
76 | ) |
77 | }) |
78 | .collect() |
79 | } |
80 | |
81 | /// Create [`Field`]s from [`syn::FieldsUnnamed`]. |
82 | pub fn from_unnamed( |
83 | derive_wheres: &[DeriveWhere], |
84 | skip_inner: &Skip, |
85 | fields: &'a FieldsUnnamed, |
86 | ) -> Result<Vec<Self>> { |
87 | (0_u32..) |
88 | .zip(&fields.unnamed) |
89 | .map(|(index, field)| { |
90 | Field::from_field( |
91 | derive_wheres, |
92 | skip_inner, |
93 | &field.attrs, |
94 | Member::Unnamed(Index { |
95 | index, |
96 | span: Span::call_site(), |
97 | }), |
98 | &field.ty, |
99 | ) |
100 | }) |
101 | .collect() |
102 | } |
103 | |
104 | /// Create [`Field`] from [`syn::Field`]. |
105 | fn from_field( |
106 | derive_wheres: &[DeriveWhere], |
107 | skip_inner: &Skip, |
108 | attrs: &[Attribute], |
109 | member: Member<'a>, |
110 | type_: &'a Type, |
111 | ) -> Result<Self> { |
112 | let attr = FieldAttr::from_attrs(derive_wheres, skip_inner, attrs)?; |
113 | let self_ident = format_ident!("__field_ {}" , member); |
114 | let other_ident = format_ident!("__other_field_ {}" , member); |
115 | |
116 | Ok(Self { |
117 | attr, |
118 | member, |
119 | self_ident, |
120 | other_ident, |
121 | type_, |
122 | }) |
123 | } |
124 | |
125 | /// Convert to [`syn::Member`]. |
126 | pub fn to_member(&self) -> syn::Member { |
127 | match self.member { |
128 | Member::Named(ident) => syn::Member::Named(ident.clone()), |
129 | Member::Unnamed(ref index) => syn::Member::Unnamed(index.clone()), |
130 | } |
131 | } |
132 | |
133 | /// Returns `true` if this field is skipped with the given [`Trait`]. |
134 | pub fn skip(&self, trait_: Trait) -> bool { |
135 | self.attr.skip.trait_skipped(trait_) |
136 | } |
137 | } |
138 | |