Skip to main content

cljrs_value/
type_hint.rs

1//! Primitive type hints (`^long`, `^double`, `^longs`, …).
2//!
3//! Clojure lets a name be tagged with a type via reader metadata
4//! (`^long x`, expanded to `{:tag long}`).  For the *primitive* tags the
5//! compiler can keep the value unboxed and emit specialized arithmetic and
6//! array access.  This enum is the parsed, normalized form of such a tag; the
7//! interpreter records one per function parameter (`CljxFnArity::param_hints`)
8//! and the IR lowering maps it onto a representation seed for type inference.
9//!
10//! Non-primitive tags (`^String`, `^MyRecord`, …) are *advisory* in Clojure and
11//! carry no unboxing semantics here, so they resolve to `None` and are ignored.
12
13/// A primitive type hint usable for unboxing / array specialization.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum TypeHint {
16    /// `^long` — unboxed `i64`.
17    Long,
18    /// `^double` — unboxed `f64`.
19    Double,
20    /// `^int` — 32-bit int (treated as a long-family scalar for inference).
21    Int,
22    /// `^float` — 32-bit float (treated as a double-family scalar).
23    Float,
24    /// `^boolean` — unboxed `i8` truth value.
25    Bool,
26    /// `^longs` — array of `i64`.
27    LongArray,
28    /// `^doubles` — array of `f64`.
29    DoubleArray,
30    /// `^ints` — array of `i32`.
31    IntArray,
32    /// `^floats` — array of `f32`.
33    FloatArray,
34    /// `^booleans` — array of `bool`.
35    BooleanArray,
36    /// `^objects` — array of boxed values.
37    ObjectArray,
38}
39
40impl TypeHint {
41    /// Resolve a `:tag` symbol name to a primitive hint, or `None` for tags
42    /// that carry no primitive/unboxing meaning (e.g. `^String`).
43    ///
44    /// Recognizes the standard Clojure spellings.  A leading namespace, if any,
45    /// is ignored by callers before this point.
46    pub fn from_tag(name: &str) -> Option<TypeHint> {
47        Some(match name {
48            "long" => TypeHint::Long,
49            "double" => TypeHint::Double,
50            "int" => TypeHint::Int,
51            "float" => TypeHint::Float,
52            "boolean" => TypeHint::Bool,
53            "longs" => TypeHint::LongArray,
54            "doubles" => TypeHint::DoubleArray,
55            "ints" => TypeHint::IntArray,
56            "floats" => TypeHint::FloatArray,
57            "booleans" => TypeHint::BooleanArray,
58            "objects" => TypeHint::ObjectArray,
59            _ => return None,
60        })
61    }
62
63    /// Whether this hint denotes a primitive array type.
64    pub fn is_array(&self) -> bool {
65        matches!(
66            self,
67            TypeHint::LongArray
68                | TypeHint::DoubleArray
69                | TypeHint::IntArray
70                | TypeHint::FloatArray
71                | TypeHint::BooleanArray
72                | TypeHint::ObjectArray
73        )
74    }
75
76    /// The scalar element hint of an array hint (`^longs` → `^long`), or `None`
77    /// for scalar hints and the boxed object array.
78    pub fn element(&self) -> Option<TypeHint> {
79        Some(match self {
80            TypeHint::LongArray => TypeHint::Long,
81            TypeHint::DoubleArray => TypeHint::Double,
82            TypeHint::IntArray => TypeHint::Int,
83            TypeHint::FloatArray => TypeHint::Float,
84            TypeHint::BooleanArray => TypeHint::Bool,
85            _ => return None,
86        })
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn resolves_scalar_tags() {
96        assert_eq!(TypeHint::from_tag("long"), Some(TypeHint::Long));
97        assert_eq!(TypeHint::from_tag("double"), Some(TypeHint::Double));
98        assert_eq!(TypeHint::from_tag("boolean"), Some(TypeHint::Bool));
99    }
100
101    #[test]
102    fn resolves_array_tags() {
103        assert_eq!(TypeHint::from_tag("longs"), Some(TypeHint::LongArray));
104        assert_eq!(TypeHint::from_tag("doubles"), Some(TypeHint::DoubleArray));
105        assert!(TypeHint::LongArray.is_array());
106        assert_eq!(TypeHint::LongArray.element(), Some(TypeHint::Long));
107    }
108
109    #[test]
110    fn unknown_tag_is_none() {
111        assert_eq!(TypeHint::from_tag("String"), None);
112        assert_eq!(TypeHint::from_tag("MyRecord"), None);
113    }
114}