1use std::{borrow::Cow, fmt, sync::Arc};
42
43use specta::{
44 Type, Types,
45 datatype::{DataType, Fields, NamedReferenceType, Primitive, Reference},
46};
47
48use crate::{
49 define,
50 primitives::{escape_typescript_string_literal, is_identifier},
51};
52
53#[derive(Clone)]
83#[non_exhaustive]
84pub struct Transform(
85 Option<Arc<dyn Fn(&str) -> String + Send + Sync>>,
91);
92
93impl fmt::Debug for Transform {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match &self.0 {
96 Some(r) => write!(f, "{r:p}"),
97 None => write!(f, "<none>"),
98 }
99 }
100}
101
102impl Transform {
103 pub fn new(runtime: impl Fn(&str) -> String + Send + Sync + 'static) -> Self {
114 Self(Some(Arc::new(runtime)))
115 }
116
117 pub fn identity() -> Self {
129 Self(None)
130 }
131
132 fn apply(&self, ident: &str) -> String {
133 match &self.0 {
134 Some(runtime) => runtime(ident),
135 None => ident.to_owned(),
136 }
137 }
138}
139
140#[derive(Clone)]
141pub(crate) struct DataTypeFn(Arc<dyn Fn(DataType) -> DataType + Send + Sync>);
142
143impl DataTypeFn {
144 pub(crate) fn new(f: impl Fn(DataType) -> DataType + Send + Sync + 'static) -> Self {
145 Self(Arc::new(f))
146 }
147}
148
149impl fmt::Debug for DataTypeFn {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.debug_tuple("DataTypeFn")
152 .field(&format!("{:p}", self.0))
153 .finish()
154 }
155}
156
157#[derive(Debug, Clone)]
159#[non_exhaustive]
160pub struct Rule {
161 pub name: Cow<'static, str>,
163 pub module_path: Cow<'static, str>,
165 pub(crate) data_type: DataTypeFn,
169 pub(crate) serialize: Option<Transform>,
172 pub(crate) deserialize: Option<Transform>,
175}
176
177#[derive(Debug, Clone)]
184pub struct Configuration {
185 rules: Vec<Rule>,
186 lossless_bigint: bool,
187 lossless_floats: bool,
188}
189
190impl Default for Configuration {
191 fn default() -> Self {
192 Self {
193 rules: vec![
194 Rule {
196 name: "Bytes".into(),
197 module_path: "bytes".into(),
198 data_type: DataTypeFn::new(|_| define("Uint8Array").into()),
199 serialize: Some(Transform::new(|i| format!("[...{i}]"))),
200 deserialize: Some(Transform::new(|i| format!("new Uint8Array({i})"))),
201 },
202 Rule {
203 name: "BytesMut".into(),
204 module_path: "bytes".into(),
205 data_type: DataTypeFn::new(|_| define("Uint8Array").into()),
206 serialize: Some(Transform::new(|i| format!("[...{i}]"))),
207 deserialize: Some(Transform::new(|i| format!("new Uint8Array({i})"))),
208 },
209 Rule {
211 name: "Url".into(),
212 module_path: "url".into(),
213 data_type: DataTypeFn::new(|_| define("URL").into()),
214 serialize: None,
215 deserialize: Some(Transform::new(|i| format!("new URL({i})"))),
216 },
217 Rule {
219 name: "DateTime".into(),
220 module_path: "chrono".into(),
221 data_type: DataTypeFn::new(|_| define("Date").into()),
222 serialize: None,
223 deserialize: Some(Transform::new(|i| format!("new Date({i})"))),
224 },
225 Rule {
226 name: "NaiveDate".into(),
227 module_path: "chrono".into(),
228 data_type: DataTypeFn::new(|_| define("Date").into()),
229 serialize: Some(Transform::new(|i| {
230 format!("{i}.toISOString().slice(0, 10)")
231 })),
232 deserialize: Some(Transform::new(|i| format!("new Date({i})"))),
233 },
234 Rule {
235 name: "Timestamp".into(),
236 module_path: "jiff".into(),
237 data_type: DataTypeFn::new(|_| define("Date").into()),
238 serialize: Some(Transform::new(|i| format!("{i}.toISOString()"))),
239 deserialize: Some(Transform::new(|i| format!("new Date({i})"))),
240 },
241 Rule {
242 name: "Date".into(),
243 module_path: "jiff::civil".into(),
244 data_type: DataTypeFn::new(|_| define("Date").into()),
245 serialize: Some(Transform::new(|i| {
246 format!("{i}.toISOString().slice(0, 10)")
247 })),
248 deserialize: Some(Transform::new(|i| format!("new Date({i})"))),
249 },
250 ],
252 lossless_bigint: false,
253 lossless_floats: false,
254 }
255 }
256}
257
258impl Configuration {
259 pub fn empty() -> Self {
264 Self {
265 rules: Default::default(),
266 lossless_bigint: false,
267 lossless_floats: false,
268 }
269 }
270
271 pub fn rules_mut(&mut self) -> &mut Vec<Rule> {
275 &mut self.rules
276 }
277
278 pub fn define<T: Type>(
304 mut self,
305 dt: impl Fn(DataType) -> DataType + Send + Sync + 'static,
306 serialize: Option<Transform>,
307 deserialize: Option<Transform>,
308 ) -> Self {
309 let mut types = Types::default();
310 let ndt = match T::definition(&mut types) {
311 DataType::Reference(Reference::Named(r)) => types.get(&r),
312 _ => None,
313 };
314 if let Some(ndt) = ndt {
315 self.rules.push(Rule {
316 name: ndt.name.clone(),
317 module_path: ndt.module_path.clone(),
318 data_type: DataTypeFn(Arc::new(dt)),
319 serialize,
320 deserialize,
321 });
322 }
323
324 self
325 }
326
327 pub fn enable_lossless_bigints(mut self) -> Self {
336 if !self.lossless_bigint {
337 self.lossless_bigint = true;
338 }
339
340 self
341 }
342
343 pub fn enable_lossless_floats(mut self) -> Self {
350 if !self.lossless_floats {
351 self.lossless_floats = true;
352 }
353
354 self
355 }
356
357 pub fn apply_types<'a>(&self, types: &'a Types) -> Cow<'a, Types> {
365 let mut types = Cow::Borrowed(types);
366
367 if self.has_builtin_remaps() {
368 types = Cow::Owned(types.into_owned().map(|mut ndt| {
369 let remap_bigint = if ndt.name.ends_with("_Serialize") {
370 serialize_bigint
371 } else {
372 deserialize_bigint
373 };
374
375 ndt.generics.to_mut().iter_mut().for_each(|generic| {
376 if let Some(dt) = &mut generic.default {
377 apply_builtin_remaps(
378 dt,
379 remap_bigint,
380 self.lossless_bigint,
381 self.lossless_floats,
382 );
383 }
384 });
385 if let Some(dt) = &mut ndt.ty {
386 apply_builtin_remaps(
387 dt,
388 remap_bigint,
389 self.lossless_bigint,
390 self.lossless_floats,
391 );
392 }
393
394 ndt
395 }));
396 }
397
398 if !self.rules.is_empty() {
399 let source = types.into_owned();
400 let lookup = source.clone();
401 types = Cow::Owned(source.map(|mut ndt| {
402 if let Some(dt) = &mut ndt.ty {
403 self.apply_rules_to_dt(&lookup, dt);
404 }
405
406 if let Some(rule) = self
407 .rules
408 .iter()
409 .find(|r| r.name == ndt.name && r.module_path == ndt.module_path)
410 && let Some(dt) = ndt.ty.take()
411 {
412 ndt.ty = Some((rule.data_type.0)(dt));
413 }
414
415 ndt
416 }));
417 }
418
419 types
420 }
421
422 fn apply_rules_to_dt(&self, types: &Types, dt: &mut DataType) {
423 if let DataType::Reference(Reference::Named(reference)) = dt
424 && let Some(rule) = self.rule_for_reference(types, reference)
425 {
426 *dt = (rule.data_type.0)(Self::reference_source_dt(types, reference));
427 return;
428 }
429
430 match dt {
431 DataType::Primitive(_) | DataType::Generic(_) => {}
432 DataType::List(list) => self.apply_rules_to_dt(types, &mut list.ty),
433 DataType::Map(map) => {
434 self.apply_rules_to_dt(types, map.key_ty_mut());
435 self.apply_rules_to_dt(types, map.value_ty_mut());
436 }
437 DataType::Struct(s) => self.apply_rules_to_fields(types, &mut s.fields),
438 DataType::Enum(e) => {
439 for (_, variant) in &mut e.variants {
440 self.apply_rules_to_fields(types, &mut variant.fields);
441 }
442 }
443 DataType::Tuple(tuple) => {
444 for dt in &mut tuple.elements {
445 self.apply_rules_to_dt(types, dt);
446 }
447 }
448 DataType::Nullable(dt) => self.apply_rules_to_dt(types, dt),
449 DataType::Intersection(dts) => {
450 for dt in dts {
451 self.apply_rules_to_dt(types, dt);
452 }
453 }
454 DataType::Reference(Reference::Named(reference)) => match &mut reference.inner {
455 NamedReferenceType::Recursive(_) | NamedReferenceType::Reference { .. } => {}
456 NamedReferenceType::Inline { dt, .. } => self.apply_rules_to_dt(types, dt),
457 },
458 DataType::Reference(Reference::Opaque(_)) => {}
459 }
460 }
461
462 fn apply_rules_to_fields(&self, types: &Types, fields: &mut Fields) {
463 match fields {
464 Fields::Unit => {}
465 Fields::Unnamed(fields) => {
466 for field in &mut fields.fields {
467 if let Some(dt) = &mut field.ty {
468 self.apply_rules_to_dt(types, dt);
469 }
470 }
471 }
472 Fields::Named(fields) => {
473 for (_, field) in &mut fields.fields {
474 if let Some(dt) = &mut field.ty {
475 self.apply_rules_to_dt(types, dt);
476 }
477 }
478 }
479 }
480 }
481
482 pub fn apply_serialize(
497 &self,
498 types: &Types,
499 dt: &DataType,
500 js_ident: &str,
501 ) -> Option<(Option<DataType>, String)> {
502 let result = self.apply_inner(
503 |rule| &rule.serialize,
504 serialize_bigint,
505 types,
506 dt,
507 js_ident,
508 &mut Vec::new(),
509 );
510 prune_identity_root(types, dt, js_ident, result)
511 }
512
513 pub fn apply_deserialize(
518 &self,
519 types: &Types,
520 dt: &DataType,
521 js_ident: &str,
522 ) -> Option<(Option<DataType>, String)> {
523 let result = self.apply_inner(
524 |rule| &rule.deserialize,
525 deserialize_bigint,
526 types,
527 dt,
528 js_ident,
529 &mut Vec::new(),
530 );
531 prune_identity_root(types, dt, js_ident, result)
532 }
533
534 fn apply_inner(
535 &self,
536 transform_for_rule: fn(&Rule) -> &Option<Transform>,
537 remap_bigint: fn() -> DataType,
538 types: &Types,
539 dt: &DataType,
540 js_ident: &str,
541 stack: &mut Vec<(Cow<'static, str>, Cow<'static, str>)>,
542 ) -> Option<(Option<DataType>, String)> {
543 let result = match dt {
544 DataType::Reference(Reference::Named(r)) => {
545 if let Some(rule) = self.rule_for_reference(types, r) {
546 return Some((
547 Some((rule.data_type.0)(Self::reference_source_dt(types, r))),
548 transform_for_rule(rule).as_ref().map_or_else(
549 || js_ident.to_owned(),
550 |transform| transform.apply(js_ident),
551 ),
552 ));
553 }
554
555 match &r.inner {
556 NamedReferenceType::Inline { dt, .. } => self.apply_inner(
557 transform_for_rule,
558 remap_bigint,
559 types,
560 dt,
561 js_ident,
562 stack,
563 ),
564 NamedReferenceType::Recursive(_) => None,
565 NamedReferenceType::Reference { .. } => {
566 let ndt = types.get(r)?;
567
568 let ty = ndt.ty.as_ref()?;
569 let key = (ndt.name.clone(), ndt.module_path.clone());
570 if stack.contains(&key) {
571 return None;
572 }
573 stack.push(key);
574 let result = self
575 .apply_inner(
576 transform_for_rule,
577 remap_bigint,
578 types,
579 ty,
580 js_ident,
581 stack,
582 )
583 .map(|(_, runtime)| (None, runtime));
584 stack.pop();
585 result
586 }
587 }
588 }
589 DataType::Struct(s) => match &s.fields {
590 Fields::Named(fields) => {
591 let mut ty = s.clone();
592 let mut changed = false;
593 let mut parts = Vec::new();
594
595 for (name, field) in &fields.fields {
596 let Some(field_ty) = &field.ty else { continue };
597 let field_ident = js_property_access(js_ident, name);
598 let Some((next_ty, runtime)) = self.apply_inner(
599 transform_for_rule,
600 remap_bigint,
601 types,
602 field_ty,
603 &field_ident,
604 stack,
605 ) else {
606 continue;
607 };
608
609 if let Some(next_ty) = next_ty
610 && let Fields::Named(fields) = &mut ty.fields
611 && let Some((_, field)) =
612 fields.fields.iter_mut().find(|(n, _)| n == name)
613 {
614 field.ty = Some(next_ty);
615 changed = true;
616 }
617 if runtime != field_ident {
618 parts.push(format!("{}:{runtime}", js_object_key(name)));
619 }
620 }
621
622 if parts.is_empty() {
623 changed.then_some((Some(DataType::Struct(ty)), js_ident.to_owned()))
624 } else {
625 Some((
626 changed.then_some(DataType::Struct(ty)),
627 spread_transform(js_ident, parts),
628 ))
629 }
630 }
631 Fields::Unnamed(fields) => {
632 let mut ty = s.clone();
633 let mut changed = false;
634 let is_newtype = fields.fields.len() == 1;
635 let parts = fields
636 .fields
637 .iter()
638 .enumerate()
639 .filter_map(|(idx, field)| {
640 let field_ty = field.ty.as_ref()?;
641 let field_ident = if is_newtype {
642 js_ident.to_owned()
643 } else {
644 format!("{js_ident}[{idx}]")
645 };
646 let (next_ty, runtime) = self.apply_inner(
647 transform_for_rule,
648 remap_bigint,
649 types,
650 field_ty,
651 &field_ident,
652 stack,
653 )?;
654
655 if let Some(next_ty) = next_ty
656 && let Fields::Unnamed(fields) = &mut ty.fields
657 {
658 fields.fields[idx].ty = Some(next_ty);
659 changed = true;
660 }
661
662 (runtime != field_ident).then_some((idx, runtime))
663 })
664 .collect::<Vec<_>>();
665
666 if parts.is_empty() {
667 changed.then_some((Some(DataType::Struct(ty)), js_ident.to_owned()))
668 } else {
669 Some((
670 changed.then_some(DataType::Struct(ty)),
671 if is_newtype {
672 parts.into_iter().next()?.1
673 } else {
674 array_transform(js_ident, fields.fields.len(), parts)
675 },
676 ))
677 }
678 }
679 Fields::Unit => None,
680 },
681 DataType::Tuple(tuple) => {
682 let mut ty = tuple.clone();
683 let mut changed = false;
684 let parts = tuple
685 .elements
686 .iter()
687 .enumerate()
688 .filter_map(|(idx, element)| {
689 let ident = format!("{js_ident}[{idx}]");
690 let (next_ty, runtime) = self.apply_inner(
691 transform_for_rule,
692 remap_bigint,
693 types,
694 element,
695 &ident,
696 stack,
697 )?;
698 if let Some(next_ty) = next_ty {
699 ty.elements[idx] = next_ty;
700 changed = true;
701 }
702 (runtime != ident).then_some((idx, runtime))
703 })
704 .collect::<Vec<_>>();
705
706 if parts.is_empty() {
707 changed.then_some((Some(DataType::Tuple(ty)), js_ident.to_owned()))
708 } else {
709 Some((
710 changed.then_some(DataType::Tuple(ty)),
711 array_transform(js_ident, tuple.elements.len(), parts),
712 ))
713 }
714 }
715 DataType::Map(map) => {
716 let item = "v";
717 let (next_ty, runtime) = self.apply_inner(
718 transform_for_rule,
719 remap_bigint,
720 types,
721 map.value_ty(),
722 item,
723 stack,
724 )?;
725
726 let mut ty = map.clone();
727 let mut changed = false;
728 if let Some(next_ty) = next_ty {
729 ty.set_value_ty(next_ty);
730 changed = true;
731 }
732
733 Some((
734 changed.then_some(DataType::Map(ty)),
735 if runtime == item {
736 js_ident.to_owned()
737 } else {
738 format!(
739 "Object.fromEntries(Object.entries({js_ident}).map(([k,{item}])=>[k,{runtime}]))"
740 )
741 },
742 ))
743 }
744 DataType::List(list) => {
745 let item = "i";
746 let (next_ty, runtime) = self.apply_inner(
747 transform_for_rule,
748 remap_bigint,
749 types,
750 &list.ty,
751 item,
752 stack,
753 )?;
754 let mut ty = list.clone();
755 let mut changed = false;
756 if let Some(next_ty) = next_ty {
757 ty.ty = Box::new(next_ty);
758 changed = true;
759 }
760 Some((
761 changed.then_some(DataType::List(ty)),
762 if runtime == item {
763 js_ident.to_owned()
764 } else {
765 format!("{js_ident}.map({item}=>{runtime})")
766 },
767 ))
768 }
769 DataType::Nullable(inner) => {
770 let (next_ty, runtime) = self.apply_inner(
771 transform_for_rule,
772 remap_bigint,
773 types,
774 inner,
775 js_ident,
776 stack,
777 )?;
778 Some((
779 next_ty.map(|dt| DataType::Nullable(Box::new(dt))),
780 if runtime == js_ident {
781 js_ident.to_owned()
782 } else {
783 format!("{js_ident}==null?{js_ident}:{runtime}")
784 },
785 ))
786 }
787 DataType::Intersection(items) => {
788 let mut ty = items.clone();
789 let mut changed = false;
790 let mut parts = items
791 .iter()
792 .enumerate()
793 .filter_map(|(idx, item)| {
794 let (next_ty, runtime) = self.apply_inner(
795 transform_for_rule,
796 remap_bigint,
797 types,
798 item,
799 js_ident,
800 stack,
801 )?;
802 if let Some(next_ty) = next_ty {
803 ty[idx] = next_ty;
804 changed = true;
805 }
806 Some(runtime)
807 })
808 .collect::<Vec<_>>();
809
810 parts.dedup();
811
812 match parts.as_slice() {
813 [] => None,
814 [runtime] => Some((
815 changed.then_some(DataType::Intersection(ty)),
816 runtime.clone(),
817 )),
818 _ => Some((
819 changed.then_some(DataType::Intersection(ty)),
820 spread_transform(
821 "",
822 parts.into_iter().map(|p| format!("...{p}")).collect(),
823 ),
824 )),
825 }
826 }
827 DataType::Enum(_)
828 | DataType::Primitive(_)
829 | DataType::Generic(_)
830 | DataType::Reference(Reference::Opaque(_)) => None,
831 };
832
833 self.apply_builtin_remaps(remap_bigint, dt, js_ident, result)
834 }
835
836 fn rule_for_reference<'a>(
837 &'a self,
838 types: &'a Types,
839 reference: &specta::datatype::NamedReference,
840 ) -> Option<&'a Rule> {
841 let ndt = types.get(reference)?;
842 self.rules
843 .iter()
844 .find(|rule| rule.name == ndt.name && rule.module_path == ndt.module_path)
845 }
846
847 fn reference_source_dt(
848 types: &Types,
849 reference: &specta::datatype::NamedReference,
850 ) -> DataType {
851 match &reference.inner {
852 NamedReferenceType::Inline { dt, .. } => (**dt).clone(),
853 NamedReferenceType::Reference { .. } | NamedReferenceType::Recursive(_) => types
854 .get(reference)
855 .and_then(|ndt| ndt.ty.clone())
856 .unwrap_or_else(|| DataType::Reference(Reference::Named(reference.clone()))),
857 }
858 }
859
860 fn has_builtin_remaps(&self) -> bool {
861 self.lossless_bigint || self.lossless_floats
862 }
863
864 fn apply_builtin_remaps(
865 &self,
866 remap_bigint: fn() -> DataType,
867 dt: &DataType,
868 js_ident: &str,
869 result: Option<(Option<DataType>, String)>,
870 ) -> Option<(Option<DataType>, String)> {
871 if !self.has_builtin_remaps() {
872 return result;
873 }
874
875 let source = result
876 .as_ref()
877 .and_then(|(dt, _)| dt.clone())
878 .unwrap_or_else(|| dt.clone());
879 let mut remapped = source.clone();
880 apply_builtin_remaps(
881 &mut remapped,
882 remap_bigint,
883 self.lossless_bigint,
884 self.lossless_floats,
885 );
886
887 let runtime = result
888 .as_ref()
889 .map(|(_, runtime)| runtime.as_str())
890 .unwrap_or(js_ident);
891 let runtime = if is_lossless_bigint_primitive(&source)
892 && self.lossless_bigint
893 && remap_bigint() == deserialize_bigint()
894 {
895 format!("BigInt({runtime})")
896 } else {
897 runtime.to_owned()
898 };
899
900 if remapped == source {
901 result
902 } else {
903 Some((Some(remapped), runtime))
904 }
905 }
906}
907
908fn prune_identity_root(
909 types: &Types,
910 dt: &DataType,
911 js_ident: &str,
912 result: Option<(Option<DataType>, String)>,
913) -> Option<(Option<DataType>, String)> {
914 let registered_root = matches!(
915 dt,
916 DataType::Reference(Reference::Named(reference))
917 if !matches!(reference.inner, NamedReferenceType::Inline { .. })
918 && types.get(reference).is_some_and(|ndt| ndt.ty.is_some())
919 );
920
921 result.filter(|(next_ty, runtime)| {
922 runtime != js_ident
923 || next_ty
924 .as_ref()
925 .is_some_and(|next_ty| !registered_root || registered_generics_changed(dt, next_ty))
926 })
927}
928
929fn registered_generics_changed(dt: &DataType, next_ty: &DataType) -> bool {
930 let (
931 DataType::Reference(Reference::Named(source)),
932 DataType::Reference(Reference::Named(remapped)),
933 ) = (dt, next_ty)
934 else {
935 return false;
936 };
937 let (
938 NamedReferenceType::Reference {
939 generics: source, ..
940 },
941 NamedReferenceType::Reference {
942 generics: remapped, ..
943 },
944 ) = (&source.inner, &remapped.inner)
945 else {
946 return false;
947 };
948
949 source != remapped
950}
951
952fn is_lossless_bigint_primitive(dt: &DataType) -> bool {
953 matches!(
954 dt,
955 DataType::Primitive(
956 Primitive::usize
957 | Primitive::isize
958 | Primitive::u64
959 | Primitive::i64
960 | Primitive::u128
961 | Primitive::i128
962 )
963 )
964}
965
966fn apply_builtin_remaps(
967 dt: &mut DataType,
968 remap_bigint: fn() -> DataType,
969 lossless_bigint: bool,
970 lossless_floats: bool,
971) {
972 if let DataType::Primitive(primitive) = dt
973 && let Some(remapped) = remap_primitive(
974 primitive.clone(),
975 remap_bigint,
976 lossless_bigint,
977 lossless_floats,
978 )
979 {
980 *dt = remapped;
981 return;
982 }
983
984 match dt {
985 DataType::Primitive(_) | DataType::Generic(_) => {}
986 DataType::List(list) => {
987 apply_builtin_remaps(&mut list.ty, remap_bigint, lossless_bigint, lossless_floats)
988 }
989 DataType::Map(map) => {
990 apply_builtin_remaps(
991 map.key_ty_mut(),
992 remap_bigint,
993 lossless_bigint,
994 lossless_floats,
995 );
996 apply_builtin_remaps(
997 map.value_ty_mut(),
998 remap_bigint,
999 lossless_bigint,
1000 lossless_floats,
1001 );
1002 }
1003 DataType::Struct(s) => apply_builtin_remaps_to_fields(
1004 &mut s.fields,
1005 remap_bigint,
1006 lossless_bigint,
1007 lossless_floats,
1008 ),
1009 DataType::Enum(e) => {
1010 for (_, variant) in &mut e.variants {
1011 apply_builtin_remaps_to_fields(
1012 &mut variant.fields,
1013 remap_bigint,
1014 lossless_bigint,
1015 lossless_floats,
1016 );
1017 }
1018 }
1019 DataType::Tuple(tuple) => {
1020 for dt in &mut tuple.elements {
1021 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1022 }
1023 }
1024 DataType::Nullable(dt) => {
1025 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1026 }
1027 DataType::Intersection(dts) => {
1028 for dt in dts {
1029 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1030 }
1031 }
1032 DataType::Reference(reference) => {
1033 let Reference::Named(reference) = reference else {
1034 return;
1035 };
1036
1037 match &mut reference.inner {
1038 NamedReferenceType::Recursive(_) => {}
1039 NamedReferenceType::Inline { dt, .. } => {
1040 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1041 }
1042 NamedReferenceType::Reference { generics, .. } => {
1043 for (_, dt) in generics {
1044 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1045 }
1046 }
1047 }
1048 }
1049 }
1050}
1051
1052fn apply_builtin_remaps_to_fields(
1053 fields: &mut Fields,
1054 remap_bigint: fn() -> DataType,
1055 lossless_bigint: bool,
1056 lossless_floats: bool,
1057) {
1058 match fields {
1059 Fields::Unit => {}
1060 Fields::Unnamed(fields) => {
1061 for field in &mut fields.fields {
1062 if let Some(dt) = &mut field.ty {
1063 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1064 }
1065 }
1066 }
1067 Fields::Named(fields) => {
1068 for (_, field) in &mut fields.fields {
1069 if let Some(dt) = &mut field.ty {
1070 apply_builtin_remaps(dt, remap_bigint, lossless_bigint, lossless_floats);
1071 }
1072 }
1073 }
1074 }
1075}
1076
1077fn remap_primitive(
1078 primitive: Primitive,
1079 remap_bigint: fn() -> DataType,
1080 lossless_bigint: bool,
1081 lossless_floats: bool,
1082) -> Option<DataType> {
1083 if lossless_bigint
1084 && matches!(
1085 primitive,
1086 Primitive::usize
1087 | Primitive::isize
1088 | Primitive::u64
1089 | Primitive::i64
1090 | Primitive::u128
1091 | Primitive::i128
1092 )
1093 {
1094 return Some(remap_bigint());
1095 }
1096
1097 if lossless_floats && matches!(primitive, Primitive::f16 | Primitive::f32 | Primitive::f64) {
1098 return Some(Reference::opaque(crate::opaque::Number).into());
1099 }
1100
1101 None
1102}
1103
1104fn serialize_bigint() -> DataType {
1105 crate::define("bigint | number").into()
1106}
1107
1108fn deserialize_bigint() -> DataType {
1109 Reference::opaque(crate::opaque::BigInt).into()
1110}
1111
1112fn spread_transform(js_ident: &str, mut parts: Vec<String>) -> String {
1113 if !js_ident.is_empty() {
1114 parts.insert(0, format!("...{js_ident}"));
1115 }
1116 format!("({{{}}})", parts.join(","))
1117}
1118
1119fn array_transform(js_ident: &str, len: usize, parts: Vec<(usize, String)>) -> String {
1120 let mut items = (0..len)
1121 .map(|idx| format!("{js_ident}[{idx}]"))
1122 .collect::<Vec<_>>();
1123
1124 for (idx, runtime) in parts {
1125 items[idx] = runtime;
1126 }
1127
1128 format!("([{}])", items.join(","))
1129}
1130
1131fn js_property_access(base: &str, name: &str) -> String {
1132 if is_identifier(name) {
1133 format!("{base}.{name}")
1134 } else {
1135 format!("{base}[\"{}\"]", escape_typescript_string_literal(name))
1136 }
1137}
1138
1139fn js_object_key(name: &str) -> Cow<'_, str> {
1140 if is_identifier(name) {
1141 Cow::Borrowed(name)
1142 } else {
1143 Cow::Owned(format!("\"{}\"", escape_typescript_string_literal(name)))
1144 }
1145}