diff --git a/specta-serde/src/validate.rs b/specta-serde/src/validate.rs index c4d0b0c..c576a16 100644 --- a/specta-serde/src/validate.rs +++ b/specta-serde/src/validate.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::HashSet; use specta::{ datatype::{DataType, Enum, EnumRepr, Fields, Generic, Literal, Primitive}, @@ -13,12 +13,7 @@ use crate::Error; /// Validate the type and apply the Serde transformations. pub fn validate(types: &TypeCollection) -> Result<(), Error> { for ndt in types.into_unsorted_iter() { - inner( - ndt.ty(), - &types, - &Default::default(), - &mut Default::default(), - )?; + inner(ndt.ty(), &types, &[], &mut Default::default())?; } Ok(()) @@ -26,15 +21,10 @@ pub fn validate(types: &TypeCollection) -> Result<(), Error> { // TODO: Remove this once we redo the Typescript exporter. pub fn validate_dt(ty: &DataType, types: &TypeCollection) -> Result<(), Error> { - inner(ty, &types, &Default::default(), &mut Default::default())?; + inner(ty, &types, &[], &mut Default::default())?; for ndt in types.into_unsorted_iter() { - inner( - ndt.ty(), - &types, - &Default::default(), - &mut Default::default(), - )?; + inner(ndt.ty(), &types, &[], &mut Default::default())?; } Ok(()) @@ -43,7 +33,7 @@ pub fn validate_dt(ty: &DataType, types: &TypeCollection) -> Result<(), Error> { fn inner( dt: &DataType, types: &TypeCollection, - generics: &BTreeMap, + generics: &[(Generic, DataType)], checked_references: &mut HashSet, ) -> Result<(), Error> { match dt { @@ -91,7 +81,7 @@ fn inner( } DataType::Reference(r) => { for (_, dt) in r.generics() { - inner(dt, types, &Default::default(), checked_references)?; + inner(dt, types, &[], checked_references)?; } #[allow(clippy::panic)] @@ -113,7 +103,7 @@ fn inner( fn is_valid_map_key( key_ty: &DataType, types: &TypeCollection, - generics: &BTreeMap, + generics: &[(Generic, DataType)], ) -> Result<(), Error> { match key_ty { DataType::Primitive(ty) => match ty { @@ -177,13 +167,16 @@ fn is_valid_map_key( } DataType::Reference(r) => { let ty = types.get(r.sid()).expect("Type was never populated"); // TODO: Error properly - is_valid_map_key(ty.ty(), types, r.generics()) } DataType::Generic(g) => { - let ty = generics.get(g).expect("bruh"); + let ty = generics + .iter() + .find(|(gen, _)| gen == g) + .map(|(_, dt)| dt) + .expect("bruh"); - is_valid_map_key(&ty, types, &Default::default()) + is_valid_map_key(ty, types, &[]) } _ => Err(Error::InvalidMapKey), } diff --git a/specta-typescript/src/inline.rs b/specta-typescript/src/inline.rs index 7e05e54..1f47930 100644 --- a/specta-typescript/src/inline.rs +++ b/specta-typescript/src/inline.rs @@ -1,25 +1,23 @@ //! Helpers for generating [Type::reference] implementations. -use std::collections::HashMap; - use specta::TypeCollection; use specta::datatype::{DataType, Field, Fields, Generic, NamedDataType}; #[doc(hidden)] // TODO: Make this private pub fn inline_and_flatten_ndt(ndt: &mut NamedDataType, types: &TypeCollection) { - inner(ndt.ty_mut(), types, false, false, &Default::default(), 0); + inner(ndt.ty_mut(), types, false, false, &[], 0); } pub(crate) fn inline(dt: &mut DataType, types: &TypeCollection) { - inner(dt, types, false, true, &Default::default(), 0) + inner(dt, types, false, true, &[], 0) } fn field( f: &mut Field, types: &TypeCollection, truely_force_inline: bool, - generics: &HashMap, + generics: &[(Generic, DataType)], depth: usize, ) { // TODO: truely_force_inline @@ -38,7 +36,7 @@ fn fields( f: &mut Fields, types: &TypeCollection, truely_force_inline: bool, - generics: &HashMap, + generics: &[(Generic, DataType)], depth: usize, ) { match f { @@ -61,7 +59,7 @@ fn inner( types: &TypeCollection, force_inline: bool, truely_force_inline: bool, - generics: &HashMap, + generics: &[(Generic, DataType)], depth: usize, ) { // TODO: Can we be smart enough to determine loops, instead of just trying X times and bailing out???? @@ -125,7 +123,12 @@ fn inner( } } DataType::Generic(g) => { - let mut ty = generics.get(g).unwrap().clone(); // TODO: Properly handle this error + let mut ty = generics + .iter() + .find(|(gen, _)| gen == g) + .map(|(_, dt)| dt) + .unwrap() + .clone(); // TODO: Properly handle this error if truely_force_inline { inner( @@ -133,7 +136,7 @@ fn inner( types, false, truely_force_inline, - &Default::default(), // TODO: What should this be? + &[], // TODO: What should this be? depth + 1, ); *dt = ty; @@ -150,13 +153,13 @@ fn inner( false, truely_force_inline, &r.generics() - .clone() - .into_iter() + .iter() + .cloned() .map(|(g, mut dt)| { resolve_generics(&mut dt, generics); (g, dt) }) - .collect(), + .collect::>(), depth + 1, ); *dt = ty; @@ -168,7 +171,7 @@ fn inner( } /// Following all `DataType::Reference`'s filling in any `DataType::Generic`'s with the correct value. -fn resolve_generics(dt: &mut DataType, generics: &HashMap) { +fn resolve_generics(dt: &mut DataType, generics: &[(Generic, DataType)]) { // TODO: This could so only re-alloc if the type has a generics that needs replacing. match dt { DataType::List(l) => { @@ -201,8 +204,9 @@ fn resolve_generics(dt: &mut DataType, generics: &HashMap) { // This method is run when not inlining so for `export` we do expect `DataType::Generic`. // TODO: Functions main documentation should explain this. *dt = generics - .get(g) - .cloned() + .iter() + .find(|(gen, _)| gen == g) + .map(|(_, dt)| dt.clone()) .unwrap_or(DataType::Generic(g.clone())); } _ => {} diff --git a/specta/src/datatype/reference.rs b/specta/src/datatype/reference.rs index 5c95bce..af08521 100644 --- a/specta/src/datatype/reference.rs +++ b/specta/src/datatype/reference.rs @@ -1,7 +1,5 @@ //! Helpers for generating [Type::reference] implementations. -use std::collections::BTreeMap; - use crate::SpectaID; use super::{DataType, Generic}; @@ -11,7 +9,7 @@ use super::{DataType, Generic}; #[non_exhaustive] pub struct Reference { pub(crate) sid: SpectaID, - pub(crate) generics: BTreeMap, + pub(crate) generics: Vec<(Generic, DataType)>, pub(crate) inline: bool, } @@ -19,7 +17,7 @@ impl Reference { /// TODO: Explain invariant. pub fn construct( sid: SpectaID, - generics: impl Into>, + generics: impl Into>, inline: bool, ) -> Self { Self { @@ -35,12 +33,12 @@ impl Reference { } /// Get the generic parameters set on this reference which will be filled in by the [NamedDataType]. - pub fn generics(&self) -> &BTreeMap { + pub fn generics(&self) -> &[(Generic, DataType)] { &self.generics } /// Get the generic parameters set on this reference which will be filled in by the [NamedDataType]. - pub fn generics_mut(&mut self) -> &mut BTreeMap { + pub fn generics_mut(&mut self) -> &mut Vec<(Generic, DataType)> { &mut self.generics } diff --git a/tests/tests/ts_rs/generics.rs b/tests/tests/ts_rs/generics.rs index e33682b..4a5bc07 100644 --- a/tests/tests/ts_rs/generics.rs +++ b/tests/tests/ts_rs/generics.rs @@ -219,3 +219,25 @@ fn inline() { // assert_ts_export!(D::<&str, 41>, "export type D = { t: Array, }") // } + +// https://github.com/specta-rs/specta/issues/400 +#[test] +fn generic_parameter_order_preserved() { + #[derive(Type)] + #[specta(export = false)] + struct Pair { + first: Z, + second: A, + } + + #[derive(Type)] + #[specta(export = false)] + struct Container { + pair: Pair, + } + + assert_ts_export!( + Container, + "export type Container = { pair: Pair };" + ); +}