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}