From b4d858cea37892daf94f412beec698640bc0c46a Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 3 Dec 2025 14:07:24 +0800 Subject: [PATCH 1/4] wip --- specta-macros/src/type/mod.rs | 18 ++++++------- specta-serde/src/validate.rs | 4 +-- specta-typescript/src/inline.rs | 4 +-- specta-typescript/src/primitives.rs | 20 +++++++------- specta-typescript/src/types.rs | 41 +++++++++++------------------ specta-util/src/json.rs | 2 +- specta/src/export.rs | 16 +++++------ specta/src/lib.rs | 2 +- specta/src/type.rs | 8 +++--- specta/src/type/macros.rs | 4 --- specta/src/type_collection.rs | 10 +++---- tests/tests/reserved_keywords.rs | 6 ++--- tests/tests/ts.rs | 15 ++++++----- 13 files changed, 68 insertions(+), 82 deletions(-) diff --git a/specta-macros/src/type/mod.rs b/specta-macros/src/type/mod.rs index a672054c..cb61a91f 100644 --- a/specta-macros/src/type/mod.rs +++ b/specta-macros/src/type/mod.rs @@ -1,11 +1,11 @@ use attr::*; -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; use r#enum::parse_enum; +use proc_macro2::TokenStream; +use quote::{ToTokens, format_ident, quote}; use r#struct::parse_struct; -use syn::{parse, Data, DeriveInput, GenericParam}; +use syn::{Data, DeriveInput, GenericParam, parse}; -use crate::utils::{parse_attrs, unraw_raw_ident, AttributeValue}; +use crate::utils::{AttributeValue, parse_attrs, unraw_raw_ident}; use self::generics::{ add_type_to_where_clause, generics_with_ident_and_bounds_only, generics_with_ident_only, @@ -76,7 +76,7 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result syn::Result { let ty = generics .iter() - .find(|(gen, _)| gen == g) + .find(|(ge, _)| ge == g) .map(|(_, dt)| dt) .expect("bruh"); diff --git a/specta-typescript/src/inline.rs b/specta-typescript/src/inline.rs index 1f479305..9dad2c4a 100644 --- a/specta-typescript/src/inline.rs +++ b/specta-typescript/src/inline.rs @@ -125,7 +125,7 @@ fn inner( DataType::Generic(g) => { let mut ty = generics .iter() - .find(|(gen, _)| gen == g) + .find(|(ge, _)| ge == g) .map(|(_, dt)| dt) .unwrap() .clone(); // TODO: Properly handle this error @@ -205,7 +205,7 @@ fn resolve_generics(dt: &mut DataType, generics: &[(Generic, DataType)]) { // TODO: Functions main documentation should explain this. *dt = generics .iter() - .find(|(gen, _)| gen == g) + .find(|(ge, _)| ge == g) .map(|(_, dt)| dt.clone()) .unwrap_or(DataType::Generic(g.clone())); } diff --git a/specta-typescript/src/primitives.rs b/specta-typescript/src/primitives.rs index 9351c617..94b1b7a8 100644 --- a/specta-typescript/src/primitives.rs +++ b/specta-typescript/src/primitives.rs @@ -9,16 +9,16 @@ use std::{ }; use specta::{ + SpectaID, TypeCollection, datatype::{ DataType, Enum, EnumRepr, Field, Fields, List, Literal, Map, NamedDataType, Primitive, Reference, Tuple, }, - NamedType, SpectaID, TypeCollection, }; use crate::{ - legacy::js_doc_builder, reserved_names::*, Any, BigIntExportBehavior, Error, Format, - Typescript, Unknown, + Any, BigIntExportBehavior, Error, Format, Typescript, Unknown, legacy::js_doc_builder, + reserved_names::*, }; /// Generate an `export Type = ...` Typescript string for a specific [`DataType`]. @@ -205,7 +205,7 @@ fn primitive_dt( BigIntExportBehavior::Fail => { return Err(Error::BigIntForbidden { path: location.join("."), - }) + }); } }, Primitive::bool => "boolean", @@ -879,11 +879,13 @@ fn reference_dt( ) -> Result<(), Error> { // TODO: Legacy stuff { - if r.sid() == Any::<()>::ID { - s.push_str("any"); - } else if r.sid() == Unknown::<()>::ID { - s.push_str("unknown"); - } else { + todo!(); + // if r.sid() == Any::<()>::ID { + // s.push_str("any"); + // } else if r.sid() == Unknown::<()>::ID { + // s.push_str("unknown"); + // } else + { let ndt = types.get(r.sid()).unwrap(); // TODO: Error handling let name = match ts.format { diff --git a/specta-typescript/src/types.rs b/specta-typescript/src/types.rs index b0ffe0d4..18241025 100644 --- a/specta-typescript/src/types.rs +++ b/specta-typescript/src/types.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; use specta::{ + Type, TypeCollection, datatype::{DataType, Reference}, - NamedType, Type, TypeCollection, }; /// Cast a Rust type to a Typescript `any` type. @@ -39,19 +39,16 @@ use specta::{ /// ``` pub struct Any(T); +const ANY_ID: specta::SpectaID = specta::internal::construct::sid( + "Any", + concat!("::", module_path!(), ":", line!(), ":", column!()), +); impl Type for Any { fn definition(types: &mut TypeCollection) -> DataType { - DataType::Reference(Reference::construct(Self::ID, [], false)) + DataType::Reference(Reference::construct(ANY_ID, [], false)) } } -impl NamedType for Any { - const ID: specta::SpectaID = specta::internal::construct::sid( - "Any", - concat!("::", module_path!(), ":", line!(), ":", column!()), - ); -} - impl Debug for Any { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Any").field(&self.0).finish() @@ -113,19 +110,16 @@ impl serde::Serialize for Any { /// ``` pub struct Unknown(T); +const UNKNOWN_ID: specta::SpectaID = specta::internal::construct::sid( + "Unknown", + concat!("::", module_path!(), ":", line!(), ":", column!()), +); impl Type for Unknown { fn definition(types: &mut TypeCollection) -> DataType { - DataType::Reference(Reference::construct(Self::ID, [], false)) + DataType::Reference(Reference::construct(UNKNOWN_ID, [], false)) } } -impl NamedType for Unknown { - const ID: specta::SpectaID = specta::internal::construct::sid( - "Unknown", - concat!("::", module_path!(), ":", line!(), ":", column!()), - ); -} - impl Debug for Unknown { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Any").field(&self.0).finish() @@ -187,19 +181,16 @@ impl serde::Serialize for Unknown { /// ``` pub struct Never(T); +const NEVER_ID: specta::SpectaID = specta::internal::construct::sid( + "Any", + concat!("::", module_path!(), ":", line!(), ":", column!()), +); impl Type for Never { fn definition(types: &mut TypeCollection) -> DataType { - DataType::Reference(Reference::construct(Self::ID, [], false)) + DataType::Reference(Reference::construct(NEVER_ID, [], false)) } } -impl NamedType for Never { - const ID: specta::SpectaID = specta::internal::construct::sid( - "Unknown", - concat!("::", module_path!(), ":", line!(), ":", column!()), - ); -} - impl Debug for Never { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Any").field(&self.0).finish() diff --git a/specta-util/src/json.rs b/specta-util/src/json.rs index e11305a5..3a9e7080 100644 --- a/specta-util/src/json.rs +++ b/specta-util/src/json.rs @@ -5,7 +5,7 @@ // - Remove dependency on `serde_json` // - Don't ignore doctests -use crate::{datatype::Literal, DataType, Type, TypeCollection}; +use specta::{DataType, Type, TypeCollection, datatype::Literal}; #[doc(hidden)] pub use serde_json; diff --git a/specta/src/export.rs b/specta/src/export.rs index e6675e2a..c0c457c6 100644 --- a/specta/src/export.rs +++ b/specta/src/export.rs @@ -1,12 +1,9 @@ -use std::{ - collections::HashMap, - sync::{Mutex, OnceLock, PoisonError}, -}; +use std::sync::{Mutex, OnceLock, PoisonError}; -use crate::{NamedType, SpectaID, TypeCollection}; +use crate::{Type, TypeCollection}; // Global type store for collecting custom types to export. -static TYPES: OnceLock>> = OnceLock::new(); +static TYPES: OnceLock>> = OnceLock::new(); /// Get the global type store containing all automatically registered types. /// @@ -15,14 +12,13 @@ static TYPES: OnceLock>> = Once /// Note that when enabling the `export` feature, you will not be able to enable the `unsafe_code` lint as [`ctor`](https://docs.rs/ctor) (which is used internally) is marked unsafe. /// pub fn export() -> TypeCollection { - // TODO: Make `TYPES` should just hold a `TypeCollection` directly??? let types = TYPES .get_or_init(Default::default) .lock() .unwrap_or_else(PoisonError::into_inner); let mut map = TypeCollection::default(); - for (_, export) in types.iter() { + for export in types.iter() { export(&mut map); } map @@ -36,13 +32,13 @@ pub mod internal { // Called within ctor functions to register a type. #[doc(hidden)] - pub fn register() { + pub fn register() { let mut types = TYPES .get_or_init(Default::default) .lock() .unwrap_or_else(PoisonError::into_inner); - types.insert(T::ID, |types| { + types.push(|types| { // The side-effect of this is registering the type. T::definition(types); }); diff --git a/specta/src/lib.rs b/specta/src/lib.rs index 111e69f3..c821fb26 100644 --- a/specta/src/lib.rs +++ b/specta/src/lib.rs @@ -21,7 +21,7 @@ mod r#type; mod type_collection; // TODO: Can we just move the trait here or `#[doc(inline)]` -pub use r#type::{Flatten, NamedType, Type}; +pub use r#type::{Flatten, Type}; // #[doc(inline)] pub use specta_id::SpectaID; pub use type_collection::TypeCollection; diff --git a/specta/src/type.rs b/specta/src/type.rs index 1c81a550..8fba8bf5 100644 --- a/specta/src/type.rs +++ b/specta/src/type.rs @@ -1,4 +1,4 @@ -use crate::{datatype::DataType, SpectaID, TypeCollection}; +use crate::{SpectaID, TypeCollection, datatype::DataType}; mod impls; mod macros; @@ -23,9 +23,9 @@ pub trait Type { /// TODO: Discuss which types this should be implemented for. /// /// This should be only implemented via the [`Type`](derive@crate::Type) macro. -pub trait NamedType: Type { - const ID: SpectaID; -} +// pub trait NamedType: Type { +// const ID: SpectaID; +// } /// A marker trait for compile-time validation of which types can be flattened. pub trait Flatten: Type {} diff --git a/specta/src/type/macros.rs b/specta/src/type/macros.rs index 8b599680..e7bf04ee 100644 --- a/specta/src/type/macros.rs +++ b/specta/src/type/macros.rs @@ -42,10 +42,6 @@ macro_rules! _impl_containers { } } - impl NamedType for $container { - const ID: SpectaID = T::ID; - } - impl Flatten for $container {} )+} } diff --git a/specta/src/type_collection.rs b/specta/src/type_collection.rs index 5a386c3e..670598d3 100644 --- a/specta/src/type_collection.rs +++ b/specta/src/type_collection.rs @@ -5,9 +5,9 @@ use std::{ }; use crate::{ + SpectaID, Type, builder::NamedDataTypeBuilder, datatype::{NamedDataType, Reference}, - NamedType, SpectaID, }; /// Define a set of types which can be exported together. @@ -28,14 +28,14 @@ impl fmt::Debug for TypeCollection { } impl TypeCollection { - /// Register a [`NamedType`] with the collection. - pub fn register(mut self) -> Self { + /// Register a [`Type`] with the collection. + pub fn register(mut self) -> Self { T::definition(&mut self); self } - /// Register a [`NamedType`] with the collection. - pub fn register_mut(&mut self) -> &mut Self { + /// Register a [`Type`] with the collection. + pub fn register_mut(&mut self) -> &mut Self { T::definition(self); self } diff --git a/tests/tests/reserved_keywords.rs b/tests/tests/reserved_keywords.rs index 5cef17cc..d9c01bb7 100644 --- a/tests/tests/reserved_keywords.rs +++ b/tests/tests/reserved_keywords.rs @@ -1,7 +1,7 @@ -use specta::{NamedType, Type, TypeCollection}; +use specta::{Type, TypeCollection}; use specta_typescript::{ - legacy::{ExportPath, NamedLocation}, Error, Typescript, + legacy::{ExportPath, NamedLocation}, }; mod astruct { @@ -57,7 +57,7 @@ fn test_ts_reserved_keyworks() { ); } -fn export() -> Result { +fn export() -> Result { let mut types = TypeCollection::default(); T::definition(&mut types); Typescript::default() diff --git a/tests/tests/ts.rs b/tests/tests/ts.rs index dfab0c1c..6010e1b9 100644 --- a/tests/tests/ts.rs +++ b/tests/tests/ts.rs @@ -10,12 +10,12 @@ use std::{ }; use serde::Serialize; -use specta::{NamedType, Type, TypeCollection}; +use specta::{Type, TypeCollection}; use specta_typescript::Any; use specta_typescript::{BigIntExportBehavior, Typescript}; // We run tests with the low-level APIs -pub fn assert_ts_export2() -> Result { +pub fn assert_ts_export2() -> Result { let mut types = TypeCollection::default(); T::definition(&mut types); specta_serde::validate(&types).map_err(|e| e.to_string())?; @@ -108,12 +108,12 @@ pub fn inline(ts: &Typescript) -> Result { .map_err(|e| e.to_string()) } -pub fn export_ref(t: &T, ts: &Typescript) -> Result { +pub fn export_ref(t: &T, ts: &Typescript) -> Result { export::(ts) } // TODO: Probally move to snapshot testing w/ high-level API's -pub fn export(ts: &Typescript) -> Result { +pub fn export(ts: &Typescript) -> Result { let mut types = TypeCollection::default(); T::definition(&mut types); @@ -176,7 +176,9 @@ fn typescript_types() { assert_ts!((String, i32), "[string, number]"); assert_ts!((String, i32, bool), "[string, number, boolean]"); assert_ts!( - (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool), + ( + bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool + ), "[boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean]" ); @@ -774,7 +776,6 @@ struct Issue374 { bar: bool, } - // https://github.com/specta-rs/specta/issues/386 // We put this test in a separate module because the parent module has `use specta::Type`, // so it clashes with our user-defined `Type`. @@ -786,4 +787,4 @@ mod type_type { fn typescript_types() { assert_ts!(Type, "never"); } -} \ No newline at end of file +} From 38c1f649d91dde398f3ce92ac3839122a7e79c5f Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 3 Dec 2025 14:15:04 +0800 Subject: [PATCH 2/4] fix Typescript exporter custom types --- specta-typescript/src/primitives.rs | 18 ++++++++++-------- specta-typescript/src/types.rs | 12 ++++++------ specta/src/type.rs | 18 ++++-------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/specta-typescript/src/primitives.rs b/specta-typescript/src/primitives.rs index 94b1b7a8..b1b01a26 100644 --- a/specta-typescript/src/primitives.rs +++ b/specta-typescript/src/primitives.rs @@ -17,8 +17,10 @@ use specta::{ }; use crate::{ - Any, BigIntExportBehavior, Error, Format, Typescript, Unknown, legacy::js_doc_builder, + Any, BigIntExportBehavior, Error, Format, Typescript, Unknown, + legacy::js_doc_builder, reserved_names::*, + types::{ANY_ID, NEVER_ID, UNKNOWN_ID}, }; /// Generate an `export Type = ...` Typescript string for a specific [`DataType`]. @@ -879,13 +881,13 @@ fn reference_dt( ) -> Result<(), Error> { // TODO: Legacy stuff { - todo!(); - // if r.sid() == Any::<()>::ID { - // s.push_str("any"); - // } else if r.sid() == Unknown::<()>::ID { - // s.push_str("unknown"); - // } else - { + if r.sid() == ANY_ID { + s.push_str("any"); + } else if r.sid() == UNKNOWN_ID { + s.push_str("unknown"); + } else if r.sid() == NEVER_ID { + s.push_str("never"); + } else { let ndt = types.get(r.sid()).unwrap(); // TODO: Error handling let name = match ts.format { diff --git a/specta-typescript/src/types.rs b/specta-typescript/src/types.rs index 18241025..43f1f257 100644 --- a/specta-typescript/src/types.rs +++ b/specta-typescript/src/types.rs @@ -39,12 +39,12 @@ use specta::{ /// ``` pub struct Any(T); -const ANY_ID: specta::SpectaID = specta::internal::construct::sid( +pub(crate) const ANY_ID: specta::SpectaID = specta::internal::construct::sid( "Any", concat!("::", module_path!(), ":", line!(), ":", column!()), ); impl Type for Any { - fn definition(types: &mut TypeCollection) -> DataType { + fn definition(_: &mut TypeCollection) -> DataType { DataType::Reference(Reference::construct(ANY_ID, [], false)) } } @@ -110,12 +110,12 @@ impl serde::Serialize for Any { /// ``` pub struct Unknown(T); -const UNKNOWN_ID: specta::SpectaID = specta::internal::construct::sid( +pub(crate) const UNKNOWN_ID: specta::SpectaID = specta::internal::construct::sid( "Unknown", concat!("::", module_path!(), ":", line!(), ":", column!()), ); impl Type for Unknown { - fn definition(types: &mut TypeCollection) -> DataType { + fn definition(_: &mut TypeCollection) -> DataType { DataType::Reference(Reference::construct(UNKNOWN_ID, [], false)) } } @@ -181,12 +181,12 @@ impl serde::Serialize for Unknown { /// ``` pub struct Never(T); -const NEVER_ID: specta::SpectaID = specta::internal::construct::sid( +pub(crate) const NEVER_ID: specta::SpectaID = specta::internal::construct::sid( "Any", concat!("::", module_path!(), ":", line!(), ":", column!()), ); impl Type for Never { - fn definition(types: &mut TypeCollection) -> DataType { + fn definition(_: &mut TypeCollection) -> DataType { DataType::Reference(Reference::construct(NEVER_ID, [], false)) } } diff --git a/specta/src/type.rs b/specta/src/type.rs index 8fba8bf5..6c99ab30 100644 --- a/specta/src/type.rs +++ b/specta/src/type.rs @@ -1,4 +1,4 @@ -use crate::{SpectaID, TypeCollection, datatype::DataType}; +use crate::{TypeCollection, datatype::DataType}; mod impls; mod macros; @@ -6,26 +6,16 @@ mod macros; #[cfg(feature = "derive")] mod legacy_impls; -/// Provides runtime type information that can be fed into a language exporter to generate a type definition in another language. +/// Provides runtime type information that can be fed into a language exporter to generate a type definition for another language. /// Avoid implementing this trait yourself where possible and use the [`Type`](derive@crate::Type) macro instead. /// -/// This should be only implemented via the [`Type`](derive@crate::Type) macro. +/// This should be implemented by the [`Type`](derive@crate::Type) macro. /// TODO: Discuss how to avoid custom implementations. pub trait Type { /// returns a [`DataType`](crate::datatype::DataType) that represents the type. - /// This will also register any dependent types into the [`TypeCollection`]. + /// This will also register this and any dependent types into the [`TypeCollection`]. fn definition(types: &mut TypeCollection) -> DataType; } -/// represents a type that can be converted into [`NamedDataType`](crate::NamedDataType). -/// This will be implemented for all types with the [Type] derive macro. -/// -/// TODO: Discuss which types this should be implemented for. -/// -/// This should be only implemented via the [`Type`](derive@crate::Type) macro. -// pub trait NamedType: Type { -// const ID: SpectaID; -// } - /// A marker trait for compile-time validation of which types can be flattened. pub trait Flatten: Type {} From 5fb3823ac5966fe1f54763ef4e4453c41f856f8e Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 3 Dec 2025 14:58:08 +0800 Subject: [PATCH 3/4] wip --- specta-typescript/examples/debug.rs | 20 +++++++++++++++++++- specta/src/datatype/reference.rs | 11 ++++++++++- specta/src/type.rs | 4 +++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/specta-typescript/examples/debug.rs b/specta-typescript/examples/debug.rs index 9729b7b2..fcfd0f85 100644 --- a/specta-typescript/examples/debug.rs +++ b/specta-typescript/examples/debug.rs @@ -20,7 +20,25 @@ pub struct B { } fn main() { - let mut types = TypeCollection::default().register::(); + let types = TypeCollection::default().register::(); + + // Using `NamedType` + let ndt = types.get(A::SID).unwrap(); + + // Naive alternative + let ndt = types + .get(match A::definition(types) { + specta::datatype::DataType::Reference(r) => r.sid(), + _ => panic!(), + }) + .unwrap(); + + // We could add this to remove one of the panics as it's an invariant of the Type trait? + let ndt = match A::definition(types) { + specta::datatype::DataType::Reference(r) => r.get(types), + _ => panic!(), + }; + // .register::() // .register::(); // println!("{:#?}", types.get(Testing::ID).unwrap()); diff --git a/specta/src/datatype/reference.rs b/specta/src/datatype/reference.rs index af085213..0af7027f 100644 --- a/specta/src/datatype/reference.rs +++ b/specta/src/datatype/reference.rs @@ -1,6 +1,6 @@ //! Helpers for generating [Type::reference] implementations. -use crate::SpectaID; +use crate::{SpectaID, TypeCollection, datatype::NamedDataType}; use super::{DataType, Generic}; @@ -15,6 +15,7 @@ pub struct Reference { impl Reference { /// TODO: Explain invariant. + /// TODO: Can this force that the type is in the typemap at the point of the `Reference` being created to ensure `Self::get` never panics. pub fn construct( sid: SpectaID, generics: impl Into>, @@ -46,6 +47,14 @@ impl Reference { pub fn inline(&self) -> bool { self.inline } + + #[doc(hidden)] // TODO: Stablise + pub fn get<'a>(&self, types: &'a TypeCollection) -> &'a NamedDataType { + // TODO: Different panic for missing vs placeholder + types + .get(self.sid) + .expect("a type with a reference must be registered to the TypeMap") + } } impl From for DataType { diff --git a/specta/src/type.rs b/specta/src/type.rs index 6c99ab30..43d27a9e 100644 --- a/specta/src/type.rs +++ b/specta/src/type.rs @@ -1,4 +1,4 @@ -use crate::{TypeCollection, datatype::DataType}; +use crate::{SpectaID, TypeCollection, datatype::DataType}; mod impls; mod macros; @@ -12,6 +12,8 @@ mod legacy_impls; /// This should be implemented by the [`Type`](derive@crate::Type) macro. /// TODO: Discuss how to avoid custom implementations. pub trait Type { + const ID: Option; // TODO: This is problematic + /// returns a [`DataType`](crate::datatype::DataType) that represents the type. /// This will also register this and any dependent types into the [`TypeCollection`]. fn definition(types: &mut TypeCollection) -> DataType; From adcae81c76d4f946ef08f77a1959a8f1b95632af Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 3 Dec 2025 15:00:45 +0800 Subject: [PATCH 4/4] fixes --- specta/src/type.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specta/src/type.rs b/specta/src/type.rs index 43d27a9e..6c99ab30 100644 --- a/specta/src/type.rs +++ b/specta/src/type.rs @@ -1,4 +1,4 @@ -use crate::{SpectaID, TypeCollection, datatype::DataType}; +use crate::{TypeCollection, datatype::DataType}; mod impls; mod macros; @@ -12,8 +12,6 @@ mod legacy_impls; /// This should be implemented by the [`Type`](derive@crate::Type) macro. /// TODO: Discuss how to avoid custom implementations. pub trait Type { - const ID: Option; // TODO: This is problematic - /// returns a [`DataType`](crate::datatype::DataType) that represents the type. /// This will also register this and any dependent types into the [`TypeCollection`]. fn definition(types: &mut TypeCollection) -> DataType;