Skip to main content

otter/types/
typed.rs

1//! `TypedTerm` and `resolve` — the typed view of a received term.
2//!
3//! [`AnyTerm`] is the bare branded word, type unknown. [`AnyTerm::resolve`]
4//! makes one `enif_term_type` call and returns a [`TypedTerm`] — a tagged enum
5//! whose 11 variants mirror BEAM's 11 type tags — so you can `match` on the
6//! shape of a received term. Resolution only learns the tag; the data stays on
7//! the BEAM heap until an accessor pulls it out. `TypedTerm` carries Erlang's
8//! own equality (`=:=`) and term order, and every concrete type converts into it
9//! via `From`.
10
11use crate::types::sealed::Sealed;
12use crate::types::{
13    AnyTerm, Atom, Binary, Bitstring, Env, Float, Fun, Integer, List, Map, Pid, Port, RawTerm,
14    Reference, Term, Tuple,
15};
16
17/// Typed enum produced by [`AnyTerm::resolve`]. One `enif_term_type` call has
18/// been made; the data is still on the BEAM heap.
19///
20/// Mirrors BEAM's `ErlNifTermType`: byte-aligned binaries and sub-byte
21/// bitstrings share the [`Bitstring`](Self::Bitstring) variant (BEAM treats
22/// every binary as a bitstring). Refine with [`Bitstring::to_binary`].
23#[derive(Clone, Copy)]
24pub enum TypedTerm<'id> {
25    /// An atom.
26    Atom(Atom),
27    /// A binary or sub-byte bitstring (`enif_term_type` reports both as
28    /// `Bitstring`). Refine to a byte-aligned [`Binary`] with
29    /// [`Bitstring::to_binary`].
30    Bitstring(Bitstring<'id>),
31    /// A float.
32    Float(Float<'id>),
33    /// A fun (closure or `&module:function/arity`).
34    Fun(Fun<'id>),
35    /// An integer (fixnum or bignum).
36    Integer(Integer<'id>),
37    /// A list (proper or improper; the empty list `[]` is also a list).
38    List(List<'id>),
39    /// A map.
40    Map(Map<'id>),
41    /// A pid (local or external).
42    Pid(Pid<'id>),
43    /// A port (local or external).
44    Port(Port<'id>),
45    /// A reference.
46    Reference(Reference<'id>),
47    /// A tuple.
48    Tuple(Tuple<'id>),
49}
50
51impl<'id> AnyTerm<'id> {
52    /// Resolve to a typed [`TypedTerm`] (`enif_term_type`). Exactly one NIF call.
53    /// `None` for a type code this otter build does not recognize (a newer-OTP
54    /// type); the original `AnyTerm` is still usable.
55    ///
56    /// The fallible counterpart is the [`Decoder`](crate::codec::Decoder) impl
57    /// for `TypedTerm`, which maps the `None` case to
58    /// [`CodecError::UnknownTermType`](crate::codec::CodecError::UnknownTermType).
59    pub fn resolve(self, env: impl Env<'id>) -> Option<TypedTerm<'id>> {
60        let raw = self.raw_term();
61        Some(match env.term_type(self)? {
62            enif_ffi::TermType::Atom => TypedTerm::Atom(Atom::from_raw(raw)),
63            enif_ffi::TermType::Bitstring => TypedTerm::Bitstring(Bitstring::from_raw(raw)),
64            enif_ffi::TermType::Float => TypedTerm::Float(Float::from_raw(raw)),
65            enif_ffi::TermType::Fun => TypedTerm::Fun(Fun::from_raw(raw)),
66            enif_ffi::TermType::Integer => TypedTerm::Integer(Integer::from_raw(raw)),
67            enif_ffi::TermType::List => TypedTerm::List(List::from_raw(raw)),
68            enif_ffi::TermType::Map => TypedTerm::Map(Map::from_raw(raw)),
69            enif_ffi::TermType::Pid => TypedTerm::Pid(Pid::from_raw(raw)),
70            enif_ffi::TermType::Port => TypedTerm::Port(Port::from_raw(raw)),
71            enif_ffi::TermType::Reference => TypedTerm::Reference(Reference::from_raw(raw)),
72            enif_ffi::TermType::Tuple => TypedTerm::Tuple(Tuple::from_raw(raw)),
73        })
74    }
75}
76
77impl<'id> Sealed for TypedTerm<'id> {}
78
79impl<'id> Term<'id> for TypedTerm<'id> {
80    /// Extract the underlying machine word, discarding the variant tag.
81    fn raw_term(self) -> RawTerm {
82        match self {
83            TypedTerm::Atom(v) => v.raw_term(),
84            TypedTerm::Bitstring(v) => v.raw_term(),
85            TypedTerm::Float(v) => v.raw_term(),
86            TypedTerm::Fun(v) => v.raw_term(),
87            TypedTerm::Integer(v) => v.raw_term(),
88            TypedTerm::List(v) => v.raw_term(),
89            TypedTerm::Map(v) => v.raw_term(),
90            TypedTerm::Pid(v) => v.raw_term(),
91            TypedTerm::Port(v) => v.raw_term(),
92            TypedTerm::Reference(v) => v.raw_term(),
93            TypedTerm::Tuple(v) => v.raw_term(),
94        }
95    }
96}
97
98impl<'id> From<Atom> for TypedTerm<'id> {
99    fn from(v: Atom) -> Self {
100        TypedTerm::Atom(v)
101    }
102}
103impl<'id> From<Binary<'id>> for TypedTerm<'id> {
104    /// A binary is a byte-aligned bitstring, so it lands in the `Bitstring` variant.
105    fn from(v: Binary<'id>) -> Self {
106        TypedTerm::Bitstring(Bitstring::from_raw(v.raw_term()))
107    }
108}
109impl<'id> From<Bitstring<'id>> for TypedTerm<'id> {
110    fn from(v: Bitstring<'id>) -> Self {
111        TypedTerm::Bitstring(v)
112    }
113}
114impl<'id> From<Float<'id>> for TypedTerm<'id> {
115    fn from(v: Float<'id>) -> Self {
116        TypedTerm::Float(v)
117    }
118}
119impl<'id> From<Fun<'id>> for TypedTerm<'id> {
120    fn from(v: Fun<'id>) -> Self {
121        TypedTerm::Fun(v)
122    }
123}
124impl<'id> From<Integer<'id>> for TypedTerm<'id> {
125    fn from(v: Integer<'id>) -> Self {
126        TypedTerm::Integer(v)
127    }
128}
129impl<'id> From<List<'id>> for TypedTerm<'id> {
130    fn from(v: List<'id>) -> Self {
131        TypedTerm::List(v)
132    }
133}
134impl<'id> From<Map<'id>> for TypedTerm<'id> {
135    fn from(v: Map<'id>) -> Self {
136        TypedTerm::Map(v)
137    }
138}
139impl<'id> From<Pid<'id>> for TypedTerm<'id> {
140    fn from(v: Pid<'id>) -> Self {
141        TypedTerm::Pid(v)
142    }
143}
144impl<'id> From<Port<'id>> for TypedTerm<'id> {
145    fn from(v: Port<'id>) -> Self {
146        TypedTerm::Port(v)
147    }
148}
149impl<'id> From<Reference<'id>> for TypedTerm<'id> {
150    fn from(v: Reference<'id>) -> Self {
151        TypedTerm::Reference(v)
152    }
153}
154impl<'id> From<Tuple<'id>> for TypedTerm<'id> {
155    fn from(v: Tuple<'id>) -> Self {
156        TypedTerm::Tuple(v)
157    }
158}
159
160/// Term identity (`enif_is_identical`) — the BEAM's `=:=`, not Rust structural
161/// equality. Ignores the variant tag and compares the underlying terms.
162impl PartialEq for TypedTerm<'_> {
163    fn eq(&self, other: &Self) -> bool {
164        unsafe { enif_ffi::is_identical(Term::raw_term(*self), Term::raw_term(*other)) != 0 }
165    }
166}
167
168impl Eq for TypedTerm<'_> {}
169
170impl PartialOrd for TypedTerm<'_> {
171    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
172        Some(self.cmp(other))
173    }
174}
175
176/// Erlang term order (`enif_compare`) — the standard cross-type ordering
177/// `number < atom < reference < fun < port < pid < tuple < map < nil < list <
178/// bitstring`, not a Rust-derived ordering.
179impl Ord for TypedTerm<'_> {
180    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
181        let c = unsafe { enif_ffi::compare(Term::raw_term(*self), Term::raw_term(*other)) };
182        c.cmp(&0)
183    }
184}