Skip to main content

object_rainbow/
enumkind.rs

1use crate::*;
2
3/// Discriminant arithmetic glue to make `#[derive(Enum)]` function.
4pub trait UsizeTag: Sized {
5    /// Used by [`EnumTag::from_const`].
6    ///
7    /// ## Panics
8    ///
9    /// Panics on out-of-bounds.
10    fn from_usize(n: usize) -> Self;
11    /// Convert a trusted value to [`usize`].
12    fn to_usize(&self) -> usize;
13    /// Convert an untrusted value to [`usize`].
14    fn try_to_usize(&self) -> Option<usize>;
15}
16
17impl UsizeTag for bool {
18    fn from_usize(n: usize) -> Self {
19        match n {
20            0 => false,
21            1 => true,
22            _ => panic!("out of bounds"),
23        }
24    }
25
26    fn to_usize(&self) -> usize {
27        *self as _
28    }
29
30    fn try_to_usize(&self) -> Option<usize> {
31        Some(self.to_usize())
32    }
33}
34
35/// [`EnumKind::Tag`].
36#[derive(
37    ToOutput, InlineOutput, ListHashes, Topological, Tagged, ParseAsInline, Size, MaybeHasNiche,
38)]
39pub struct EnumTag<T, const MAX: usize>(T);
40
41impl<T: UsizeTag, const MAX: usize> UsizeTag for EnumTag<T, MAX> {
42    fn from_usize(n: usize) -> Self {
43        assert!(n < MAX);
44        Self(UsizeTag::from_usize(n))
45    }
46
47    fn to_usize(&self) -> usize {
48        self.0.to_usize()
49    }
50
51    fn try_to_usize(&self) -> Option<usize> {
52        self.0.try_to_usize()
53    }
54}
55
56impl<T: UsizeTag, const MAX: usize> EnumTag<T, MAX> {
57    /// Inherent alias of [`UsizeTag::to_usize`].
58    pub fn to_usize(&self) -> usize {
59        self.0.to_usize()
60    }
61
62    /// Generate the tag from a statically known index.
63    pub fn from_const<const N: usize>() -> Self {
64        assert!(N < MAX);
65        Self(UsizeTag::from_usize(N))
66    }
67}
68
69impl<T: ParseInline<I> + UsizeTag, I: ParseInput, const MAX: usize> ParseInline<I>
70    for EnumTag<T, MAX>
71{
72    fn parse_inline(input: &mut I) -> crate::Result<Self> {
73        let n_raw = T::parse_inline(input)?;
74        let n: Option<usize> = n_raw.try_to_usize();
75        if let Some(n) = n {
76            if n < MAX {
77                return Ok(Self(n_raw));
78            }
79        }
80        Err(Error::DiscriminantOverflow)
81    }
82}
83
84/// An [`Inline`] identifying variants of an [`Enum`].
85pub trait EnumKind: Copy {
86    /// Underlying [`Inline`]. Typically [`EnumTag`].
87    type Tag;
88    /// Get the underlying [`Inline`].
89    fn to_tag(self) -> Self::Tag;
90    /// Convert from an [`EnumTag`] assuming the value is within bounds, such that otherwise we have
91    /// caught their violation during parsing of that tag.
92    ///
93    /// Panics on out-of-bounds.
94    fn from_tag(tag: Self::Tag) -> Self;
95}
96
97/// `enum`.
98pub trait Enum {
99    /// Discriminant uniquely identifying this `enum`'s variants.
100    type Kind: EnumKind;
101    /// Get the [`EnumKind`] of this variant.
102    fn kind(&self) -> Self::Kind;
103}
104
105/// [`Enum`]-specific [`Parse`].
106pub trait EnumParse<I: ParseInput>: Enum + Parse<I> {
107    /// Given an already-parsed [`EnumKind`], parse the rest of a [`Parse`] [`Enum`].
108    fn enum_parse(kind: Self::Kind, input: I) -> crate::Result<Self>;
109    /// For implementing [`Parse::parse`].
110    fn parse_as_enum(mut input: I) -> crate::Result<Self>
111    where
112        <Self::Kind as EnumKind>::Tag: ParseInline<I>,
113    {
114        Self::enum_parse(Self::Kind::from_tag(input.parse_inline()?), input)
115    }
116}
117
118/// [`Enum`]-specific [`ParseInline`].
119pub trait EnumParseInline<I: ParseInput>: Enum + ParseInline<I> {
120    /// Given an already-parsed [`EnumKind`], parse the rest of a [`ParseInline`] [`Enum`].
121    fn enum_parse_inline(kind: Self::Kind, input: &mut I) -> crate::Result<Self>;
122    /// For implementing [`ParseInline::parse_inline`].
123    fn parse_as_inline_enum(input: &mut I) -> crate::Result<Self>
124    where
125        <Self::Kind as EnumKind>::Tag: ParseInline<I>,
126    {
127        Self::enum_parse_inline(Self::Kind::from_tag(input.parse_inline()?), input)
128    }
129}