Skip to main content

specta/datatype/
reference.rs

1use std::{
2    any::{Any, TypeId},
3    borrow::Cow,
4    fmt, hash,
5    sync::Arc,
6};
7
8use crate::{
9    Types,
10    datatype::{Generic, Map, NamedDataType, Primitive},
11};
12
13use super::DataType;
14
15/// Reference to another datatype.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub enum Reference {
18    /// Reference to a named type collected in a [`Types`](crate::Types).
19    ///
20    /// This can either render as a named reference, such as `TypeName<T>`, or as
21    /// an inlined datatype depending on [`NamedReference::inner`].
22    Named(NamedReference),
23    /// Reference to an opaque exporter-specific type.
24    Opaque(OpaqueReference),
25}
26
27/// Reference to a [`NamedDataType`](crate::datatype::NamedDataType).
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29#[non_exhaustive]
30pub struct NamedReference {
31    pub(crate) id: NamedId,
32    /// How this named type should be referenced at the use site.
33    pub inner: NamedReferenceType,
34}
35
36/// Use-site representation for a [`NamedReference`].
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub enum NamedReferenceType {
39    /// Recursive reference encountered while resolving an inline type.
40    ///
41    /// Exporters can use this marker to avoid infinitely expanding recursive
42    /// inline definitions that they would stack overflow resolving.
43    Recursive(RecursiveInlineType),
44    /// Inline the contained datatype at the reference site.
45    /// These are emitted when `#[specta(inline)]` is used on a field or container.
46    #[non_exhaustive]
47    Inline {
48        /// Datatype to render in place of the named reference.
49        dt: Box<DataType>,
50    },
51    /// Render a reference to the named datatype.
52    #[non_exhaustive]
53    Reference {
54        /// Concrete generic arguments for this use site.
55        generics: Vec<(Generic, DataType)>,
56    },
57}
58
59/// Debug-only description of a type in a recursive inline cycle.
60#[derive(Clone, PartialEq, Eq, Hash)]
61#[non_exhaustive]
62pub struct RecursiveInlineType {
63    cycle: Vec<RecursiveInlineFrame>,
64}
65
66#[derive(Clone, PartialEq, Eq, Hash)]
67pub(crate) struct RecursiveInlineFrame {
68    type_name: Cow<'static, str>,
69    generics: Vec<Cow<'static, str>>,
70}
71
72impl RecursiveInlineType {
73    pub(crate) fn from_cycle(cycle: Vec<RecursiveInlineFrame>) -> Self {
74        Self { cycle }
75    }
76
77    fn last_frame(&self) -> Option<&RecursiveInlineFrame> {
78        self.cycle.last()
79    }
80}
81
82impl fmt::Debug for RecursiveInlineType {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        for (idx, ty) in self.cycle.iter().enumerate() {
85            if idx != 0 {
86                f.write_str(" -> ")?;
87            }
88            write!(f, "{ty:?}")?;
89        }
90        Ok(())
91    }
92}
93
94impl RecursiveInlineFrame {
95    pub(crate) fn new(
96        types: &Types,
97        ndt: &NamedDataType,
98        generics: &[(Generic, DataType)],
99    ) -> Self {
100        Self::new_inner(types, named_type_path(ndt), generics)
101    }
102
103    pub(crate) fn from_type_path(
104        types: &Types,
105        type_name: Cow<'static, str>,
106        generics: &[(Generic, DataType)],
107    ) -> Self {
108        Self::new_inner(types, type_name, generics)
109    }
110
111    fn new_inner(
112        types: &Types,
113        type_name: Cow<'static, str>,
114        generics: &[(Generic, DataType)],
115    ) -> Self {
116        Self {
117            type_name,
118            generics: generics
119                .iter()
120                .map(|(_, dt)| render_recursive_inline_generic(types, dt))
121                .collect(),
122        }
123    }
124}
125
126impl fmt::Debug for RecursiveInlineFrame {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.write_str(&self.type_name)?;
129        if self.generics.is_empty() {
130            return Ok(());
131        }
132
133        f.write_str("<")?;
134        for (idx, generic) in self.generics.iter().enumerate() {
135            if idx != 0 {
136                f.write_str(", ")?;
137            }
138            f.write_str(generic)?;
139        }
140        f.write_str(">")
141    }
142}
143
144fn named_type_path(ndt: &NamedDataType) -> Cow<'static, str> {
145    if ndt.module_path.is_empty() {
146        ndt.name.clone()
147    } else {
148        Cow::Owned(format!("{}::{}", ndt.module_path, ndt.name))
149    }
150}
151
152fn render_recursive_inline_generic(types: &Types, dt: &DataType) -> Cow<'static, str> {
153    match dt {
154        DataType::Primitive(primitive) => Cow::Borrowed(primitive_type_name(primitive)),
155        DataType::Generic(generic) => generic.name().clone(),
156        DataType::Reference(Reference::Named(reference)) => {
157            render_recursive_inline_named(types, reference)
158        }
159        DataType::Reference(Reference::Opaque(reference)) => Cow::Borrowed(reference.type_name()),
160        DataType::List(list) => {
161            let ty = render_recursive_inline_generic(types, &list.ty);
162            match list.length {
163                Some(length) => Cow::Owned(format!("[{ty}; {length}]")),
164                None => Cow::Owned(format!("Vec<{ty}>")),
165            }
166        }
167        DataType::Map(map) => render_recursive_inline_map(types, map),
168        DataType::Nullable(inner) => Cow::Owned(format!(
169            "Option<{}>",
170            render_recursive_inline_generic(types, inner)
171        )),
172        DataType::Tuple(tuple) => Cow::Owned(format!(
173            "({})",
174            tuple
175                .elements
176                .iter()
177                .map(|dt| render_recursive_inline_generic(types, dt).into_owned())
178                .collect::<Vec<_>>()
179                .join(", ")
180        )),
181        dt => Cow::Owned(format!("{dt:?}")),
182    }
183}
184
185fn render_recursive_inline_named(types: &Types, reference: &NamedReference) -> Cow<'static, str> {
186    if let NamedReferenceType::Recursive(cycle) = &reference.inner
187        && let Some(ty) = cycle.last_frame()
188    {
189        return Cow::Owned(format!("{ty:?}"));
190    }
191
192    let Some(ndt) = types.get(reference) else {
193        return Cow::Owned(format!("{reference:?}"));
194    };
195
196    let mut out = named_type_path(ndt).into_owned();
197    if let NamedReferenceType::Reference { generics } = &reference.inner
198        && !generics.is_empty()
199    {
200        out.push('<');
201        out.push_str(
202            &generics
203                .iter()
204                .map(|(_, dt)| render_recursive_inline_generic(types, dt).into_owned())
205                .collect::<Vec<_>>()
206                .join(", "),
207        );
208        out.push('>');
209    }
210    Cow::Owned(out)
211}
212
213fn render_recursive_inline_map(types: &Types, map: &Map) -> Cow<'static, str> {
214    Cow::Owned(format!(
215        "HashMap<{}, {}>",
216        render_recursive_inline_generic(types, map.key_ty()),
217        render_recursive_inline_generic(types, map.value_ty())
218    ))
219}
220
221fn primitive_type_name(primitive: &Primitive) -> &'static str {
222    match primitive {
223        Primitive::i8 => "i8",
224        Primitive::i16 => "i16",
225        Primitive::i32 => "i32",
226        Primitive::i64 => "i64",
227        Primitive::i128 => "i128",
228        Primitive::u8 => "u8",
229        Primitive::u16 => "u16",
230        Primitive::u32 => "u32",
231        Primitive::u64 => "u64",
232        Primitive::u128 => "u128",
233        Primitive::isize => "isize",
234        Primitive::usize => "usize",
235        Primitive::f16 => "f16",
236        Primitive::f32 => "f32",
237        Primitive::f64 => "f64",
238        Primitive::f128 => "f128",
239        Primitive::bool => "bool",
240        Primitive::str => "String",
241        Primitive::char => "char",
242    }
243}
244
245/// Reference to a type not understood by Specta's core datatype model.
246///
247/// These are implemented by the language exporter to implement cool features like
248/// [`specta_typescript::branded!`](https://docs.rs/specta-typescript/latest/specta_typescript/macro.branded.html),
249/// [`specta_typescript::define`](https://docs.rs/specta-typescript/latest/specta_typescript/fn.define.html), and more.
250///
251/// # Invariants
252///
253/// Equality and hashing are delegated to the stored opaque state. If two opaque
254/// references should be distinct, their state values must compare and hash
255/// distinctly.
256///
257/// This is an advanced feature designed for language exporters and framework
258/// integrations. Most end users should prefer ordinary [`DataType`] variants.
259#[derive(Clone)]
260pub struct OpaqueReference(Arc<dyn DynOpaqueReference>);
261
262trait DynOpaqueReference: Any + Send + Sync {
263    fn type_name(&self) -> &'static str;
264    fn hash(&self, hasher: &mut dyn hash::Hasher);
265    fn eq(&self, other: &dyn Any) -> bool;
266    fn as_any(&self) -> &dyn Any;
267}
268
269#[derive(Debug)]
270struct OpaqueReferenceInner<T>(T);
271impl<T: hash::Hash + Eq + Send + Sync + 'static> DynOpaqueReference for OpaqueReferenceInner<T> {
272    fn type_name(&self) -> &'static str {
273        std::any::type_name::<T>()
274    }
275    fn hash(&self, mut hasher: &mut dyn hash::Hasher) {
276        self.0.hash(&mut hasher)
277    }
278    fn eq(&self, other: &dyn Any) -> bool {
279        other
280            .downcast_ref::<T>()
281            .map(|other| self.0 == *other)
282            .unwrap_or_default()
283    }
284    fn as_any(&self) -> &dyn Any {
285        &self.0
286    }
287}
288
289impl fmt::Debug for OpaqueReference {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        f.debug_tuple("OpaqueReference")
292            .field(&self.0.type_name())
293            .finish()
294    }
295}
296
297impl PartialEq for OpaqueReference {
298    fn eq(&self, other: &Self) -> bool {
299        self.0.eq(other.0.as_any())
300    }
301}
302
303impl Eq for OpaqueReference {}
304
305impl hash::Hash for OpaqueReference {
306    fn hash<H: hash::Hasher>(&self, state: &mut H) {
307        self.0.hash(state)
308    }
309}
310
311impl OpaqueReference {
312    /// Returns the Rust type name of the stored opaque state.
313    pub fn type_name(&self) -> &'static str {
314        self.0.type_name()
315    }
316
317    /// Returns the [`TypeId`] of the stored opaque state.
318    pub fn type_id(&self) -> TypeId {
319        self.0.as_any().type_id()
320    }
321
322    /// Attempts to downcast the opaque state to `T`.
323    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
324        self.0.as_any().downcast_ref::<T>()
325    }
326}
327
328impl Reference {
329    /// Constructs a new reference to an opaque type.
330    ///
331    /// An opaque type cannot be represented with the core [`DataType`] model and
332    /// requires specific exporter integration.
333    ///
334    /// Opaque [`Reference`]s are compared using the state's [`PartialEq`]
335    /// implementation. For example, `Reference::opaque(()) ==
336    /// Reference::opaque(())`, so unique references need unique state.
337    pub fn opaque<T: hash::Hash + Eq + Send + Sync + 'static>(state: T) -> Self {
338        Self::Opaque(OpaqueReference(Arc::new(OpaqueReferenceInner(state))))
339    }
340
341    /// Returns whether two references point to the same underlying type.
342    ///
343    /// This differs from [`Eq`], [`PartialEq`], and [`Hash`] because those compare
344    /// the full [`Reference`] which includes generic arguments and inline state.
345    pub fn ty_eq(&self, other: &Reference) -> bool {
346        match (self, other) {
347            (Reference::Named(a), Reference::Named(b)) => a.id == b.id,
348            (Reference::Opaque(a), Reference::Opaque(b)) => *a == *b,
349            _ => false,
350        }
351    }
352}
353
354impl From<Reference> for DataType {
355    fn from(r: Reference) -> Self {
356        Self::Reference(r)
357    }
358}
359
360/// Unique identifier for a [NamedDataType].
361///
362/// For static types (from derive macros), we use a unique string based on the
363/// type's module path and name. For dynamic types, we use an Arc pointer.
364#[derive(Clone)]
365pub(crate) enum NamedId {
366    // A unique string identifying the type (module_path::TypeName).
367    Static(&'static str),
368    Dynamic(Arc<()>),
369}
370
371impl PartialEq for NamedId {
372    fn eq(&self, other: &Self) -> bool {
373        match (self, other) {
374            (NamedId::Static(a), NamedId::Static(b)) => a == b,
375            (NamedId::Dynamic(a), NamedId::Dynamic(b)) => Arc::ptr_eq(a, b),
376            _ => false,
377        }
378    }
379}
380impl Eq for NamedId {}
381
382impl hash::Hash for NamedId {
383    fn hash<H: hash::Hasher>(&self, state: &mut H) {
384        match self {
385            NamedId::Static(s) => s.hash(state),
386            NamedId::Dynamic(p) => std::ptr::hash(Arc::as_ptr(p), state),
387        }
388    }
389}
390
391impl fmt::Debug for NamedId {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        match self {
394            NamedId::Static(s) => write!(f, "s:{}", s),
395            NamedId::Dynamic(p) => write!(f, "d{:p}", Arc::as_ptr(p)),
396        }
397    }
398}