1use std::hash::Hash;
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use crate::symbol::Name;
8use crate::Type;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct FnParam {
16 pub name: Name,
17 pub ty: Option<crate::compact::SimpleType>,
20 #[serde(default)]
25 pub out_ty: Option<crate::compact::SimpleType>,
26 pub default: Option<crate::compact::SimpleType>,
28 pub is_variadic: bool,
29 pub is_byref: bool,
30 pub is_optional: bool,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
38pub enum Variance {
39 #[default]
41 Invariant,
42 Covariant,
44 Contravariant,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct TemplateParam {
54 pub name: Name,
55 pub bound: Option<Type>,
56 pub variance: Variance,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum ArrayKey {
65 String(Arc<str>),
66 Int(i64),
67}
68
69impl PartialOrd for ArrayKey {
70 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
71 Some(self.cmp(other))
72 }
73}
74
75impl Ord for ArrayKey {
76 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
77 match (self, other) {
78 (ArrayKey::Int(a), ArrayKey::Int(b)) => a.cmp(b),
79 (ArrayKey::String(a), ArrayKey::String(b)) => a.cmp(b),
80 (ArrayKey::Int(_), ArrayKey::String(_)) => std::cmp::Ordering::Less,
82 (ArrayKey::String(_), ArrayKey::Int(_)) => std::cmp::Ordering::Greater,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
88pub struct KeyedProperty {
89 pub ty: Type,
90 pub optional: bool,
91}
92
93type KeyedMap = IndexMap<ArrayKey, KeyedProperty, rustc_hash::FxBuildHasher>;
94
95#[derive(Default, Serialize, Deserialize)]
99#[serde(transparent)]
100pub struct KeyedProperties {
101 map: KeyedMap,
102 #[serde(skip)]
103 hash: std::sync::OnceLock<u64>,
104}
105
106impl KeyedProperties {
107 pub fn with_capacity_and_hasher(n: usize, hasher: rustc_hash::FxBuildHasher) -> Self {
108 KeyedMap::with_capacity_and_hasher(n, hasher).into()
109 }
110
111 pub fn entries_hash(&self) -> u64 {
113 *self.hash.get_or_init(|| {
114 self.map.iter().fold(0u64, |combined, (k, v)| {
115 let mut entry_hasher = rustc_hash::FxHasher::default();
116 k.hash(&mut entry_hasher);
117 v.hash(&mut entry_hasher);
118 combined.wrapping_add(std::hash::Hasher::finish(&entry_hasher))
119 })
120 })
121 }
122}
123
124impl From<KeyedMap> for KeyedProperties {
125 fn from(map: KeyedMap) -> Self {
126 KeyedProperties {
127 map,
128 hash: std::sync::OnceLock::new(),
129 }
130 }
131}
132
133impl Clone for KeyedProperties {
134 fn clone(&self) -> Self {
135 KeyedProperties {
136 map: self.map.clone(),
137 hash: self.hash.clone(),
138 }
139 }
140}
141
142impl std::fmt::Debug for KeyedProperties {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 self.map.fmt(f)
145 }
146}
147
148impl PartialEq for KeyedProperties {
149 fn eq(&self, other: &Self) -> bool {
150 if let (Some(a), Some(b)) = (self.hash.get(), other.hash.get()) {
152 if a != b {
153 return false;
154 }
155 }
156 self.map == other.map
157 }
158}
159
160impl Eq for KeyedProperties {}
161
162impl std::ops::Deref for KeyedProperties {
163 type Target = KeyedMap;
164 fn deref(&self) -> &KeyedMap {
165 &self.map
166 }
167}
168
169impl std::ops::DerefMut for KeyedProperties {
170 fn deref_mut(&mut self) -> &mut KeyedMap {
171 self.hash = std::sync::OnceLock::new();
172 &mut self.map
173 }
174}
175
176impl FromIterator<(ArrayKey, KeyedProperty)> for KeyedProperties {
177 fn from_iter<I: IntoIterator<Item = (ArrayKey, KeyedProperty)>>(iter: I) -> Self {
178 KeyedMap::from_iter(iter).into()
179 }
180}
181
182impl Extend<(ArrayKey, KeyedProperty)> for KeyedProperties {
183 fn extend<I: IntoIterator<Item = (ArrayKey, KeyedProperty)>>(&mut self, iter: I) {
184 std::ops::DerefMut::deref_mut(self).extend(iter);
185 }
186}
187
188impl IntoIterator for KeyedProperties {
189 type Item = (ArrayKey, KeyedProperty);
190 type IntoIter = indexmap::map::IntoIter<ArrayKey, KeyedProperty>;
191 fn into_iter(self) -> Self::IntoIter {
192 self.map.into_iter()
193 }
194}
195
196impl<'a> IntoIterator for &'a KeyedProperties {
197 type Item = (&'a ArrayKey, &'a KeyedProperty);
198 type IntoIter = indexmap::map::Iter<'a, ArrayKey, KeyedProperty>;
199 fn into_iter(self) -> Self::IntoIter {
200 self.map.iter()
201 }
202}
203
204impl<'a> IntoIterator for &'a mut KeyedProperties {
205 type Item = (&'a ArrayKey, &'a mut KeyedProperty);
206 type IntoIter = indexmap::map::IterMut<'a, ArrayKey, KeyedProperty>;
207 fn into_iter(self) -> Self::IntoIter {
208 std::ops::DerefMut::deref_mut(self).iter_mut()
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
218pub struct ClosureData {
219 pub params: Box<[FnParam]>,
220 pub return_type: Type,
221 pub this_type: Option<Type>,
222 pub is_pure: bool,
224 pub is_static: bool,
226 #[serde(default)]
228 pub rejects_surplus_args: bool,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
233pub struct ConditionalData {
234 pub param_name: Option<Name>,
237 pub subject: Type,
238 pub if_true: Type,
239 pub if_false: Type,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub enum Atomic {
248 TString,
251 TLiteralString(Arc<str>),
253 TCallableString,
255 TClassString(Option<Name>),
257 TNonEmptyString,
259 TNumericString,
261
262 TInt,
264 TLiteralInt(i64),
266 TIntRange { min: Option<i64>, max: Option<i64> },
268 TPositiveInt,
270 TNegativeInt,
272 TNonNegativeInt,
274
275 TFloat,
277 TIntegralFloat,
281 TLiteralFloat(i64, i64), TBool,
286 TTrue,
288 TFalse,
290
291 TNull,
293
294 TVoid,
297 TNever,
299 TMixed,
301 TScalar,
303 TNumeric,
305
306 TObject,
309 TNamedObject {
311 fqcn: Name,
312 type_params: Arc<[Type]>,
314 },
315 TStaticObject { fqcn: Name },
317 TSelf { fqcn: Name },
319 TParent { fqcn: Name },
321
322 TCallable {
325 params: Option<Box<Vec<FnParam>>>,
329 return_type: Option<Box<Type>>,
330 is_pure: bool,
332 },
333 TClosure { data: Box<ClosureData> },
336
337 TArray { key: Box<Type>, value: Box<Type> },
340 TList { value: Box<Type> },
342 TNonEmptyArray { key: Box<Type>, value: Box<Type> },
344 TNonEmptyList { value: Box<Type> },
346 TKeyedArray {
348 properties: Arc<KeyedProperties>,
350 is_open: bool,
352 is_list: bool,
354 },
355
356 TTemplateParam {
359 name: Name,
360 as_type: Box<Type>,
361 defining_entity: Name,
363 },
364 TKeyOf { target: Box<Type> },
366 TValueOf { target: Box<Type> },
368 TConditional { data: Box<ConditionalData> },
371
372 TInterfaceString(Option<Name>),
375 TEnumString,
377 TTraitString,
379
380 TLiteralEnumCase { enum_fqcn: Name, case_name: Name },
383
384 TIntersection { parts: Arc<[Type]> },
387
388 TObjectShape { properties: Arc<KeyedProperties> },
390}
391
392impl Atomic {
393 pub fn can_be_falsy(&self) -> bool {
395 match self {
396 Atomic::TNull
397 | Atomic::TFalse
398 | Atomic::TBool
399 | Atomic::TNever
400 | Atomic::TLiteralInt(0)
401 | Atomic::TLiteralFloat(0, 0)
402 | Atomic::TInt
403 | Atomic::TFloat
404 | Atomic::TNumeric
405 | Atomic::TScalar
406 | Atomic::TMixed
407 | Atomic::TString
408 | Atomic::TNonEmptyString | Atomic::TNumericString | Atomic::TKeyOf { .. }
411 | Atomic::TValueOf { .. }
412 | Atomic::TArray { .. }
413 | Atomic::TList { .. } => true,
414 Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
418
419 Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
420
421 Atomic::TIntRange { min, max } => {
423 min.is_none_or(|lo| lo <= 0) && max.is_none_or(|hi| hi >= 0)
424 }
425 Atomic::TNonNegativeInt => true,
427
428 _ => false,
429 }
430 }
431
432 pub fn can_be_truthy(&self) -> bool {
434 match self {
435 Atomic::TNever
436 | Atomic::TVoid
437 | Atomic::TNull
438 | Atomic::TFalse
439 | Atomic::TLiteralInt(0)
440 | Atomic::TLiteralFloat(0, 0) => false,
441 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
442 Atomic::TIntRange {
444 min: Some(0),
445 max: Some(0),
446 } => false,
447 Atomic::TKeyedArray {
449 properties,
450 is_open,
451 ..
452 } if !is_open && properties.is_empty() => false,
453 _ => true,
454 }
455 }
456
457 pub fn is_numeric(&self) -> bool {
459 matches!(
460 self,
461 Atomic::TInt
462 | Atomic::TLiteralInt(_)
463 | Atomic::TIntRange { .. }
464 | Atomic::TPositiveInt
465 | Atomic::TNegativeInt
466 | Atomic::TNonNegativeInt
467 | Atomic::TFloat
468 | Atomic::TIntegralFloat
469 | Atomic::TLiteralFloat(..)
470 | Atomic::TNumeric
471 | Atomic::TNumericString
472 )
473 }
474
475 pub fn is_int(&self) -> bool {
477 matches!(
478 self,
479 Atomic::TInt
480 | Atomic::TLiteralInt(_)
481 | Atomic::TIntRange { .. }
482 | Atomic::TPositiveInt
483 | Atomic::TNegativeInt
484 | Atomic::TNonNegativeInt
485 )
486 }
487
488 pub fn is_string(&self) -> bool {
490 matches!(
491 self,
492 Atomic::TString
493 | Atomic::TLiteralString(_)
494 | Atomic::TCallableString
495 | Atomic::TClassString(_)
496 | Atomic::TNonEmptyString
497 | Atomic::TNumericString
498 | Atomic::TInterfaceString(_)
499 | Atomic::TEnumString
500 | Atomic::TTraitString
501 )
502 }
503
504 pub fn is_array(&self) -> bool {
506 matches!(
507 self,
508 Atomic::TArray { .. }
509 | Atomic::TList { .. }
510 | Atomic::TNonEmptyArray { .. }
511 | Atomic::TNonEmptyList { .. }
512 | Atomic::TKeyedArray { .. }
513 )
514 }
515
516 pub fn is_object(&self) -> bool {
518 matches!(
519 self,
520 Atomic::TObject
521 | Atomic::TObjectShape { .. }
522 | Atomic::TNamedObject { .. }
523 | Atomic::TStaticObject { .. }
524 | Atomic::TSelf { .. }
525 | Atomic::TParent { .. }
526 | Atomic::TIntersection { .. }
527 )
528 }
529
530 pub fn is_definitely_non_object(&self) -> bool {
535 matches!(
536 self,
537 Atomic::TString
538 | Atomic::TLiteralString(_)
539 | Atomic::TCallableString
540 | Atomic::TClassString(_)
541 | Atomic::TNonEmptyString
542 | Atomic::TNumericString
543 | Atomic::TInterfaceString(_)
544 | Atomic::TEnumString
545 | Atomic::TTraitString
546 | Atomic::TInt
547 | Atomic::TLiteralInt(_)
548 | Atomic::TIntRange { .. }
549 | Atomic::TPositiveInt
550 | Atomic::TNegativeInt
551 | Atomic::TNonNegativeInt
552 | Atomic::TFloat
553 | Atomic::TIntegralFloat
554 | Atomic::TLiteralFloat(..)
555 | Atomic::TBool
556 | Atomic::TTrue
557 | Atomic::TFalse
558 | Atomic::TNull
559 | Atomic::TVoid
560 | Atomic::TArray { .. }
561 | Atomic::TList { .. }
562 | Atomic::TNonEmptyArray { .. }
563 | Atomic::TNonEmptyList { .. }
564 | Atomic::TKeyedArray { .. }
565 | Atomic::TScalar
566 | Atomic::TNumeric
567 )
568 }
569
570 pub fn is_callable(&self) -> bool {
575 matches!(
576 self,
577 Atomic::TCallable { .. } | Atomic::TClosure { .. } | Atomic::TCallableString
578 )
579 }
580
581 pub fn named_object_fqcn(&self) -> Option<&str> {
583 match self {
584 Atomic::TNamedObject { fqcn, .. }
585 | Atomic::TStaticObject { fqcn }
586 | Atomic::TSelf { fqcn }
587 | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
588 _ => None,
589 }
590 }
591
592 pub fn type_name(&self) -> &'static str {
594 match self {
595 Atomic::TString
596 | Atomic::TLiteralString(_)
597 | Atomic::TNonEmptyString
598 | Atomic::TNumericString => "string",
599 Atomic::TCallableString => "callable-string",
600 Atomic::TClassString(_) => "class-string",
601 Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
602 Atomic::TPositiveInt => "positive-int",
603 Atomic::TNegativeInt => "negative-int",
604 Atomic::TNonNegativeInt => "non-negative-int",
605 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..) => "float",
606 Atomic::TBool => "bool",
607 Atomic::TTrue => "true",
608 Atomic::TFalse => "false",
609 Atomic::TNull => "null",
610 Atomic::TVoid => "void",
611 Atomic::TNever => "never",
612 Atomic::TMixed => "mixed",
613 Atomic::TScalar => "scalar",
614 Atomic::TNumeric => "numeric",
615 Atomic::TObject => "object",
616 Atomic::TNamedObject { .. } => "object",
617 Atomic::TStaticObject { .. } => "static",
618 Atomic::TSelf { .. } => "self",
619 Atomic::TParent { .. } => "parent",
620 Atomic::TCallable { .. } => "callable",
621 Atomic::TClosure { .. } => "Closure",
622 Atomic::TArray { .. } => "array",
623 Atomic::TList { .. } => "list",
624 Atomic::TNonEmptyArray { .. } => "non-empty-array",
625 Atomic::TNonEmptyList { .. } => "non-empty-list",
626 Atomic::TKeyedArray { .. } => "array",
627 Atomic::TTemplateParam { .. } => "template-param",
628 Atomic::TKeyOf { .. } => "key-of",
629 Atomic::TValueOf { .. } => "value-of",
630 Atomic::TConditional { .. } => "conditional-type",
631 Atomic::TInterfaceString(_) => "interface-string",
632 Atomic::TEnumString => "enum-string",
633 Atomic::TTraitString => "trait-string",
634 Atomic::TLiteralEnumCase { .. } => "enum-case",
635 Atomic::TIntersection { .. } => "intersection",
636 Atomic::TObjectShape { .. } => "object",
637 }
638 }
639}
640
641impl Hash for FnParam {
646 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
647 self.name.hash(state);
648 self.ty.hash(state);
649 self.out_ty.hash(state);
650 self.default.hash(state);
651 self.is_variadic.hash(state);
652 self.is_byref.hash(state);
653 self.is_optional.hash(state);
654 }
655}
656
657#[allow(non_camel_case_types, clippy::enum_variant_names)]
659#[repr(u8)]
660enum AtomicTag {
661 TString = 0,
662 TLiteralString,
663 TCallableString,
664 TClassString,
665 TNonEmptyString,
666 TNumericString,
667 TInt,
668 TLiteralInt,
669 TIntRange,
670 TPositiveInt,
671 TNegativeInt,
672 TNonNegativeInt,
673 TFloat,
674 TIntegralFloat,
675 TLiteralFloat,
676 TBool,
677 TTrue,
678 TFalse,
679 TNull,
680 TVoid,
681 TNever,
682 TMixed,
683 TScalar,
684 TNumeric,
685 TObject,
686 TNamedObject,
687 TStaticObject,
688 TSelf,
689 TParent,
690 TCallable,
691 TClosure,
692 TArray,
693 TList,
694 TNonEmptyArray,
695 TNonEmptyList,
696 TKeyedArray,
697 TTemplateParam,
698 TKeyOf,
699 TValueOf,
700 TConditional,
701 TInterfaceString,
702 TEnumString,
703 TTraitString,
704 TLiteralEnumCase,
705 TIntersection,
706 TObjectShape,
707}
708
709impl Hash for Atomic {
710 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
711 use AtomicTag as T;
712 match self {
713 Atomic::TString => (T::TString as u8).hash(state),
715 Atomic::TCallableString => (T::TCallableString as u8).hash(state),
716 Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
717 Atomic::TNumericString => (T::TNumericString as u8).hash(state),
718 Atomic::TInt => (T::TInt as u8).hash(state),
719 Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
720 Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
721 Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
722 Atomic::TFloat => (T::TFloat as u8).hash(state),
723 Atomic::TIntegralFloat => (T::TIntegralFloat as u8).hash(state),
724 Atomic::TBool => (T::TBool as u8).hash(state),
725 Atomic::TTrue => (T::TTrue as u8).hash(state),
726 Atomic::TFalse => (T::TFalse as u8).hash(state),
727 Atomic::TNull => (T::TNull as u8).hash(state),
728 Atomic::TVoid => (T::TVoid as u8).hash(state),
729 Atomic::TNever => (T::TNever as u8).hash(state),
730 Atomic::TMixed => (T::TMixed as u8).hash(state),
731 Atomic::TScalar => (T::TScalar as u8).hash(state),
732 Atomic::TNumeric => (T::TNumeric as u8).hash(state),
733 Atomic::TObject => (T::TObject as u8).hash(state),
734 Atomic::TEnumString => (T::TEnumString as u8).hash(state),
735 Atomic::TTraitString => (T::TTraitString as u8).hash(state),
736
737 Atomic::TLiteralString(s) => {
739 (T::TLiteralString as u8).hash(state);
740 s.hash(state);
741 }
742 Atomic::TClassString(opt) => {
743 (T::TClassString as u8).hash(state);
744 opt.hash(state);
745 }
746 Atomic::TInterfaceString(opt) => {
747 (T::TInterfaceString as u8).hash(state);
748 opt.hash(state);
749 }
750 Atomic::TLiteralInt(n) => {
751 (T::TLiteralInt as u8).hash(state);
752 n.hash(state);
753 }
754 Atomic::TIntRange { min, max } => {
755 (T::TIntRange as u8).hash(state);
756 min.hash(state);
757 max.hash(state);
758 }
759 Atomic::TLiteralFloat(int_bits, frac_bits) => {
760 (T::TLiteralFloat as u8).hash(state);
761 int_bits.hash(state);
762 frac_bits.hash(state);
763 }
764 Atomic::TNamedObject { fqcn, type_params } => {
765 (T::TNamedObject as u8).hash(state);
766 fqcn.hash(state);
767 type_params.hash(state);
768 }
769 Atomic::TStaticObject { fqcn } => {
770 (T::TStaticObject as u8).hash(state);
771 fqcn.hash(state);
772 }
773 Atomic::TSelf { fqcn } => {
774 (T::TSelf as u8).hash(state);
775 fqcn.hash(state);
776 }
777 Atomic::TParent { fqcn } => {
778 (T::TParent as u8).hash(state);
779 fqcn.hash(state);
780 }
781 Atomic::TCallable {
782 params,
783 return_type,
784 is_pure,
785 } => {
786 (T::TCallable as u8).hash(state);
787 params.hash(state);
788 return_type.hash(state);
789 is_pure.hash(state);
790 }
791 Atomic::TClosure { data } => {
792 (T::TClosure as u8).hash(state);
793 data.hash(state);
794 }
795 Atomic::TArray { key, value } => {
796 (T::TArray as u8).hash(state);
797 key.hash(state);
798 value.hash(state);
799 }
800 Atomic::TList { value } => {
801 (T::TList as u8).hash(state);
802 value.hash(state);
803 }
804 Atomic::TNonEmptyArray { key, value } => {
805 (T::TNonEmptyArray as u8).hash(state);
806 key.hash(state);
807 value.hash(state);
808 }
809 Atomic::TNonEmptyList { value } => {
810 (T::TNonEmptyList as u8).hash(state);
811 value.hash(state);
812 }
813 Atomic::TObjectShape { properties } => {
814 (T::TObjectShape as u8).hash(state);
815 properties.entries_hash().hash(state);
816 properties.len().hash(state);
817 }
818 Atomic::TKeyedArray {
819 properties,
820 is_open,
821 is_list,
822 } => {
823 (T::TKeyedArray as u8).hash(state);
824 properties.entries_hash().hash(state);
825 properties.len().hash(state);
826 is_open.hash(state);
827 is_list.hash(state);
828 }
829 Atomic::TTemplateParam {
830 name,
831 as_type,
832 defining_entity,
833 } => {
834 (T::TTemplateParam as u8).hash(state);
835 name.hash(state);
836 as_type.hash(state);
837 defining_entity.hash(state);
838 }
839 Atomic::TKeyOf { target } => {
840 (T::TKeyOf as u8).hash(state);
841 target.hash(state);
842 }
843 Atomic::TValueOf { target } => {
844 (T::TValueOf as u8).hash(state);
845 target.hash(state);
846 }
847 Atomic::TConditional { data } => {
848 (T::TConditional as u8).hash(state);
849 data.hash(state);
850 }
851 Atomic::TLiteralEnumCase {
852 enum_fqcn,
853 case_name,
854 } => {
855 (T::TLiteralEnumCase as u8).hash(state);
856 enum_fqcn.hash(state);
857 case_name.hash(state);
858 }
859 Atomic::TIntersection { parts } => {
860 (T::TIntersection as u8).hash(state);
861 parts.hash(state);
862 }
863 }
864 }
865}