diff --git a/Cargo.lock b/Cargo.lock index 00dc0f11..cdcad54b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,7 +474,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-link", ] @@ -651,6 +653,12 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5663fd2b4e3742545500462e4b4929ffc5148f13acbb775a9fca2ea6b6df4d" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -1089,6 +1097,7 @@ checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" dependencies = [ "console", "once_cell", + "serde", "similar", "tempfile", ] @@ -1300,6 +1309,17 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openapiv3" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05" +dependencies = [ + "indexmap", + "serde", + "serde_json", +] + [[package]] name = "paste" version = "1.0.15" @@ -1536,6 +1556,26 @@ dependencies = [ "rand 0.9.2", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.12.3" @@ -1609,6 +1649,18 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "semver" version = "1.0.27" @@ -1776,6 +1828,34 @@ dependencies = [ "uuid", ] +[[package]] +name = "specta-go" +version = "0.0.1" +dependencies = [ + "specta", +] + +[[package]] +name = "specta-jsonschema" +version = "0.0.1" +dependencies = [ + "insta", + "schemars", + "serde", + "serde_json", + "specta", + "specta-serde", + "specta-util", + "thiserror", +] + +[[package]] +name = "specta-kotlin" +version = "0.0.1" +dependencies = [ + "specta", +] + [[package]] name = "specta-macros" version = "2.0.0-rc.23" @@ -1786,6 +1866,14 @@ dependencies = [ "syn", ] +[[package]] +name = "specta-openapi" +version = "0.0.1" +dependencies = [ + "openapiv3", + "specta", +] + [[package]] name = "specta-serde" version = "0.0.10" @@ -1796,6 +1884,20 @@ dependencies = [ "specta-macros", ] +[[package]] +name = "specta-swift" +version = "0.0.1" +dependencies = [ + "chrono", + "insta", + "serde", + "specta", + "specta-serde", + "thiserror", + "trybuild", + "uuid", +] + [[package]] name = "specta-tests" version = "0.0.0" @@ -1838,6 +1940,17 @@ dependencies = [ "specta", ] +[[package]] +name = "specta-zod" +version = "0.0.1" +dependencies = [ + "specta", + "specta-serde", + "specta-typescript", + "specta-util", + "thiserror", +] + [[package]] name = "spin" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index f6a0123b..b6505be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,10 @@ unwrap_used = { level = "warn", priority = -1 } panic = { level = "warn", priority = -1 } todo = { level = "warn", priority = -1 } panic_in_result_fn = { level = "warn", priority = -1 } + +# I hate these. For internal code I don't care. too_many_arguments = { level = "allow", priority = 0 } +type_complexity = { level = "allow", priority = 0 } [profile.dev.package] insta.opt-level = 3 diff --git a/specta-jsonschema/src/import.rs b/specta-jsonschema/src/import.rs index 72c9ae72..dd4df233 100644 --- a/specta-jsonschema/src/import.rs +++ b/specta-jsonschema/src/import.rs @@ -40,7 +40,7 @@ fn schema_object_to_datatype(obj: &JsonMap) -> Result Ok(DataType::Primitive(Primitive::bool)), - "string" => Ok(DataType::Primitive(Primitive::String)), + "string" => Ok(DataType::Primitive(Primitive::str)), "number" => { if let Some(format) = obj.get("format").and_then(Value::as_str) { match format { @@ -190,13 +190,13 @@ fn instance_type_name_to_datatype( Value::Object(_) => { let value_dt = value_to_datatype(additional)?; return Ok(DataType::Map(Map::new( - DataType::Primitive(Primitive::String), + DataType::Primitive(Primitive::str), value_dt, ))); } Value::Bool(true) => { return Ok(DataType::Map(Map::new( - DataType::Primitive(Primitive::String), + DataType::Primitive(Primitive::str), Struct::named().build(), ))); } diff --git a/specta-jsonschema/src/primitives.rs b/specta-jsonschema/src/primitives.rs index 3c196752..ae683bf5 100644 --- a/specta-jsonschema/src/primitives.rs +++ b/specta-jsonschema/src/primitives.rs @@ -107,22 +107,18 @@ pub fn datatype_to_schema( Reference::Opaque(_) => Err(Error::UnsupportedDataType( "Opaque references are not supported by JSON Schema exporter".to_string(), )), + // JsonSchema doesn't have generics, so we use a placeholder, + // This should typically be resolved before export. + Reference::Generic(_) => Ok(json!({})), // Empty schema accepts anything } } - - // Generic - DataType::Generic(_g) => { - // JSON Schema doesn't have generics, so we use a placeholder - // This should typically be resolved before export - Ok(json!({})) // Empty schema accepts anything - } } } fn primitive_to_schema(p: &Primitive) -> Value { match p { Primitive::bool => json!({"type": "boolean"}), - Primitive::String => json!({"type": "string"}), + Primitive::str => json!({"type": "string"}), Primitive::char => json!({"type": "string", "minLength": 1, "maxLength": 1}), // Integers diff --git a/specta-kotlin/src/lib.rs b/specta-kotlin/src/lib.rs index 8034b0bc..d4e266f1 100644 --- a/specta-kotlin/src/lib.rs +++ b/specta-kotlin/src/lib.rs @@ -26,7 +26,7 @@ // fn datatype(t: &DataType) -> Result { // Ok(match t { // DataType::Primitive(p) => match p { -// Primitive::String => "String", +// Primitive::str => "String", // Primitive::char => "Char", // Primitive::i8 => "Byte", // Primitive::i16 => "Short", diff --git a/specta-macros/src/type/mod.rs b/specta-macros/src/type/mod.rs index 7b63f7c5..cfb68897 100644 --- a/specta-macros/src/type/mod.rs +++ b/specta-macros/src/type/mod.rs @@ -142,34 +142,47 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result quote!(), - // We shadow the generics to replace them. - GenericParam::Type(t) => { - let ident = &t.ident; - let placeholder_ident = format_ident!("PLACEHOLDER_{}", t.ident); - quote!(type #ident = datatype::GenericPlaceholder<#placeholder_ident>;) - } - }); - - quote!(#(#g)*) - }; - - let generic_placeholders = generics.params.iter().filter_map(|param| match param { + let (generic_placeholders, shadow_generics): (Vec<_>, Vec<_>) = generics.params.iter().filter_map(|param| match param { GenericParam::Lifetime(_) | GenericParam::Const(_) => None, GenericParam::Type(t) => { - let ident = format_ident!("PLACEHOLDER_{}", t.ident); - let ident_str = t.ident.to_string(); - Some(quote!( - pub struct #ident; - impl datatype::ConstGenericPlaceholder for #ident { - const PLACEHOLDER: &'static str = #ident_str; + let ident = &t.ident; + let placeholder_ident = format_ident!("PLACEHOLDER_{ident}"); + Some((quote!( + pub struct #placeholder_ident; + impl #crate_ref::Type for #placeholder_ident { + fn definition(_: &mut #crate_ref::TypeCollection) -> datatype::DataType { + datatype::GenericReference::new::().into() + } } - )) + ), quote!(type #ident = #placeholder_ident;))) } - }); + }).unzip(); + + let (generics_for_ndt, generics_for_ref): (Vec<_>, Vec<_>) = generics + .params + .iter() + .filter_map(|param| match param { + GenericParam::Lifetime(_) | GenericParam::Const(_) => None, + GenericParam::Type(t) => { + let i = &t.ident; + let placeholder_ident = format_ident!("PLACEHOLDER_{}", t.ident); + if !used_generic_types.iter().any(|used| used == i) { + return None; + } + let i_str = i.to_string(); + Some(( + quote!(( + #crate_ref::datatype::GenericReference::new::<#placeholder_ident>(), + Cow::Borrowed(#i_str), + )), + quote!(( + #crate_ref::datatype::GenericReference::new::<#placeholder_ident>(), + <#i as #crate_ref::Type>::definition(types), + )), + )) + } + }) + .unzip(); let collect = (cfg!(feature = "DO_NOT_USE_collect") && container_attrs.collect.unwrap_or(true)) .then(|| { @@ -194,26 +207,6 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result None, - GenericParam::Type(t) => { - let i = &t.ident; - if !used_generic_types.iter().any(|used| used == i) { - return None; - } - let i_str = i.to_string(); - Some(quote!((#crate_ref::datatype::Generic::new(#i_str), <#i as #crate_ref::Type>::definition(types)))) - } - }); - - let definition_generics = generics.params.iter().filter_map(|p| match p { - GenericParam::Type(t) => { - let ident = t.ident.to_string(); - Some(quote!(std::borrow::Cow::Borrowed(#ident).into())) - } - _ => None, - }); - Ok(quote! { #[allow(non_camel_case_types)] const _: () = { @@ -226,9 +219,11 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result)] = &[#(#generics_for_ndt),*]; datatype::DataType::Reference( datatype::NamedDataType::init_with_sentinel( - vec![#(#reference_generics),*], + GENERICS, + vec![#(#generics_for_ref),*], #inline, types, SENTINEL, @@ -237,9 +232,8 @@ pub fn derive(input: proc_macro::TokenStream) -> syn::Result ReferenceOr { schema_data, schema_kind: SchemaKind::Type(Type::Number(NumberType::default())), // TODO: Configure number type. Ts: `bigint` }), - primitive_def!(String char) => ReferenceOr::Item(Schema { + primitive_def!(str char) => ReferenceOr::Item(Schema { schema_data, schema_kind: SchemaKind::Type(Type::String(StringType::default())), // TODO: Configure string type. Ts: `string` }), diff --git a/specta-serde/src/lib.rs b/specta-serde/src/lib.rs index f33d18be..5644226c 100644 --- a/specta-serde/src/lib.rs +++ b/specta-serde/src/lib.rs @@ -32,7 +32,7 @@ //! use specta::DataType; //! use specta_serde::{apply_to_dt, SerdeMode}; //! -//! let dt = DataType::Primitive(specta::datatype::Primitive::String); +//! let dt = DataType::Primitive(specta::datatype::Primitive::str); //! let transformed = apply_to_dt(dt, SerdeMode::Serialize)?; //! ``` //! @@ -66,8 +66,8 @@ pub use serde_attrs::{SerdeMode, apply_serde_transformations}; use specta::TypeCollection; use specta::datatype::{ - Attribute, AttributeMeta, AttributeNestedMeta, DataType, Enum, Fields, Generic, NamedReference, - Primitive, Reference, skip_fields, skip_fields_named, + Attribute, AttributeMeta, AttributeNestedMeta, DataType, Enum, Fields, GenericReference, + NamedReference, Primitive, Reference, skip_fields, skip_fields_named, }; use std::collections::HashSet; @@ -145,7 +145,7 @@ pub fn apply(types: &mut TypeCollection, mode: SerdeMode) -> Result<(), Error> { /// /// # Example /// ```ignore -/// let dt = DataType::Primitive(Primitive::String); +/// let dt = DataType::Primitive(Primitive::str); /// let transformed = specta_serde::apply_to_dt(dt, SerdeMode::Serialize)?; /// ``` pub fn apply_to_dt(dt: DataType, mode: SerdeMode) -> Result { @@ -206,7 +206,7 @@ pub fn process_for_both(types: &TypeCollection) -> Result<(TypeCollection, TypeC fn validate_type( dt: &DataType, types: &TypeCollection, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], checked_references: &mut HashSet, ) -> Result<(), Error> { match dt { @@ -271,6 +271,7 @@ fn validate_type( } } } + DataType::Reference(Reference::Generic(_)) => {} DataType::Reference(Reference::Opaque(_)) => {} _ => {} } @@ -282,7 +283,7 @@ fn validate_type( fn is_valid_map_key( key_ty: &DataType, types: &TypeCollection, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { match key_ty { DataType::Primitive( @@ -300,7 +301,7 @@ fn is_valid_map_key( | Primitive::usize | Primitive::f32 | Primitive::f64 - | Primitive::String + | Primitive::str | Primitive::char, ) => Ok(()), DataType::Primitive(_) => Err(Error::InvalidMapKey), @@ -338,16 +339,14 @@ fn is_valid_map_key( } Ok(()) } - DataType::Reference(Reference::Opaque(_)) => Ok(()), - DataType::Generic(g) => { - let ty = generics - .iter() - .find(|(ge, _)| ge == g) - .map(|(_, dt)| dt) - .expect("unable to find expected generic type"); // TODO: Proper error instead of panicking + DataType::Reference(Reference::Generic(g)) => { + let Some((_, ty)) = generics.iter().find(|(ge, _)| ge == g) else { + return Ok(()); + }; is_valid_map_key(ty, types, &[]) } + DataType::Reference(Reference::Opaque(_)) => Ok(()), _ => Err(Error::InvalidMapKey), } } @@ -463,7 +462,9 @@ fn validate_internally_tag_enum_datatype( DataType::Nullable(ty) => { validate_internally_tag_enum_datatype(ty, types, checked_references) } - DataType::Reference(Reference::Opaque(_)) | DataType::Generic(_) => Ok(()), + DataType::Reference(Reference::Opaque(_)) | DataType::Reference(Reference::Generic(_)) => { + Ok(()) + } _ => Err(Error::InvalidInternallyTaggedEnum), } } diff --git a/specta-serde/src/serde_attrs.rs b/specta-serde/src/serde_attrs.rs index e299a599..bad144b2 100644 --- a/specta-serde/src/serde_attrs.rs +++ b/specta-serde/src/serde_attrs.rs @@ -8,8 +8,8 @@ use std::{borrow::Cow, fmt}; use specta::{ datatype::{ - DataType, Enum, Fields, Attribute, AttributeLiteral, AttributeMeta, AttributeNestedMeta, - AttributeValue, Struct, Tuple, + Attribute, AttributeLiteral, AttributeMeta, AttributeNestedMeta, AttributeValue, DataType, + Enum, Fields, Struct, Tuple, }, internal, }; @@ -174,7 +174,6 @@ impl SerdeTransformer { DataType::Enum(e) => self.transform_enum(e), DataType::Tuple(t) => self.transform_tuple(t), DataType::Reference(r) => Ok(DataType::Reference(r.clone())), - DataType::Generic(g) => Ok(DataType::Generic(g.clone())), } } @@ -1277,7 +1276,7 @@ fn parse_field_serde_attributes( // #[test] // fn test_primitive_type_passthrough() { // let mut transformer = SerdeTransformer::new(SerdeMode::Serialize, None); -// let primitive = DataType::Primitive(Primitive::String); +// let primitive = DataType::Primitive(Primitive::str); // let result = transformer.transform_datatype(&primitive).unwrap(); // assert_eq!(result, primitive); @@ -1286,12 +1285,12 @@ fn parse_field_serde_attributes( // #[test] // fn test_nullable_type_transformation() { // let mut transformer = SerdeTransformer::new(SerdeMode::Serialize, None); -// let nullable = DataType::Nullable(Box::new(DataType::Primitive(Primitive::String))); +// let nullable = DataType::Nullable(Box::new(DataType::Primitive(Primitive::str))); // let result = transformer.transform_datatype(&nullable).unwrap(); // match result { // DataType::Nullable(inner) => { -// assert_eq!(*inner.as_ref(), DataType::Primitive(Primitive::String)); +// assert_eq!(*inner.as_ref(), DataType::Primitive(Primitive::str)); // } // _ => panic!("Expected nullable type"), // } @@ -1301,13 +1300,13 @@ fn parse_field_serde_attributes( // fn test_list_type_transformation() { // let mut transformer = SerdeTransformer::new(SerdeMode::Serialize, None); // let list = DataType::List(specta::datatype::List::new(DataType::Primitive( -// Primitive::String, +// Primitive::str, // ))); // let result = transformer.transform_datatype(&list).unwrap(); // match result { // DataType::List(list_result) => { -// assert_eq!(*list_result.ty(), DataType::Primitive(Primitive::String)); +// assert_eq!(*list_result.ty(), DataType::Primitive(Primitive::str)); // } // _ => panic!("Expected list type"), // } @@ -1352,7 +1351,7 @@ fn parse_field_serde_attributes( // ))]), // }; -// let field = specta::datatype::Field::new(DataType::Primitive(Primitive::String)); +// let field = specta::datatype::Field::new(DataType::Primitive(Primitive::str)); // let mut struct_dt = match Struct::unnamed().field(field).build() { // DataType::Struct(s) => s, // _ => unreachable!(), @@ -1363,7 +1362,7 @@ fn parse_field_serde_attributes( // let result = transformer.transform_datatype(&datatype).unwrap(); // // Should resolve to the inner type -// assert_eq!(result, DataType::Primitive(Primitive::String)); +// assert_eq!(result, DataType::Primitive(Primitive::str)); // } // #[test] diff --git a/specta-serde/tests/integration.rs b/specta-serde/tests/integration.rs index d3dd86dd..3a068e41 100644 --- a/specta-serde/tests/integration.rs +++ b/specta-serde/tests/integration.rs @@ -16,7 +16,7 @@ use specta_serde::{ #[test] fn test_basic_transformation() { // Create a simple struct DataType - let field = Field::new(DataType::Primitive(Primitive::String)); + let field = Field::new(DataType::Primitive(Primitive::str)); let struct_dt = Struct::named().field("user_name", field).build(); // Transform for serialization @@ -31,7 +31,7 @@ fn test_basic_transformation() { #[test] fn test_optional_fields_with_skip_serializing_if_and_default() { use specta::datatype::{ - DataType, Field, Primitive, Attribute, AttributeLiteral, AttributeMeta, Struct, + Attribute, AttributeLiteral, AttributeMeta, DataType, Field, Primitive, Struct, }; use specta_serde::{SerdeMode, apply_serde_transformations}; @@ -45,7 +45,7 @@ fn test_optional_fields_with_skip_serializing_if_and_default() { }; let mut field_with_skip_if = Field::new(DataType::Nullable(Box::new(DataType::Primitive( - Primitive::String, + Primitive::str, )))); field_with_skip_if.set_attributes(vec![skip_if_attr]); @@ -110,7 +110,7 @@ fn test_optional_fields_with_skip_serializing_if_and_default() { #[test] fn test_skip_serializing() { // Create a struct with skip_serializing on a field - let field_with_skip = Field::new(DataType::Primitive(Primitive::String)); + let field_with_skip = Field::new(DataType::Primitive(Primitive::str)); let normal_field = Field::new(DataType::Primitive(Primitive::u32)); let struct_dt = Struct::named() @@ -264,7 +264,7 @@ fn test_type_collection_processing() { #[test] fn test_nested_type_transformation() { // Create nested types - List of structs - let field = Field::new(DataType::Primitive(Primitive::String)); + let field = Field::new(DataType::Primitive(Primitive::str)); let inner_struct = Struct::named().field("name", field).build(); let list_type = DataType::List(specta::datatype::List::new(inner_struct)); @@ -279,7 +279,7 @@ fn test_nested_type_transformation() { #[test] fn test_nullable_type_transformation() { // Create nullable type - let inner_type = DataType::Primitive(Primitive::String); + let inner_type = DataType::Primitive(Primitive::str); let nullable_type = DataType::Nullable(Box::new(inner_type)); // Transform @@ -293,7 +293,7 @@ fn test_nullable_type_transformation() { #[test] fn test_field_level_skip_attributes() { // Create fields with different skip attributes - let mut field_skip = Field::new(DataType::Primitive(Primitive::String)); + let mut field_skip = Field::new(DataType::Primitive(Primitive::str)); field_skip.set_attributes(vec![Attribute { path: "serde".to_string(), kind: AttributeMeta::NameValue { @@ -341,7 +341,7 @@ fn test_field_level_skip_attributes() { #[test] fn test_field_level_rename_attributes() { // Create a field with rename attribute - let mut field_renamed = Field::new(DataType::Primitive(Primitive::String)); + let mut field_renamed = Field::new(DataType::Primitive(Primitive::str)); field_renamed.set_attributes(vec![Attribute { path: "serde".to_string(), kind: AttributeMeta::NameValue { @@ -369,7 +369,6 @@ fn test_field_level_rename_attributes() { #[cfg(test)] mod derive_tests { use super::*; - use specta::Type; use specta_macros::Type as TypeDerive; #[derive(TypeDerive, serde::Serialize, serde::Deserialize)] @@ -410,9 +409,17 @@ mod derive_tests { // Process for deserialization let de_types = process_for_deserialization(&types).unwrap(); - // Both should succeed - assert_eq!(ser_types.len(), 1); - assert_eq!(de_types.len(), 1); + // Both should include the requested root type. + assert!( + ser_types + .into_unsorted_iter() + .any(|ty| ty.name() == "TestStruct") + ); + assert!( + de_types + .into_unsorted_iter() + .any(|ty| ty.name() == "TestStruct") + ); } #[test] @@ -425,9 +432,17 @@ mod derive_tests { // Process for deserialization let de_types = process_for_deserialization(&types).unwrap(); - // Both should succeed - assert_eq!(ser_types.len(), 1); - assert_eq!(de_types.len(), 1); + // Both should include the requested root type. + assert!( + ser_types + .into_unsorted_iter() + .any(|ty| ty.name() == "TestEnum") + ); + assert!( + de_types + .into_unsorted_iter() + .any(|ty| ty.name() == "TestEnum") + ); } } @@ -483,7 +498,7 @@ fn test_skip_path_attribute() { ))]), }; - let field1 = Field::new(DataType::Primitive(Primitive::String)); + let field1 = Field::new(DataType::Primitive(Primitive::str)); let mut field2 = Field::new(DataType::Primitive(Primitive::u32)); field2.set_attributes(vec![skip_attr]); @@ -512,7 +527,7 @@ fn test_flatten_path_attribute() { ))]), }; - let field1 = Field::new(DataType::Primitive(Primitive::String)); + let field1 = Field::new(DataType::Primitive(Primitive::str)); let mut field2 = Field::new(DataType::Primitive(Primitive::u32)); field2.set_attributes(vec![flatten_attr]); @@ -540,7 +555,7 @@ fn test_both_mode_with_common_attributes() { }, }; - let field1 = Field::new(DataType::Primitive(Primitive::String)); + let field1 = Field::new(DataType::Primitive(Primitive::str)); let field2 = Field::new(DataType::Primitive(Primitive::u32)); let mut s = match Struct::named() @@ -591,7 +606,7 @@ fn test_both_mode_skip_behavior() { kind: AttributeMeta::Path("skip_serializing".to_string()), }; - let mut field1 = Field::new(DataType::Primitive(Primitive::String)); + let mut field1 = Field::new(DataType::Primitive(Primitive::str)); field1.set_attributes(vec![field1_attr]); let field2 = Field::new(DataType::Primitive(Primitive::u32)); @@ -642,7 +657,7 @@ fn test_both_mode_with_universal_skip() { kind: AttributeMeta::Path("skip".to_string()), }; - let mut field1 = Field::new(DataType::Primitive(Primitive::String)); + let mut field1 = Field::new(DataType::Primitive(Primitive::str)); field1.set_attributes(vec![field1_attr]); let field2 = Field::new(DataType::Primitive(Primitive::u32)); diff --git a/specta-swift/src/primitives.rs b/specta-swift/src/primitives.rs index 08ef177e..90dcf27b 100644 --- a/specta-swift/src/primitives.rs +++ b/specta-swift/src/primitives.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use specta::{ TypeCollection, - datatype::{DataType, Primitive, Reference}, + datatype::{DataType, GenericReference, Primitive, Reference}, }; use crate::error::{Error, Result}; @@ -19,9 +19,7 @@ fn is_string_enum(e: &specta::datatype::Enum) -> bool { } /// Helper function to get rename_all from serde attributes -fn get_rename_all_from_attributes( - attributes: &[specta::datatype::Attribute], -) -> Option { +fn get_rename_all_from_attributes(attributes: &[specta::datatype::Attribute]) -> Option { use specta::datatype::AttributeMeta; for attr in attributes { @@ -118,6 +116,10 @@ pub fn export_type( types: &TypeCollection, ndt: &specta::datatype::NamedDataType, ) -> Result { + if !matches!(ndt.ty(), DataType::Struct(_) | DataType::Enum(_)) { + return Ok(String::new()); + } + let mut result = String::new(); // Add JSDoc-style comments if present @@ -145,12 +147,13 @@ pub fn export_type( )); } - // Generate the type definition - let type_def = datatype_to_swift(swift, types, ndt.ty(), vec![], false, None)?; + let generic_scope = ndt.generics().to_vec(); // Format based on type match ndt.ty() { DataType::Struct(_) => { + let type_def = + datatype_to_swift(swift, types, ndt.ty(), generic_scope.clone(), false, None)?; let name = swift.naming.convert(ndt.name()); let generics = if ndt.generics().is_empty() { String::new() @@ -159,7 +162,7 @@ pub fn export_type( "<{}>", ndt.generics() .iter() - .map(|g| g.to_string()) + .map(|(_, g)| g.as_ref().to_string()) .collect::>() .join(", ") ) @@ -178,7 +181,7 @@ pub fn export_type( "<{}>", ndt.generics() .iter() - .map(|g| g.to_string()) + .map(|(_, g)| g.as_ref().to_string()) .collect::>() .join(", ") ) @@ -215,13 +218,21 @@ pub fn export_type( "public enum {}{}{} {{\n", name, generics, protocol_part )); - let enum_body = enum_to_swift(swift, types, e, vec![], false, None, Some(&name))?; + let enum_body = enum_to_swift( + swift, + types, + e, + generic_scope.clone(), + false, + None, + Some(&name), + )?; result.push_str(&enum_body); result.push('}'); // Generate struct definitions for named field variants let struct_definitions = - generate_enum_structs(swift, types, e, vec![], false, None, &name)?; + generate_enum_structs(swift, types, e, generic_scope, false, None, &name)?; result.push_str(&struct_definitions); // Generate custom Codable implementation for enums with struct variants @@ -231,8 +242,7 @@ pub fn export_type( } } _ => { - // For other types, just use the type definition - result.push_str(&type_def); + return Ok(String::new()); } } @@ -244,7 +254,7 @@ pub fn datatype_to_swift( swift: &Swift, types: &TypeCollection, dt: &DataType, - location: Vec>, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, is_export: bool, reference: Option<&specta::datatype::Reference>, ) -> Result { @@ -256,10 +266,10 @@ pub fn datatype_to_swift( match dt { DataType::Primitive(p) => primitive_to_swift(p), // DataType::Literal(l) => literal_to_swift(l), - DataType::List(l) => list_to_swift(swift, types, l), - DataType::Map(m) => map_to_swift(swift, types, m), + DataType::List(l) => list_to_swift(swift, types, l, generic_scope.clone()), + DataType::Map(m) => map_to_swift(swift, types, m, generic_scope.clone()), DataType::Nullable(def) => { - let inner = datatype_to_swift(swift, types, def, location, is_export, None)?; + let inner = datatype_to_swift(swift, types, def, generic_scope, is_export, None)?; Ok(match swift.optionals { crate::swift::OptionalStyle::QuestionMark => format!("{}?", inner), crate::swift::OptionalStyle::Optional => format!("Optional<{}>", inner), @@ -270,12 +280,11 @@ pub fn datatype_to_swift( if is_duration_struct(s) { return Ok("RustDuration".to_string()); } - struct_to_swift(swift, types, s, location, is_export, None) + struct_to_swift(swift, types, s, generic_scope, is_export, None) } - DataType::Enum(e) => enum_to_swift(swift, types, e, location, is_export, None, None), - DataType::Tuple(t) => tuple_to_swift(swift, types, t), - DataType::Reference(r) => reference_to_swift(swift, types, r), - DataType::Generic(g) => generic_to_swift(swift, g), + DataType::Enum(e) => enum_to_swift(swift, types, e, generic_scope, is_export, None, None), + DataType::Tuple(t) => tuple_to_swift(swift, types, t, generic_scope.clone()), + DataType::Reference(r) => reference_to_swift(swift, types, r, &generic_scope), } } @@ -334,7 +343,7 @@ fn primitive_to_swift(primitive: &Primitive) -> Result { Primitive::f64 => "Double".to_string(), Primitive::bool => "Bool".to_string(), Primitive::char => "Character".to_string(), - Primitive::String => "String".to_string(), + Primitive::str => "String".to_string(), Primitive::i128 | Primitive::u128 => { return Err(Error::UnsupportedType( "Swift does not support 128-bit integers".to_string(), @@ -376,8 +385,9 @@ fn list_to_swift( swift: &Swift, types: &TypeCollection, list: &specta::datatype::List, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, ) -> Result { - let element_type = datatype_to_swift(swift, types, list.ty(), vec![], false, None)?; + let element_type = datatype_to_swift(swift, types, list.ty(), generic_scope, false, None)?; Ok(format!("[{}]", element_type)) } @@ -386,9 +396,17 @@ fn map_to_swift( swift: &Swift, types: &TypeCollection, map: &specta::datatype::Map, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, ) -> Result { - let key_type = datatype_to_swift(swift, types, map.key_ty(), vec![], false, None)?; - let value_type = datatype_to_swift(swift, types, map.value_ty(), vec![], false, None)?; + let key_type = datatype_to_swift( + swift, + types, + map.key_ty(), + generic_scope.clone(), + false, + None, + )?; + let value_type = datatype_to_swift(swift, types, map.value_ty(), generic_scope, false, None)?; Ok(format!("[{}: {}]", key_type, value_type)) } @@ -397,7 +415,7 @@ fn struct_to_swift( swift: &Swift, types: &TypeCollection, s: &specta::datatype::Struct, - location: Vec>, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, is_export: bool, _reference: Option<&specta::datatype::Reference>, ) -> Result { @@ -414,7 +432,7 @@ fn struct_to_swift( fields.fields()[0] .ty() .expect("tuple field should have a type"), - location, + generic_scope, is_export, None, )?; @@ -427,7 +445,7 @@ fn struct_to_swift( swift, types, field.ty().expect("tuple field should have a type"), - location.clone(), + generic_scope.clone(), is_export, None, )?; @@ -442,7 +460,7 @@ fn struct_to_swift( for (original_field_name, field) in fields.fields() { let field_type = if let Some(ty) = field.ty() { - datatype_to_swift(swift, types, ty, location.clone(), is_export, None)? + datatype_to_swift(swift, types, ty, generic_scope.clone(), is_export, None)? } else { continue; }; @@ -551,7 +569,7 @@ fn enum_to_swift( swift: &Swift, types: &TypeCollection, e: &specta::datatype::Enum, - location: Vec>, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, is_export: bool, _reference: Option<&specta::datatype::Reference>, enum_name: Option<&str>, @@ -593,7 +611,7 @@ fn enum_to_swift( swift, types, f.ty().expect("enum variant field should have a type"), - location.clone(), + generic_scope.clone(), is_export, None, ) @@ -631,7 +649,7 @@ fn generate_enum_structs( swift: &Swift, types: &TypeCollection, e: &specta::datatype::Enum, - location: Vec>, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, is_export: bool, _reference: Option<&specta::datatype::Reference>, enum_name: &str, @@ -656,8 +674,14 @@ fn generate_enum_structs( let mut field_mappings = Vec::new(); for (original_field_name, field) in fields.fields() { if let Some(ty) = field.ty() { - let field_type = - datatype_to_swift(swift, types, ty, location.clone(), is_export, None)?; + let field_type = datatype_to_swift( + swift, + types, + ty, + generic_scope.clone(), + is_export, + None, + )?; let optional_marker = if field.optional() { "?" } else { "" }; let swift_field_name = swift.naming.convert_field(original_field_name); result.push_str(&format!( @@ -720,16 +744,17 @@ fn tuple_to_swift( swift: &Swift, types: &TypeCollection, t: &specta::datatype::Tuple, + generic_scope: Vec<(GenericReference, Cow<'static, str>)>, ) -> Result { if t.elements().is_empty() { Ok("Void".to_string()) } else if t.elements().len() == 1 { - datatype_to_swift(swift, types, &t.elements()[0], vec![], false, None) + datatype_to_swift(swift, types, &t.elements()[0], generic_scope, false, None) } else { let types_str = t .elements() .iter() - .map(|e| datatype_to_swift(swift, types, e, vec![], false, None)) + .map(|e| datatype_to_swift(swift, types, e, generic_scope.clone(), false, None)) .collect::, _>>()? .join(", "); Ok(format!("({})", types_str)) @@ -741,24 +766,43 @@ fn reference_to_swift( swift: &Swift, types: &TypeCollection, r: &specta::datatype::Reference, + generic_scope: &[(GenericReference, Cow<'static, str>)], ) -> Result { match r { Reference::Named(r) => { - let name = if let Some(ndt) = r.get(types) { - swift.naming.convert(ndt.name()) - } else { + let Some(ndt) = r.get(types) else { return Err(Error::InvalidIdentifier( "Reference to unknown type".to_string(), )); }; + if ndt.name() == "String" { + return Ok("String".to_string()); + } + + if matches!(ndt.name().as_ref(), "Uuid" | "DateTime" | "NaiveDateTime") { + return Ok("String".to_string()); + } + + if ndt.name() == "Vec" + && let Some((_, inner_ty)) = r.generics().first() + { + let inner = + datatype_to_swift(swift, types, inner_ty, generic_scope.to_vec(), false, None)?; + return Ok(format!("[{inner}]")); + } + + let name = swift.naming.convert(ndt.name()); + if r.generics().is_empty() { Ok(name) } else { let generics = r .generics() .iter() - .map(|(_, t)| datatype_to_swift(swift, types, t, vec![], false, None)) + .map(|(_, t)| { + datatype_to_swift(swift, types, t, generic_scope.to_vec(), false, None) + }) .collect::, _>>()? .join(", "); Ok(format!("{}<{}>", name, generics)) @@ -767,12 +811,20 @@ fn reference_to_swift( Reference::Opaque(_) => Err(Error::UnsupportedType( "Opaque references are not supported by Swift exporter".to_string(), )), + Reference::Generic(g) => generic_to_swift(swift, g, generic_scope), } } /// Convert generic types to Swift. -fn generic_to_swift(_swift: &Swift, g: &specta::datatype::Generic) -> Result { - Ok(g.to_string()) +fn generic_to_swift( + _swift: &Swift, + g: &specta::datatype::GenericReference, + generic_scope: &[(GenericReference, Cow<'static, str>)], +) -> Result { + generic_scope + .iter() + .find_map(|(candidate, name)| (candidate == g).then(|| name.to_string())) + .ok_or_else(|| Error::GenericConstraint(format!("Unresolved generic reference: {g:?}"))) } /// Generate custom Codable implementation for enums with struct-like variants diff --git a/specta-swift/src/swift.rs b/specta-swift/src/swift.rs index 94801632..6694fd57 100644 --- a/specta-swift/src/swift.rs +++ b/specta-swift/src/swift.rs @@ -193,8 +193,11 @@ impl Swift { // Export types for ndt in types.into_sorted_iter() { - result.push_str(&export_type(self, types, ndt)?); - result.push_str("\n\n"); + let exported = export_type(self, types, ndt)?; + if !exported.is_empty() { + result.push_str(&exported); + result.push_str("\n\n"); + } } Ok(result) diff --git a/specta-typescript/examples/typescript.rs b/specta-typescript/examples/typescript.rs index a1cf456d..1bc3f952 100644 --- a/specta-typescript/examples/typescript.rs +++ b/specta-typescript/examples/typescript.rs @@ -1,9 +1,6 @@ -use std::{any::Any, collections::HashMap}; +use std::collections::HashMap; -use specta::{ - Type, TypeCollection, - datatype::{self, FunctionReturnType}, -}; +use specta::{Type, TypeCollection}; use specta_typescript::Typescript; #[derive(Type)] @@ -99,7 +96,7 @@ fn main() { // println!("{:?}", specta_typescript::legacy::inline::(&Default::default())); // println!("{:?}", specta_typescript::export::(&Default::default())); - let mut types = TypeCollection::default() + let types = TypeCollection::default() .register::() // notice how we don't list `TypeTwo`. It's a dependency of `TypeOne` and will be exported automatically. .register::() diff --git a/specta-typescript/src/error.rs b/specta-typescript/src/error.rs index 8c1a8b50..e65c3bff 100644 --- a/specta-typescript/src/error.rs +++ b/specta-typescript/src/error.rs @@ -71,6 +71,10 @@ enum ErrorKind { DanglingNamedReference { reference: String, }, + /// Found a generic reference that cannot be resolved to a declared generic name. + UnresolvedGenericReference { + reference: String, + }, /// An error occurred in your exporter framework. Framework { message: Cow<'static, str>, @@ -169,6 +173,12 @@ impl Error { } } + pub(crate) fn unresolved_generic_reference(reference: String) -> Self { + Self { + kind: ErrorKind::UnresolvedGenericReference { reference }, + } + } + pub(crate) fn forbidden_name_legacy(path: ExportPath, name: &'static str) -> Self { Self { kind: ErrorKind::ForbiddenNameLegacy(path, name), @@ -272,6 +282,10 @@ impl fmt::Display for Error { f, "Found dangling named reference {reference}. The referenced type is missing from `TypeCollection`." ), + ErrorKind::UnresolvedGenericReference { reference } => write!( + f, + "Found unresolved generic reference {reference}. The generic is missing from the active named type scope." + ), ErrorKind::Framework { message, source } => { let source = source.to_string(); if message.is_empty() && source.is_empty() { diff --git a/specta-typescript/src/legacy.rs b/specta-typescript/src/legacy.rs index 7d6947d9..d946da54 100644 --- a/specta-typescript/src/legacy.rs +++ b/specta-typescript/src/legacy.rs @@ -9,8 +9,8 @@ use std::{ use specta::{ TypeCollection, datatype::{ - DataType, DeprecatedType, Enum, EnumVariant, Fields, FunctionReturnType, Generic, - NonSkipField, Reference, Struct, Tuple, skip_fields, skip_fields_named, + DataType, DeprecatedType, Enum, EnumVariant, Fields, GenericReference, NonSkipField, + Reference, Struct, Tuple, skip_fields, skip_fields_named, }, }; @@ -130,45 +130,11 @@ fn inner_comments( pub(crate) fn datatype_inner( ctx: ExportContext, - typ: &FunctionReturnType, + typ: &DataType, types: &TypeCollection, s: &mut String, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<()> { - let typ = match typ { - FunctionReturnType::Value(t) => t, - FunctionReturnType::Result(t, e) => { - let mut variants = vec![ - { - let mut v = String::new(); - datatype_inner( - ctx.clone(), - &FunctionReturnType::Value(t.clone()), - types, - &mut v, - generics, - )?; - v - }, - { - let mut v = String::new(); - datatype_inner( - ctx, - &FunctionReturnType::Value(e.clone()), - types, - &mut v, - generics, - )?; - v - }, - ]; - let mut seen = BTreeSet::new(); - variants.retain(|variant| seen.insert(variant.clone())); - s.push_str(&variants.join(" | ")); - return Ok(()); - } - }; - crate::primitives::datatype(s, ctx.cfg, types, typ, vec![], None, "", generics) } @@ -179,7 +145,7 @@ fn unnamed_fields_datatype( types: &TypeCollection, s: &mut String, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], force_inline: bool, ) -> Result<()> { match fields { @@ -246,7 +212,7 @@ pub(crate) fn tuple_datatype( ctx: ExportContext, tuple: &Tuple, types: &TypeCollection, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Output { match &tuple.elements() { [] => Ok(NULL.to_string()), @@ -255,14 +221,7 @@ pub(crate) fn tuple_datatype( tys.iter() .map(|v| { let mut s = String::new(); - datatype_inner( - ctx.clone(), - &FunctionReturnType::Value(v.clone()), - types, - &mut s, - generics, - ) - .map(|_| s) + datatype_inner(ctx.clone(), v, types, &mut s, generics).map(|_| s) }) .collect::>>()? .join(", ") @@ -277,7 +236,7 @@ pub(crate) fn struct_datatype( types: &TypeCollection, s: &mut String, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<()> { match &strct.fields() { Fields::Unit => s.push_str(NULL), @@ -412,7 +371,7 @@ fn enum_variant_datatype( name: Cow<'static, str>, variant: &EnumVariant, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result> { match &variant.fields() { Fields::Unit => Ok(Some(sanitise_key(name, false).to_string())), @@ -662,7 +621,7 @@ pub(crate) fn enum_datatype( types: &TypeCollection, s: &mut String, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<()> { if e.variants().is_empty() { return Ok(write!(s, "{NEVER}")?); @@ -820,7 +779,7 @@ fn object_field_to_ts( field_ref: NonSkipField, types: &TypeCollection, s: &mut String, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], prefix: &str, force_inline: bool, ) -> Result<()> { @@ -955,7 +914,7 @@ fn validate_type_for_tagged_intersection( | DataType::Nullable(_) | DataType::List(_) | DataType::Map(_) - | DataType::Generic(_) => Ok(false), + | DataType::Reference(Reference::Generic(_)) => Ok(false), // DataType::Literal(v) => match v { // Literal::None => Ok(true), // _ => Ok(false), diff --git a/specta-typescript/src/primitives.rs b/specta-typescript/src/primitives.rs index 4f8405ab..58c23fa5 100644 --- a/specta-typescript/src/primitives.rs +++ b/specta-typescript/src/primitives.rs @@ -2,17 +2,13 @@ //! //! These are for advanced usecases, you should generally use [Typescript] or [JSDoc] in end-user applications. -use std::{ - borrow::{Borrow, Cow}, - fmt::Write as _, - iter, -}; +use std::{borrow::Cow, cell::RefCell, fmt::Write as _, iter}; use specta::{ TypeCollection, datatype::{ - DataType, DeprecatedType, Enum, Fields, Generic, List, Map, NamedDataType, NamedReference, - OpaqueReference, Primitive, Reference, Tuple, + DataType, DeprecatedType, Enum, Fields, GenericReference, List, Map, NamedDataType, + NamedReference, OpaqueReference, Primitive, Reference, Tuple, }, }; @@ -107,7 +103,10 @@ fn export_single_internal( let generics = (!ndt.generics().is_empty()) .then(|| { iter::once("<") - .chain(intersperse(ndt.generics().iter().map(|g| g.borrow()), ", ")) + .chain(intersperse( + ndt.generics().iter().map(|(_, g)| g.as_ref()), + ", ", + )) .chain(iter::once(">")) }) .into_iter() @@ -148,6 +147,7 @@ fn export_single_internal( } s.push_str(" = "); + let _generic_scope = push_generic_scope(ndt.generics()); datatype( s, exporter, @@ -379,7 +379,10 @@ fn append_typedef_body( let generics = (!dt.generics().is_empty()) .then(|| { iter::once("<") - .chain(intersperse(dt.generics().iter().map(|g| g.borrow()), ", ")) + .chain(intersperse( + dt.generics().iter().map(|(_, g)| g.as_ref()), + ", ", + )) .chain(iter::once(">")) }) .into_iter() @@ -393,6 +396,7 @@ fn append_typedef_body( let mut typedef_ty = String::new(); let datatype_prefix = format!("{indent}\t*\t"); + let _generic_scope = push_generic_scope(dt.generics()); datatype( &mut typedef_ty, exporter, @@ -486,7 +490,7 @@ pub(crate) fn datatype_with_inline_attr( location: Vec>, parent_name: Option<&str>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], inline: bool, ) -> Result<(), Error> { if inline { @@ -515,16 +519,66 @@ pub(crate) fn datatype_with_inline_attr( } fn merged_generics( - parent: &[(Generic, DataType)], - child: &[(Generic, DataType)], -) -> Vec<(Generic, DataType)> { + parent: &[(GenericReference, DataType)], + child: &[(GenericReference, DataType)], +) -> Vec<(GenericReference, DataType)> { + let unshadowed_parent = parent + .iter() + .filter(|(parent_generic, _)| { + !child + .iter() + .any(|(child_generic, _)| child_generic == parent_generic) + }) + .cloned(); + child .iter() .map(|(generic, dt)| (generic.clone(), resolve_generics_in_datatype(dt, parent))) - .chain(parent.iter().cloned()) + .chain(unshadowed_parent) .collect() } +thread_local! { + static INLINE_REFERENCE_STACK: RefCell, Cow<'static, str>, Vec<(GenericReference, DataType)>)>> = const { RefCell::new(Vec::new()) }; + static RESOLVING_GENERICS: RefCell> = const { RefCell::new(Vec::new()) }; + static GENERIC_NAME_STACK: RefCell)>>> = const { RefCell::new(Vec::new()) }; +} + +struct GenericScopeGuard; + +impl Drop for GenericScopeGuard { + fn drop(&mut self) { + GENERIC_NAME_STACK.with(|stack| { + stack.borrow_mut().pop(); + }); + } +} + +fn push_generic_scope(generics: &[(GenericReference, Cow<'static, str>)]) -> GenericScopeGuard { + GENERIC_NAME_STACK.with(|stack| { + stack.borrow_mut().push(generics.to_vec()); + }); + GenericScopeGuard +} + +fn resolve_generic_name(generic: &GenericReference) -> Option> { + GENERIC_NAME_STACK.with(|stack| { + stack.borrow().iter().rev().find_map(|scope| { + scope + .iter() + .find(|(candidate, _)| candidate == generic) + .map(|(_, name)| name.clone()) + }) + }) +} + +fn write_generic_reference(s: &mut String, generic: &GenericReference) -> Result<(), Error> { + let generic_name = resolve_generic_name(generic) + .ok_or_else(|| Error::unresolved_generic_reference(format!("{generic:?}")))?; + s.push_str(generic_name.as_ref()); + Ok(()) +} + fn shallow_inline_datatype( s: &mut String, exporter: &Exporter, @@ -533,7 +587,7 @@ fn shallow_inline_datatype( location: Vec>, parent_name: Option<&str>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { match dt { DataType::Primitive(p) => s.push_str(primitive_dt(&exporter.bigint, p, location)?), @@ -703,113 +757,146 @@ fn shallow_inline_datatype( &combined_generics, ) } - Reference::Opaque(r) => reference_opaque_dt(s, exporter, types, r), - }?, - DataType::Generic(g) => { - if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { - if matches!(resolved_dt, DataType::Generic(inner) if inner == g) { - s.push_str(g.borrow()); + Reference::Generic(g) => { + if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { + if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g) + { + write_generic_reference(s, g)?; + } else { + let already_resolving = RESOLVING_GENERICS + .with(|stack| stack.borrow().iter().any(|seen| seen == g)); + if already_resolving { + write_generic_reference(s, g)?; + } else { + RESOLVING_GENERICS.with(|stack| stack.borrow_mut().push(g.clone())); + let result = shallow_inline_datatype( + s, + exporter, + types, + resolved_dt, + location, + parent_name, + prefix, + generics, + ); + RESOLVING_GENERICS.with(|stack| { + stack.borrow_mut().pop(); + }); + result?; + } + } } else { - shallow_inline_datatype( - s, - exporter, - types, - resolved_dt, - location, - parent_name, - prefix, - generics, - )?; + write_generic_reference(s, g)?; } - } else { - s.push_str(g.borrow()); + Ok(()) } - } + Reference::Opaque(_) => reference_dt(s, exporter, types, r, location, prefix, generics), + }?, } Ok(()) } -fn resolve_generics_in_datatype(dt: &DataType, generics: &[(Generic, DataType)]) -> DataType { - match dt { - DataType::Primitive(_) | DataType::Reference(_) => dt.clone(), - DataType::List(l) => { - let mut out = l.clone(); - out.set_ty(resolve_generics_in_datatype(l.ty(), generics)); - DataType::List(out) - } - DataType::Map(m) => { - let mut out = m.clone(); - out.set_key_ty(resolve_generics_in_datatype(m.key_ty(), generics)); - out.set_value_ty(resolve_generics_in_datatype(m.value_ty(), generics)); - DataType::Map(out) - } - DataType::Nullable(def) => { - DataType::Nullable(Box::new(resolve_generics_in_datatype(def, generics))) - } - DataType::Struct(st) => { - let mut out = st.clone(); - match out.fields_mut() { - specta::datatype::Fields::Unit => {} - specta::datatype::Fields::Unnamed(unnamed) => { - for field in unnamed.fields_mut() { - if let Some(ty) = field.ty_mut() { - *ty = resolve_generics_in_datatype(ty, generics); - } - } - } - specta::datatype::Fields::Named(named) => { - for (_, field) in named.fields_mut() { - if let Some(ty) = field.ty_mut() { - *ty = resolve_generics_in_datatype(ty, generics); - } - } - } +fn resolve_generics_in_datatype( + dt: &DataType, + generics: &[(GenericReference, DataType)], +) -> DataType { + fn resolve( + dt: &DataType, + generics: &[(GenericReference, DataType)], + visiting: &mut Vec, + ) -> DataType { + match dt { + DataType::Primitive(_) => dt.clone(), + DataType::List(l) => { + let mut out = l.clone(); + out.set_ty(resolve(l.ty(), generics, visiting)); + DataType::List(out) } - DataType::Struct(out) - } - DataType::Enum(en) => { - let mut out = en.clone(); - for (_, variant) in out.variants_mut() { - match variant.fields_mut() { + DataType::Map(m) => { + let mut out = m.clone(); + out.set_key_ty(resolve(m.key_ty(), generics, visiting)); + out.set_value_ty(resolve(m.value_ty(), generics, visiting)); + DataType::Map(out) + } + DataType::Nullable(def) => { + DataType::Nullable(Box::new(resolve(def, generics, visiting))) + } + DataType::Struct(st) => { + let mut out = st.clone(); + match out.fields_mut() { specta::datatype::Fields::Unit => {} specta::datatype::Fields::Unnamed(unnamed) => { for field in unnamed.fields_mut() { if let Some(ty) = field.ty_mut() { - *ty = resolve_generics_in_datatype(ty, generics); + *ty = resolve(ty, generics, visiting); } } } specta::datatype::Fields::Named(named) => { for (_, field) in named.fields_mut() { if let Some(ty) = field.ty_mut() { - *ty = resolve_generics_in_datatype(ty, generics); + *ty = resolve(ty, generics, visiting); } } } } + DataType::Struct(out) } - DataType::Enum(out) - } - DataType::Tuple(t) => { - let mut out = t.clone(); - for element in out.elements_mut() { - *element = resolve_generics_in_datatype(element, generics); + DataType::Enum(en) => { + let mut out = en.clone(); + for (_, variant) in out.variants_mut() { + match variant.fields_mut() { + specta::datatype::Fields::Unit => {} + specta::datatype::Fields::Unnamed(unnamed) => { + for field in unnamed.fields_mut() { + if let Some(ty) = field.ty_mut() { + *ty = resolve(ty, generics, visiting); + } + } + } + specta::datatype::Fields::Named(named) => { + for (_, field) in named.fields_mut() { + if let Some(ty) = field.ty_mut() { + *ty = resolve(ty, generics, visiting); + } + } + } + } + } + DataType::Enum(out) } - DataType::Tuple(out) - } - DataType::Generic(g) => { - if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { - if matches!(resolved_dt, DataType::Generic(inner) if inner == g) { - dt.clone() + DataType::Tuple(t) => { + let mut out = t.clone(); + for element in out.elements_mut() { + *element = resolve(element, generics, visiting); + } + DataType::Tuple(out) + } + DataType::Reference(Reference::Generic(g)) => { + if visiting.iter().any(|seen| seen == g) { + return dt.clone(); + } + + if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { + if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g) + { + dt.clone() + } else { + visiting.push(g.clone()); + let out = resolve(resolved_dt, generics, visiting); + visiting.pop(); + out + } } else { - resolve_generics_in_datatype(resolved_dt, generics) + dt.clone() } - } else { - dt.clone() } + DataType::Reference(_) => dt.clone(), } } + + resolve(dt, generics, &mut Vec::new()) } // Internal function to handle inlining without cloning DataType nodes @@ -822,7 +909,7 @@ fn inline_datatype( parent_name: Option<&str>, prefix: &str, depth: usize, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { // Prevent infinite recursion if depth == 25 { @@ -842,7 +929,7 @@ fn inline_datatype( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value(l.ty().clone()), + l.ty(), types, &mut dt_str, generics, @@ -966,7 +1053,6 @@ fn inline_datatype( DataType::Enum(e) => enum_dt(s, exporter, types, e, location, prefix, generics)?, DataType::Tuple(t) => tuple_dt(s, exporter, types, t, location, generics)?, DataType::Reference(r) => { - // Always inline references when in inline mode if let Reference::Named(r) = r && let Some(ndt) = r.get(types) { @@ -983,35 +1069,9 @@ fn inline_datatype( &combined_generics, )?; } else { - // Fallback to regular reference if type not found reference_dt(s, exporter, types, r, location, prefix, generics)?; } } - DataType::Generic(g) => { - // Try to resolve the generic from the generics map - if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { - if matches!(resolved_dt, DataType::Generic(inner) if inner == g) { - s.push_str(>::borrow(g)); - return Ok(()); - } - // Recursively inline the resolved type - inline_datatype( - s, - exporter, - types, - resolved_dt, - location, - parent_name, - prefix, - depth + 1, - generics, - )?; - } else { - // Fallback to placeholder name if not found in the generics map - // This can happen for unsubstituted generic types - s.push_str(>::borrow(g)); - } - } } Ok(()) @@ -1025,7 +1085,7 @@ pub(crate) fn datatype( location: Vec>, parent_name: Option<&str>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { // TODO: Validating the variant from `dt` can be flattened @@ -1041,7 +1101,7 @@ pub(crate) fn datatype( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value((**def).clone()), + def, types, &mut inner, generics, @@ -1078,26 +1138,6 @@ pub(crate) fn datatype( DataType::Enum(e) => enum_dt(s, exporter, types, e, location, prefix, generics)?, DataType::Tuple(t) => tuple_dt(s, exporter, types, t, location, generics)?, DataType::Reference(r) => reference_dt(s, exporter, types, r, location, prefix, generics)?, - DataType::Generic(g) => { - if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { - if matches!(resolved_dt, DataType::Generic(inner) if inner == g) { - s.push_str(g.borrow()); - return Ok(()); - } - datatype( - s, - exporter, - types, - resolved_dt, - location, - parent_name, - prefix, - generics, - )?; - } else { - s.push_str(g.borrow()); - } - } }; Ok(()) @@ -1121,7 +1161,7 @@ fn primitive_dt( } }, Primitive::bool => "boolean", - String | char => "string", + str | char => "string", }) } @@ -1131,7 +1171,7 @@ fn list_dt( types: &TypeCollection, l: &List, _location: Vec>, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { // TODO: This is the legacy stuff { @@ -1141,7 +1181,7 @@ fn list_dt( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value(l.ty().clone()), + l.ty(), types, &mut dt, generics, @@ -1217,7 +1257,7 @@ fn map_dt( types: &TypeCollection, m: &Map, _location: Vec>, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { { fn is_exhaustive(dt: &DataType, types: &TypeCollection) -> bool { @@ -1235,7 +1275,8 @@ fn map_dt( } } - let is_exhaustive = is_exhaustive(m.key_ty(), types); + let resolved_key = resolve_generics_in_datatype(m.key_ty(), generics); + let is_exhaustive = is_exhaustive(&resolved_key, types); // We use `{ [key in K]: V }` instead of `Record` to avoid issues with circular references. // Wrapped in Partial<> because otherwise TypeScript would enforce exhaustiveness. @@ -1248,7 +1289,7 @@ fn map_dt( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value(m.key_ty().clone()), + &resolved_key, types, s, generics, @@ -1259,7 +1300,7 @@ fn map_dt( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value(m.value_ty().clone()), + m.value_ty(), types, s, generics, @@ -1288,7 +1329,7 @@ fn enum_dt( e: &Enum, _location: Vec>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { // TODO: Drop legacy stuff { @@ -1722,7 +1763,7 @@ fn tuple_dt( types: &TypeCollection, t: &Tuple, _location: Vec>, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { { s.push_str(&crate::legacy::tuple_datatype( @@ -1764,12 +1805,35 @@ fn reference_dt( r: &Reference, location: Vec>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { match r { Reference::Named(r) => { reference_named_dt(s, exporter, types, r, location, prefix, generics) } + Reference::Generic(g) => { + if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) { + if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g) + { + write_generic_reference(s, g)?; + Ok(()) + } else { + datatype( + s, + exporter, + types, + resolved_dt, + location, + None, + prefix, + generics, + ) + } + } else { + write_generic_reference(s, g)?; + Ok(()) + } + } Reference::Opaque(r) => reference_opaque_dt(s, exporter, types, r), } } @@ -1824,28 +1888,46 @@ fn reference_named_dt( r: &NamedReference, location: Vec>, prefix: &str, - generics: &[(Generic, DataType)], + generics: &[(GenericReference, DataType)], ) -> Result<(), Error> { // TODO: Legacy stuff { let ndt = r .get(types) .ok_or_else(|| Error::dangling_named_reference(format!("{r:?}")))?; + let _generic_scope = push_generic_scope(ndt.generics()); // Check if this reference should be inlined if r.inline() { - let combined_generics = merged_generics(generics, r.generics()); - let resolved = resolve_generics_in_datatype(ndt.ty(), &combined_generics); - return datatype( - s, - exporter, - types, - &resolved, - location, - None, - prefix, - &combined_generics, + let inline_key = ( + ndt.module_path().clone(), + ndt.name().clone(), + r.generics().to_vec(), ); + let already_inlining = INLINE_REFERENCE_STACK + .with(|stack| stack.borrow().iter().any(|key| key == &inline_key)); + + if already_inlining { + // Fall through and emit a named reference to break recursive inline expansions. + } else { + INLINE_REFERENCE_STACK.with(|stack| stack.borrow_mut().push(inline_key)); + let combined_generics = merged_generics(generics, r.generics()); + let resolved = resolve_generics_in_datatype(ndt.ty(), &combined_generics); + let result = datatype( + s, + exporter, + types, + &resolved, + location, + None, + prefix, + &combined_generics, + ); + INLINE_REFERENCE_STACK.with(|stack| { + stack.borrow_mut().pop(); + }); + return result; + } } // We check it's valid before tracking @@ -1890,6 +1972,16 @@ fn reference_named_dt( _ => ndt.name().clone(), }; + let scoped_generics = generics + .iter() + .filter(|(parent_generic, _)| { + !r.generics() + .iter() + .any(|(child_generic, _)| child_generic == parent_generic) + }) + .cloned() + .collect::>(); + s.push_str(&name); if !r.generics().is_empty() { s.push('<'); @@ -1904,10 +1996,10 @@ fn reference_named_dt( cfg: exporter, path: vec![], }, - &specta::datatype::FunctionReturnType::Value(v.clone()), + v, types, s, - generics, + &scoped_generics, )?; } diff --git a/specta/src/datatype.rs b/specta/src/datatype.rs index b82a61f1..8a5c382d 100644 --- a/specta/src/datatype.rs +++ b/specta/src/datatype.rs @@ -5,7 +5,6 @@ mod builders; mod r#enum; mod fields; mod function; -mod generic; mod list; mod map; mod named; @@ -20,13 +19,12 @@ pub use r#enum::{Enum, EnumVariant}; pub use fields::{ Field, Fields, NamedFields, NonSkipField, UnnamedFields, skip_fields, skip_fields_named, }; -pub use function::{Function, FunctionReturnType}; -pub use generic::{ConstGenericPlaceholder, Generic, GenericPlaceholder}; +pub use function::Function; pub use list::List; pub use map::Map; pub use named::{DeprecatedType, NamedDataType}; pub use primitive::Primitive; -pub use reference::{NamedReference, OpaqueReference, Reference}; +pub use reference::{GenericReference, NamedReference, OpaqueReference, Reference}; pub use r#struct::Struct; pub use tuple::Tuple; @@ -43,16 +41,14 @@ pub enum DataType { List(List), /// A map/dictionary type. Map(Map), - /// A nullable wrapper around another type. - Nullable(Box), /// A struct type with named, unnamed, or unit fields. Struct(Struct), /// An enum type. Enum(Enum), /// A tuple type. Tuple(Tuple), + /// A nullable wrapper around another type. + Nullable(Box), /// A reference to another named or opaque type. Reference(Reference), - /// A generic placeholder type parameter. - Generic(Generic), } diff --git a/specta/src/datatype/builders.rs b/specta/src/datatype/builders.rs index a28af000..85c8991b 100644 --- a/specta/src/datatype/builders.rs +++ b/specta/src/datatype/builders.rs @@ -7,8 +7,8 @@ use std::{borrow::Cow, fmt::Debug}; use crate::{ TypeCollection, datatype::{ - Attribute, DataType, DeprecatedType, EnumVariant, Field, Fields, Generic, NamedDataType, - NamedFields, Struct, UnnamedFields, + Attribute, DataType, DeprecatedType, EnumVariant, Field, Fields, GenericReference, + NamedDataType, NamedFields, Struct, UnnamedFields, }, }; @@ -180,14 +180,18 @@ pub struct NamedDataTypeBuilder { pub(crate) docs: Cow<'static, str>, pub(crate) deprecated: Option, pub(crate) module_path: Option>, - pub(crate) generics: Vec, + pub(crate) generics: Vec<(GenericReference, Cow<'static, str>)>, pub(crate) inline: bool, pub(crate) inner: DataType, } impl NamedDataTypeBuilder { /// Construct a new named datatype builder. - pub fn new(name: impl Into>, generics: Vec, dt: DataType) -> Self { + pub fn new( + name: impl Into>, + generics: Vec<(GenericReference, Cow<'static, str>)>, + dt: DataType, + ) -> Self { Self { name: name.into(), docs: Cow::Borrowed(""), diff --git a/specta/src/datatype/function.rs b/specta/src/datatype/function.rs index cc6a04e3..e587c7f9 100644 --- a/specta/src/datatype/function.rs +++ b/specta/src/datatype/function.rs @@ -13,30 +13,13 @@ pub struct Function { /// The name and type of each of the function's arguments. pub(crate) args: Vec<(Cow<'static, str>, DataType)>, /// The return type of the function. - pub(crate) result: Option, + pub(crate) result: Option, /// The function's documentation. Detects both `///` and `#[doc = ...]` style documentation. pub(crate) docs: Cow<'static, str>, /// The deprecated status of the function. pub(crate) deprecated: Option, } -/// The type of a function's return type. -/// -/// This gives the flexibility of the result's structure to the downstream implementer. -#[derive(Debug, Clone, PartialEq)] -pub enum FunctionReturnType { - /// The function returns a `T`. - Value(DataType), - /// The function returns a `Result`. - Result(DataType, DataType), -} - -impl From for FunctionReturnType { - fn from(value: DataType) -> Self { - FunctionReturnType::Value(value) - } -} - impl Function { /// Is this function defined with the `async` keyword? pub fn asyncness(&self) -> bool { @@ -74,17 +57,17 @@ impl Function { } /// Get the result of the function. - pub fn result(&self) -> Option<&FunctionReturnType> { + pub fn result(&self) -> Option<&DataType> { self.result.as_ref() } /// Get the result of the function as mutable reference. - pub fn result_mut(&mut self) -> Option<&mut FunctionReturnType> { + pub fn result_mut(&mut self) -> Option<&mut DataType> { self.result.as_mut() } /// Set the result of the function. - pub fn set_result(&mut self, result: FunctionReturnType) { + pub fn set_result(&mut self, result: DataType) { self.result = Some(result); } diff --git a/specta/src/datatype/generic.rs b/specta/src/datatype/generic.rs deleted file mode 100644 index 71928f62..00000000 --- a/specta/src/datatype/generic.rs +++ /dev/null @@ -1,106 +0,0 @@ -use core::fmt; -use std::{ - borrow::{Borrow, Cow}, - fmt::Display, - marker::PhantomData, -}; - -use crate::Type; - -use super::DataType; - -/// A generic ("placeholder") argument to a Specta-enabled type. -/// -/// A generic does not hold a specific type instead it acts as a slot where a type can be provided when referencing this type. -/// -/// A `GenericType` holds the identifier of the generic. Eg. Given a generic type `struct A(T);` the generics will be represented as `vec![GenericType("A".into())]` -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub struct Generic(pub(crate) Cow<'static, str>); - -impl Generic { - /// Construct a generic placeholder by name. - pub fn new(name: impl Into>) -> Self { - Self(Into::into(name)) - } -} - -impl Display for Generic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -// TODO: Deref instead? -impl Borrow for Generic { - fn borrow(&self) -> &str { - &self.0 - } -} - -impl From> for Generic { - fn from(value: Cow<'static, str>) -> Self { - Self(value) - } -} - -impl From for DataType { - fn from(t: Generic) -> Self { - Self::Generic(t) - } -} - -/// A generic placeholder. -pub trait ConstGenericPlaceholder { - /// Static identifier used when rendering this generic placeholder. - const PLACEHOLDER: &'static str; -} - -/// A placeholder for a generic type. -/// -/// # How this works? -/// -/// When you use the [`Type`](crate::Type) derive macro on a type we transform all generics to be a `Generic` before resolving the types. -/// This ensures we have placeholders to the correct generic when exporting. -/// -/// This major downside to this approach is that if you have custom generic bounds, they might not be implemented by `Generic`. -/// -/// TODO: Show detailed transformation. -/// -// TODO: We should replace the `Generic `trait with `const P: &'static str` if we do a Specta v3. -pub struct GenericPlaceholder(PhantomData); - -impl fmt::Debug for GenericPlaceholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(T::PLACEHOLDER) - } -} - -impl Default for GenericPlaceholder { - fn default() -> Self { - unreachable!("Can't construct a generic type without a placeholder"); - } -} - -impl Clone for GenericPlaceholder { - fn clone(&self) -> Self { - unreachable!(); - } -} - -impl PartialEq for GenericPlaceholder { - fn eq(&self, _: &Self) -> bool { - unreachable!(); - } -} - -impl std::hash::Hash for GenericPlaceholder { - fn hash(&self, _: &mut H) { - unreachable!(); - } -} - -impl Type for GenericPlaceholder { - fn definition(_: &mut crate::TypeCollection) -> DataType { - DataType::Generic(Generic(Cow::Borrowed(T::PLACEHOLDER))) - } -} diff --git a/specta/src/datatype/named.rs b/specta/src/datatype/named.rs index b7597d69..c25a0c9a 100644 --- a/specta/src/datatype/named.rs +++ b/specta/src/datatype/named.rs @@ -3,8 +3,8 @@ use std::{borrow::Cow, panic::Location, sync::Arc}; use crate::{ TypeCollection, datatype::{ - DataType, Generic, NamedDataTypeBuilder, NamedReference, Reference, - reference::{self, NamedId}, + DataType, NamedDataTypeBuilder, NamedReference, Reference, + reference::{self, GenericReference, NamedId}, }, }; @@ -17,7 +17,7 @@ pub struct NamedDataType { pub(crate) deprecated: Option, pub(crate) module_path: Cow<'static, str>, pub(crate) location: Location<'static>, - pub(crate) generics: Vec, + pub(crate) generics: Cow<'static, [(GenericReference, Cow<'static, str>)]>, pub(crate) inline: bool, pub(crate) inner: DataType, } @@ -31,7 +31,8 @@ impl NamedDataType { #[doc(hidden)] #[track_caller] pub fn init_with_sentinel( - generics: Vec<(Generic, DataType)>, + generics_for_ndt: &'static [(GenericReference, Cow<'static, str>)], + generics_for_ref: Vec<(GenericReference, DataType)>, mut inline: bool, types: &mut TypeCollection, sentinel: &'static str, @@ -55,7 +56,7 @@ impl NamedDataType { docs: Cow::Borrowed(""), deprecated: None, module_path: Cow::Borrowed(""), - generics: vec![], + generics: Cow::Borrowed(generics_for_ndt), inline, inner: DataType::Primitive(super::Primitive::i8), }; @@ -79,7 +80,7 @@ impl NamedDataType { Reference::Named(NamedReference { id, - generics, + generics: generics_for_ref, inline, }) } @@ -103,7 +104,7 @@ impl NamedDataType { deprecated: builder.deprecated, module_path, location: location.to_owned(), - generics: builder.generics, + generics: Cow::Owned(builder.generics), inline: builder.inline, inner: builder.inner, }; @@ -117,7 +118,7 @@ impl NamedDataType { /// This can be included in a `DataType::Reference` within another type. /// /// This reference will be inlined if the type is inlined, otherwise you can inline it with [Reference::inline]. - pub fn reference(&self, generics: Vec<(Generic, DataType)>) -> Reference { + pub fn reference(&self, generics: Vec<(GenericReference, DataType)>) -> Reference { // TODO: allow generics to be `Cow` // TODO: HashMap instead of array for better typesafety?? @@ -212,12 +213,12 @@ impl NamedDataType { } /// The generics that are defined on this type - pub fn generics(&self) -> &[Generic] { + pub fn generics(&self) -> &[(GenericReference, Cow<'static, str>)] { &self.generics } /// Get a mutable reference to the generics that are defined on this type - pub fn generics_mut(&mut self) -> &mut Vec { + pub fn generics_mut(&mut self) -> &mut Cow<'static, [(GenericReference, Cow<'static, str>)]> { &mut self.generics } diff --git a/specta/src/datatype/primitive.rs b/specta/src/datatype/primitive.rs index d66d085a..3d5d9ca5 100644 --- a/specta/src/datatype/primitive.rs +++ b/specta/src/datatype/primitive.rs @@ -38,8 +38,8 @@ pub enum Primitive { bool, /// A `char` primitive. char, - /// A `String` primitive. - String, + /// A `str` primitive. + str, } impl From for DataType { diff --git a/specta/src/datatype/reference.rs b/specta/src/datatype/reference.rs index a4b355e1..018a736a 100644 --- a/specta/src/datatype/reference.rs +++ b/specta/src/datatype/reference.rs @@ -6,7 +6,7 @@ use std::{ use crate::{TypeCollection, datatype::NamedDataType}; -use super::{DataType, Generic}; +use super::DataType; /// A reference to another type. /// This can either an [NamedReference] or [OpaqueReference]. @@ -14,6 +14,8 @@ use super::{DataType, Generic}; pub enum Reference { /// A reference to a named type collected in a [`TypeCollection`]. Named(NamedReference), + /// A reference to a generic type parameter. + Generic(GenericReference), /// A reference to an opaque exporter-specific type. Opaque(OpaqueReference), } @@ -22,8 +24,7 @@ pub enum Reference { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NamedReference { pub(crate) id: NamedId, - // TODO: Should this be a map-type??? - pub(crate) generics: Vec<(Generic, DataType)>, // TODO: Cow<'static, [(Generic, DataType)]>, + pub(crate) generics: Vec<(GenericReference, DataType)>, pub(crate) inline: bool, } @@ -37,7 +38,7 @@ impl NamedReference { } /// Get the generic parameters set on this reference which will be filled in by the [NamedDataType]. - pub fn generics(&self) -> &[(Generic, DataType)] { + pub fn generics(&self) -> &[(GenericReference, DataType)] { &self.generics } @@ -47,6 +48,35 @@ impl NamedReference { } } +/// A reference to a generic type parameter. +/// These are resolved in the exporter to the parent's [NamedDataType]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GenericReference { + pub(crate) id: TypeId, +} + +impl GenericReference { + /// Build a new [GenericReference] for a generic type parameter marker. + /// `T` should be a unique type which identifies the generic (Eg. `pub struct GenericT;`) and must be registered on the parent [`NamedDataType`]. + pub const fn new() -> Self { + Self { + id: TypeId::of::(), + } + } + + /// Compare two [GenericReference]s for equality. + /// If this returns true they are both a reference to the same generic type. + pub fn eq(&self) -> bool { + self.id == TypeId::of::() + } +} + +impl From for DataType { + fn from(v: GenericReference) -> Self { + DataType::Reference(Reference::Generic(v)) + } +} + /// A reference to an opaque type which is understood by the type exporter. /// This powers [specta_typescript::branded], [specta_typescript::define] and more. /// @@ -161,6 +191,7 @@ impl Reference { pub fn ty_eq(&self, other: &Reference) -> bool { match (self, other) { (Reference::Named(a), Reference::Named(b)) => a.id == b.id, + (Reference::Generic(a), Reference::Generic(b)) => a.id == b.id, (Reference::Opaque(a), Reference::Opaque(b)) => *a == *b, _ => false, } diff --git a/specta/src/function.rs b/specta/src/function.rs index 1177fd42..f7de3781 100644 --- a/specta/src/function.rs +++ b/specta/src/function.rs @@ -9,9 +9,7 @@ mod specta_fn; pub use arg::FunctionArg; pub use result::FunctionResult; #[doc(hidden)] -pub use result::{ - FunctionFutureMarker, FunctionResultFutureMarker, FunctionResultMarker, FunctionValueMarker, -}; +pub use result::{FunctionFutureMarker, FunctionValueMarker}; pub(crate) use specta_fn::SpectaFn; /// Returns a [`Function`](crate::datatype::Function) for a given function that has been annotated with @@ -32,7 +30,7 @@ pub(crate) use specta_fn::SpectaFn; /// /// assert_eq!(typ.name(), "some_function"); /// assert_eq!(typ.args().len(), 2); -/// assert_eq!(typ.result(), Some(&FunctionReturnType::Value(DataType::Primitive(Primitive::bool)))); +/// assert_eq!(typ.result(), Some(&DataType::Primitive(Primitive::bool))); /// } /// ``` /// diff --git a/specta/src/function/result.rs b/specta/src/function/result.rs index 313913b9..f3950af8 100644 --- a/specta/src/function/result.rs +++ b/specta/src/function/result.rs @@ -1,27 +1,19 @@ use std::future::Future; -use crate::{Type, TypeCollection, datatype::FunctionReturnType}; +use crate::{Type, TypeCollection, datatype::DataType}; /// Implemented by types that can be returned from a function annotated with /// [`specta`](crate::specta). pub trait FunctionResult { - /// Gets the type of the result as a [`FunctionReturnType`](crate::datatype::FunctionReturnType). - fn to_datatype(types: &mut TypeCollection) -> FunctionReturnType; + /// Gets the function return type as a [`DataType`]. + fn to_datatype(types: &mut TypeCollection) -> DataType; } #[doc(hidden)] pub enum FunctionValueMarker {} impl FunctionResult for T { - fn to_datatype(types: &mut TypeCollection) -> FunctionReturnType { - FunctionReturnType::Value(T::definition(types)) - } -} - -#[doc(hidden)] -pub enum FunctionResultMarker {} -impl FunctionResult for Result { - fn to_datatype(types: &mut TypeCollection) -> FunctionReturnType { - FunctionReturnType::Result(T::definition(types), E::definition(types)) + fn to_datatype(types: &mut TypeCollection) -> DataType { + T::definition(types) } } @@ -32,20 +24,7 @@ where F: Future, F::Output: Type, { - fn to_datatype(types: &mut TypeCollection) -> FunctionReturnType { - FunctionReturnType::Value(F::Output::definition(types)) - } -} - -#[doc(hidden)] -pub enum FunctionResultFutureMarker {} -impl FunctionResult for F -where - F: Future>, - T: Type, - E: Type, -{ - fn to_datatype(types: &mut TypeCollection) -> FunctionReturnType { - FunctionReturnType::Result(T::definition(types), E::definition(types)) + fn to_datatype(types: &mut TypeCollection) -> DataType { + F::Output::definition(types) } } diff --git a/specta/src/function/specta_fn.rs b/specta/src/function/specta_fn.rs index 0ecbcd98..80c0a9c8 100644 --- a/specta/src/function/specta_fn.rs +++ b/specta/src/function/specta_fn.rs @@ -1,6 +1,9 @@ use std::borrow::Cow; -use crate::{TypeCollection, datatype::DeprecatedType, datatype::Function}; +use crate::{ + TypeCollection, + datatype::{DeprecatedType, Function}, +}; use super::{FunctionArg, FunctionResult}; @@ -20,8 +23,9 @@ pub trait SpectaFn { ) -> Function; } -impl> SpectaFn - for fn() -> TResult +impl SpectaFn for fn() -> TResult +where + TResult: FunctionResult, { fn to_datatype( asyncness: bool, @@ -47,10 +51,13 @@ macro_rules! impl_typed_command { ( impl $($i:ident),* ) => { paste::paste! { impl< + TResult, TResultMarker, - TResult: FunctionResult, $($i: FunctionArg),* - > SpectaFn for fn($($i),*) -> TResult { + > SpectaFn for fn($($i),*) -> TResult + where + TResult: FunctionResult, + { fn to_datatype( asyncness: bool, name: Cow<'static, str>, diff --git a/specta/src/type.rs b/specta/src/type.rs index f936124f..e4c7e4bf 100644 --- a/specta/src/type.rs +++ b/specta/src/type.rs @@ -1,9 +1,9 @@ use crate::{TypeCollection, datatype::DataType}; +pub(crate) mod generics; mod impls; mod macros; // TODO: We don't care much about these cause they are gonna go so this will do for now. -#[cfg(feature = "derive")] mod legacy_impls; /// Provides runtime type information that can be fed into a language exporter to generate a type definition for another language. diff --git a/specta/src/type/generics.rs b/specta/src/type/generics.rs new file mode 100644 index 00000000..f3e3a503 --- /dev/null +++ b/specta/src/type/generics.rs @@ -0,0 +1,24 @@ +//! We define a struct with implements `Type` for all generics we need in `impls.rs` and `legacy_impls.rs` +//! These allow us to transform the properly support generics. + +use crate::{ + Type, TypeCollection, + datatype::{self, DataType}, +}; + +macro_rules! impl_generic { + ($($ident:ident),* $(,)?) => { + $( + #[allow(dead_code)] + pub(crate) struct $ident; + + impl Type for $ident { + fn definition(_: &mut TypeCollection) -> DataType { + datatype::GenericReference::new::().into() + } + } + )* + }; +} + +impl_generic!(T, K, V, E, L, R); diff --git a/specta/src/type/impls.rs b/specta/src/type/impls.rs index 2f50dd61..ff5dbc71 100644 --- a/specta/src/type/impls.rs +++ b/specta/src/type/impls.rs @@ -1,16 +1,20 @@ -use crate::{datatype::*, r#type::macros::*, *}; +use std::marker::PhantomData; -use std::borrow::Cow; +use crate::{ + Type, TypeCollection, + datatype::{self, DataType, Enum, EnumVariant, Field, List}, + internal, + r#type::{generics, macros::*}, +}; impl_primitives!( i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 bool char - String + str ); -// TODO: Reenable this at some point. It's being really annoying. #[cfg(is_nightly)] impl Type for f16 { fn definition(_: &mut TypeCollection) -> DataType { @@ -25,121 +29,190 @@ impl Type for f128 { } } -impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); // Technically we only support 12-tuples but the `T13` is required due to how the macro works - -#[cfg(feature = "std")] -const _: () = { - use std::{ - cell::{Cell, RefCell}, - collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, - convert::Infallible, - ffi::{CStr, CString, OsStr, OsString}, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, - num::{ - NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, - NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, - }, - ops::{Range, RangeInclusive}, - path::{Path, PathBuf}, - rc::Rc, - sync::{ - Arc, - atomic::{ - AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicI64, AtomicIsize, AtomicU8, - AtomicU16, AtomicU32, AtomicU64, AtomicUsize, - }, - }, - time::{Duration, SystemTime}, - }; - - impl_containers!(Box Rc Arc Cell RefCell); - - use std::sync::{Mutex, RwLock}; - impl_containers!(Mutex RwLock); - - impl Type for Box { - impl_passthrough!(String); - } +// Technically we only support 12-tuples but the `T13` is required due to how the macro works +impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); - impl Type for Rc { - impl_passthrough!(String); +pub(crate) struct PrimitiveSet(PhantomData); +impl Type for PrimitiveSet { + fn definition(types: &mut TypeCollection) -> DataType { + let mut l = List::new(::definition(types)); + l.set_unique(true); + DataType::List(l) } +} - impl Type for Arc { - impl_passthrough!(String); +pub(crate) struct PrimitiveMap(PhantomData, PhantomData); +impl Type for PrimitiveMap { + fn definition(types: &mut TypeCollection) -> DataType { + DataType::Map(crate::datatype::Map::new( + K::definition(types), + V::definition(types), + )) } +} - impl<'a, T: ?Sized + ToOwned + Type + 'a> Type for Cow<'a, T> { - impl_passthrough!(T); - } +#[cfg(feature = "std")] +const _: () = { + impl_ndt_as!( + std::string::String as str + + // Non-unique sets + std::vec::Vec as [generics::T] + std::collections::VecDeque as [generics::T] + std::collections::BinaryHeap as [generics::T] + std::collections::LinkedList as [generics::T] + + // Unique sets + std::collections::HashSet as PrimitiveSet + std::collections::BTreeSet as PrimitiveSet + + // Maps + std::collections::HashMap as PrimitiveMap + std::collections::BTreeMap as PrimitiveMap + + // Containers + std::boxed::Box where { T: ?Sized } as generics::T + std::rc::Rc where { T: ?Sized } as generics::T + std::sync::Arc where { T: ?Sized } as generics::T + std::cell::Cell where { T: ?Sized } as generics::T + std::cell::RefCell where { T: ?Sized } as generics::T + + std::sync::Mutex where { T: ?Sized } as generics::T + std::sync::RwLock where { T: ?Sized } as generics::T + + std::ffi::CString as str + std::ffi::CStr as str + std::ffi::OsString as str + std::ffi::OsStr as str + + std::path::Path as str + std::path::PathBuf as str + + std::net::IpAddr as str + std::net::Ipv4Addr as str + std::net::Ipv6Addr as str + + std::net::SocketAddr as str + std::net::SocketAddrV4 as str + std::net::SocketAddrV6 as str + + std::sync::atomic::AtomicBool as bool + std::sync::atomic::AtomicI8 as i8 + std::sync::atomic::AtomicI16 as i16 + std::sync::atomic::AtomicI32 as i32 + std::sync::atomic::AtomicIsize as isize + std::sync::atomic::AtomicU8 as u8 + std::sync::atomic::AtomicU16 as u16 + std::sync::atomic::AtomicU32 as u32 + std::sync::atomic::AtomicUsize as usize + std::sync::atomic::AtomicI64 as i64 + std::sync::atomic::AtomicU64 as u64 + + std::num::NonZeroU8 as u8 + std::num::NonZeroU16 as u16 + std::num::NonZeroU32 as u32 + std::num::NonZeroU64 as u64 + std::num::NonZeroUsize as usize + std::num::NonZeroI8 as i8 + std::num::NonZeroI16 as i16 + std::num::NonZeroI32 as i32 + std::num::NonZeroI64 as i64 + std::num::NonZeroIsize as isize + std::num::NonZeroU128 as u128 + std::num::NonZeroI128 as i128 + + // Serde are cringe so this is how it is :( + std::ops::Range as BaseRange + std::ops::RangeInclusive as BaseRange + ); - impl<'a> Type for Cow<'a, str> { - impl_passthrough!(String); - } + impl_ndt!( + impl Type for std::convert::Infallible { + inline: true; + build: |_types, ndt| { + // Serde does no support `Infallible` as it can't be constructed as a `&self` method is uncallable on it. + ndt.inner = DataType::Enum(Enum::default()); + } + } - impl_as!( - CString as String - CStr as String - OsString as String - OsStr as String - - Path as String - PathBuf as String - - IpAddr as String - Ipv4Addr as String - Ipv6Addr as String - - SocketAddr as String - SocketAddrV4 as String - SocketAddrV6 as String - - AtomicBool as bool - AtomicI8 as i8 - AtomicI16 as i16 - AtomicI32 as i32 - AtomicIsize as isize - AtomicU8 as u8 - AtomicU16 as u16 - AtomicU32 as u32 - AtomicUsize as usize - AtomicI64 as i64 - AtomicU64 as u64 - - NonZeroU8 as u8 - NonZeroU16 as u16 - NonZeroU32 as u32 - NonZeroU64 as u64 - NonZeroUsize as usize - NonZeroI8 as i8 - NonZeroI16 as i16 - NonZeroI32 as i32 - NonZeroI64 as i64 - NonZeroIsize as isize - NonZeroU128 as u128 - NonZeroI128 as i128 - ); + impl Type for std::time::SystemTime { + inline: true; + build: |types, ndt| { + let mut s = crate::datatype::Struct::unit(); + s.set_fields(internal::construct::fields_named( + vec![ + ( + "duration_since_epoch".into(), + Field::new(::definition(types)), + ), + ( + "duration_since_unix_epoch".into(), + Field::new(::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } + } - impl_for_list!( - false; Vec - false; VecDeque - false; BinaryHeap - false; LinkedList - true; HashSet - true; BTreeSet + impl Type for std::time::Duration { + inline: true; + build: |types, ndt| { + let mut s = crate::datatype::Struct::unit(); + s.set_fields(internal::construct::fields_named( + vec![ + ( + "secs".into(), + Field::new(::definition(types)), + ), + ( + "nanos".into(), + Field::new(::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } + } ); - impl_for_map!(HashMap); - impl_for_map!(BTreeMap); - - // Serde does no support `Infallible` as it can't be constructed as a `&self` method is uncallable on it. - impl Type for Infallible { - fn definition(_: &mut TypeCollection) -> DataType { - DataType::Enum(Enum::default()) + impl<'a, T: ?Sized + ToOwned + Type + 'a> Type for std::borrow::Cow<'a, T> { + fn definition(types: &mut TypeCollection) -> DataType { + use std::borrow::Cow; + + use crate::datatype::GenericReference; + + // This API is internal. Use [NamedDataType::register] if you want a custom implementation. + static SENTINEL: &str = "std::borrow::Cow<'a, T>"; + static GENERICS: &[(GenericReference, Cow<'static, str>)] = &[( + datatype::GenericReference::new::(), + std::borrow::Cow::Borrowed("T"), + )]; + + DataType::Reference(datatype::NamedDataType::init_with_sentinel( + GENERICS, + vec![( + datatype::GenericReference::new::(), + ::definition(types), + )], + true, + types, + SENTINEL, + |_types, ndt| { + *ndt.name_mut() = std::borrow::Cow::Borrowed("Cow"); + *ndt.module_path_mut() = std::borrow::Cow::Borrowed("std::borrow"); + ndt.inner = datatype::GenericReference::new::().into(); + }, + )) } } - impl Type for Range { + struct BaseRange(PhantomData); + impl Type for BaseRange { fn definition(types: &mut TypeCollection) -> DataType { let ty = T::definition(types); let mut s = crate::datatype::Struct::unit(); @@ -150,75 +223,28 @@ const _: () = { ], vec![], )); - DataType::Struct(s) - } - } - - impl Type for RangeInclusive { - impl_passthrough!(Range); // Yeah Serde are cringe - } - - impl Type for SystemTime { - fn definition(types: &mut TypeCollection) -> DataType { - let mut s = crate::datatype::Struct::unit(); - s.set_fields(internal::construct::fields_named( - vec![ - ( - "duration_since_epoch".into(), - Field::new(::definition(types)), - ), - ( - "duration_since_unix_epoch".into(), - Field::new(::definition(types)), - ), - ], - vec![], - )); - DataType::Struct(s) - } - } - impl Type for Duration { - fn definition(types: &mut TypeCollection) -> DataType { - let mut s = crate::datatype::Struct::unit(); - s.set_fields(internal::construct::fields_named( - vec![ - ( - "secs".into(), - Field::new(::definition(types)), - ), - ( - "nanos".into(), - Field::new(::definition(types)), - ), - ], - vec![], - )); DataType::Struct(s) } } }; #[cfg(feature = "tokio")] -const _: () = { - use tokio::sync::{Mutex, RwLock}; - impl_containers!(Mutex RwLock); -}; - -impl Type for &str { - impl_passthrough!(String); -} +impl_ndt_as!( + tokio::sync::Mutex where { T: ?Sized } as generics::T + tokio::sync::RwLock where { T: ?Sized } as generics::T +); -impl Type for &T { +impl Type for &T { impl_passthrough!(T); } impl Type for [T] { - impl_passthrough!(Vec); -} - -impl Type for &[T] { - impl_passthrough!(Vec); + fn definition(types: &mut TypeCollection) -> DataType { + let mut l = List::new(::definition(types)); + l.set_unique(false); + DataType::List(l) + } } impl Type for [T; N] { @@ -235,9 +261,32 @@ impl Type for Option { } } -impl Type for std::marker::PhantomData { - fn definition(types: &mut TypeCollection) -> DataType { - // TODO: Does this hold up for non-Typescript languages -> This should probs be a named type so the exporter can modify it. - <() as Type>::definition(types) +impl_ndt_as!( + std::marker::PhantomData as () +); + +impl_ndt!( + impl Type for std::result::Result where { T: Type, E: Type } { + inline: true; + build: |types, ndt| { + let mut ok_variant = EnumVariant::unit(); + ok_variant.set_fields(internal::construct::fields_unnamed( + vec![Field::new( + datatype::GenericReference::new::().into(), + )], + vec![], + )); + let mut err_variant = EnumVariant::unit(); + err_variant.set_fields(internal::construct::fields_unnamed( + vec![Field::new( + datatype::GenericReference::new::().into(), + )], + vec![], + )); + ndt.inner = DataType::Enum(Enum { + variants: vec![("Ok".into(), ok_variant), ("Err".into(), err_variant)], + attributes: vec![], + }); + } } -} +); diff --git a/specta/src/type/legacy_impls.rs b/specta/src/type/legacy_impls.rs index be28d35e..3e7de1cb 100644 --- a/specta/src/type/legacy_impls.rs +++ b/specta/src/type/legacy_impls.rs @@ -1,840 +1,757 @@ #![allow(unused)] //! The plan is to try and move these into the ecosystem for the v2 release. -use super::macros::*; -use crate::{Type, TypeCollection, datatype::*}; +use super::macros::{impl_ndt, impl_ndt_as}; +use crate::{ + Type, TypeCollection, + datatype::{ + self, Attribute, AttributeMeta, AttributeNestedMeta, DataType, Enum, EnumVariant, Field, + Fields, NamedFields, Primitive, Reference, Struct, + }, + r#type::{generics, impls::*}, +}; use std::borrow::Cow; #[cfg(feature = "indexmap")] -const _: () = { - impl_for_list!(true; indexmap::IndexSet); - impl_for_map!(indexmap::IndexMap); -}; +impl_ndt_as!( + indexmap::IndexSet as PrimitiveSet + indexmap::IndexMap as PrimitiveMap +); #[cfg(feature = "bytes")] -const _: () = { - use bytes::{Bytes, BytesMut}; - - impl_as!( - Bytes as Vec - BytesMut as Vec - ); -}; +impl_ndt_as!( + bytes::Bytes as [u8] + bytes::BytesMut as [u8] +); #[cfg(feature = "serde_json")] const _: () = { use serde_json::{Map, Number, Value}; - impl_for_map!(Map); + impl_ndt_as!( + serde_json::Map as PrimitiveMap + ); - impl Type for Value { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ("Null".into(), EnumVariant::unit()), - ( - "Bool".into(), - EnumVariant::unnamed() - .field(Field::new(bool::definition(types))) - .build(), - ), - ( - "Number".into(), - EnumVariant::unnamed() - .field(Field::new(Number::definition(types))) - .build(), - ), - ( - "String".into(), - EnumVariant::unnamed() - .field(Field::new(String::definition(types))) - .build(), - ), - ( - "Array".into(), - EnumVariant::unnamed() - .field(Field::new(Vec::::definition(types))) - .build(), - ), - ( - "Object".into(), - EnumVariant::unnamed() - .field(Field::new(Map::::definition(types))) - .build(), - ), - ], - attributes: vec![], - }) + impl_ndt!( + impl Type for serde_json::Value { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Null".into(), EnumVariant::unit()), + ( + "Bool".into(), + EnumVariant::unnamed() + .field(Field::new(bool::definition(types))) + .build(), + ), + ( + "Number".into(), + EnumVariant::unnamed() + .field(Field::new(Number::definition(types))) + .build(), + ), + ( + "String".into(), + EnumVariant::unnamed() + .field(Field::new(String::definition(types))) + .build(), + ), + ( + "Array".into(), + EnumVariant::unnamed() + .field(Field::new(Vec::::definition(types))) + .build(), + ), + ( + "Object".into(), + EnumVariant::unnamed() + .field(Field::new(Map::::definition(types))) + .build(), + ), + ], + attributes: vec![], + }) + } } - } - impl Type for Number { - fn definition(_: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ( - "f64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::f64))) - .build(), - ), - ( - "i64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::i64))) - .build(), - ), - ( - "u64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::u64))) - .build(), - ), - ], - attributes: vec![Attribute { - path: String::from("serde"), - kind: AttributeMeta::List(vec![AttributeNestedMeta::Meta(AttributeMeta::Path( - String::from("untagged"), - ))]), - }], - }) + impl Type for serde_json::Number { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ( + "f64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::f64))) + .build(), + ), + ( + "i64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::i64))) + .build(), + ), + ( + "u64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::u64))) + .build(), + ), + ], + attributes: vec![Attribute { + path: String::from("serde"), + kind: AttributeMeta::List(vec![AttributeNestedMeta::Meta(AttributeMeta::Path( + String::from("untagged"), + ))]), + }], + }); + } } - } + ); }; #[cfg(feature = "serde_yaml")] const _: () = { use serde_yaml::{Number, Value, value::TaggedValue}; - impl Type for serde_yaml::Mapping { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Map(crate::datatype::Map::new( - serde_yaml::Value::definition(types), - serde_yaml::Value::definition(types), - )) - } - } - - impl Type for serde_yaml::value::TaggedValue { - fn definition(types: &mut TypeCollection) -> DataType { - std::collections::HashMap::::definition(types) - } - } + impl_ndt_as!( + serde_yaml::Mapping as PrimitiveMap + serde_yaml::value::TaggedValue as PrimitiveMap + ); - impl Type for Value { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ("Null".into(), EnumVariant::unit()), - ( - "Bool".into(), - EnumVariant::unnamed() - .field(Field::new(bool::definition(types))) - .build(), - ), - ( - "Number".into(), - EnumVariant::unnamed() - .field(Field::new(Number::definition(types))) - .build(), - ), - ( - "String".into(), - EnumVariant::unnamed() - .field(Field::new(String::definition(types))) - .build(), - ), - ( - "Sequence".into(), - EnumVariant::unnamed() - .field(Field::new(Vec::::definition(types))) - .build(), - ), - ( - "Mapping".into(), - EnumVariant::unnamed() - .field(Field::new(std::collections::BTreeMap::< - serde_yaml::Value, - serde_yaml::Value, - >::definition(types))) - .build(), - ), - ( - "Tagged".into(), - EnumVariant::unnamed() - .field(Field::new(Box::::definition(types))) - .build(), - ), - ], - attributes: vec![], - }) + impl_ndt!( + impl Type for serde_yaml::Value { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Null".into(), EnumVariant::unit()), + ( + "Bool".into(), + EnumVariant::unnamed() + .field(Field::new(bool::definition(types))) + .build(), + ), + ( + "Number".into(), + EnumVariant::unnamed() + .field(Field::new(Number::definition(types))) + .build(), + ), + ( + "String".into(), + EnumVariant::unnamed() + .field(Field::new(String::definition(types))) + .build(), + ), + ( + "Sequence".into(), + EnumVariant::unnamed() + .field(Field::new(Vec::::definition(types))) + .build(), + ), + ( + "Mapping".into(), + EnumVariant::unnamed() + .field(Field::new(std::collections::BTreeMap::< + serde_yaml::Value, + serde_yaml::Value, + >::definition(types))) + .build(), + ), + ( + "Tagged".into(), + EnumVariant::unnamed() + .field(Field::new(Box::::definition(types))) + .build(), + ), + ], + attributes: vec![], + }) + } } - } - impl Type for serde_yaml::Number { - fn definition(_: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ( - "f64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::f64))) - .build(), - ), - ( - "i64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::i64))) - .build(), - ), - ( - "u64".into(), - EnumVariant::unnamed() - .field(Field::new(DataType::Primitive(Primitive::u64))) - .build(), - ), - ], - attributes: vec![], - }) + impl Type for serde_yaml::Number { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ( + "f64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::f64))) + .build(), + ), + ( + "i64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::i64))) + .build(), + ), + ( + "u64".into(), + EnumVariant::unnamed() + .field(Field::new(DataType::Primitive(Primitive::u64))) + .build(), + ), + ], + attributes: vec![], + }) + } } - } + ); }; #[cfg(feature = "toml")] const _: () = { use toml::{Value, value}; - impl_for_map!(toml::map::Map); - - impl Type for value::Datetime { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![( - "v".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(String::definition(types)), - attributes: Vec::new(), - }, - )], + impl_ndt_as!(toml::map::Map as PrimitiveMap); + + impl_ndt!( + impl Type for toml::value::Datetime { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Struct(Struct { + fields: Fields::Named(NamedFields { + fields: vec![( + "v".into(), + Field { + optional: false, + + inline: false, + deprecated: None, + docs: Cow::Borrowed(""), + ty: Some(String::definition(types)), + attributes: Vec::new(), + }, + )], + attributes: Vec::new(), + }), attributes: Vec::new(), - }), - attributes: Vec::new(), - }) + }) + } } - } - impl Type for Value { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ( - "String".into(), - EnumVariant::unnamed() - .field(Field::new(String::definition(types))) - .build(), - ), - ( - "Integer".into(), - EnumVariant::unnamed() - .field(Field::new(i64::definition(types))) - .build(), - ), - ( - "Float".into(), - EnumVariant::unnamed() - .field(Field::new(f64::definition(types))) - .build(), - ), - ( - "Boolean".into(), - EnumVariant::unnamed() - .field(Field::new(bool::definition(types))) - .build(), - ), - ( - "Datetime".into(), - EnumVariant::unnamed() - .field(Field::new(value::Datetime::definition(types))) - .build(), - ), - ( - "Array".into(), - EnumVariant::unnamed() - .field(Field::new(Vec::::definition(types))) - .build(), - ), - ( - "Table".into(), - EnumVariant::unnamed() - .field(Field::new( - std::collections::BTreeMap::::definition(types), - )) - .build(), - ), - ], - attributes: vec![], - }) + impl Type for toml::Value { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ( + "String".into(), + EnumVariant::unnamed() + .field(Field::new(String::definition(types))) + .build(), + ), + ( + "Integer".into(), + EnumVariant::unnamed() + .field(Field::new(i64::definition(types))) + .build(), + ), + ( + "Float".into(), + EnumVariant::unnamed() + .field(Field::new(f64::definition(types))) + .build(), + ), + ( + "Boolean".into(), + EnumVariant::unnamed() + .field(Field::new(bool::definition(types))) + .build(), + ), + ( + "Datetime".into(), + EnumVariant::unnamed() + .field(Field::new(value::Datetime::definition(types))) + .build(), + ), + ( + "Array".into(), + EnumVariant::unnamed() + .field(Field::new(Vec::::definition(types))) + .build(), + ), + ( + "Table".into(), + EnumVariant::unnamed() + .field(Field::new( + std::collections::BTreeMap::::definition(types), + )) + .build(), + ), + ], + attributes: vec![], + }) + } } - } + ); }; #[cfg(feature = "ulid")] -impl_as!(ulid::Ulid as String); +impl_ndt_as!(ulid::Ulid as str); #[cfg(feature = "uuid")] -impl_as!( - uuid::Uuid as String - uuid::fmt::Hyphenated as String +impl_ndt_as!( + uuid::Uuid as str + uuid::fmt::Hyphenated as str ); #[cfg(feature = "chrono")] +#[allow(deprecated)] const _: () = { - use chrono::*; - - impl_as!( - NaiveDateTime as String - NaiveDate as String - NaiveTime as String - chrono::Duration as String + impl_ndt_as!( + chrono::NaiveDateTime as str + chrono::NaiveDate as str + chrono::NaiveTime as str + chrono::Duration as str ); - impl Type for DateTime { - impl_passthrough!(String); + // This is special cause of how it ignores the `generics` param to `NamedDataType::init_with_sentinel` + // These needs generics which also aren't `Type` & aren't in `References` param so `impl_ndt` doesn't work. + macro_rules! impl_as_str { + ($module:ident :: $type_name:ident) => { + fn definition(types: &mut TypeCollection) -> DataType { + // This API is internal. Use [NamedDataType::register] if you want a custom implementation. + static SENTINEL: &str = stringify!($module::$type_name); + static GENERICS: &[(datatype::GenericReference, Cow<'static, str>)] = &[]; + DataType::Reference(datatype::NamedDataType::init_with_sentinel( + GENERICS, + vec![], + true, + types, + SENTINEL, + |types, ndt| { + ndt.set_name(::std::borrow::Cow::Borrowed(stringify!($type_name))); + ndt.set_module_path(::std::borrow::Cow::Borrowed(stringify!($module))); + ndt.inner = str::definition(types); + }, + )) + } + }; } - #[allow(deprecated)] - impl Type for Date { - impl_passthrough!(String); + impl Type for chrono::Date { + impl_as_str!(chrono::Date); + } + impl Type for chrono::DateTime { + impl_as_str!(chrono::DateTime); } }; #[cfg(feature = "time")] -impl_as!( - time::PrimitiveDateTime as String - time::OffsetDateTime as String - time::Date as String - time::Time as String - time::Duration as String - time::Weekday as String +impl_ndt_as!( + time::PrimitiveDateTime as str + time::OffsetDateTime as str + time::Date as str + time::Time as str + time::Duration as str + time::Weekday as str ); #[cfg(feature = "jiff")] -impl_as!( - jiff::Timestamp as String - jiff::Zoned as String - jiff::Span as String - jiff::civil::Date as String - jiff::civil::Time as String - jiff::civil::DateTime as String - jiff::tz::TimeZone as String +impl_ndt_as!( + jiff::Timestamp as str + jiff::Zoned as str + jiff::Span as str + jiff::civil::Date as str + jiff::civil::Time as str + jiff::civil::DateTime as str + jiff::tz::TimeZone as str ); #[cfg(feature = "bigdecimal")] -impl_as!(bigdecimal::BigDecimal as String); +impl_ndt_as!(bigdecimal::BigDecimal as str); // This assumes the `serde-with-str` feature is enabled. Check #26 for more info. #[cfg(feature = "rust_decimal")] -impl_as!(rust_decimal::Decimal as String); +impl_ndt_as!(rust_decimal::Decimal as str); #[cfg(feature = "ipnetwork")] -impl_as!( - ipnetwork::IpNetwork as String - ipnetwork::Ipv4Network as String - ipnetwork::Ipv6Network as String +impl_ndt_as!( + ipnetwork::IpNetwork as str + ipnetwork::Ipv4Network as str + ipnetwork::Ipv6Network as str ); #[cfg(feature = "mac_address")] -impl_as!(mac_address::MacAddress as String); +impl_ndt_as!(mac_address::MacAddress as str); #[cfg(feature = "chrono")] -impl_as!( - chrono::FixedOffset as String - chrono::Utc as String - chrono::Local as String +impl_ndt_as!( + chrono::FixedOffset as str + chrono::Utc as str + chrono::Local as str ); #[cfg(feature = "bson")] -impl_as!( - bson::oid::ObjectId as String +impl_ndt_as!( + bson::oid::ObjectId as str bson::Decimal128 as i128 - bson::DateTime as String - bson::Uuid as String + bson::DateTime as str + bson::Uuid as str ); // TODO: bson::bson // TODO: bson::Document #[cfg(feature = "bytesize")] -impl_as!(bytesize::ByteSize as u64); +impl_ndt_as!(bytesize::ByteSize as u64); #[cfg(feature = "uhlc")] const _: () = { - use std::num::NonZeroU128; - - use uhlc::*; - - impl_as!( - NTP64 as u64 - ID as NonZeroU128 + impl_ndt_as!( + uhlc::NTP64 as u64 + uhlc::ID as std::num::NonZeroU128 ); - impl Type for Timestamp { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![ - ( - "time".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(NTP64::definition(types)), - attributes: Vec::new(), - }, - ), - ( - "id".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(ID::definition(types)), - attributes: Vec::new(), - }, - ), - ], + impl_ndt!( + impl Type for uhlc::Timestamp { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Struct(Struct { + fields: Fields::Named(NamedFields { + fields: vec![ + ( + "time".into(), + Field { + optional: false, + + inline: false, + deprecated: None, + docs: Cow::Borrowed(""), + ty: Some(uhlc::NTP64::definition(types)), + attributes: Vec::new(), + }, + ), + ( + "id".into(), + Field { + optional: false, + + inline: false, + deprecated: None, + docs: Cow::Borrowed(""), + ty: Some(uhlc::ID::definition(types)), + attributes: Vec::new(), + }, + ), + ], + attributes: Vec::new(), + }), attributes: Vec::new(), - }), - attributes: Vec::new(), - }) + }); + } } - } + ); }; #[cfg(feature = "glam")] -const _: () = { - macro_rules! implement_specta_type_for_glam_type { - ( - $name: ident as $representation: ty - ) => { - impl Type for glam::$name { - fn definition(types: &mut TypeCollection) -> DataType { - <$representation>::definition(types) - } - } - }; - } - - // Implementations for https://docs.rs/glam/latest/glam/f32/index.html +impl_ndt_as!( // Affines - implement_specta_type_for_glam_type!(Affine2 as [f32; 6]); - implement_specta_type_for_glam_type!(Affine3A as [f32; 12]); + glam::Affine2 as [f32; 6] + glam::Affine3A as [f32; 12] // Matrices - implement_specta_type_for_glam_type!(Mat2 as [f32; 4]); - implement_specta_type_for_glam_type!(Mat3 as [f32; 9]); - implement_specta_type_for_glam_type!(Mat3A as [f32; 9]); - implement_specta_type_for_glam_type!(Mat4 as [f32; 16]); + glam::Mat2 as [f32; 4] + glam::Mat3 as [f32; 9] + glam::Mat3A as [f32; 9] + glam::Mat4 as [f32; 16] // Quaternions - implement_specta_type_for_glam_type!(Quat as [f32; 4]); + glam::Quat as [f32; 4] // Vectors - implement_specta_type_for_glam_type!(Vec2 as [f32; 2]); - implement_specta_type_for_glam_type!(Vec3 as [f32; 3]); - implement_specta_type_for_glam_type!(Vec3A as [f32; 3]); - implement_specta_type_for_glam_type!(Vec4 as [f32; 4]); + glam::Vec2 as [f32; 2] + glam::Vec3 as [f32; 3] + glam::Vec3A as [f32; 3] + glam::Vec4 as [f32; 4] - // Implementations for https://docs.rs/glam/latest/glam/f64/index.html // Affines - implement_specta_type_for_glam_type!(DAffine2 as [f64; 6]); - implement_specta_type_for_glam_type!(DAffine3 as [f64; 12]); + glam::DAffine2 as [f64; 6] + glam::DAffine3 as [f64; 12] // Matrices - implement_specta_type_for_glam_type!(DMat2 as [f64; 4]); - implement_specta_type_for_glam_type!(DMat3 as [f64; 9]); - implement_specta_type_for_glam_type!(DMat4 as [f64; 16]); + glam::DMat2 as [f64; 4] + glam::DMat3 as [f64; 9] + glam::DMat4 as [f64; 16] // Quaternions - implement_specta_type_for_glam_type!(DQuat as [f64; 4]); + glam::DQuat as [f64; 4] // Vectors - implement_specta_type_for_glam_type!(DVec2 as [f64; 2]); - implement_specta_type_for_glam_type!(DVec3 as [f64; 3]); - implement_specta_type_for_glam_type!(DVec4 as [f64; 4]); + glam::DVec2 as [f64; 2] + glam::DVec3 as [f64; 3] + glam::DVec4 as [f64; 4] // Implementations for https://docs.rs/glam/latest/glam/i8/index.html - implement_specta_type_for_glam_type!(I8Vec2 as [i8; 2]); - implement_specta_type_for_glam_type!(I8Vec3 as [i8; 3]); - implement_specta_type_for_glam_type!(I8Vec4 as [i8; 4]); + glam::I8Vec2 as [i8; 2] + glam::I8Vec3 as [i8; 3] + glam::I8Vec4 as [i8; 4] // Implementations for https://docs.rs/glam/latest/glam/u8/index.html - implement_specta_type_for_glam_type!(U8Vec2 as [u8; 2]); - implement_specta_type_for_glam_type!(U8Vec3 as [u8; 3]); - implement_specta_type_for_glam_type!(U8Vec4 as [u8; 4]); + glam::U8Vec2 as [u8; 2] + glam::U8Vec3 as [u8; 3] + glam::U8Vec4 as [u8; 4] // Implementations for https://docs.rs/glam/latest/glam/i16/index.html - implement_specta_type_for_glam_type!(I16Vec2 as [i16; 2]); - implement_specta_type_for_glam_type!(I16Vec3 as [i16; 3]); - implement_specta_type_for_glam_type!(I16Vec4 as [i16; 4]); + glam::I16Vec2 as [i16; 2] + glam::I16Vec3 as [i16; 3] + glam::I16Vec4 as [i16; 4] // Implementations for https://docs.rs/glam/latest/glam/u16/index.html - implement_specta_type_for_glam_type!(U16Vec2 as [u16; 2]); - implement_specta_type_for_glam_type!(U16Vec3 as [u16; 3]); - implement_specta_type_for_glam_type!(U16Vec4 as [u16; 4]); + glam::U16Vec2 as [u16; 2] + glam::U16Vec3 as [u16; 3] + glam::U16Vec4 as [u16; 4] // Implementations for https://docs.rs/glam/latest/glam/i32/index.html - implement_specta_type_for_glam_type!(IVec2 as [i32; 2]); - implement_specta_type_for_glam_type!(IVec3 as [i32; 3]); - implement_specta_type_for_glam_type!(IVec4 as [i32; 4]); + glam::IVec2 as [i32; 2] + glam::IVec3 as [i32; 3] + glam::IVec4 as [i32; 4] // Implementations for https://docs.rs/glam/latest/glam/u32/index.html - implement_specta_type_for_glam_type!(UVec2 as [u32; 2]); - implement_specta_type_for_glam_type!(UVec3 as [u32; 3]); - implement_specta_type_for_glam_type!(UVec4 as [u32; 4]); + glam::UVec2 as [u32; 2] + glam::UVec3 as [u32; 3] + glam::UVec4 as [u32; 4] // Implementation for https://docs.rs/glam/latest/glam/i64/index.html - implement_specta_type_for_glam_type!(I64Vec2 as [i64; 2]); - implement_specta_type_for_glam_type!(I64Vec3 as [i64; 3]); - implement_specta_type_for_glam_type!(I64Vec4 as [i64; 4]); + glam::I64Vec2 as [i64; 2] + glam::I64Vec3 as [i64; 3] + glam::I64Vec4 as [i64; 4] // Implementation for https://docs.rs/glam/latest/glam/u64/index.html - implement_specta_type_for_glam_type!(U64Vec2 as [u64; 2]); - implement_specta_type_for_glam_type!(U64Vec3 as [u64; 3]); - implement_specta_type_for_glam_type!(U64Vec4 as [u64; 4]); + glam::U64Vec2 as [u64; 2] + glam::U64Vec3 as [u64; 3] + glam::U64Vec4 as [u64; 4] // implementation for https://docs.rs/glam/latest/glam/usize/index.html - implement_specta_type_for_glam_type!(USizeVec2 as [usize; 2]); - implement_specta_type_for_glam_type!(USizeVec3 as [usize; 3]); - implement_specta_type_for_glam_type!(USizeVec4 as [usize; 4]); + glam::USizeVec2 as [usize; 2] + glam::USizeVec3 as [usize; 3] + glam::USizeVec4 as [usize; 4] // Implementation for https://docs.rs/glam/latest/glam/bool/index.html - implement_specta_type_for_glam_type!(BVec2 as [bool; 2]); - implement_specta_type_for_glam_type!(BVec3 as [bool; 3]); - implement_specta_type_for_glam_type!(BVec4 as [bool; 4]); -}; + glam::BVec2 as [bool; 2] + glam::BVec3 as [bool; 3] + glam::BVec4 as [bool; 4] +); #[cfg(feature = "url")] -impl_as!(url::Url as String); +impl_ndt_as!(url::Url as str); #[cfg(feature = "either")] -impl Type for either::Either { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ( - "Left".into(), - EnumVariant::unnamed() - .field(Field::new(L::definition(types))) - .build(), - ), - ( - "Right".into(), - EnumVariant::unnamed() - .field(Field::new(R::definition(types))) - .build(), - ), - ], - attributes: vec![], - }) +impl_ndt!( + impl Type for either::Either where { L: Type, R: Type } { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ( + "Left".into(), + EnumVariant::unnamed() + .field(Field::new( + datatype::GenericReference::new::().into(), + )) + .build(), + ), + ( + "Right".into(), + EnumVariant::unnamed() + .field(Field::new( + datatype::GenericReference::new::().into(), + )) + .build(), + ), + ], + attributes: vec![], + }); + } } -} +); #[cfg(feature = "bevy_ecs")] -const _: () = { +impl_ndt!( impl Type for bevy_ecs::entity::Entity { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Unnamed(UnnamedFields { - fields: vec![Field { - optional: false, - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(u64::definition(types)), - attributes: Vec::new(), - }], - attributes: Vec::new(), - }), - attributes: Vec::new(), - }) + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_unnamed( + vec![Field::new(u64::definition(types))], + vec![], + )); + + ndt.inner = DataType::Struct(s); } } -}; +); #[cfg(feature = "bevy_input")] const _: () = { - impl Type for bevy_input::ButtonState { - fn definition(_: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ("Pressed".into(), EnumVariant::unit()), - ("Released".into(), EnumVariant::unit()), - ], - attributes: vec![], - }) + // Reduced KeyCode and Key to str to avoid redefining a quite large enum (for now) + impl_ndt_as!( + bevy_input::keyboard::KeyCode as str + bevy_input::keyboard::Key as str + ); + + impl_ndt!( + impl Type for bevy_input::ButtonState { + inline: true; + build: |_types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Pressed".into(), EnumVariant::unit()), + ("Released".into(), EnumVariant::unit()), + ], + attributes: vec![], + }); + } } - } - impl Type for bevy_input::keyboard::KeyboardInput { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![ + impl Type for bevy_input::keyboard::KeyboardInput { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ ( "key_code".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::keyboard::KeyCode::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_input::keyboard::KeyCode::definition(types)), ), ( "logical_key".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::keyboard::Key::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_input::keyboard::Key::definition(types)), ), ( "state".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::ButtonState::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_input::ButtonState::definition(types)), ), ( "window".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_ecs::entity::Entity::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_ecs::entity::Entity::definition(types)), ), ], - attributes: Vec::new(), - }), - attributes: Vec::new(), - }) - } - } + vec![], + )); - // Reduced KeyCode and Key to String to avoid redefining a quite large enum (for now) - impl_as!( - bevy_input::keyboard::KeyCode as String - bevy_input::keyboard::Key as String - ); + ndt.inner = DataType::Struct(s); + } + } - impl Type for bevy_input::mouse::MouseButtonInput { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![ + impl Type for bevy_input::mouse::MouseButtonInput { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ ( "button".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::mouse::MouseButton::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_input::mouse::MouseButton::definition(types)), ), ( "state".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::ButtonState::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_input::ButtonState::definition(types)), ), ( "window".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_ecs::entity::Entity::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_ecs::entity::Entity::definition(types)), ), ], - attributes: Vec::new(), - }), - attributes: Vec::new(), - }) - } - } + vec![], + )); - impl Type for bevy_input::mouse::MouseButton { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ("Left".into(), EnumVariant::unit()), - ("Right".into(), EnumVariant::unit()), - ("Middle".into(), EnumVariant::unit()), - ("Back".into(), EnumVariant::unit()), - ("Forward".into(), EnumVariant::unit()), - ( - "Other".into(), - EnumVariant::unnamed() - .field(Field::new(u16::definition(types))) - .build(), - ), - ], - attributes: vec![], - }) + ndt.inner = DataType::Struct(s); + } } - } - impl Type for bevy_input::mouse::MouseWheel { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![ + impl Type for bevy_input::mouse::MouseButton { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Left".into(), EnumVariant::unit()), + ("Right".into(), EnumVariant::unit()), + ("Middle".into(), EnumVariant::unit()), + ("Back".into(), EnumVariant::unit()), + ("Forward".into(), EnumVariant::unit()), ( - "unit".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_input::mouse::MouseScrollUnit::definition(types)), - attributes: Vec::new(), - }, + "Other".into(), + EnumVariant::unnamed() + .field(Field::new(u16::definition(types))) + .build(), ), - ( - "x".into(), - Field { - optional: false, + ], + attributes: vec![], + }); + } + } - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(f32::definition(types)), - attributes: Vec::new(), - }, - ), + impl Type for bevy_input::mouse::MouseWheel { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ ( - "y".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(f32::definition(types)), - attributes: Vec::new(), - }, + "unit".into(), + Field::new(bevy_input::mouse::MouseScrollUnit::definition(types)), ), + ("x".into(), Field::new(f32::definition(types))), + ("y".into(), Field::new(f32::definition(types))), ( "window".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(bevy_ecs::entity::Entity::definition(types)), - attributes: Vec::new(), - }, + Field::new(bevy_ecs::entity::Entity::definition(types)), ), ], - attributes: Vec::new(), - }), - attributes: Vec::new(), - }) + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - } - impl Type for bevy_input::mouse::MouseScrollUnit { - fn definition(_: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { - variants: vec![ - ("Line".into(), EnumVariant::unit()), - ("Pixel".into(), EnumVariant::unit()), - ], - attributes: vec![], - }) + impl Type for bevy_input::mouse::MouseScrollUnit { + inline: true; + build: |_types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Line".into(), EnumVariant::unit()), + ("Pixel".into(), EnumVariant::unit()), + ], + attributes: vec![], + }); + } } - } - impl Type for bevy_input::mouse::MouseMotion { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Struct(Struct { - fields: Fields::Named(NamedFields { - fields: vec![( - "delta".into(), - Field { - optional: false, - - inline: false, - deprecated: None, - docs: Cow::Borrowed(""), - ty: Some(glam::Vec2::definition(types)), - attributes: Vec::new(), - }, - )], - attributes: Vec::new(), - }), - attributes: Vec::new(), - }) + impl Type for bevy_input::mouse::MouseMotion { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![("delta".into(), Field::new(glam::Vec2::definition(types)))], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - } + ); }; #[cfg(feature = "camino")] -impl_as!( - camino::Utf8Path as String - camino::Utf8PathBuf as String +impl_ndt_as!( + camino::Utf8Path as str + camino::Utf8PathBuf as str ); #[cfg(feature = "geojson")] -const _: () = { - use geojson::{Feature, FeatureCollection, Geometry, Value}; - - impl Type for Value { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { +impl_ndt!( + impl Type for geojson::Value { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { variants: vec![ ( "Point".into(), @@ -877,7 +794,7 @@ const _: () = { ( "GeometryCollection".into(), EnumVariant::unnamed() - .field(Field::new(Vec::::definition(types))) + .field(Field::new(Vec::::definition(types))) .build(), ), ], @@ -887,47 +804,102 @@ const _: () = { String::from("untagged"), ))]), }], - }) + }); } } - #[derive(Type)] - #[specta(remote = Geometry, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoJsonGeometry { - pub bbox: Option, - pub value: Value, - pub foreign_members: Option, + impl Type for geojson::Geometry { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ( + "bbox".into(), + Field::new(Option::::definition(types)), + ), + ("value".into(), Field::new(geojson::Value::definition(types))), + ( + "foreign_members".into(), + Field::new(Option::::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = Feature, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoJsonFeature { - pub bbox: Option, - pub geometry: Option, - pub id: Option, - pub properties: Option, - pub foreign_members: Option, + impl Type for geojson::Feature { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ( + "bbox".into(), + Field::new(Option::::definition(types)), + ), + ( + "geometry".into(), + Field::new(Option::::definition(types)), + ), + ( + "id".into(), + Field::new(Option::::definition(types)), + ), + ( + "properties".into(), + Field::new(Option::::definition(types)), + ), + ( + "foreign_members".into(), + Field::new(Option::::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = FeatureCollection, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoJsonFeatureCollection { - pub bbox: Option, - pub features: Vec, - pub foreign_members: Option, + impl Type for geojson::FeatureCollection { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ( + "bbox".into(), + Field::new(Option::::definition(types)), + ), + ( + "features".into(), + Field::new(Vec::::definition(types)), + ), + ( + "foreign_members".into(), + Field::new(Option::::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } impl Type for geojson::feature::Id { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Enum(Enum { + inline: true; + build: |types, ndt| { + ndt.inner = DataType::Enum(Enum { variants: vec![ ( "String".into(), EnumVariant::unnamed() - .field(Field::new(String::definition(types))) + .field(Field::new(str::definition(types))) .build(), ), ( @@ -943,64 +915,124 @@ const _: () = { String::from("untagged"), ))]), }], - }) + }); } } -}; +); #[cfg(feature = "geozero")] -const _: () = { - use geozero::mvt::tile; - - #[derive(Type)] - #[specta(remote = geozero::mvt::Tile, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoZeroTile { - pub layers: Vec, +impl_ndt!( + impl Type for geozero::mvt::Tile { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![( + "layers".into(), + Field::new(Vec::::definition(types)), + )], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = tile::Value, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoZeroValue { - pub string_value: Option, - pub float_value: Option, - pub double_value: Option, - pub int_value: Option, - pub uint_value: Option, - pub sint_value: Option, - pub bool_value: Option, + impl Type for geozero::mvt::tile::Value { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ( + "string_value".into(), + Field::new(Option::::definition(types)), + ), + ( + "float_value".into(), + Field::new(Option::::definition(types)), + ), + ( + "double_value".into(), + Field::new(Option::::definition(types)), + ), + ("int_value".into(), Field::new(Option::::definition(types))), + ( + "uint_value".into(), + Field::new(Option::::definition(types)), + ), + ( + "sint_value".into(), + Field::new(Option::::definition(types)), + ), + ( + "bool_value".into(), + Field::new(Option::::definition(types)), + ), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = tile::Feature, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoZeroFeature { - pub id: Option, - pub tags: Vec, - pub r#type: Option, - pub geometry: Vec, + impl Type for geozero::mvt::tile::Feature { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ("id".into(), Field::new(Option::::definition(types))), + ("tags".into(), Field::new(Vec::::definition(types))), + ("type".into(), Field::new(Option::::definition(types))), + ("geometry".into(), Field::new(Vec::::definition(types))), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = tile::Layer, crate = crate, collect = false)] - #[allow(dead_code)] - pub struct GeoZeroLayer { - pub version: u32, - pub name: String, - pub features: Vec, - pub keys: Vec, - pub values: Vec, - pub extent: Option, + impl Type for geozero::mvt::tile::Layer { + inline: true; + build: |types, ndt| { + let mut s = Struct::unit(); + s.set_fields(crate::internal::construct::fields_named( + vec![ + ("version".into(), Field::new(u32::definition(types))), + ("name".into(), Field::new(String::definition(types))), + ( + "features".into(), + Field::new(Vec::::definition(types)), + ), + ("keys".into(), Field::new(Vec::::definition(types))), + ( + "values".into(), + Field::new(Vec::::definition(types)), + ), + ("extent".into(), Field::new(Option::::definition(types))), + ], + vec![], + )); + + ndt.inner = DataType::Struct(s); + } } - #[derive(Type)] - #[specta(remote = tile::GeomType, crate = crate, collect = false)] - #[allow(dead_code)] - pub enum GeoZeroGeomType { - Unknown = 0, - Point = 1, - Linestring = 2, - Polygon = 3, + impl Type for geozero::mvt::tile::GeomType { + inline: true; + build: |_types, ndt| { + ndt.inner = DataType::Enum(Enum { + variants: vec![ + ("Unknown".into(), EnumVariant::unit()), + ("Point".into(), EnumVariant::unit()), + ("Linestring".into(), EnumVariant::unit()), + ("Polygon".into(), EnumVariant::unit()), + ], + attributes: vec![], + }); + } } -}; +); diff --git a/specta/src/type/macros.rs b/specta/src/type/macros.rs index 84211159..3d3b6940 100644 --- a/specta/src/type/macros.rs +++ b/specta/src/type/macros.rs @@ -1,11 +1,3 @@ -macro_rules! _impl_passthrough { - ($t:ty) => { - fn definition(types: &mut TypeCollection) -> DataType { - <$t>::definition(types) - } - }; -} - macro_rules! _impl_primitives { ($($i:ident)+) => {$( impl Type for $i { @@ -34,57 +26,96 @@ macro_rules! _impl_tuple { () => {}; } -macro_rules! _impl_containers { - ($($container:ident)+) => {$( - impl Type for $container { - fn definition(types: &mut TypeCollection) -> DataType { - ::definition(types) - } +macro_rules! _impl_passthrough { + ($t:ty) => { + fn definition(types: &mut TypeCollection) -> DataType { + <$t>::definition(types) } - )+} + }; } -macro_rules! _impl_as { - ($($ty:path as $tty:ty)+) => {$( - impl Type for $ty { - fn definition(types: &mut TypeCollection) -> DataType { - <$tty as Type>::definition(types) - } - } - )+}; +macro_rules! _impl_ndt_as { + ( $($head:ident :: $( $tail:ident )::+ $(<$($generic:ident),*>)? $( where { $($bounds:tt)* } )? as $ty2:ty )* ) => { + impl_ndt!( + $( + impl$(<$($generic),*>)? Type for $head::$( $tail )::+ $(<$($generic),*>)? where { + // `(): Sized` is meaningless and is used to add a base-condition to avoid branching in the macro. + (): Sized $(, $($generic: Type),*)? $(, $($bounds)*)? + } { + inline: true; + build: |types, ndt| { + ndt.inner = <$ty2 as Type>::definition(types); + } + } + )* + ); + }; } -macro_rules! _impl_for_list { - ($($unique:expr; $ty:path)+) => {$( - impl Type for $ty { - fn definition(types: &mut TypeCollection) -> DataType { - let mut l = List::new( - ::definition(types), - ); - l.set_unique($unique); - DataType::List(l) +macro_rules! _impl_ndt { + ( + $( + impl $(<$($generic:ident),*>)? Type for $type_path:path $( where { $($bounds:tt)* } )? { + inline: $inline:expr; + build: |$types:ident, $ndt:ident| $build:block } - } - )+}; -} + )+ + ) => { + $( + impl$(<$($generic),*>)? Type for $type_path $(where $($bounds)*)? { + fn definition(types: &mut TypeCollection) -> DataType { + // This API is internal. Use [NamedDataType::register] if you want a custom implementation. + static SENTINEL: &str = stringify!($type_path); + static GENERICS: &[(datatype::GenericReference, ::std::borrow::Cow<'static, str>)] = &[ + $($( + ( + datatype::GenericReference::new::(), + ::std::borrow::Cow::Borrowed(stringify!($generic)), + ) + ),*)? + ]; + DataType::Reference(datatype::NamedDataType::init_with_sentinel( + GENERICS, + vec![ + $($( + ( + datatype::GenericReference::new::(), + <$generic as Type>::definition(types), + ) + ),*)? + ], + $inline, + types, + SENTINEL, + |$types, $ndt| { + let _ = &$types; -macro_rules! _impl_for_map { - ($ty:path) => { - impl Type for $ty { - fn definition(types: &mut TypeCollection) -> DataType { - DataType::Map(crate::datatype::Map::new( - K::definition(types), - V::definition(types), - )) + // TODO: This should be doable in the macro instead of the runtime. This will do for now though. + let (type_name, module_path) = { + let s = stringify!($type_path); + let cleaned: String = s.chars().filter(|c| !c.is_whitespace()).collect(); + let s = cleaned.split('<').next().unwrap(); + if let Some((path, name)) = s.rsplit_once("::") { + (name.to_string(), path.to_string()) + } else { + (s.to_string(), String::new()) + } + }; + + $ndt.set_name(::std::borrow::Cow::Owned(type_name)); + $ndt.set_module_path(::std::borrow::Cow::Owned(module_path)); + + $build + }, + )) + } } - } + )+ }; } -pub(crate) use _impl_as as impl_as; -pub(crate) use _impl_containers as impl_containers; -pub(crate) use _impl_for_list as impl_for_list; -pub(crate) use _impl_for_map as impl_for_map; +pub(crate) use _impl_ndt as impl_ndt; +pub(crate) use _impl_ndt_as as impl_ndt_as; pub(crate) use _impl_passthrough as impl_passthrough; pub(crate) use _impl_primitives as impl_primitives; pub(crate) use _impl_tuple as impl_tuple; diff --git a/tests/tests/functions.rs b/tests/tests/functions.rs index 0ff99f34..e20f2cd8 100644 --- a/tests/tests/functions.rs +++ b/tests/tests/functions.rs @@ -2,12 +2,19 @@ use std::fmt; use specta::{ Type, TypeCollection, - datatype::{DataType, Function, FunctionReturnType}, + datatype::{DataType, Function}, function::{self, fn_datatype}, specta, }; use specta_typescript::{Typescript, primitives}; +fn render_datatype(ts: &Typescript, types: &TypeCollection, dt: &DataType) -> Option { + match dt { + DataType::Reference(r) => primitives::reference(ts, types, r).ok(), + dt => primitives::inline(ts, types, dt).ok(), + } +} + /// Multiline /// Docs #[specta] @@ -181,25 +188,7 @@ fn test_function_exporting() { ); insta::assert_snapshot!( def.result() - .and_then(|result| match result { - FunctionReturnType::Value(dt) => match dt { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - } - FunctionReturnType::Result(ok, err) => { - let ok_str = match ok { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let err_str = match err { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let mut variants = vec![ok_str, err_str]; - variants.dedup(); - Some(variants.join(" | ")) - } - }) + .and_then(|result| render_datatype(&ts, &types, result)) .as_deref() .unwrap_or("None"), @"number" @@ -239,25 +228,7 @@ fn test_function_exporting() { ); insta::assert_snapshot!( def.result() - .and_then(|result| match result { - FunctionReturnType::Value(dt) => match dt { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - } - FunctionReturnType::Result(ok, err) => { - let ok_str = match ok { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let err_str = match err { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let mut variants = vec![ok_str, err_str]; - variants.dedup(); - Some(variants.join(" | ")) - } - }) + .and_then(|result| render_datatype(&ts, &types, result)) .as_deref() .unwrap_or("None"), @"number" @@ -307,28 +278,10 @@ fn test_function_exporting() { insta::assert_snapshot!(def.args().len(), @"0"); insta::assert_snapshot!( def.result() - .and_then(|result| match result { - FunctionReturnType::Value(dt) => match dt { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - } - FunctionReturnType::Result(ok, err) => { - let ok_str = match ok { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let err_str = match err { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let mut variants = vec![ok_str, err_str]; - variants.dedup(); - Some(variants.join(" | ")) - } - }) + .and_then(|result| render_datatype(&ts, &types, result)) .as_deref() .unwrap_or("None"), - @"number" + @"({ Ok: number }) & { Err?: never } | ({ Err: number }) & { Ok?: never }" ); } @@ -341,28 +294,10 @@ fn test_function_exporting() { insta::assert_snapshot!(def.args().len(), @"0"); insta::assert_snapshot!( def.result() - .and_then(|result| match result { - FunctionReturnType::Value(dt) => match dt { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - } - FunctionReturnType::Result(ok, err) => { - let ok_str = match ok { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let err_str = match err { - DataType::Reference(r) => primitives::reference(&ts, &types, r).ok(), - dt => primitives::inline(&ts, &types, dt).ok(), - }?; - let mut variants = vec![ok_str, err_str]; - variants.dedup(); - Some(variants.join(" | ")) - } - }) + .and_then(|result| render_datatype(&ts, &types, result)) .as_deref() .unwrap_or("None"), - @"string | number" + @"({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }" ); } diff --git a/tests/tests/macro/compile_error.stderr b/tests/tests/macro/compile_error.stderr index 7d481060..c819595c 100644 --- a/tests/tests/macro/compile_error.stderr +++ b/tests/tests/macro/compile_error.stderr @@ -89,17 +89,17 @@ error[E0255]: the name `__specta__fn__testing` is defined multiple times = note: `__specta__fn__testing` must be defined only once in the macro namespace of this module error: cannot find attribute `serde` in this scope - --> tests/macro/compile_error.rs:8:3 - | -8 | #[serde(rename_all = "camelCase123")] - | ^^^^^ - | - = note: `serde` is in scope, but it is a crate, not an attribute + --> tests/macro/compile_error.rs:8:3 + | + 8 | #[serde(rename_all = "camelCase123")] + | ^^^^^ + | + = note: `serde` is in scope, but it is a crate, not an attribute help: `serde` is an attribute that can be used by the derive macros `Deserialize` and `Serialize`, you might be missing a `derive` attribute - | -9 + #[derive(Deserialize, Serialize)] -10| pub enum Demo2 {} - | + | + 9 + #[derive(Deserialize, Serialize)] +10 | pub enum Demo2 {} + | error: cannot find attribute `serde` in this scope --> tests/macro/compile_error.rs:36:7 @@ -197,38 +197,22 @@ error[E0277]: the trait `specta::Type` is not implemented for `dyn std::error::E 15 | pub(crate) cause: Option>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `dyn std::error::Error + Send + Sync` must implement `Type` | - = help: the trait `Type` is not implemented for `dyn std::error::Error + Send + Sync` + = help: the trait `specta::Type` is not implemented for `dyn std::error::Error + Send + Sync` = note: Depending on your use case, this can be fixed in multiple ways: - If your using an type defined in one of your own crates, ensure you have `#[derive(specta::Type)]` on it. - If your using a crate with official Specta support enable the feature flag on the 'specta' crate, refer to the documentation at https://docs.rs/specta/latest/specta/#feature-flags. - If your using an external crate without Specta support, you may need to wrap your type in a new-type wrapper, refer to the examples at https://docs.rs/specta/latest/specta/trait.Type.html - = help: the following other types implement trait `Type`: + = help: the following other types implement trait `specta::Type`: &T - &[T] - &str () (T10, T11, T12, T13) (T11, T12, T13) (T12, T13) (T13,) + (T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) + (T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) and $N others - = note: required for `Box` to implement `Type` - = note: 1 redundant requirement hidden - = note: required for `Option>` to implement `Type` - -error[E0277]: the size for values of type `dyn std::error::Error + Send + Sync` cannot be known at compilation time - --> tests/macro/compile_error.rs:15:23 - | -15 | pub(crate) cause: Option>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` -help: the trait `Type` is implemented for `Option` - --> $WORKSPACE/specta/src/type/impls.rs - | - | impl Type for Option { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: required for `Box` to implement `Type` + = note: required for `Box` to implement `specta::Type` = note: 1 redundant requirement hidden - = note: required for `Option>` to implement `Type` + = note: required for `Option>` to implement `specta::Type` diff --git a/tests/tests/snapshots/test__jsdoc__inline-both.snap b/tests/tests/snapshots/test__jsdoc__inline-both.snap index c9dd5bd0..23da33f9 100644 --- a/tests/tests/snapshots/test__jsdoc__inline-both.snap +++ b/tests/tests/snapshots/test__jsdoc__inline-both.snap @@ -71,9 +71,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * @@ -786,6 +784,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * "Option": number | null, * "Option<()>": null, * "Option>": number[] | null, + * "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, * "Vec>>": (number | null)[], * "Option>>": number[] | null, * "[Vec; 3]": [string[], string[], string[]], @@ -1046,6 +1045,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {number | null} "Option" * @property {null} "Option<()>" * @property {number[] | null} "Option>" + * @property {({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }} "Result" * @property {(number | null)[]} "Vec>>" * @property {number[] | null} "Option>>" * @property {[string[], string[], string[]]} "[Vec; 3]" diff --git a/tests/tests/snapshots/test__jsdoc__inline-deserialize.snap b/tests/tests/snapshots/test__jsdoc__inline-deserialize.snap index c9dd5bd0..23da33f9 100644 --- a/tests/tests/snapshots/test__jsdoc__inline-deserialize.snap +++ b/tests/tests/snapshots/test__jsdoc__inline-deserialize.snap @@ -71,9 +71,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * @@ -786,6 +784,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * "Option": number | null, * "Option<()>": null, * "Option>": number[] | null, + * "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, * "Vec>>": (number | null)[], * "Option>>": number[] | null, * "[Vec; 3]": [string[], string[], string[]], @@ -1046,6 +1045,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {number | null} "Option" * @property {null} "Option<()>" * @property {number[] | null} "Option>" + * @property {({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }} "Result" * @property {(number | null)[]} "Vec>>" * @property {number[] | null} "Option>>" * @property {[string[], string[], string[]]} "[Vec; 3]" diff --git a/tests/tests/snapshots/test__jsdoc__inline-serialize.snap b/tests/tests/snapshots/test__jsdoc__inline-serialize.snap index c9dd5bd0..23da33f9 100644 --- a/tests/tests/snapshots/test__jsdoc__inline-serialize.snap +++ b/tests/tests/snapshots/test__jsdoc__inline-serialize.snap @@ -71,9 +71,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * @@ -786,6 +784,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * "Option": number | null, * "Option<()>": null, * "Option>": number[] | null, + * "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, * "Vec>>": (number | null)[], * "Option>>": number[] | null, * "[Vec; 3]": [string[], string[], string[]], @@ -1046,6 +1045,7 @@ expression: "JSDoc::default().with_serde(mode).bigint(BigIntExportBehavior::Numb * @property {number | null} "Option" * @property {null} "Option<()>" * @property {number[] | null} "Option>" + * @property {({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }} "Result" * @property {(number | null)[]} "Vec>>" * @property {number[] | null} "Option>>" * @property {[string[], string[], string[]]} "[Vec; 3]" diff --git a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-both.snap b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-both.snap index 1a637b65..666825c0 100644 --- a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-both.snap +++ b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-both.snap @@ -3,6 +3,70 @@ source: tests/tests/jsdoc.rs expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap()" --- /** + * @typedef {{ + * start: T, + * end: T, + * }} Range + * @property {T} start + * @property {T} end + * + * @typedef {{ + * start: T, + * end: T, + * }} RangeInclusive + * @property {T} start + * @property {T} end + * + * @typedef {string} String + * + * @typedef {string} PathBuf + * + * @typedef {string} IpAddr + * + * @typedef {string} Ipv4Addr + * + * @typedef {string} Ipv6Addr + * + * @typedef {string} SocketAddr + * + * @typedef {string} SocketAddrV4 + * + * @typedef {string} SocketAddrV6 + * + * @typedef {T} Cow + * + * @typedef {T} Cow + * + * @typedef {{ + * duration_since_epoch: number, + * duration_since_unix_epoch: number, + * }} SystemTime + * @property {number} duration_since_epoch + * @property {number} duration_since_unix_epoch + * + * @typedef {{ + * secs: number, + * nanos: number, + * }} Duration + * @property {number} secs + * @property {number} nanos + * + * @typedef {T[]} Vec + * + * @typedef {T[]} Vec + * + * @typedef {({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }} Result + * @property {{ Ok: T }} Ok + * @property {{ Err: E }} Err + * + * @typedef {T[]} Vec + * + * @typedef {null} PhantomData + * + * @typedef {null} PhantomData + * + * @typedef {never} Infallible + * * @typedef {null} Unit1 * * @typedef {Record} Unit2 @@ -118,6 +182,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @typedef {string} TransparentTypeWithOverride * @property {string} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {T[]} Vec + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * a: Partial<{ [key in BasicEnum]: number }>, * }} EnumReferenceRecordKey @@ -131,12 +201,16 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {Record} MyEmptyInput * + * @typedef {string} String + * * @typedef {{ A: string }} ExtraBracketsInTupleVariant * @property {{ A: string }} A * * @typedef {string} ExtraBracketsInUnnamedStruct * @property {string} "0" * + * @typedef {T[]} Vec + * * @typedef {{ * demo: [string, boolean], * }} InlineTuple @@ -149,6 +223,10 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * }} InlineTuple2 * @property {[InlineTuple, boolean]} demo * + * @typedef {T} Box + * + * @typedef {T} Box + * * @typedef {{ type: "A" } | { type: "B"; data: string }} SkippedFieldWithinVariant * @property {{ type: "A" }} A * @property {{ type: "B"; data: string }} B @@ -231,7 +309,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {string} ContainerTypeOverrideEnum * - * @typedef {string} ContainerTypeOverrideGeneric + * @typedef {string} ContainerTypeOverrideGeneric * * @typedef {T} ContainerTypeOverrideToGeneric * @@ -333,9 +411,19 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {number} B * @property {null} C * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ [key in string]: null }} Regular * @property {{ [key in string]: null }} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {string} MacroStruct * @property {string} "0" * @@ -546,6 +634,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {A} a * @property {B} b * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * field: Demo, * }} AGenericStruct @@ -588,9 +682,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * diff --git a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-deserialize.snap b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-deserialize.snap index 1a637b65..666825c0 100644 --- a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-deserialize.snap +++ b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-deserialize.snap @@ -3,6 +3,70 @@ source: tests/tests/jsdoc.rs expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap()" --- /** + * @typedef {{ + * start: T, + * end: T, + * }} Range + * @property {T} start + * @property {T} end + * + * @typedef {{ + * start: T, + * end: T, + * }} RangeInclusive + * @property {T} start + * @property {T} end + * + * @typedef {string} String + * + * @typedef {string} PathBuf + * + * @typedef {string} IpAddr + * + * @typedef {string} Ipv4Addr + * + * @typedef {string} Ipv6Addr + * + * @typedef {string} SocketAddr + * + * @typedef {string} SocketAddrV4 + * + * @typedef {string} SocketAddrV6 + * + * @typedef {T} Cow + * + * @typedef {T} Cow + * + * @typedef {{ + * duration_since_epoch: number, + * duration_since_unix_epoch: number, + * }} SystemTime + * @property {number} duration_since_epoch + * @property {number} duration_since_unix_epoch + * + * @typedef {{ + * secs: number, + * nanos: number, + * }} Duration + * @property {number} secs + * @property {number} nanos + * + * @typedef {T[]} Vec + * + * @typedef {T[]} Vec + * + * @typedef {({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }} Result + * @property {{ Ok: T }} Ok + * @property {{ Err: E }} Err + * + * @typedef {T[]} Vec + * + * @typedef {null} PhantomData + * + * @typedef {null} PhantomData + * + * @typedef {never} Infallible + * * @typedef {null} Unit1 * * @typedef {Record} Unit2 @@ -118,6 +182,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @typedef {string} TransparentTypeWithOverride * @property {string} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {T[]} Vec + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * a: Partial<{ [key in BasicEnum]: number }>, * }} EnumReferenceRecordKey @@ -131,12 +201,16 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {Record} MyEmptyInput * + * @typedef {string} String + * * @typedef {{ A: string }} ExtraBracketsInTupleVariant * @property {{ A: string }} A * * @typedef {string} ExtraBracketsInUnnamedStruct * @property {string} "0" * + * @typedef {T[]} Vec + * * @typedef {{ * demo: [string, boolean], * }} InlineTuple @@ -149,6 +223,10 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * }} InlineTuple2 * @property {[InlineTuple, boolean]} demo * + * @typedef {T} Box + * + * @typedef {T} Box + * * @typedef {{ type: "A" } | { type: "B"; data: string }} SkippedFieldWithinVariant * @property {{ type: "A" }} A * @property {{ type: "B"; data: string }} B @@ -231,7 +309,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {string} ContainerTypeOverrideEnum * - * @typedef {string} ContainerTypeOverrideGeneric + * @typedef {string} ContainerTypeOverrideGeneric * * @typedef {T} ContainerTypeOverrideToGeneric * @@ -333,9 +411,19 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {number} B * @property {null} C * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ [key in string]: null }} Regular * @property {{ [key in string]: null }} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {string} MacroStruct * @property {string} "0" * @@ -546,6 +634,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {A} a * @property {B} b * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * field: Demo, * }} AGenericStruct @@ -588,9 +682,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * diff --git a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-serialize.snap b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-serialize.snap index 1a637b65..666825c0 100644 --- a/tests/tests/snapshots/test__jsdoc__primitives-many-inline-serialize.snap +++ b/tests/tests/snapshots/test__jsdoc__primitives-many-inline-serialize.snap @@ -3,6 +3,70 @@ source: tests/tests/jsdoc.rs expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap()" --- /** + * @typedef {{ + * start: T, + * end: T, + * }} Range + * @property {T} start + * @property {T} end + * + * @typedef {{ + * start: T, + * end: T, + * }} RangeInclusive + * @property {T} start + * @property {T} end + * + * @typedef {string} String + * + * @typedef {string} PathBuf + * + * @typedef {string} IpAddr + * + * @typedef {string} Ipv4Addr + * + * @typedef {string} Ipv6Addr + * + * @typedef {string} SocketAddr + * + * @typedef {string} SocketAddrV4 + * + * @typedef {string} SocketAddrV6 + * + * @typedef {T} Cow + * + * @typedef {T} Cow + * + * @typedef {{ + * duration_since_epoch: number, + * duration_since_unix_epoch: number, + * }} SystemTime + * @property {number} duration_since_epoch + * @property {number} duration_since_unix_epoch + * + * @typedef {{ + * secs: number, + * nanos: number, + * }} Duration + * @property {number} secs + * @property {number} nanos + * + * @typedef {T[]} Vec + * + * @typedef {T[]} Vec + * + * @typedef {({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }} Result + * @property {{ Ok: T }} Ok + * @property {{ Err: E }} Err + * + * @typedef {T[]} Vec + * + * @typedef {null} PhantomData + * + * @typedef {null} PhantomData + * + * @typedef {never} Infallible + * * @typedef {null} Unit1 * * @typedef {Record} Unit2 @@ -118,6 +182,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @typedef {string} TransparentTypeWithOverride * @property {string} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {T[]} Vec + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * a: Partial<{ [key in BasicEnum]: number }>, * }} EnumReferenceRecordKey @@ -131,12 +201,16 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {Record} MyEmptyInput * + * @typedef {string} String + * * @typedef {{ A: string }} ExtraBracketsInTupleVariant * @property {{ A: string }} A * * @typedef {string} ExtraBracketsInUnnamedStruct * @property {string} "0" * + * @typedef {T[]} Vec + * * @typedef {{ * demo: [string, boolean], * }} InlineTuple @@ -149,6 +223,10 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * }} InlineTuple2 * @property {[InlineTuple, boolean]} demo * + * @typedef {T} Box + * + * @typedef {T} Box + * * @typedef {{ type: "A" } | { type: "B"; data: string }} SkippedFieldWithinVariant * @property {{ type: "A" }} A * @property {{ type: "B"; data: string }} B @@ -231,7 +309,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * * @typedef {string} ContainerTypeOverrideEnum * - * @typedef {string} ContainerTypeOverrideGeneric + * @typedef {string} ContainerTypeOverrideGeneric * * @typedef {T} ContainerTypeOverrideToGeneric * @@ -333,9 +411,19 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {number} B * @property {null} C * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ [key in string]: null }} Regular * @property {{ [key in string]: null }} "0" * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {string} MacroStruct * @property {string} "0" * @@ -546,6 +634,12 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {A} a * @property {B} b * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * + * @typedef {{ [key in K]: V }} HashMap + * * @typedef {{ * field: Demo, * }} AGenericStruct @@ -588,9 +682,7 @@ expression: "primitives::export(&jsdoc, &types, ndts.into_iter(), \"\").unwrap() * @property {BoxedInner} b * * @typedef {{ - * c: { - * a: number, - * }, + * c: BoxedInner, * }} BoxInline * @property {BoxedInner} c * diff --git a/tests/tests/snapshots/test__typescript__export-both.snap b/tests/tests/snapshots/test__typescript__export-both.snap index 30cd64c9..6cf3233c 100644 --- a/tests/tests/snapshots/test__typescript__export-both.snap +++ b/tests/tests/snapshots/test__typescript__export-both.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(Reference::Named(r)) =>\n r.get(&types).cloned().map(|ty| (s, ty)), _ => None,\n}).map(|(s, ty)|\nprimitives::export(&ts, &types, iter::once(&ty),\n\"\").map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: export type Range = { + start: T, + end: T, +}; + +RangeInclusive: export type RangeInclusive = { + start: T, + end: T, +}; + +String: export type String = string; + +PathBuf: export type PathBuf = string; + +IpAddr: export type IpAddr = string; + +Ipv4Addr: export type Ipv4Addr = string; + +Ipv6Addr: export type Ipv6Addr = string; + +SocketAddr: export type SocketAddr = string; + +SocketAddrV4: export type SocketAddrV4 = string; + +SocketAddrV6: export type SocketAddrV6 = string; + +Cow<'static, str>: export type Cow = T; + +Cow<'static, i32>: export type Cow = T; + +SystemTime: export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +Duration: export type Duration = { + secs: number, + nanos: number, +}; + +Vec: export type Vec = T[]; + +Vec: export type Vec = T[]; + +Result: export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +Vec>>: export type Vec = T[]; + +PhantomData<()>: export type PhantomData = null; + +PhantomData: export type PhantomData = null; + +Infallible: export type Infallible = never; + Unit1: export type Unit1 = null; Unit2: export type Unit2 = Record; @@ -78,6 +132,12 @@ TransparentType2: export type TransparentType2 = null; TransparentTypeWithOverride: export type TransparentTypeWithOverride = string; +HashMap: export type HashMap = { [key in K]: V }; + +Vec: export type Vec = T[]; + +HashMap: export type HashMap = { [key in K]: V }; + EnumReferenceRecordKey: export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ FlattenOnNestedEnum: export type FlattenOnNestedEnum = (NestedEnum) & { MyEmptyInput: export type MyEmptyInput = Record; +(String): export type String = string; + ExtraBracketsInTupleVariant: export type ExtraBracketsInTupleVariant = { A: string }; ExtraBracketsInUnnamedStruct: export type ExtraBracketsInUnnamedStruct = string; +Vec: export type Vec = T[]; + InlineTuple: export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ InlineTuple2: export type InlineTuple2 = { }, boolean], }; +Box: export type Box = T; + +Box: export type Box = T; + SkippedFieldWithinVariant: export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; KebabCase: export type KebabCase = { @@ -159,7 +227,7 @@ ContainerTypeOverrideStruct: export type ContainerTypeOverrideStruct = string; ContainerTypeOverrideEnum: export type ContainerTypeOverrideEnum = string; -ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; +ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; ContainerTypeOverrideToGeneric: export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ UntaggedVariantsWithoutValue: export type UntaggedVariantsWithoutValue = string UntaggedVariantsWithDuplicateBranches: export type UntaggedVariantsWithDuplicateBranches = null | number; +HashMap: export type HashMap = { [key in K]: V }; + Regular: export type Regular = { [key in string]: null }; +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + MacroStruct: export type MacroStruct = string; MacroStruct2: export type MacroStruct2 = { @@ -367,6 +445,12 @@ Another: export type Demo = { b: B, }; +MapA: export type HashMap = { [key in K]: V }; + +MapB: export type HashMap = { [key in K]: V }; + +MapC: export type HashMap = { [key in K]: V }; + AGenericStruct: export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ BoxFlattened: export type BoxFlattened = { }; BoxInline: export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; First: export type First = { diff --git a/tests/tests/snapshots/test__typescript__export-deserialize.snap b/tests/tests/snapshots/test__typescript__export-deserialize.snap index 30cd64c9..6cf3233c 100644 --- a/tests/tests/snapshots/test__typescript__export-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__export-deserialize.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(Reference::Named(r)) =>\n r.get(&types).cloned().map(|ty| (s, ty)), _ => None,\n}).map(|(s, ty)|\nprimitives::export(&ts, &types, iter::once(&ty),\n\"\").map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: export type Range = { + start: T, + end: T, +}; + +RangeInclusive: export type RangeInclusive = { + start: T, + end: T, +}; + +String: export type String = string; + +PathBuf: export type PathBuf = string; + +IpAddr: export type IpAddr = string; + +Ipv4Addr: export type Ipv4Addr = string; + +Ipv6Addr: export type Ipv6Addr = string; + +SocketAddr: export type SocketAddr = string; + +SocketAddrV4: export type SocketAddrV4 = string; + +SocketAddrV6: export type SocketAddrV6 = string; + +Cow<'static, str>: export type Cow = T; + +Cow<'static, i32>: export type Cow = T; + +SystemTime: export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +Duration: export type Duration = { + secs: number, + nanos: number, +}; + +Vec: export type Vec = T[]; + +Vec: export type Vec = T[]; + +Result: export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +Vec>>: export type Vec = T[]; + +PhantomData<()>: export type PhantomData = null; + +PhantomData: export type PhantomData = null; + +Infallible: export type Infallible = never; + Unit1: export type Unit1 = null; Unit2: export type Unit2 = Record; @@ -78,6 +132,12 @@ TransparentType2: export type TransparentType2 = null; TransparentTypeWithOverride: export type TransparentTypeWithOverride = string; +HashMap: export type HashMap = { [key in K]: V }; + +Vec: export type Vec = T[]; + +HashMap: export type HashMap = { [key in K]: V }; + EnumReferenceRecordKey: export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ FlattenOnNestedEnum: export type FlattenOnNestedEnum = (NestedEnum) & { MyEmptyInput: export type MyEmptyInput = Record; +(String): export type String = string; + ExtraBracketsInTupleVariant: export type ExtraBracketsInTupleVariant = { A: string }; ExtraBracketsInUnnamedStruct: export type ExtraBracketsInUnnamedStruct = string; +Vec: export type Vec = T[]; + InlineTuple: export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ InlineTuple2: export type InlineTuple2 = { }, boolean], }; +Box: export type Box = T; + +Box: export type Box = T; + SkippedFieldWithinVariant: export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; KebabCase: export type KebabCase = { @@ -159,7 +227,7 @@ ContainerTypeOverrideStruct: export type ContainerTypeOverrideStruct = string; ContainerTypeOverrideEnum: export type ContainerTypeOverrideEnum = string; -ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; +ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; ContainerTypeOverrideToGeneric: export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ UntaggedVariantsWithoutValue: export type UntaggedVariantsWithoutValue = string UntaggedVariantsWithDuplicateBranches: export type UntaggedVariantsWithDuplicateBranches = null | number; +HashMap: export type HashMap = { [key in K]: V }; + Regular: export type Regular = { [key in string]: null }; +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + MacroStruct: export type MacroStruct = string; MacroStruct2: export type MacroStruct2 = { @@ -367,6 +445,12 @@ Another: export type Demo = { b: B, }; +MapA: export type HashMap = { [key in K]: V }; + +MapB: export type HashMap = { [key in K]: V }; + +MapC: export type HashMap = { [key in K]: V }; + AGenericStruct: export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ BoxFlattened: export type BoxFlattened = { }; BoxInline: export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; First: export type First = { diff --git a/tests/tests/snapshots/test__typescript__export-many-both.snap b/tests/tests/snapshots/test__typescript__export-many-both.snap index ab3a612f..2a42c294 100644 --- a/tests/tests/snapshots/test__typescript__export-many-both.snap +++ b/tests/tests/snapshots/test__typescript__export-many-both.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "primitives::export(&ts, &types, ndts.into_iter(), \"\").unwrap()" --- +export type Range = { + start: T, + end: T, +}; + +export type RangeInclusive = { + start: T, + end: T, +}; + +export type String = string; + +export type PathBuf = string; + +export type IpAddr = string; + +export type Ipv4Addr = string; + +export type Ipv6Addr = string; + +export type SocketAddr = string; + +export type SocketAddrV4 = string; + +export type SocketAddrV6 = string; + +export type Cow = T; + +export type Cow = T; + +export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +export type Duration = { + secs: number, + nanos: number, +}; + +export type Vec = T[]; + +export type Vec = T[]; + +export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +export type Vec = T[]; + +export type PhantomData = null; + +export type PhantomData = null; + +export type Infallible = never; + export type Unit1 = null; export type Unit2 = Record; @@ -78,6 +132,12 @@ export type TransparentType2 = null; export type TransparentTypeWithOverride = string; +export type HashMap = { [key in K]: V }; + +export type Vec = T[]; + +export type HashMap = { [key in K]: V }; + export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ export type FlattenOnNestedEnum = (NestedEnum) & { export type MyEmptyInput = Record; +export type String = string; + export type ExtraBracketsInTupleVariant = { A: string }; export type ExtraBracketsInUnnamedStruct = string; +export type Vec = T[]; + export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ export type InlineTuple2 = { }, boolean], }; +export type Box = T; + +export type Box = T; + export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; export type KebabCase = { @@ -159,7 +227,7 @@ export type ContainerTypeOverrideStruct = string; export type ContainerTypeOverrideEnum = string; -export type ContainerTypeOverrideGeneric = string; +export type ContainerTypeOverrideGeneric = string; export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ export type UntaggedVariantsWithoutValue = string | [number, string] | number; export type UntaggedVariantsWithDuplicateBranches = null | number; +export type HashMap = { [key in K]: V }; + export type Regular = { [key in string]: null }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type MacroStruct = string; export type MacroStruct2 = { @@ -367,6 +445,12 @@ export type Demo = { b: B, }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type First = { diff --git a/tests/tests/snapshots/test__typescript__export-many-deserialize.snap b/tests/tests/snapshots/test__typescript__export-many-deserialize.snap index ab3a612f..2a42c294 100644 --- a/tests/tests/snapshots/test__typescript__export-many-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__export-many-deserialize.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "primitives::export(&ts, &types, ndts.into_iter(), \"\").unwrap()" --- +export type Range = { + start: T, + end: T, +}; + +export type RangeInclusive = { + start: T, + end: T, +}; + +export type String = string; + +export type PathBuf = string; + +export type IpAddr = string; + +export type Ipv4Addr = string; + +export type Ipv6Addr = string; + +export type SocketAddr = string; + +export type SocketAddrV4 = string; + +export type SocketAddrV6 = string; + +export type Cow = T; + +export type Cow = T; + +export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +export type Duration = { + secs: number, + nanos: number, +}; + +export type Vec = T[]; + +export type Vec = T[]; + +export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +export type Vec = T[]; + +export type PhantomData = null; + +export type PhantomData = null; + +export type Infallible = never; + export type Unit1 = null; export type Unit2 = Record; @@ -78,6 +132,12 @@ export type TransparentType2 = null; export type TransparentTypeWithOverride = string; +export type HashMap = { [key in K]: V }; + +export type Vec = T[]; + +export type HashMap = { [key in K]: V }; + export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ export type FlattenOnNestedEnum = (NestedEnum) & { export type MyEmptyInput = Record; +export type String = string; + export type ExtraBracketsInTupleVariant = { A: string }; export type ExtraBracketsInUnnamedStruct = string; +export type Vec = T[]; + export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ export type InlineTuple2 = { }, boolean], }; +export type Box = T; + +export type Box = T; + export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; export type KebabCase = { @@ -159,7 +227,7 @@ export type ContainerTypeOverrideStruct = string; export type ContainerTypeOverrideEnum = string; -export type ContainerTypeOverrideGeneric = string; +export type ContainerTypeOverrideGeneric = string; export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ export type UntaggedVariantsWithoutValue = string | [number, string] | number; export type UntaggedVariantsWithDuplicateBranches = null | number; +export type HashMap = { [key in K]: V }; + export type Regular = { [key in string]: null }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type MacroStruct = string; export type MacroStruct2 = { @@ -367,6 +445,12 @@ export type Demo = { b: B, }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type First = { diff --git a/tests/tests/snapshots/test__typescript__export-many-serialize.snap b/tests/tests/snapshots/test__typescript__export-many-serialize.snap index ab3a612f..2a42c294 100644 --- a/tests/tests/snapshots/test__typescript__export-many-serialize.snap +++ b/tests/tests/snapshots/test__typescript__export-many-serialize.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "primitives::export(&ts, &types, ndts.into_iter(), \"\").unwrap()" --- +export type Range = { + start: T, + end: T, +}; + +export type RangeInclusive = { + start: T, + end: T, +}; + +export type String = string; + +export type PathBuf = string; + +export type IpAddr = string; + +export type Ipv4Addr = string; + +export type Ipv6Addr = string; + +export type SocketAddr = string; + +export type SocketAddrV4 = string; + +export type SocketAddrV6 = string; + +export type Cow = T; + +export type Cow = T; + +export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +export type Duration = { + secs: number, + nanos: number, +}; + +export type Vec = T[]; + +export type Vec = T[]; + +export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +export type Vec = T[]; + +export type PhantomData = null; + +export type PhantomData = null; + +export type Infallible = never; + export type Unit1 = null; export type Unit2 = Record; @@ -78,6 +132,12 @@ export type TransparentType2 = null; export type TransparentTypeWithOverride = string; +export type HashMap = { [key in K]: V }; + +export type Vec = T[]; + +export type HashMap = { [key in K]: V }; + export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ export type FlattenOnNestedEnum = (NestedEnum) & { export type MyEmptyInput = Record; +export type String = string; + export type ExtraBracketsInTupleVariant = { A: string }; export type ExtraBracketsInUnnamedStruct = string; +export type Vec = T[]; + export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ export type InlineTuple2 = { }, boolean], }; +export type Box = T; + +export type Box = T; + export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; export type KebabCase = { @@ -159,7 +227,7 @@ export type ContainerTypeOverrideStruct = string; export type ContainerTypeOverrideEnum = string; -export type ContainerTypeOverrideGeneric = string; +export type ContainerTypeOverrideGeneric = string; export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ export type UntaggedVariantsWithoutValue = string | [number, string] | number; export type UntaggedVariantsWithDuplicateBranches = null | number; +export type HashMap = { [key in K]: V }; + export type Regular = { [key in string]: null }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type MacroStruct = string; export type MacroStruct2 = { @@ -367,6 +445,12 @@ export type Demo = { b: B, }; +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + +export type HashMap = { [key in K]: V }; + export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type First = { diff --git a/tests/tests/snapshots/test__typescript__export-serialize.snap b/tests/tests/snapshots/test__typescript__export-serialize.snap index 30cd64c9..6cf3233c 100644 --- a/tests/tests/snapshots/test__typescript__export-serialize.snap +++ b/tests/tests/snapshots/test__typescript__export-serialize.snap @@ -2,6 +2,60 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(Reference::Named(r)) =>\n r.get(&types).cloned().map(|ty| (s, ty)), _ => None,\n}).map(|(s, ty)|\nprimitives::export(&ts, &types, iter::once(&ty),\n\"\").map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: export type Range = { + start: T, + end: T, +}; + +RangeInclusive: export type RangeInclusive = { + start: T, + end: T, +}; + +String: export type String = string; + +PathBuf: export type PathBuf = string; + +IpAddr: export type IpAddr = string; + +Ipv4Addr: export type Ipv4Addr = string; + +Ipv6Addr: export type Ipv6Addr = string; + +SocketAddr: export type SocketAddr = string; + +SocketAddrV4: export type SocketAddrV4 = string; + +SocketAddrV6: export type SocketAddrV6 = string; + +Cow<'static, str>: export type Cow = T; + +Cow<'static, i32>: export type Cow = T; + +SystemTime: export type SystemTime = { + duration_since_epoch: number, + duration_since_unix_epoch: number, +}; + +Duration: export type Duration = { + secs: number, + nanos: number, +}; + +Vec: export type Vec = T[]; + +Vec: export type Vec = T[]; + +Result: export type Result = ({ Ok: T }) & { Err?: never } | ({ Err: E }) & { Ok?: never }; + +Vec>>: export type Vec = T[]; + +PhantomData<()>: export type PhantomData = null; + +PhantomData: export type PhantomData = null; + +Infallible: export type Infallible = never; + Unit1: export type Unit1 = null; Unit2: export type Unit2 = Record; @@ -78,6 +132,12 @@ TransparentType2: export type TransparentType2 = null; TransparentTypeWithOverride: export type TransparentTypeWithOverride = string; +HashMap: export type HashMap = { [key in K]: V }; + +Vec: export type Vec = T[]; + +HashMap: export type HashMap = { [key in K]: V }; + EnumReferenceRecordKey: export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }>, }; @@ -88,10 +148,14 @@ FlattenOnNestedEnum: export type FlattenOnNestedEnum = (NestedEnum) & { MyEmptyInput: export type MyEmptyInput = Record; +(String): export type String = string; + ExtraBracketsInTupleVariant: export type ExtraBracketsInTupleVariant = { A: string }; ExtraBracketsInUnnamedStruct: export type ExtraBracketsInUnnamedStruct = string; +Vec: export type Vec = T[]; + InlineTuple: export type InlineTuple = { demo: [string, boolean], }; @@ -102,6 +166,10 @@ InlineTuple2: export type InlineTuple2 = { }, boolean], }; +Box: export type Box = T; + +Box: export type Box = T; + SkippedFieldWithinVariant: export type SkippedFieldWithinVariant = { type: "A" } | { type: "B"; data: string }; KebabCase: export type KebabCase = { @@ -159,7 +227,7 @@ ContainerTypeOverrideStruct: export type ContainerTypeOverrideStruct = string; ContainerTypeOverrideEnum: export type ContainerTypeOverrideEnum = string; -ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; +ContainerTypeOverrideGeneric>: export type ContainerTypeOverrideGeneric = string; ContainerTypeOverrideToGeneric: export type ContainerTypeOverrideToGeneric = T; @@ -225,8 +293,18 @@ UntaggedVariantsWithoutValue: export type UntaggedVariantsWithoutValue = string UntaggedVariantsWithDuplicateBranches: export type UntaggedVariantsWithDuplicateBranches = null | number; +HashMap: export type HashMap = { [key in K]: V }; + Regular: export type Regular = { [key in string]: null }; +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + +HashMap: export type HashMap = { [key in K]: V }; + MacroStruct: export type MacroStruct = string; MacroStruct2: export type MacroStruct2 = { @@ -367,6 +445,12 @@ Another: export type Demo = { b: B, }; +MapA: export type HashMap = { [key in K]: V }; + +MapB: export type HashMap = { [key in K]: V }; + +MapC: export type HashMap = { [key in K]: V }; + AGenericStruct: export type AGenericStruct = { field: Demo, }; @@ -399,9 +483,7 @@ BoxFlattened: export type BoxFlattened = { }; BoxInline: export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; First: export type First = { diff --git a/tests/tests/snapshots/test__typescript__inline-both.snap b/tests/tests/snapshots/test__typescript__inline-both.snap index 3df277da..711dbd09 100644 --- a/tests/tests/snapshots/test__typescript__inline-both.snap +++ b/tests/tests/snapshots/test__typescript__inline-both.snap @@ -66,6 +66,7 @@ Vec: MyEnum[] Option: number | null Option<()>: null Option>: number[] | null +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } Vec>>: (number | null)[] Option>>: number[] | null [Vec; 3]: [string[], string[], string[]] @@ -380,9 +381,7 @@ BoxFlattened: { b: BoxedInner, } BoxInline: { - c: { - a: number, - }, + c: BoxedInner, } First: { a: string, diff --git a/tests/tests/snapshots/test__typescript__inline-deserialize.snap b/tests/tests/snapshots/test__typescript__inline-deserialize.snap index 3df277da..711dbd09 100644 --- a/tests/tests/snapshots/test__typescript__inline-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__inline-deserialize.snap @@ -66,6 +66,7 @@ Vec: MyEnum[] Option: number | null Option<()>: null Option>: number[] | null +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } Vec>>: (number | null)[] Option>>: number[] | null [Vec; 3]: [string[], string[], string[]] @@ -380,9 +381,7 @@ BoxFlattened: { b: BoxedInner, } BoxInline: { - c: { - a: number, - }, + c: BoxedInner, } First: { a: string, diff --git a/tests/tests/snapshots/test__typescript__inline-serialize.snap b/tests/tests/snapshots/test__typescript__inline-serialize.snap index 3df277da..711dbd09 100644 --- a/tests/tests/snapshots/test__typescript__inline-serialize.snap +++ b/tests/tests/snapshots/test__typescript__inline-serialize.snap @@ -66,6 +66,7 @@ Vec: MyEnum[] Option: number | null Option<()>: null Option>: number[] | null +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } Vec>>: (number | null)[] Option>>: number[] | null [Vec; 3]: [string[], string[], string[]] @@ -380,9 +381,7 @@ BoxFlattened: { b: BoxedInner, } BoxInline: { - c: { - a: number, - }, + c: BoxedInner, } First: { a: string, diff --git a/tests/tests/snapshots/test__typescript__reference-both.snap b/tests/tests/snapshots/test__typescript__reference-both.snap index 74ac404b..598d7d4c 100644 --- a/tests/tests/snapshots/test__typescript__reference-both.snap +++ b/tests/tests/snapshots/test__typescript__reference-both.snap @@ -2,6 +2,39 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(r) => Some((s, r)), _ => None,\n}).map(|(s, ty)|\nprimitives::reference(&ts, &types,\nty).map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: { + start: number, + end: number, +} +RangeInclusive: { + start: number, + end: number, +} +String: string +PathBuf: string +IpAddr: string +Ipv4Addr: string +Ipv6Addr: string +SocketAddr: string +SocketAddrV4: string +SocketAddrV6: string +Cow<'static, str>: string +Cow<'static, i32>: number +SystemTime: { + duration_since_epoch: number, + duration_since_unix_epoch: number, +} +Duration: { + secs: number, + nanos: number, +} +Vec: number[] +Vec: MyEnum[] +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } +Vec>>: (number | null)[] +PhantomData<()>: null +PhantomData: null +Infallible: never Unit1: Unit1 Unit2: Unit2 Unit3: Unit3 @@ -27,13 +60,20 @@ Rename: Rename TransparentType: TransparentType TransparentType2: TransparentType2 TransparentTypeWithOverride: TransparentTypeWithOverride +HashMap: Partial<{ [key in BasicEnum]: null }> +Vec: PlaceholderInnerField[] +HashMap: Partial<{ [key in BasicEnum]: number }> EnumReferenceRecordKey: EnumReferenceRecordKey FlattenOnNestedEnum: FlattenOnNestedEnum MyEmptyInput: MyEmptyInput +(String): string ExtraBracketsInTupleVariant: ExtraBracketsInTupleVariant ExtraBracketsInUnnamedStruct: ExtraBracketsInUnnamedStruct +Vec: MyEnum[] InlineTuple: InlineTuple InlineTuple2: InlineTuple2 +Box: string +Box: string SkippedFieldWithinVariant: SkippedFieldWithinVariant KebabCase: KebabCase Issue281<'_>: Issue281 @@ -73,7 +113,12 @@ Optional: Optional UntaggedVariants: UntaggedVariants UntaggedVariantsWithoutValue: UntaggedVariantsWithoutValue UntaggedVariantsWithDuplicateBranches: UntaggedVariantsWithDuplicateBranches +HashMap: { [key in string]: null } Regular: Regular +HashMap: { [key in never]: null } +HashMap: Partial<{ [key in UnitVariants]: null }> +HashMap: Partial<{ [key in UntaggedVariantsKey]: null }> +HashMap: Partial<{ [key in Variants]: null }> MacroStruct: MacroStruct MacroStruct2: MacroStruct2 MacroEnum: MacroEnum @@ -91,6 +136,9 @@ HalfGenericA: Demo HalfGenericB: Demo FullGeneric: Demo Another: Demo +MapA: { [key in string]: number } +MapB: { [key in number]: string } +MapC: { [key in string]: AGenericStruct } AGenericStruct: AGenericStruct A: A DoubleFlattened: DoubleFlattened diff --git a/tests/tests/snapshots/test__typescript__reference-deserialize.snap b/tests/tests/snapshots/test__typescript__reference-deserialize.snap index 74ac404b..598d7d4c 100644 --- a/tests/tests/snapshots/test__typescript__reference-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__reference-deserialize.snap @@ -2,6 +2,39 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(r) => Some((s, r)), _ => None,\n}).map(|(s, ty)|\nprimitives::reference(&ts, &types,\nty).map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: { + start: number, + end: number, +} +RangeInclusive: { + start: number, + end: number, +} +String: string +PathBuf: string +IpAddr: string +Ipv4Addr: string +Ipv6Addr: string +SocketAddr: string +SocketAddrV4: string +SocketAddrV6: string +Cow<'static, str>: string +Cow<'static, i32>: number +SystemTime: { + duration_since_epoch: number, + duration_since_unix_epoch: number, +} +Duration: { + secs: number, + nanos: number, +} +Vec: number[] +Vec: MyEnum[] +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } +Vec>>: (number | null)[] +PhantomData<()>: null +PhantomData: null +Infallible: never Unit1: Unit1 Unit2: Unit2 Unit3: Unit3 @@ -27,13 +60,20 @@ Rename: Rename TransparentType: TransparentType TransparentType2: TransparentType2 TransparentTypeWithOverride: TransparentTypeWithOverride +HashMap: Partial<{ [key in BasicEnum]: null }> +Vec: PlaceholderInnerField[] +HashMap: Partial<{ [key in BasicEnum]: number }> EnumReferenceRecordKey: EnumReferenceRecordKey FlattenOnNestedEnum: FlattenOnNestedEnum MyEmptyInput: MyEmptyInput +(String): string ExtraBracketsInTupleVariant: ExtraBracketsInTupleVariant ExtraBracketsInUnnamedStruct: ExtraBracketsInUnnamedStruct +Vec: MyEnum[] InlineTuple: InlineTuple InlineTuple2: InlineTuple2 +Box: string +Box: string SkippedFieldWithinVariant: SkippedFieldWithinVariant KebabCase: KebabCase Issue281<'_>: Issue281 @@ -73,7 +113,12 @@ Optional: Optional UntaggedVariants: UntaggedVariants UntaggedVariantsWithoutValue: UntaggedVariantsWithoutValue UntaggedVariantsWithDuplicateBranches: UntaggedVariantsWithDuplicateBranches +HashMap: { [key in string]: null } Regular: Regular +HashMap: { [key in never]: null } +HashMap: Partial<{ [key in UnitVariants]: null }> +HashMap: Partial<{ [key in UntaggedVariantsKey]: null }> +HashMap: Partial<{ [key in Variants]: null }> MacroStruct: MacroStruct MacroStruct2: MacroStruct2 MacroEnum: MacroEnum @@ -91,6 +136,9 @@ HalfGenericA: Demo HalfGenericB: Demo FullGeneric: Demo Another: Demo +MapA: { [key in string]: number } +MapB: { [key in number]: string } +MapC: { [key in string]: AGenericStruct } AGenericStruct: AGenericStruct A: A DoubleFlattened: DoubleFlattened diff --git a/tests/tests/snapshots/test__typescript__reference-serialize.snap b/tests/tests/snapshots/test__typescript__reference-serialize.snap index 74ac404b..598d7d4c 100644 --- a/tests/tests/snapshots/test__typescript__reference-serialize.snap +++ b/tests/tests/snapshots/test__typescript__reference-serialize.snap @@ -2,6 +2,39 @@ source: tests/tests/typescript.rs expression: "dts.iter().filter_map(|(s, ty)| match ty\n{\n DataType::Reference(r) => Some((s, r)), _ => None,\n}).map(|(s, ty)|\nprimitives::reference(&ts, &types,\nty).map(|ty| format!(\"{s}: {ty}\"))).collect::,\n_>>().unwrap().join(\"\\n\")" --- +Range: { + start: number, + end: number, +} +RangeInclusive: { + start: number, + end: number, +} +String: string +PathBuf: string +IpAddr: string +Ipv4Addr: string +Ipv6Addr: string +SocketAddr: string +SocketAddrV4: string +SocketAddrV6: string +Cow<'static, str>: string +Cow<'static, i32>: number +SystemTime: { + duration_since_epoch: number, + duration_since_unix_epoch: number, +} +Duration: { + secs: number, + nanos: number, +} +Vec: number[] +Vec: MyEnum[] +Result: ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never } +Vec>>: (number | null)[] +PhantomData<()>: null +PhantomData: null +Infallible: never Unit1: Unit1 Unit2: Unit2 Unit3: Unit3 @@ -27,13 +60,20 @@ Rename: Rename TransparentType: TransparentType TransparentType2: TransparentType2 TransparentTypeWithOverride: TransparentTypeWithOverride +HashMap: Partial<{ [key in BasicEnum]: null }> +Vec: PlaceholderInnerField[] +HashMap: Partial<{ [key in BasicEnum]: number }> EnumReferenceRecordKey: EnumReferenceRecordKey FlattenOnNestedEnum: FlattenOnNestedEnum MyEmptyInput: MyEmptyInput +(String): string ExtraBracketsInTupleVariant: ExtraBracketsInTupleVariant ExtraBracketsInUnnamedStruct: ExtraBracketsInUnnamedStruct +Vec: MyEnum[] InlineTuple: InlineTuple InlineTuple2: InlineTuple2 +Box: string +Box: string SkippedFieldWithinVariant: SkippedFieldWithinVariant KebabCase: KebabCase Issue281<'_>: Issue281 @@ -73,7 +113,12 @@ Optional: Optional UntaggedVariants: UntaggedVariants UntaggedVariantsWithoutValue: UntaggedVariantsWithoutValue UntaggedVariantsWithDuplicateBranches: UntaggedVariantsWithDuplicateBranches +HashMap: { [key in string]: null } Regular: Regular +HashMap: { [key in never]: null } +HashMap: Partial<{ [key in UnitVariants]: null }> +HashMap: Partial<{ [key in UntaggedVariantsKey]: null }> +HashMap: Partial<{ [key in Variants]: null }> MacroStruct: MacroStruct MacroStruct2: MacroStruct2 MacroEnum: MacroEnum @@ -91,6 +136,9 @@ HalfGenericA: Demo HalfGenericB: Demo FullGeneric: Demo Another: Demo +MapA: { [key in string]: number } +MapB: { [key in number]: string } +MapC: { [key in string]: AGenericStruct } AGenericStruct: AGenericStruct A: A DoubleFlattened: DoubleFlattened diff --git a/tests/tests/snapshots/test__typescript__ts-export-both.snap b/tests/tests/snapshots/test__typescript__ts-export-both.snap index d34879d6..c0b9cecc 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-both.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-both.snap @@ -47,9 +47,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -553,6 +551,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-deserialize.snap b/tests/tests/snapshots/test__typescript__ts-export-deserialize.snap index d34879d6..c0b9cecc 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-deserialize.snap @@ -47,9 +47,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -553,6 +551,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-serialize.snap b/tests/tests/snapshots/test__typescript__ts-export-serialize.snap index d34879d6..c0b9cecc 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-serialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-serialize.snap @@ -47,9 +47,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -553,6 +551,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-files-both.snap b/tests/tests/snapshots/test__typescript__ts-export-to-files-both.snap index fa7b70be..6f70e52c 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-files-both.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-files-both.snap @@ -10,7 +10,7 @@ test/ export type Type = never; ════════════════════════════════════════ - types.ts (14363 bytes) + types.ts (14356 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -57,9 +57,7 @@ test/ }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -706,7 +704,7 @@ test/ tests/ tests/ - types.ts (11610 bytes) + types.ts (11707 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -777,6 +775,7 @@ tests/ "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-files-deserialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-files-deserialize.snap index fa7b70be..6f70e52c 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-files-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-files-deserialize.snap @@ -10,7 +10,7 @@ test/ export type Type = never; ════════════════════════════════════════ - types.ts (14363 bytes) + types.ts (14356 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -57,9 +57,7 @@ test/ }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -706,7 +704,7 @@ test/ tests/ tests/ - types.ts (11610 bytes) + types.ts (11707 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -777,6 +775,7 @@ tests/ "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-files-serialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-files-serialize.snap index fa7b70be..6f70e52c 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-files-serialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-files-serialize.snap @@ -10,7 +10,7 @@ test/ export type Type = never; ════════════════════════════════════════ - types.ts (14363 bytes) + types.ts (14356 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -57,9 +57,7 @@ test/ }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -706,7 +704,7 @@ test/ tests/ tests/ - types.ts (11610 bytes) + types.ts (11707 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -777,6 +775,7 @@ tests/ "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-both.snap b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-both.snap index c0396273..ee90461a 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-both.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-both.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-flatfile-both (23876 bytes) +ts-export-to-flatfile-both (23966 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -49,9 +49,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -555,6 +553,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-deserialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-deserialize.snap index eb1bad19..af141ec5 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-deserialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-flatfile-deserialize (23876 bytes) +ts-export-to-flatfile-deserialize (23966 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -49,9 +49,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -555,6 +553,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-serialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-serialize.snap index 7f974a48..f70cc714 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-serialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-flatfile-serialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-flatfile-serialize (23876 bytes) +ts-export-to-flatfile-serialize (23966 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type A = { @@ -49,9 +49,7 @@ export type BoxFlattened = { }; export type BoxInline = { - c: { - a: number, - }, + c: BoxedInner, }; export type BoxedInner = { @@ -555,6 +553,7 @@ export type Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-both.snap b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-both.snap index 717e8df8..b2d4f489 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-both.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-both.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-moduleprefixedname-both (28385 bytes) +ts-export-to-moduleprefixedname-both (28486 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type test_types_A = { @@ -49,9 +49,7 @@ export type test_types_BoxFlattened = { }; export type test_types_BoxInline = { - c: { - a: number, - }, + c: test_types_BoxedInner, }; export type test_types_BoxedInner = { @@ -555,6 +553,7 @@ export type tests_tests_types_Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-deserialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-deserialize.snap index 8bac0659..4388b51b 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-deserialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-moduleprefixedname-deserialize (28385 bytes) +ts-export-to-moduleprefixedname-deserialize (28486 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type test_types_A = { @@ -49,9 +49,7 @@ export type test_types_BoxFlattened = { }; export type test_types_BoxInline = { - c: { - a: number, - }, + c: test_types_BoxedInner, }; export type test_types_BoxedInner = { @@ -555,6 +553,7 @@ export type tests_tests_types_Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-serialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-serialize.snap index 43b23b91..e6d62399 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-serialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-moduleprefixedname-serialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-moduleprefixedname-serialize (28385 bytes) +ts-export-to-moduleprefixedname-serialize (28486 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. export type test_types_A = { @@ -49,9 +49,7 @@ export type test_types_BoxFlattened = { }; export type test_types_BoxInline = { - c: { - a: number, - }, + c: test_types_BoxedInner, }; export type test_types_BoxedInner = { @@ -555,6 +553,7 @@ export type tests_tests_types_Primitives = { "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-both.snap b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-both.snap index 20314472..0505af0d 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-both.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-both.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-namespaces-both (30082 bytes) +ts-export-to-namespaces-both (30185 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -55,9 +55,7 @@ namespace $s$ { }; export type BoxInline = { - c: { - a: number, - }, + c: $s$.test.types.BoxedInner, }; export type BoxedInner = { @@ -775,6 +773,7 @@ a: number } }) & { A?: never }; "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-deserialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-deserialize.snap index 61808a44..8348a343 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-deserialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-deserialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-namespaces-deserialize (30082 bytes) +ts-export-to-namespaces-deserialize (30185 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -55,9 +55,7 @@ namespace $s$ { }; export type BoxInline = { - c: { - a: number, - }, + c: $s$.test.types.BoxedInner, }; export type BoxedInner = { @@ -775,6 +773,7 @@ a: number } }) & { A?: never }; "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-serialize.snap b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-serialize.snap index e31712fb..fd5a4ed4 100644 --- a/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-serialize.snap +++ b/tests/tests/snapshots/test__typescript__ts-export-to-namespaces-serialize.snap @@ -2,7 +2,7 @@ source: tests/tests/typescript.rs expression: fs_to_string(&path).unwrap() --- -ts-export-to-namespaces-serialize (30082 bytes) +ts-export-to-namespaces-serialize (30185 bytes) ──────────────────────────────────────── // This file has been generated by Specta. Do not edit this file manually. @@ -55,9 +55,7 @@ namespace $s$ { }; export type BoxInline = { - c: { - a: number, - }, + c: $s$.test.types.BoxedInner, }; export type BoxedInner = { @@ -775,6 +773,7 @@ a: number } }) & { A?: never }; "Option": number | null, "Option<()>": null, "Option>": number[] | null, + "Result": ({ Ok: string }) & { Err?: never } | ({ Err: number }) & { Ok?: never }, "Vec>>": (number | null)[], "Option>>": number[] | null, "[Vec; 3]": [string[], string[], string[]], diff --git a/tests/tests/types.rs b/tests/tests/types.rs index c686c259..bd1e76f8 100644 --- a/tests/tests/types.rs +++ b/tests/tests/types.rs @@ -116,6 +116,7 @@ pub fn types() -> (TypeCollection, Vec<(&'static str, DataType)>) { Vec, &'static [MyEnum], &'static [MyEnum; 6], [MyEnum; 2], &'static [i32; 1], &'static [i32; 0], Option, Option<()>, Option>, + Result, Vec>>, Option>>, [Vec; 3], Option>, @@ -2464,13 +2465,26 @@ fn transparent_wrappers_have_distinct_ids() { let mut types = TypeCollection::default(); let id_a = TransparentA::definition(&mut types); let id_b = TransparentB::definition(&mut types); + let names = types + .into_unsorted_iter() + .map(|ndt| ndt.name().as_ref()) + .collect::>(); + assert_ne!(format!("{:?}", id_a), format!("{:?}", id_b)); - assert_eq!(types.len(), 2); + assert!(names.contains(&"TransparentA")); + assert!(names.contains(&"TransparentB")); } #[test] fn struct_collects_all_transparent_field_types() { let mut types = TypeCollection::default(); UsesTransparent::definition(&mut types); - assert_eq!(types.len(), 3); // UsesTransparent + TransparentA + TransparentB + let names = types + .into_unsorted_iter() + .map(|ndt| ndt.name().as_ref()) + .collect::>(); + + assert!(names.contains(&"UsesTransparent")); + assert!(names.contains(&"TransparentA")); + assert!(names.contains(&"TransparentB")); }