jtd_infer/
inferred_number.rs

1use jtd::Type;
2
3#[derive(Debug)]
4pub struct InferredNumber {
5    min: f64,
6    max: f64,
7    int: bool,
8}
9
10impl InferredNumber {
11    pub fn new() -> Self {
12        Self {
13            min: f64::MAX,
14            max: f64::MIN,
15            int: true,
16        }
17    }
18
19    pub fn infer(&self, n: f64) -> Self {
20        Self {
21            min: self.min.min(n),
22            max: self.max.max(n),
23            int: self.int && n.fract() == 0.0,
24        }
25    }
26
27    pub fn into_type(&self, default: &NumType) -> Type {
28        if self.contained_by(default) {
29            return default.into_type();
30        }
31
32        let types = [
33            NumType::Uint8,
34            NumType::Int8,
35            NumType::Uint16,
36            NumType::Int16,
37            NumType::Uint32,
38            NumType::Int32,
39        ];
40
41        for type_ in &types {
42            if self.contained_by(type_) {
43                return type_.into_type();
44            }
45        }
46
47        return NumType::Float64.into_type();
48    }
49
50    fn contained_by(&self, type_: &NumType) -> bool {
51        if !self.int && !type_.is_float() {
52            return false;
53        }
54
55        let (min, max) = type_.as_range();
56        min <= self.min && max >= self.max
57    }
58}
59
60/// A type of number to infer by default.
61///
62/// See [`Hints`][`crate::Hints`] for how this enum is used.
63#[derive(Clone)]
64pub enum NumType {
65    /// Corresponds to [`jtd::Type::Int8`].
66    Int8,
67
68    /// Corresponds to [`jtd::Type::Uint8`].
69    Uint8,
70
71    /// Corresponds to [`jtd::Type::Int16`].
72    Int16,
73
74    /// Corresponds to [`jtd::Type::Uint16`].
75    Uint16,
76
77    /// Corresponds to [`jtd::Type::Int32`].
78    Int32,
79
80    /// Corresponds to [`jtd::Type::Uint32`].
81    Uint32,
82
83    /// Corresponds to [`jtd::Type::Float32`].
84    Float32,
85
86    /// Corresponds to [`jtd::Type::Float64`].
87    Float64,
88}
89
90impl NumType {
91    fn is_float(&self) -> bool {
92        match self {
93            Self::Float32 | Self::Float64 => true,
94            _ => false,
95        }
96    }
97
98    fn as_range(&self) -> (f64, f64) {
99        match self {
100            Self::Int8 => (i8::MIN as f64, i8::MAX as f64),
101            Self::Uint8 => (u8::MIN as f64, u8::MAX as f64),
102            Self::Int16 => (i16::MIN as f64, i16::MAX as f64),
103            Self::Uint16 => (u16::MIN as f64, u16::MAX as f64),
104            Self::Int32 => (i32::MIN as f64, i32::MAX as f64),
105            Self::Uint32 => (u32::MIN as f64, u32::MAX as f64),
106            Self::Float32 | Self::Float64 => (f64::MIN, f64::MAX),
107        }
108    }
109
110    fn into_type(&self) -> Type {
111        match self {
112            Self::Int8 => Type::Int8,
113            Self::Uint8 => Type::Uint8,
114            Self::Int16 => Type::Int16,
115            Self::Uint16 => Type::Uint16,
116            Self::Int32 => Type::Int32,
117            Self::Uint32 => Type::Uint32,
118            Self::Float32 => Type::Float32,
119            Self::Float64 => Type::Float64,
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn inferred_number() {
130        let n = InferredNumber::new();
131
132        // At first, default always honored.
133        assert_eq!(Type::Uint8, n.into_type(&NumType::Uint8));
134        assert_eq!(Type::Int8, n.into_type(&NumType::Int8));
135        assert_eq!(Type::Uint16, n.into_type(&NumType::Uint16));
136        assert_eq!(Type::Int16, n.into_type(&NumType::Int16));
137        assert_eq!(Type::Uint32, n.into_type(&NumType::Uint32));
138        assert_eq!(Type::Int32, n.into_type(&NumType::Int32));
139        assert_eq!(Type::Float32, n.into_type(&NumType::Float32));
140        assert_eq!(Type::Float64, n.into_type(&NumType::Float64));
141
142        // Test expanding to limits of uint8.
143        let n = InferredNumber::new()
144            .infer(u8::MIN as f64)
145            .infer(u8::MAX as f64);
146
147        assert_eq!(Type::Uint8, n.into_type(&NumType::Uint8));
148        assert_eq!(Type::Uint8, n.into_type(&NumType::Int8));
149        assert_eq!(Type::Uint16, n.into_type(&NumType::Uint16));
150        assert_eq!(Type::Int16, n.into_type(&NumType::Int16));
151        assert_eq!(Type::Uint32, n.into_type(&NumType::Uint32));
152        assert_eq!(Type::Int32, n.into_type(&NumType::Int32));
153        assert_eq!(Type::Float32, n.into_type(&NumType::Float32));
154        assert_eq!(Type::Float64, n.into_type(&NumType::Float64));
155
156        // Test expanding to limits of int8.
157        let n = InferredNumber::new()
158            .infer(i8::MIN as f64)
159            .infer(i8::MAX as f64);
160
161        assert_eq!(Type::Int8, n.into_type(&NumType::Uint8));
162        assert_eq!(Type::Int8, n.into_type(&NumType::Int8));
163        assert_eq!(Type::Int8, n.into_type(&NumType::Uint16));
164        assert_eq!(Type::Int16, n.into_type(&NumType::Int16));
165        assert_eq!(Type::Int8, n.into_type(&NumType::Uint32));
166        assert_eq!(Type::Int32, n.into_type(&NumType::Int32));
167        assert_eq!(Type::Float32, n.into_type(&NumType::Float32));
168        assert_eq!(Type::Float64, n.into_type(&NumType::Float64));
169
170        // Test including a non-integer.
171        let n = InferredNumber::new().infer(0.5);
172        assert_eq!(Type::Float64, n.into_type(&NumType::Uint8));
173        assert_eq!(Type::Float64, n.into_type(&NumType::Int8));
174        assert_eq!(Type::Float64, n.into_type(&NumType::Uint16));
175        assert_eq!(Type::Float64, n.into_type(&NumType::Int16));
176        assert_eq!(Type::Float64, n.into_type(&NumType::Uint32));
177        assert_eq!(Type::Float64, n.into_type(&NumType::Int32));
178        assert_eq!(Type::Float32, n.into_type(&NumType::Float32));
179        assert_eq!(Type::Float64, n.into_type(&NumType::Float64));
180    }
181}