Skip to main content

serde_shape/
lib.rs

1// Copyright 2026 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Reflect the Serde deserialization shape of Rust types.
16
17#![cfg_attr(docsrs, feature(doc_cfg))]
18#![deny(missing_docs)]
19
20use std::collections::BTreeMap;
21
22#[cfg(feature = "derive")]
23#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
24pub use serde_shape_derive::SerdeShape;
25
26/// A type that can describe the shape accepted by its Serde deserializer.
27pub trait SerdeShape {
28    /// Build this type's shape inside the provided context.
29    fn shape_in(context: &mut ShapeContext) -> ShapeRef;
30
31    /// Build a complete shape graph rooted at this type.
32    fn shape() -> Shape
33    where
34        Self: Sized,
35    {
36        Shape::for_type::<Self>()
37    }
38}
39
40/// A complete shape graph rooted at one type.
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct Shape {
43    /// The root shape reference.
44    pub root: ShapeRef,
45    /// Named type definitions reachable from the root.
46    pub definitions: Vec<DefinitionShape>,
47}
48
49impl Shape {
50    /// Build a complete shape graph rooted at `T`.
51    pub fn for_type<T>() -> Self
52    where
53        T: SerdeShape + ?Sized,
54    {
55        let mut context = ShapeContext::default();
56        let root = T::shape_in(&mut context);
57        Self {
58            root,
59            definitions: context.finish(),
60        }
61    }
62
63    /// Return a definition by id.
64    pub fn definition(&self, id: ShapeId) -> Option<&DefinitionShape> {
65        self.definitions.get(id.0)
66    }
67}
68
69/// Accumulates named definitions while a shape graph is built.
70#[derive(Debug, Default)]
71pub struct ShapeContext {
72    definitions: Vec<Option<DefinitionShape>>,
73    definitions_by_rust_name: BTreeMap<&'static str, ShapeId>,
74}
75
76impl ShapeContext {
77    /// Define a named type once and return a reference to its definition.
78    pub fn define_named_type<F>(&mut self, type_name: TypeName, build: F) -> ShapeRef
79    where
80        F: FnOnce(&mut Self) -> DefinitionKind,
81    {
82        if let Some(id) = self.definitions_by_rust_name.get(type_name.rust_name) {
83            return ShapeRef::Definition(*id);
84        }
85
86        let id = ShapeId(self.definitions.len());
87        self.definitions_by_rust_name
88            .insert(type_name.rust_name, id);
89        self.definitions.push(None);
90
91        let kind = build(self);
92        self.definitions[id.0] = Some(DefinitionShape {
93            id,
94            type_name,
95            kind,
96        });
97        ShapeRef::Definition(id)
98    }
99
100    fn finish(self) -> Vec<DefinitionShape> {
101        self.definitions
102            .into_iter()
103            .map(|definition| definition.expect("shape definition was reserved but not filled"))
104            .collect()
105    }
106}
107
108/// Identifies a named shape definition.
109#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
110pub struct ShapeId(pub usize);
111
112/// Names associated with a Rust type and its Serde container.
113#[derive(Clone, Debug, Eq, PartialEq)]
114pub struct TypeName {
115    /// The fully qualified Rust type name, including generic arguments.
116    pub rust_name: &'static str,
117    /// The Serde deserialize name after container rename rules are applied.
118    pub serde_name: &'static str,
119}
120
121/// A reference to a shape node.
122#[derive(Clone, Debug, Eq, PartialEq)]
123pub enum ShapeRef {
124    /// Unit shape.
125    Unit,
126    /// Boolean shape.
127    Bool,
128    /// Character shape.
129    Char,
130    /// `i8` shape.
131    I8,
132    /// `i16` shape.
133    I16,
134    /// `i32` shape.
135    I32,
136    /// `i64` shape.
137    I64,
138    /// `i128` shape.
139    I128,
140    /// `isize` shape.
141    Isize,
142    /// `u8` shape.
143    U8,
144    /// `u16` shape.
145    U16,
146    /// `u32` shape.
147    U32,
148    /// `u64` shape.
149    U64,
150    /// `u128` shape.
151    U128,
152    /// `usize` shape.
153    Usize,
154    /// `f32` shape.
155    F32,
156    /// `f64` shape.
157    F64,
158    /// UTF-8 string shape.
159    String,
160    /// Byte buffer shape.
161    Bytes,
162    /// Optional value shape.
163    Option(Box<ShapeRef>),
164    /// Sequence shape.
165    Seq(Box<ShapeRef>),
166    /// Fixed-size array shape.
167    Array {
168        /// The array item shape.
169        item: Box<ShapeRef>,
170        /// The array length.
171        len: usize,
172    },
173    /// Map shape.
174    Map {
175        /// The map key shape.
176        key: Box<ShapeRef>,
177        /// The map value shape.
178        value: Box<ShapeRef>,
179    },
180    /// Tuple shape.
181    Tuple(Vec<ShapeRef>),
182    /// Named type definition reference.
183    Definition(ShapeId),
184    /// Shape intentionally left opaque.
185    Opaque(OpaqueShape),
186}
187
188impl ShapeRef {
189    /// Return whether this is a signed integer shape.
190    pub fn is_signed_integer(&self) -> bool {
191        matches!(
192            self,
193            Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize
194        )
195    }
196
197    /// Return whether this is an unsigned integer shape.
198    pub fn is_unsigned_integer(&self) -> bool {
199        matches!(
200            self,
201            Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 | Self::Usize
202        )
203    }
204
205    /// Return whether this is any integer shape.
206    pub fn is_integer(&self) -> bool {
207        self.is_signed_integer() || self.is_unsigned_integer()
208    }
209
210    /// Return whether this is a floating point shape.
211    pub fn is_float(&self) -> bool {
212        matches!(self, Self::F32 | Self::F64)
213    }
214
215    /// Return whether this is any numeric shape.
216    pub fn is_number(&self) -> bool {
217        self.is_integer() || self.is_float()
218    }
219}
220
221/// A named type definition in a shape graph.
222#[derive(Clone, Debug, Eq, PartialEq)]
223pub struct DefinitionShape {
224    /// The stable id of this definition inside its graph.
225    pub id: ShapeId,
226    /// The Rust and Serde names for this definition.
227    pub type_name: TypeName,
228    /// The definition body.
229    pub kind: DefinitionKind,
230}
231
232/// The body of a named type definition.
233#[derive(Clone, Debug, Eq, PartialEq)]
234pub enum DefinitionKind {
235    /// Struct-like Serde input.
236    Struct(StructShape),
237    /// Enum-like Serde input.
238    Enum(EnumShape),
239    /// Input shape that cannot be inferred faithfully.
240    Opaque(OpaqueShape),
241}
242
243/// Serde attributes that apply to a whole container.
244#[derive(Clone, Debug, Eq, PartialEq)]
245pub struct ContainerAttributes {
246    /// The container tagging representation.
247    pub tagging: Tagging,
248    /// Whether unknown fields are rejected.
249    pub deny_unknown_fields: bool,
250    /// The default used for missing fields.
251    pub default: DefaultShape,
252    /// Whether any field is flattened.
253    pub has_flatten: bool,
254    /// Whether the container uses `#[serde(transparent)]`.
255    pub transparent: bool,
256    /// Custom Serde expectation text, if present.
257    pub expecting: Option<&'static str>,
258    /// Whether the Rust item is marked `#[non_exhaustive]`.
259    pub non_exhaustive: bool,
260}
261
262/// Serde container or enum tagging representation.
263#[derive(Clone, Debug, Eq, PartialEq)]
264pub enum Tagging {
265    /// The default externally tagged representation.
266    External,
267    /// `#[serde(tag = "...")]`.
268    Internal {
269        /// The tag field name.
270        tag: &'static str,
271    },
272    /// `#[serde(tag = "...", content = "...")]`.
273    Adjacent {
274        /// The tag field name.
275        tag: &'static str,
276        /// The content field name.
277        content: &'static str,
278    },
279    /// `#[serde(untagged)]`.
280    Untagged,
281}
282
283/// Struct-like shape metadata.
284#[derive(Clone, Debug, Eq, PartialEq)]
285pub struct StructShape {
286    /// The struct field style.
287    pub style: FieldsStyle,
288    /// The accepted deserialization fields.
289    pub fields: Vec<FieldShape>,
290    /// Container-level Serde attributes.
291    pub attributes: ContainerAttributes,
292}
293
294/// Enum-like shape metadata.
295#[derive(Clone, Debug, Eq, PartialEq)]
296pub struct EnumShape {
297    /// The enum representation.
298    pub repr: Tagging,
299    /// The accepted deserialization variants.
300    pub variants: Vec<VariantShape>,
301    /// Container-level Serde attributes.
302    pub attributes: ContainerAttributes,
303}
304
305/// The style of a struct, variant, or tuple field list.
306#[derive(Clone, Copy, Debug, Eq, PartialEq)]
307pub enum FieldsStyle {
308    /// Named fields.
309    Struct,
310    /// Multiple unnamed fields.
311    Tuple,
312    /// One unnamed field.
313    Newtype,
314    /// No fields.
315    Unit,
316}
317
318/// Field-level shape metadata.
319#[derive(Clone, Debug, Eq, PartialEq)]
320pub struct FieldShape {
321    /// The original Rust field member.
322    pub member: FieldMember,
323    /// The primary Serde deserialize name.
324    pub deserialize_name: &'static str,
325    /// All accepted Serde deserialize names, including the primary name.
326    pub deserialize_aliases: Vec<&'static str>,
327    /// The field input shape, or `None` when the field has no inferred input.
328    pub shape: Option<ShapeRef>,
329    /// The default used if this field is missing.
330    pub default: DefaultShape,
331    /// Whether the field is flattened into the containing map.
332    pub flatten: bool,
333    /// Whether Serde skips this field during deserialization.
334    pub skip_deserializing: bool,
335    /// Whether this field uses a custom deserializer.
336    pub custom_deserializer: bool,
337    /// Whether this is the transparent field of a transparent container.
338    pub transparent: bool,
339}
340
341/// The Rust member represented by a field.
342#[derive(Clone, Debug, Eq, PartialEq)]
343pub enum FieldMember {
344    /// A named Rust field.
345    Named(&'static str),
346    /// An unnamed tuple field index.
347    Unnamed(usize),
348}
349
350/// Variant-level shape metadata.
351#[derive(Clone, Debug, Eq, PartialEq)]
352pub struct VariantShape {
353    /// The original Rust variant name.
354    pub rust_name: &'static str,
355    /// The primary Serde deserialize name.
356    pub deserialize_name: &'static str,
357    /// All accepted Serde deserialize names, including the primary name.
358    pub deserialize_aliases: Vec<&'static str>,
359    /// The variant field style.
360    pub style: FieldsStyle,
361    /// The variant fields, if their input shape can be inferred.
362    pub fields: Vec<FieldShape>,
363    /// Whether Serde skips this variant during deserialization.
364    pub skip_deserializing: bool,
365    /// Whether this variant uses a custom deserializer.
366    pub custom_deserializer: bool,
367    /// Whether this is a Serde `other` catch-all variant.
368    pub other: bool,
369    /// Whether this variant is individually marked untagged.
370    pub untagged: bool,
371}
372
373/// A Serde default marker.
374#[derive(Clone, Debug, Eq, PartialEq)]
375pub enum DefaultShape {
376    /// No default is configured.
377    None,
378    /// `Default::default()` is used.
379    Default,
380    /// A custom default function path is used.
381    Path(&'static str),
382}
383
384impl DefaultShape {
385    /// Return whether this value represents no default.
386    pub fn is_none(&self) -> bool {
387        matches!(self, Self::None)
388    }
389}
390
391/// Shape intentionally left opaque.
392#[derive(Clone, Debug, Eq, PartialEq)]
393pub struct OpaqueShape {
394    /// The Rust type or Serde item that is opaque.
395    pub type_name: &'static str,
396    /// Why the shape is opaque.
397    pub reason: OpaqueReason,
398    /// Additional human-readable detail.
399    pub detail: Option<&'static str>,
400}
401
402/// Reason a shape cannot be represented precisely.
403#[derive(Clone, Copy, Debug, Eq, PartialEq)]
404pub enum OpaqueReason {
405    /// The type uses `#[serde(from = "...")]`.
406    FromType,
407    /// The type uses `#[serde(try_from = "...")]`.
408    TryFromType,
409    /// The type uses `#[serde(remote = "...")]`.
410    Remote,
411    /// A custom deserializer controls the input.
412    CustomDeserializer,
413    /// The type has no built-in shape implementation.
414    Unsupported,
415}
416
417macro_rules! primitive_shape {
418    ($($ty:ty => $shape:expr;)+) => {
419        $(
420            impl SerdeShape for $ty {
421                fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
422                    $shape
423                }
424            }
425        )+
426    };
427}
428
429primitive_shape! {
430    () => ShapeRef::Unit;
431    bool => ShapeRef::Bool;
432    char => ShapeRef::Char;
433    i8 => ShapeRef::I8;
434    i16 => ShapeRef::I16;
435    i32 => ShapeRef::I32;
436    i64 => ShapeRef::I64;
437    i128 => ShapeRef::I128;
438    isize => ShapeRef::Isize;
439    u8 => ShapeRef::U8;
440    u16 => ShapeRef::U16;
441    u32 => ShapeRef::U32;
442    u64 => ShapeRef::U64;
443    u128 => ShapeRef::U128;
444    usize => ShapeRef::Usize;
445    f32 => ShapeRef::F32;
446    f64 => ShapeRef::F64;
447    str => ShapeRef::String;
448    String => ShapeRef::String;
449    std::path::Path => ShapeRef::String;
450    std::path::PathBuf => ShapeRef::String;
451    std::net::IpAddr => ShapeRef::String;
452    std::net::Ipv4Addr => ShapeRef::String;
453    std::net::Ipv6Addr => ShapeRef::String;
454    std::net::SocketAddr => ShapeRef::String;
455    std::net::SocketAddrV4 => ShapeRef::String;
456    std::net::SocketAddrV6 => ShapeRef::String;
457    std::num::NonZeroI8 => ShapeRef::I8;
458    std::num::NonZeroI16 => ShapeRef::I16;
459    std::num::NonZeroI32 => ShapeRef::I32;
460    std::num::NonZeroI64 => ShapeRef::I64;
461    std::num::NonZeroI128 => ShapeRef::I128;
462    std::num::NonZeroIsize => ShapeRef::Isize;
463    std::num::NonZeroU8 => ShapeRef::U8;
464    std::num::NonZeroU16 => ShapeRef::U16;
465    std::num::NonZeroU32 => ShapeRef::U32;
466    std::num::NonZeroU64 => ShapeRef::U64;
467    std::num::NonZeroU128 => ShapeRef::U128;
468    std::num::NonZeroUsize => ShapeRef::Usize;
469}
470
471#[cfg(target_has_atomic = "8")]
472primitive_shape! {
473    std::sync::atomic::AtomicBool => ShapeRef::Bool;
474    std::sync::atomic::AtomicI8 => ShapeRef::I8;
475    std::sync::atomic::AtomicU8 => ShapeRef::U8;
476}
477
478#[cfg(target_has_atomic = "16")]
479primitive_shape! {
480    std::sync::atomic::AtomicI16 => ShapeRef::I16;
481    std::sync::atomic::AtomicU16 => ShapeRef::U16;
482}
483
484#[cfg(target_has_atomic = "32")]
485primitive_shape! {
486    std::sync::atomic::AtomicI32 => ShapeRef::I32;
487    std::sync::atomic::AtomicU32 => ShapeRef::U32;
488}
489
490#[cfg(target_has_atomic = "64")]
491primitive_shape! {
492    std::sync::atomic::AtomicI64 => ShapeRef::I64;
493    std::sync::atomic::AtomicU64 => ShapeRef::U64;
494}
495
496#[cfg(target_has_atomic = "ptr")]
497primitive_shape! {
498    std::sync::atomic::AtomicIsize => ShapeRef::Isize;
499    std::sync::atomic::AtomicUsize => ShapeRef::Usize;
500}
501
502impl SerdeShape for [u8] {
503    fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
504        ShapeRef::Bytes
505    }
506}
507
508impl<T> SerdeShape for &T
509where
510    T: SerdeShape + ?Sized,
511{
512    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
513        T::shape_in(context)
514    }
515}
516
517impl<T> SerdeShape for &mut T
518where
519    T: SerdeShape + ?Sized,
520{
521    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
522        T::shape_in(context)
523    }
524}
525
526impl<T> SerdeShape for Box<T>
527where
528    T: SerdeShape + ?Sized,
529{
530    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
531        T::shape_in(context)
532    }
533}
534
535impl<'a, T> SerdeShape for std::borrow::Cow<'a, T>
536where
537    T: ToOwned + ?Sized,
538    T::Owned: SerdeShape,
539{
540    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
541        T::Owned::shape_in(context)
542    }
543}
544
545impl<T> SerdeShape for std::cell::Cell<T>
546where
547    T: Copy + SerdeShape,
548{
549    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
550        T::shape_in(context)
551    }
552}
553
554impl<T> SerdeShape for std::cell::RefCell<T>
555where
556    T: SerdeShape,
557{
558    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
559        T::shape_in(context)
560    }
561}
562
563impl<T> SerdeShape for std::sync::Mutex<T>
564where
565    T: SerdeShape,
566{
567    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
568        T::shape_in(context)
569    }
570}
571
572impl<T> SerdeShape for std::sync::RwLock<T>
573where
574    T: SerdeShape,
575{
576    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
577        T::shape_in(context)
578    }
579}
580
581impl<T> SerdeShape for std::num::Wrapping<T>
582where
583    T: SerdeShape,
584{
585    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
586        T::shape_in(context)
587    }
588}
589
590impl<T> SerdeShape for std::cmp::Reverse<T>
591where
592    T: SerdeShape,
593{
594    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
595        T::shape_in(context)
596    }
597}
598
599impl<T> SerdeShape for Option<T>
600where
601    T: SerdeShape,
602{
603    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
604        ShapeRef::Option(Box::new(T::shape_in(context)))
605    }
606}
607
608impl<T> SerdeShape for Vec<T>
609where
610    T: SerdeShape,
611{
612    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
613        ShapeRef::Seq(Box::new(T::shape_in(context)))
614    }
615}
616
617impl<T> SerdeShape for std::collections::VecDeque<T>
618where
619    T: SerdeShape,
620{
621    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
622        ShapeRef::Seq(Box::new(T::shape_in(context)))
623    }
624}
625
626impl<T> SerdeShape for std::collections::LinkedList<T>
627where
628    T: SerdeShape,
629{
630    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
631        ShapeRef::Seq(Box::new(T::shape_in(context)))
632    }
633}
634
635impl<T> SerdeShape for std::collections::BinaryHeap<T>
636where
637    T: Ord + SerdeShape,
638{
639    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
640        ShapeRef::Seq(Box::new(T::shape_in(context)))
641    }
642}
643
644impl<T, const N: usize> SerdeShape for [T; N]
645where
646    T: SerdeShape,
647{
648    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
649        ShapeRef::Array {
650            item: Box::new(T::shape_in(context)),
651            len: N,
652        }
653    }
654}
655
656impl<K, V> SerdeShape for BTreeMap<K, V>
657where
658    K: SerdeShape,
659    V: SerdeShape,
660{
661    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
662        ShapeRef::Map {
663            key: Box::new(K::shape_in(context)),
664            value: Box::new(V::shape_in(context)),
665        }
666    }
667}
668
669impl<K, V, S> SerdeShape for std::collections::HashMap<K, V, S>
670where
671    K: SerdeShape,
672    V: SerdeShape,
673{
674    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
675        ShapeRef::Map {
676            key: Box::new(K::shape_in(context)),
677            value: Box::new(V::shape_in(context)),
678        }
679    }
680}
681
682impl<T> SerdeShape for std::collections::BTreeSet<T>
683where
684    T: SerdeShape,
685{
686    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
687        ShapeRef::Seq(Box::new(T::shape_in(context)))
688    }
689}
690
691impl<T, S> SerdeShape for std::collections::HashSet<T, S>
692where
693    T: SerdeShape,
694{
695    fn shape_in(context: &mut ShapeContext) -> ShapeRef {
696        ShapeRef::Seq(Box::new(T::shape_in(context)))
697    }
698}
699
700impl<T> SerdeShape for std::marker::PhantomData<T> {
701    fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
702        ShapeRef::Unit
703    }
704}
705
706macro_rules! tuple_shape {
707    ($($name:ident),+ $(,)?) => {
708        impl<$($name),+> SerdeShape for ($($name,)+)
709        where
710            $($name: SerdeShape,)+
711        {
712            fn shape_in(context: &mut ShapeContext) -> ShapeRef {
713                ShapeRef::Tuple(vec![$($name::shape_in(context),)+])
714            }
715        }
716    };
717}
718
719tuple_shape!(T0);
720tuple_shape!(T0, T1);
721tuple_shape!(T0, T1, T2);
722tuple_shape!(T0, T1, T2, T3);
723tuple_shape!(T0, T1, T2, T3, T4);
724tuple_shape!(T0, T1, T2, T3, T4, T5);
725tuple_shape!(T0, T1, T2, T3, T4, T5, T6);
726tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7);
727tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
728tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
729tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
730tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735
736    #[test]
737    fn builds_map_shape() {
738        let shape = Shape::for_type::<BTreeMap<String, Option<u16>>>();
739
740        assert_eq!(
741            shape.root,
742            ShapeRef::Map {
743                key: Box::new(ShapeRef::String),
744                value: Box::new(ShapeRef::Option(Box::new(ShapeRef::U16))),
745            }
746        );
747        assert!(shape.definitions.is_empty());
748    }
749
750    #[test]
751    fn maps_common_std_shapes() {
752        assert_eq!(Shape::for_type::<std::path::Path>().root, ShapeRef::String);
753        assert_eq!(
754            Shape::for_type::<std::path::PathBuf>().root,
755            ShapeRef::String
756        );
757        assert_eq!(
758            Shape::for_type::<std::borrow::Cow<'static, str>>().root,
759            ShapeRef::String
760        );
761        assert_eq!(Shape::for_type::<std::cell::Cell<u8>>().root, ShapeRef::U8);
762        assert_eq!(
763            Shape::for_type::<std::num::Wrapping<i16>>().root,
764            ShapeRef::I16
765        );
766        assert_eq!(
767            Shape::for_type::<std::cmp::Reverse<u32>>().root,
768            ShapeRef::U32
769        );
770        assert_eq!(
771            Shape::for_type::<std::collections::VecDeque<u8>>().root,
772            ShapeRef::Seq(Box::new(ShapeRef::U8))
773        );
774        assert_eq!(
775            Shape::for_type::<std::collections::LinkedList<i32>>().root,
776            ShapeRef::Seq(Box::new(ShapeRef::I32))
777        );
778        assert_eq!(
779            Shape::for_type::<std::collections::BinaryHeap<u16>>().root,
780            ShapeRef::Seq(Box::new(ShapeRef::U16))
781        );
782    }
783
784    #[test]
785    fn classifies_flat_numeric_shapes() {
786        assert!(ShapeRef::I8.is_signed_integer());
787        assert!(ShapeRef::Usize.is_unsigned_integer());
788        assert!(ShapeRef::I128.is_integer());
789        assert!(ShapeRef::U64.is_integer());
790        assert!(ShapeRef::F32.is_float());
791        assert!(ShapeRef::F64.is_number());
792        assert!(!ShapeRef::String.is_number());
793    }
794
795    #[cfg(target_has_atomic = "ptr")]
796    #[test]
797    fn maps_atomic_shapes() {
798        assert_eq!(
799            Shape::for_type::<std::sync::atomic::AtomicUsize>().root,
800            ShapeRef::Usize
801        );
802    }
803}