1use core::fmt::{self, Display, Formatter};
10
11use {
12 proc_macro2::Span,
13 syn::punctuated::Punctuated,
14 syn::spanned::Spanned,
15 syn::token::Comma,
16 syn::{Attribute, DeriveInput, Error, LitInt, Meta},
17};
18
19pub struct Config<Repr: KindRepr> {
20 pub allowed_combinations_message: &'static str,
24 pub derive_unaligned: bool,
31 pub allowed_combinations: &'static [&'static [Repr]],
33 pub disallowed_but_legal_combinations: &'static [&'static [Repr]],
41}
42
43impl<R: KindRepr> Config<R> {
44 pub fn validate_reprs(&self, input: &DeriveInput) -> Result<Vec<R>, Vec<Error>> {
52 let mut metas_reprs = reprs(&input.attrs)?;
53 metas_reprs.sort_by(|a: &(_, R), b| a.1.partial_cmp(&b.1).unwrap());
54
55 if self.derive_unaligned {
56 if let Some((meta, _)) =
57 metas_reprs.iter().find(|&repr: &&(_, R)| repr.1.is_align_gt_one())
58 {
59 return Err(vec![Error::new_spanned(
60 meta,
61 "cannot derive Unaligned with repr(align(N > 1))",
62 )]);
63 }
64 }
65
66 let mut metas = Vec::new();
67 let mut reprs = Vec::new();
68 metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| {
69 metas.push(meta);
70 reprs.push(repr)
71 });
72
73 if reprs.is_empty() {
74 return Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]);
77 }
78
79 let initial_sp = metas[0].span();
80 let err_span = metas.iter().skip(1).try_fold(initial_sp, |sp, meta| sp.join(meta.span()));
81
82 if self.allowed_combinations.contains(&reprs.as_slice()) {
83 Ok(reprs)
84 } else if self.disallowed_but_legal_combinations.contains(&reprs.as_slice()) {
85 Err(vec![Error::new(
86 err_span.unwrap_or_else(|| input.span()),
87 self.allowed_combinations_message,
88 )])
89 } else {
90 Err(vec![Error::new(
91 err_span.unwrap_or_else(|| input.span()),
92 "conflicting representation hints",
93 )])
94 }
95 }
96}
97
98pub trait KindRepr: 'static + Sized + Ord {
100 fn is_align(&self) -> bool;
101 fn is_align_gt_one(&self) -> bool;
102 fn parse(meta: &Meta) -> syn::Result<Self>;
103}
104
105macro_rules! define_kind_specific_repr {
109 ($type_name:expr, $repr_name:ident, [ $($repr_variant:ident),* ] , [ $($repr_variant_aligned:ident),* ]) => {
110 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
111 pub enum $repr_name {
112 $($repr_variant,)*
113 $($repr_variant_aligned(u64),)*
114 }
115
116 impl KindRepr for $repr_name {
117 fn is_align(&self) -> bool {
118 match self {
119 $($repr_name::$repr_variant_aligned(_) => true,)*
120 _ => false,
121 }
122 }
123
124 fn is_align_gt_one(&self) -> bool {
125 match self {
126 $repr_name::Align(n) => n > &1,
128 _ => false,
129 }
130 }
131
132 fn parse(meta: &Meta) -> syn::Result<$repr_name> {
133 match Repr::from_meta(meta)? {
134 $(Repr::$repr_variant => Ok($repr_name::$repr_variant),)*
135 $(Repr::$repr_variant_aligned(u) => Ok($repr_name::$repr_variant_aligned(u)),)*
136 _ => Err(Error::new_spanned(meta, concat!("unsupported representation for deriving FromBytes, AsBytes, or Unaligned on ", $type_name)))
137 }
138 }
139 }
140
141 impl PartialOrd for $repr_name {
144 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
145 Some(self.cmp(other))
146 }
147 }
148
149 impl Ord for $repr_name {
150 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
151 format!("{:?}", self).cmp(&format!("{:?}", other))
152 }
153 }
154
155 impl core::fmt::Display for $repr_name {
156 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157 match self {
158 $($repr_name::$repr_variant => Repr::$repr_variant,)*
159 $($repr_name::$repr_variant_aligned(u) => Repr::$repr_variant_aligned(*u),)*
160 }.fmt(f)
161 }
162 }
163 }
164}
165
166define_kind_specific_repr!("a struct", StructRepr, [C, Transparent, Packed], [Align, PackedN]);
167define_kind_specific_repr!(
168 "an enum",
169 EnumRepr,
170 [C, U8, U16, U32, U64, Usize, I8, I16, I32, I64, Isize],
171 [Align]
172);
173
174#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
176pub enum Repr {
177 U8,
178 U16,
179 U32,
180 U64,
181 Usize,
182 I8,
183 I16,
184 I32,
185 I64,
186 Isize,
187 C,
188 Transparent,
189 Packed,
190 PackedN(u64),
191 Align(u64),
192}
193
194impl Repr {
195 fn from_meta(meta: &Meta) -> Result<Repr, Error> {
196 let (path, list) = match meta {
197 Meta::Path(path) => (path, None),
198 Meta::List(list) => (&list.path, Some(list)),
199 _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")),
200 };
201
202 let ident = path
203 .get_ident()
204 .ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?;
205
206 Ok(match (ident.to_string().as_str(), list) {
207 ("u8", None) => Repr::U8,
208 ("u16", None) => Repr::U16,
209 ("u32", None) => Repr::U32,
210 ("u64", None) => Repr::U64,
211 ("usize", None) => Repr::Usize,
212 ("i8", None) => Repr::I8,
213 ("i16", None) => Repr::I16,
214 ("i32", None) => Repr::I32,
215 ("i64", None) => Repr::I64,
216 ("isize", None) => Repr::Isize,
217 ("C", None) => Repr::C,
218 ("transparent", None) => Repr::Transparent,
219 ("packed", None) => Repr::Packed,
220 ("packed", Some(list)) => {
221 Repr::PackedN(list.parse_args::<LitInt>()?.base10_parse::<u64>()?)
222 }
223 ("align", Some(list)) => {
224 Repr::Align(list.parse_args::<LitInt>()?.base10_parse::<u64>()?)
225 }
226 _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")),
227 })
228 }
229}
230
231impl KindRepr for Repr {
232 fn is_align(&self) -> bool {
233 false
234 }
235
236 fn is_align_gt_one(&self) -> bool {
237 false
238 }
239
240 fn parse(meta: &Meta) -> syn::Result<Self> {
241 Self::from_meta(meta)
242 }
243}
244
245impl Display for Repr {
246 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
247 if let Repr::Align(n) = self {
248 return write!(f, "repr(align({}))", n);
249 }
250 if let Repr::PackedN(n) = self {
251 return write!(f, "repr(packed({}))", n);
252 }
253 write!(
254 f,
255 "repr({})",
256 match self {
257 Repr::U8 => "u8",
258 Repr::U16 => "u16",
259 Repr::U32 => "u32",
260 Repr::U64 => "u64",
261 Repr::Usize => "usize",
262 Repr::I8 => "i8",
263 Repr::I16 => "i16",
264 Repr::I32 => "i32",
265 Repr::I64 => "i64",
266 Repr::Isize => "isize",
267 Repr::C => "C",
268 Repr::Transparent => "transparent",
269 Repr::Packed => "packed",
270 _ => unreachable!(),
271 }
272 )
273 }
274}
275
276pub(crate) fn reprs<R: KindRepr>(attrs: &[Attribute]) -> Result<Vec<(Meta, R)>, Vec<Error>> {
277 let mut reprs = Vec::new();
278 let mut errors = Vec::new();
279 for attr in attrs {
280 if attr.path().is_ident("doc") {
282 continue;
283 }
284 if let Meta::List(ref meta_list) = attr.meta {
285 if meta_list.path.is_ident("repr") {
286 let parsed: Punctuated<Meta, Comma> =
287 match meta_list.parse_args_with(Punctuated::parse_terminated) {
288 Ok(parsed) => parsed,
289 Err(_) => {
290 errors.push(Error::new_spanned(
291 &meta_list.tokens,
292 "unrecognized representation hint",
293 ));
294 continue;
295 }
296 };
297 for meta in parsed {
298 match R::parse(&meta) {
299 Ok(repr) => reprs.push((meta, repr)),
300 Err(err) => errors.push(err),
301 }
302 }
303 }
304 }
305 }
306
307 if !errors.is_empty() {
308 return Err(errors);
309 }
310 Ok(reprs)
311}