Skip to main content

zerocopy_derive/
repr.rs

1// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2
3// Copyright 2019 The Fuchsia Authors
4//
5// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
6// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
7// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
8// This file may not be copied, modified, or distributed except according to
9// those terms.
10
11use core::{
12    convert::{Infallible, TryFrom},
13    num::NonZeroU32,
14};
15
16use proc_macro2::{Span, TokenStream};
17use quote::{quote_spanned, ToTokens, TokenStreamExt as _};
18use syn::{
19    punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, Error, LitInt, Meta,
20    MetaList,
21};
22
23/// The computed representation of a type.
24///
25/// This is the result of processing all `#[repr(...)]` attributes on a type, if
26/// any. A `Repr` is only capable of representing legal combinations of
27/// `#[repr(...)]` attributes.
28#[cfg_attr(test, derive(Copy, Clone, Debug))]
29pub(crate) enum Repr<Prim, Packed> {
30    /// `#[repr(transparent)]`
31    Transparent(Span),
32    /// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`
33    /// optionally combined with `repr(packed(...))` or `repr(align(...))`
34    Compound(Spanned<CompoundRepr<Prim>>, Option<Spanned<AlignRepr<Packed>>>),
35}
36
37/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`.
38#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
39pub(crate) enum CompoundRepr<Prim> {
40    C,
41    Rust,
42    Primitive(Prim),
43}
44
45/// `repr(Int)`
46#[derive(Copy, Clone)]
47#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
48pub(crate) enum PrimitiveRepr {
49    U8,
50    U16,
51    U32,
52    U64,
53    U128,
54    Usize,
55    I8,
56    I16,
57    I32,
58    I64,
59    I128,
60    Isize,
61}
62
63/// `repr(packed(...))` or `repr(align(...))`
64#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
65pub(crate) enum AlignRepr<Packed> {
66    Packed(Packed),
67    Align(NonZeroU32),
68}
69
70/// The representations which can legally appear on a struct or union type.
71pub(crate) type StructUnionRepr = Repr<Infallible, NonZeroU32>;
72
73/// The representations which can legally appear on an enum type.
74pub(crate) type EnumRepr = Repr<PrimitiveRepr, Infallible>;
75
76impl<Prim, Packed> Repr<Prim, Packed> {
77    /// Gets the name of this "repr type" - the non-align `repr(X)` that is used
78    /// in prose to refer to this type.
79    ///
80    /// For example, we would refer to `#[repr(C, align(4))] struct Foo { ... }`
81    /// as a "`repr(C)` struct".
82    pub(crate) fn repr_type_name(&self) -> &str
83    where
84        Prim: Copy + With<PrimitiveRepr>,
85    {
86        use CompoundRepr::*;
87        use PrimitiveRepr::*;
88        use Repr::*;
89        match self {
90            Transparent(_span) => "repr(transparent)",
91            Compound(Spanned { t: repr, span: _ }, _align) => match repr {
92                C => "repr(C)",
93                Rust => "repr(Rust)",
94                Primitive(prim) => prim.with(|prim| match prim {
95                    U8 => "repr(u8)",
96                    U16 => "repr(u16)",
97                    U32 => "repr(u32)",
98                    U64 => "repr(u64)",
99                    U128 => "repr(u128)",
100                    Usize => "repr(usize)",
101                    I8 => "repr(i8)",
102                    I16 => "repr(i16)",
103                    I32 => "repr(i32)",
104                    I64 => "repr(i64)",
105                    I128 => "repr(i128)",
106                    Isize => "repr(isize)",
107                }),
108            },
109        }
110    }
111
112    pub(crate) fn is_transparent(&self) -> bool {
113        matches!(self, Repr::Transparent(_))
114    }
115
116    pub(crate) fn is_c(&self) -> bool {
117        use CompoundRepr::*;
118        matches!(self, Repr::Compound(Spanned { t: C, span: _ }, _align))
119    }
120
121    pub(crate) fn is_primitive(&self) -> bool {
122        use CompoundRepr::*;
123        matches!(self, Repr::Compound(Spanned { t: Primitive(_), span: _ }, _align))
124    }
125
126    pub(crate) fn get_packed(&self) -> Option<&Packed> {
127        use AlignRepr::*;
128        use Repr::*;
129        if let Compound(_, Some(Spanned { t: Packed(p), span: _ })) = self {
130            Some(p)
131        } else {
132            None
133        }
134    }
135
136    pub(crate) fn get_align(&self) -> Option<Spanned<NonZeroU32>> {
137        use AlignRepr::*;
138        use Repr::*;
139        if let Compound(_, Some(Spanned { t: Align(n), span })) = self {
140            Some(Spanned::new(*n, *span))
141        } else {
142            None
143        }
144    }
145
146    pub(crate) fn is_align_gt_1(&self) -> bool {
147        self.get_align().map(|n| n.t.get() > 1).unwrap_or(false)
148    }
149
150    /// When deriving `Unaligned`, validate that the decorated type has no
151    /// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists
152    /// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns
153    /// a descriptive error.
154    pub(crate) fn unaligned_validate_no_align_gt_1(&self) -> Result<(), Error> {
155        if let Some(n) = self.get_align().filter(|n| n.t.get() > 1) {
156            Err(Error::new(
157                n.span,
158                "cannot derive `Unaligned` on type with alignment greater than 1",
159            ))
160        } else {
161            Ok(())
162        }
163    }
164}
165
166impl<Prim> Repr<Prim, NonZeroU32> {
167    /// Does `self` describe a `#[repr(packed)]` or `#[repr(packed(1))]` type?
168    pub(crate) fn is_packed_1(&self) -> bool {
169        self.get_packed().map(|n| n.get() == 1).unwrap_or(false)
170    }
171}
172
173impl<Packed> Repr<PrimitiveRepr, Packed> {
174    fn get_primitive(&self) -> Option<&PrimitiveRepr> {
175        use CompoundRepr::*;
176        use Repr::*;
177        if let Compound(Spanned { t: Primitive(p), span: _ }, _align) = self {
178            Some(p)
179        } else {
180            None
181        }
182    }
183
184    /// Does `self` describe a `#[repr(u8)]` type?
185    pub(crate) fn is_u8(&self) -> bool {
186        matches!(self.get_primitive(), Some(PrimitiveRepr::U8))
187    }
188
189    /// Does `self` describe a `#[repr(i8)]` type?
190    pub(crate) fn is_i8(&self) -> bool {
191        matches!(self.get_primitive(), Some(PrimitiveRepr::I8))
192    }
193}
194
195impl<Prim, Packed> ToTokens for Repr<Prim, Packed>
196where
197    Prim: With<PrimitiveRepr> + Copy,
198    Packed: With<NonZeroU32> + Copy,
199{
200    fn to_tokens(&self, ts: &mut TokenStream) {
201        use Repr::*;
202        match self {
203            Transparent(span) => ts.append_all(quote_spanned! { *span=> #[repr(transparent)] }),
204            Compound(repr, align) => {
205                repr.to_tokens(ts);
206                if let Some(align) = align {
207                    align.to_tokens(ts);
208                }
209            }
210        }
211    }
212}
213
214impl<Prim: With<PrimitiveRepr> + Copy> ToTokens for Spanned<CompoundRepr<Prim>> {
215    fn to_tokens(&self, ts: &mut TokenStream) {
216        use CompoundRepr::*;
217        match &self.t {
218            C => ts.append_all(quote_spanned! { self.span=> #[repr(C)] }),
219            Rust => ts.append_all(quote_spanned! { self.span=> #[repr(Rust)] }),
220            Primitive(prim) => prim.with(|prim| Spanned::new(prim, self.span).to_tokens(ts)),
221        }
222    }
223}
224
225impl ToTokens for Spanned<PrimitiveRepr> {
226    fn to_tokens(&self, ts: &mut TokenStream) {
227        use PrimitiveRepr::*;
228        match self.t {
229            U8 => ts.append_all(quote_spanned! { self.span => #[repr(u8)] }),
230            U16 => ts.append_all(quote_spanned! { self.span => #[repr(u16)] }),
231            U32 => ts.append_all(quote_spanned! { self.span => #[repr(u32)] }),
232            U64 => ts.append_all(quote_spanned! { self.span => #[repr(u64)] }),
233            U128 => ts.append_all(quote_spanned! { self.span => #[repr(u128)] }),
234            Usize => ts.append_all(quote_spanned! { self.span => #[repr(usize)] }),
235            I8 => ts.append_all(quote_spanned! { self.span => #[repr(i8)] }),
236            I16 => ts.append_all(quote_spanned! { self.span => #[repr(i16)] }),
237            I32 => ts.append_all(quote_spanned! { self.span => #[repr(i32)] }),
238            I64 => ts.append_all(quote_spanned! { self.span => #[repr(i64)] }),
239            I128 => ts.append_all(quote_spanned! { self.span => #[repr(i128)] }),
240            Isize => ts.append_all(quote_spanned! { self.span => #[repr(isize)] }),
241        }
242    }
243}
244
245impl<Packed: With<NonZeroU32> + Copy> ToTokens for Spanned<AlignRepr<Packed>> {
246    fn to_tokens(&self, ts: &mut TokenStream) {
247        use AlignRepr::*;
248        // We use `syn::Index` instead of `u32` because `quote_spanned!`
249        // serializes `u32` literals as `123u32`, not just `123`. Rust doesn't
250        // recognize that as a valid argument to `#[repr(align(...))]` or
251        // `#[repr(packed(...))]`.
252        let to_index = |n: NonZeroU32| syn::Index { index: n.get(), span: self.span };
253        match self.t {
254            Packed(n) => n.with(|n| {
255                let n = to_index(n);
256                ts.append_all(quote_spanned! { self.span => #[repr(packed(#n))] })
257            }),
258            Align(n) => {
259                let n = to_index(n);
260                ts.append_all(quote_spanned! { self.span => #[repr(align(#n))] })
261            }
262        }
263    }
264}
265
266/// The result of parsing a single `#[repr(...)]` attribute or a single
267/// directive inside a compound `#[repr(..., ...)]` attribute.
268#[derive(Copy, Clone, PartialEq, Eq)]
269#[cfg_attr(test, derive(Debug))]
270pub(crate) enum RawRepr {
271    Transparent,
272    C,
273    Rust,
274    U8,
275    U16,
276    U32,
277    U64,
278    U128,
279    Usize,
280    I8,
281    I16,
282    I32,
283    I64,
284    I128,
285    Isize,
286    Align(NonZeroU32),
287    PackedN(NonZeroU32),
288    Packed,
289}
290
291/// The error from converting from a `RawRepr`.
292#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
293pub(crate) enum FromRawReprError<E> {
294    /// The `RawRepr` doesn't affect the high-level repr we're parsing (e.g.
295    /// it's `align(...)` and we're parsing a `CompoundRepr`).
296    None,
297    /// The `RawRepr` is invalid for the high-level repr we're parsing (e.g.
298    /// it's `packed` repr and we're parsing an `AlignRepr` for an enum type).
299    Err(E),
300}
301
302/// The representation hint is not supported for the decorated type.
303#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
304pub(crate) struct UnsupportedReprError;
305
306impl<Prim: With<PrimitiveRepr>> TryFrom<RawRepr> for CompoundRepr<Prim> {
307    type Error = FromRawReprError<UnsupportedReprError>;
308    fn try_from(
309        raw: RawRepr,
310    ) -> Result<CompoundRepr<Prim>, FromRawReprError<UnsupportedReprError>> {
311        use RawRepr::*;
312        match raw {
313            C => Ok(CompoundRepr::C),
314            Rust => Ok(CompoundRepr::Rust),
315            raw @ (U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize) => {
316                Prim::try_with_or(
317                    || match raw {
318                        U8 => Ok(PrimitiveRepr::U8),
319                        U16 => Ok(PrimitiveRepr::U16),
320                        U32 => Ok(PrimitiveRepr::U32),
321                        U64 => Ok(PrimitiveRepr::U64),
322                        U128 => Ok(PrimitiveRepr::U128),
323                        Usize => Ok(PrimitiveRepr::Usize),
324                        I8 => Ok(PrimitiveRepr::I8),
325                        I16 => Ok(PrimitiveRepr::I16),
326                        I32 => Ok(PrimitiveRepr::I32),
327                        I64 => Ok(PrimitiveRepr::I64),
328                        I128 => Ok(PrimitiveRepr::I128),
329                        Isize => Ok(PrimitiveRepr::Isize),
330                        Transparent | C | Rust | Align(_) | PackedN(_) | Packed => {
331                            Err(UnsupportedReprError)
332                        }
333                    },
334                    UnsupportedReprError,
335                )
336                .map(CompoundRepr::Primitive)
337                .map_err(FromRawReprError::Err)
338            }
339            Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None),
340        }
341    }
342}
343
344impl<Pcked: With<NonZeroU32>> TryFrom<RawRepr> for AlignRepr<Pcked> {
345    type Error = FromRawReprError<UnsupportedReprError>;
346    fn try_from(raw: RawRepr) -> Result<AlignRepr<Pcked>, FromRawReprError<UnsupportedReprError>> {
347        use RawRepr::*;
348        match raw {
349            Packed | PackedN(_) => Pcked::try_with_or(
350                || match raw {
351                    Packed => Ok(NonZeroU32::new(1).unwrap()),
352                    PackedN(n) => Ok(n),
353                    U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
354                    | Transparent | C | Rust | Align(_) => Err(UnsupportedReprError),
355                },
356                UnsupportedReprError,
357            )
358            .map(AlignRepr::Packed)
359            .map_err(FromRawReprError::Err),
360            Align(n) => Ok(AlignRepr::Align(n)),
361            U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
362            | Transparent | C | Rust => Err(FromRawReprError::None),
363        }
364    }
365}
366
367/// The error from extracting a high-level repr type from a list of `RawRepr`s.
368#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
369enum FromRawReprsError<E> {
370    /// One of the `RawRepr`s is invalid for the high-level repr we're parsing
371    /// (e.g. there's a `packed` repr and we're parsing an `AlignRepr` for an
372    /// enum type).
373    Single(E),
374    /// Two `RawRepr`s appear which both affect the high-level repr we're
375    /// parsing (e.g., the list is `#[repr(align(2), packed)]`). Note that we
376    /// conservatively treat redundant reprs as conflicting (e.g.
377    /// `#[repr(packed, packed)]`).
378    Conflict,
379}
380
381/// Tries to extract a high-level repr from a list of `RawRepr`s.
382fn try_from_raw_reprs<'a, E, R: TryFrom<RawRepr, Error = FromRawReprError<E>>>(
383    r: impl IntoIterator<Item = &'a Spanned<RawRepr>>,
384) -> Result<Option<Spanned<R>>, Spanned<FromRawReprsError<E>>> {
385    // Walk the list of `RawRepr`s and attempt to convert each to an `R`. Bail
386    // if we find any errors. If we find more than one which converts to an `R`,
387    // bail with a `Conflict` error.
388    r.into_iter().try_fold(None, |found: Option<Spanned<R>>, raw| {
389        let new = match Spanned::<R>::try_from(*raw) {
390            Ok(r) => r,
391            // This `RawRepr` doesn't convert to an `R`, so keep the current
392            // found `R`, if any.
393            Err(FromRawReprError::None) => return Ok(found),
394            // This repr is unsupported for the decorated type (e.g.
395            // `repr(packed)` on an enum).
396            Err(FromRawReprError::Err(Spanned { t: err, span })) => {
397                return Err(Spanned::new(FromRawReprsError::Single(err), span))
398            }
399        };
400
401        if let Some(found) = found {
402            // We already found an `R`, but this `RawRepr` also converts to an
403            // `R`, so that's a conflict.
404            //
405            // `Span::join` returns `None` if the two spans are from different
406            // files or if we're not on the nightly compiler. In that case, just
407            // use `new`'s span.
408            let span = found.span.join(new.span).unwrap_or(new.span);
409            Err(Spanned::new(FromRawReprsError::Conflict, span))
410        } else {
411            Ok(Some(new))
412        }
413    })
414}
415
416/// The error returned from [`Repr::from_attrs`].
417#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
418enum FromAttrsError {
419    FromRawReprs(FromRawReprsError<UnsupportedReprError>),
420    Unrecognized,
421}
422
423impl From<FromRawReprsError<UnsupportedReprError>> for FromAttrsError {
424    fn from(err: FromRawReprsError<UnsupportedReprError>) -> FromAttrsError {
425        FromAttrsError::FromRawReprs(err)
426    }
427}
428
429impl From<UnrecognizedReprError> for FromAttrsError {
430    fn from(_err: UnrecognizedReprError) -> FromAttrsError {
431        FromAttrsError::Unrecognized
432    }
433}
434
435impl From<Spanned<FromAttrsError>> for Error {
436    fn from(err: Spanned<FromAttrsError>) -> Error {
437        let Spanned { t: err, span } = err;
438        match err {
439            FromAttrsError::FromRawReprs(FromRawReprsError::Single(
440                _err @ UnsupportedReprError,
441            )) => Error::new(span, "unsupported representation hint for the decorated type"),
442            FromAttrsError::FromRawReprs(FromRawReprsError::Conflict) => {
443                // NOTE: This says "another" rather than "a preceding" because
444                // when one of the reprs involved is `transparent`, we detect
445                // that condition in `Repr::from_attrs`, and at that point we
446                // can't tell which repr came first, so we might report this on
447                // the first involved repr rather than the second, third, etc.
448                Error::new(span, "this conflicts with another representation hint")
449            }
450            FromAttrsError::Unrecognized => Error::new(span, "unrecognized representation hint"),
451        }
452    }
453}
454
455impl<Prim, Packed> Repr<Prim, Packed> {
456    fn from_attrs_inner(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Spanned<FromAttrsError>>
457    where
458        Prim: With<PrimitiveRepr>,
459        Packed: With<NonZeroU32>,
460    {
461        let raw_reprs = RawRepr::from_attrs(attrs).map_err(Spanned::from)?;
462
463        let transparent = {
464            let mut transparents = raw_reprs.iter().filter_map(|Spanned { t, span }| match t {
465                RawRepr::Transparent => Some(span),
466                _ => None,
467            });
468            let first = transparents.next();
469            let second = transparents.next();
470            match (first, second) {
471                (None, None) => None,
472                (Some(span), None) => Some(*span),
473                (Some(_), Some(second)) => {
474                    return Err(Spanned::new(
475                        FromAttrsError::FromRawReprs(FromRawReprsError::Conflict),
476                        *second,
477                    ))
478                }
479                // An iterator can't produce a value only on the second call to
480                // `.next()`.
481                (None, Some(_)) => unreachable!(),
482            }
483        };
484
485        let compound: Option<Spanned<CompoundRepr<Prim>>> =
486            try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
487        let align: Option<Spanned<AlignRepr<Packed>>> =
488            try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
489
490        if let Some(span) = transparent {
491            if compound.is_some() || align.is_some() {
492                // Arbitrarily report the problem on the `transparent` span. Any
493                // span will do.
494                return Err(Spanned::new(FromRawReprsError::Conflict.into(), span));
495            }
496
497            Ok(Repr::Transparent(span))
498        } else {
499            Ok(Repr::Compound(
500                compound.unwrap_or(Spanned::new(CompoundRepr::Rust, Span::call_site())),
501                align,
502            ))
503        }
504    }
505}
506
507impl<Prim, Packed> Repr<Prim, Packed> {
508    pub(crate) fn from_attrs(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Error>
509    where
510        Prim: With<PrimitiveRepr>,
511        Packed: With<NonZeroU32>,
512    {
513        Repr::from_attrs_inner(attrs).map_err(Into::into)
514    }
515}
516
517/// The representation hint could not be parsed or was unrecognized.
518struct UnrecognizedReprError;
519
520impl RawRepr {
521    fn from_attrs(
522        attrs: &[Attribute],
523    ) -> Result<Vec<Spanned<RawRepr>>, Spanned<UnrecognizedReprError>> {
524        let mut reprs = Vec::new();
525        for attr in attrs {
526            // Ignore documentation attributes.
527            if attr.path().is_ident("doc") {
528                continue;
529            }
530            if let Meta::List(ref meta_list) = attr.meta {
531                if meta_list.path.is_ident("repr") {
532                    let parsed: Punctuated<Meta, Comma> =
533                        match meta_list.parse_args_with(Punctuated::parse_terminated) {
534                            Ok(parsed) => parsed,
535                            Err(_) => {
536                                return Err(Spanned::new(
537                                    UnrecognizedReprError,
538                                    meta_list.tokens.span(),
539                                ))
540                            }
541                        };
542                    for meta in parsed {
543                        let s = meta.span();
544                        reprs.push(
545                            RawRepr::from_meta(&meta)
546                                .map(|r| Spanned::new(r, s))
547                                .map_err(|e| Spanned::new(e, s))?,
548                        );
549                    }
550                }
551            }
552        }
553
554        Ok(reprs)
555    }
556
557    fn from_meta(meta: &Meta) -> Result<RawRepr, UnrecognizedReprError> {
558        let (path, list) = match meta {
559            Meta::Path(path) => (path, None),
560            Meta::List(list) => (&list.path, Some(list)),
561            _ => return Err(UnrecognizedReprError),
562        };
563
564        let ident = path.get_ident().ok_or(UnrecognizedReprError)?;
565
566        // Only returns `Ok` for non-zero power-of-two values.
567        let parse_nzu64 = |list: &MetaList| {
568            list.parse_args::<LitInt>()
569                .and_then(|int| int.base10_parse::<NonZeroU32>())
570                .map_err(|_| UnrecognizedReprError)
571                .and_then(|nz| {
572                    if nz.get().is_power_of_two() {
573                        Ok(nz)
574                    } else {
575                        Err(UnrecognizedReprError)
576                    }
577                })
578        };
579
580        use RawRepr::*;
581        Ok(match (ident.to_string().as_str(), list) {
582            ("u8", None) => U8,
583            ("u16", None) => U16,
584            ("u32", None) => U32,
585            ("u64", None) => U64,
586            ("u128", None) => U128,
587            ("usize", None) => Usize,
588            ("i8", None) => I8,
589            ("i16", None) => I16,
590            ("i32", None) => I32,
591            ("i64", None) => I64,
592            ("i128", None) => I128,
593            ("isize", None) => Isize,
594            ("C", None) => C,
595            ("transparent", None) => Transparent,
596            ("Rust", None) => Rust,
597            ("packed", None) => Packed,
598            ("packed", Some(list)) => PackedN(parse_nzu64(list)?),
599            ("align", Some(list)) => Align(parse_nzu64(list)?),
600            _ => return Err(UnrecognizedReprError),
601        })
602    }
603}
604
605pub(crate) use util::*;
606mod util {
607    use super::*;
608    /// A value with an associated span.
609    #[derive(Copy, Clone)]
610    #[cfg_attr(test, derive(Debug))]
611    pub(crate) struct Spanned<T> {
612        pub(crate) t: T,
613        pub(crate) span: Span,
614    }
615
616    impl<T> Spanned<T> {
617        pub(super) fn new(t: T, span: Span) -> Spanned<T> {
618            Spanned { t, span }
619        }
620
621        pub(super) fn from<U>(s: Spanned<U>) -> Spanned<T>
622        where
623            T: From<U>,
624        {
625            let Spanned { t: u, span } = s;
626            Spanned::new(u.into(), span)
627        }
628
629        /// Delegates to `T: TryFrom`, preserving span information in both the
630        /// success and error cases.
631        pub(super) fn try_from<E, U>(
632            u: Spanned<U>,
633        ) -> Result<Spanned<T>, FromRawReprError<Spanned<E>>>
634        where
635            T: TryFrom<U, Error = FromRawReprError<E>>,
636        {
637            let Spanned { t: u, span } = u;
638            T::try_from(u).map(|t| Spanned { t, span }).map_err(|err| match err {
639                FromRawReprError::None => FromRawReprError::None,
640                FromRawReprError::Err(e) => FromRawReprError::Err(Spanned::new(e, span)),
641            })
642        }
643    }
644
645    // Used to permit implementing `With<T> for T: Inhabited` and for
646    // `Infallible` without a blanket impl conflict.
647    pub(crate) trait Inhabited {}
648    impl Inhabited for PrimitiveRepr {}
649    impl Inhabited for NonZeroU32 {}
650
651    pub(crate) trait With<T> {
652        fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O;
653        fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, err: E) -> Result<Self, E>
654        where
655            Self: Sized;
656    }
657
658    impl<T: Inhabited> With<T> for T {
659        fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O {
660            f(self)
661        }
662
663        fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, _err: E) -> Result<Self, E> {
664            f()
665        }
666    }
667
668    impl<T> With<T> for Infallible {
669        fn with<O, F: FnOnce(T) -> O>(self, _f: F) -> O {
670            match self {}
671        }
672
673        fn try_with_or<E, F: FnOnce() -> Result<T, E>>(_f: F, err: E) -> Result<Self, E> {
674            Err(err)
675        }
676    }
677}
678
679#[cfg(test)]
680mod tests {
681    use syn::parse_quote;
682
683    use super::*;
684
685    impl<T> From<T> for Spanned<T> {
686        fn from(t: T) -> Spanned<T> {
687            Spanned::new(t, Span::call_site())
688        }
689    }
690
691    // We ignore spans for equality in testing since real spans are hard to
692    // synthesize and don't implement `PartialEq`.
693    impl<T: PartialEq> PartialEq for Spanned<T> {
694        fn eq(&self, other: &Spanned<T>) -> bool {
695            self.t.eq(&other.t)
696        }
697    }
698
699    impl<T: Eq> Eq for Spanned<T> {}
700
701    impl<Prim: PartialEq, Packed: PartialEq> PartialEq for Repr<Prim, Packed> {
702        fn eq(&self, other: &Repr<Prim, Packed>) -> bool {
703            match (self, other) {
704                (Repr::Transparent(_), Repr::Transparent(_)) => true,
705                (Repr::Compound(sc, sa), Repr::Compound(oc, oa)) => (sc, sa) == (oc, oa),
706                _ => false,
707            }
708        }
709    }
710
711    fn s() -> Span {
712        Span::call_site()
713    }
714
715    #[test]
716    fn test() {
717        // Test that a given `#[repr(...)]` attribute parses and returns the
718        // given `Repr` or error.
719        macro_rules! test {
720            ($(#[$attr:meta])* => $repr:expr) => {
721                test!(@inner $(#[$attr])* => Repr => Ok($repr));
722            };
723            // In the error case, the caller must explicitly provide the name of
724            // the `Repr` type to assist in type inference.
725            (@error $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
726                test!(@inner $(#[$attr])* => $typ => Err($repr));
727            };
728            (@inner $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
729                let attr: Attribute = parse_quote!($(#[$attr])*);
730                let mut got = $typ::from_attrs_inner(&[attr]);
731                let expect: Result<Repr<_, _>, _> = $repr;
732                if false {
733                    // Force Rust to infer `got` as having the same type as
734                    // `expect`.
735                    got = expect;
736                }
737                assert_eq!(got, expect, stringify!($(#[$attr])*));
738            };
739        }
740
741        use AlignRepr::*;
742        use CompoundRepr::*;
743        use PrimitiveRepr::*;
744        let nz = |n: u32| NonZeroU32::new(n).unwrap();
745
746        test!(#[repr(transparent)] => StructUnionRepr::Transparent(s()));
747        test!(#[repr()] => StructUnionRepr::Compound(Rust.into(), None));
748        test!(#[repr(packed)] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(1)).into())));
749        test!(#[repr(packed(2))] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(2)).into())));
750        test!(#[repr(align(1))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
751        test!(#[repr(align(2))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
752        test!(#[repr(C)] => StructUnionRepr::Compound(C.into(), None));
753        test!(#[repr(C, packed)] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(1)).into())));
754        test!(#[repr(C, packed(2))] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(2)).into())));
755        test!(#[repr(C, align(1))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(1)).into())));
756        test!(#[repr(C, align(2))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(2)).into())));
757
758        test!(#[repr(transparent)] => EnumRepr::Transparent(s()));
759        test!(#[repr()] => EnumRepr::Compound(Rust.into(), None));
760        test!(#[repr(align(1))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
761        test!(#[repr(align(2))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
762
763        macro_rules! for_each_compound_repr {
764            ($($r:tt => $var:expr),*) => {
765                $(
766                    test!(#[repr($r)] => EnumRepr::Compound($var.into(), None));
767                    test!(#[repr($r, align(1))] => EnumRepr::Compound($var.into(), Some(Align(nz(1)).into())));
768                    test!(#[repr($r, align(2))] => EnumRepr::Compound($var.into(), Some(Align(nz(2)).into())));
769                )*
770            }
771        }
772
773        for_each_compound_repr!(
774            C => C,
775            u8 => Primitive(U8),
776            u16 => Primitive(U16),
777            u32 => Primitive(U32),
778            u64 => Primitive(U64),
779            usize => Primitive(Usize),
780            i8 => Primitive(I8),
781            i16 => Primitive(I16),
782            i32 => Primitive(I32),
783            i64 => Primitive(I64),
784            isize => Primitive(Isize)
785        );
786
787        use FromAttrsError::*;
788        use FromRawReprsError::*;
789
790        // Run failure tests which are valid for both `StructUnionRepr` and
791        // `EnumRepr`.
792        macro_rules! for_each_repr_type {
793            ($($repr:ident),*) => {
794                $(
795                    // Invalid packed or align attributes
796                    test!(@error #[repr(packed(0))] => $repr => Unrecognized.into());
797                    test!(@error #[repr(packed(3))] => $repr => Unrecognized.into());
798                    test!(@error #[repr(align(0))] => $repr => Unrecognized.into());
799                    test!(@error #[repr(align(3))] => $repr => Unrecognized.into());
800
801                    // Conflicts
802                    test!(@error #[repr(transparent, transparent)] => $repr => FromRawReprs(Conflict).into());
803                    test!(@error #[repr(transparent, C)] => $repr => FromRawReprs(Conflict).into());
804                    test!(@error #[repr(transparent, Rust)] => $repr => FromRawReprs(Conflict).into());
805
806                    test!(@error #[repr(C, transparent)] => $repr => FromRawReprs(Conflict).into());
807                    test!(@error #[repr(C, C)] => $repr => FromRawReprs(Conflict).into());
808                    test!(@error #[repr(C, Rust)] => $repr => FromRawReprs(Conflict).into());
809
810                    test!(@error #[repr(Rust, transparent)] => $repr => FromRawReprs(Conflict).into());
811                    test!(@error #[repr(Rust, C)] => $repr => FromRawReprs(Conflict).into());
812                    test!(@error #[repr(Rust, Rust)] => $repr => FromRawReprs(Conflict).into());
813                )*
814            }
815        }
816
817        for_each_repr_type!(StructUnionRepr, EnumRepr);
818
819        // Enum-specific conflicts.
820        //
821        // We don't bother to test every combination since that would be a huge
822        // number (enums can have primitive reprs u8, u16, u32, u64, usize, i8,
823        // i16, i32, i64, and isize). Instead, since the conflict logic doesn't
824        // care what specific value of `PrimitiveRepr` is present, we assume
825        // that testing against u8 alone is fine.
826        test!(@error #[repr(transparent, u8)] => EnumRepr => FromRawReprs(Conflict).into());
827        test!(@error #[repr(u8, transparent)] => EnumRepr => FromRawReprs(Conflict).into());
828        test!(@error #[repr(C, u8)] => EnumRepr => FromRawReprs(Conflict).into());
829        test!(@error #[repr(u8, C)] => EnumRepr => FromRawReprs(Conflict).into());
830        test!(@error #[repr(Rust, u8)] => EnumRepr => FromRawReprs(Conflict).into());
831        test!(@error #[repr(u8, Rust)] => EnumRepr => FromRawReprs(Conflict).into());
832        test!(@error #[repr(u8, u8)] => EnumRepr => FromRawReprs(Conflict).into());
833
834        // Illegal struct/union reprs
835        test!(@error #[repr(u8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
836        test!(@error #[repr(u16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
837        test!(@error #[repr(u32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
838        test!(@error #[repr(u64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
839        test!(@error #[repr(usize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
840        test!(@error #[repr(i8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
841        test!(@error #[repr(i16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
842        test!(@error #[repr(i32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
843        test!(@error #[repr(i64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
844        test!(@error #[repr(isize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
845
846        // Illegal enum reprs
847        test!(@error #[repr(packed)] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
848        test!(@error #[repr(packed(1))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
849        test!(@error #[repr(packed(2))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
850    }
851}