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
389impl Atomic {
390 pub fn can_be_falsy(&self) -> bool {
392 match self {
393 Atomic::TNull
394 | Atomic::TFalse
395 | Atomic::TBool
396 | Atomic::TNever
397 | Atomic::TLiteralInt(0)
398 | Atomic::TLiteralFloat(0, 0)
399 | Atomic::TInt
400 | Atomic::TFloat
401 | Atomic::TNumeric
402 | Atomic::TScalar
403 | Atomic::TMixed
404 | Atomic::TString
405 | Atomic::TNonEmptyString | Atomic::TNumericString | Atomic::TKeyOf { .. }
408 | Atomic::TValueOf { .. }
409 | Atomic::TArray { .. }
410 | Atomic::TList { .. } => true,
411 Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
415
416 Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
417
418 Atomic::TIntRange { min, max } => {
420 min.is_none_or(|lo| lo <= 0) && max.is_none_or(|hi| hi >= 0)
421 }
422 Atomic::TNonNegativeInt => true,
424
425 _ => false,
426 }
427 }
428
429 pub fn can_be_truthy(&self) -> bool {
431 match self {
432 Atomic::TNever
433 | Atomic::TVoid
434 | Atomic::TNull
435 | Atomic::TFalse
436 | Atomic::TLiteralInt(0)
437 | Atomic::TLiteralFloat(0, 0) => false,
438 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
439 Atomic::TIntRange {
441 min: Some(0),
442 max: Some(0),
443 } => false,
444 Atomic::TKeyedArray {
446 properties,
447 is_open,
448 ..
449 } if !is_open && properties.is_empty() => false,
450 _ => true,
451 }
452 }
453
454 pub fn is_numeric(&self) -> bool {
456 matches!(
457 self,
458 Atomic::TInt
459 | Atomic::TLiteralInt(_)
460 | Atomic::TIntRange { .. }
461 | Atomic::TPositiveInt
462 | Atomic::TNegativeInt
463 | Atomic::TNonNegativeInt
464 | Atomic::TFloat
465 | Atomic::TIntegralFloat
466 | Atomic::TLiteralFloat(..)
467 | Atomic::TNumeric
468 | Atomic::TNumericString
469 )
470 }
471
472 pub fn is_int(&self) -> bool {
474 matches!(
475 self,
476 Atomic::TInt
477 | Atomic::TLiteralInt(_)
478 | Atomic::TIntRange { .. }
479 | Atomic::TPositiveInt
480 | Atomic::TNegativeInt
481 | Atomic::TNonNegativeInt
482 )
483 }
484
485 pub fn is_string(&self) -> bool {
487 matches!(
488 self,
489 Atomic::TString
490 | Atomic::TLiteralString(_)
491 | Atomic::TCallableString
492 | Atomic::TClassString(_)
493 | Atomic::TNonEmptyString
494 | Atomic::TNumericString
495 | Atomic::TInterfaceString(_)
496 | Atomic::TEnumString
497 | Atomic::TTraitString
498 )
499 }
500
501 pub fn is_array(&self) -> bool {
503 matches!(
504 self,
505 Atomic::TArray { .. }
506 | Atomic::TList { .. }
507 | Atomic::TNonEmptyArray { .. }
508 | Atomic::TNonEmptyList { .. }
509 | Atomic::TKeyedArray { .. }
510 )
511 }
512
513 pub fn is_object(&self) -> bool {
515 matches!(
516 self,
517 Atomic::TObject
518 | Atomic::TNamedObject { .. }
519 | Atomic::TStaticObject { .. }
520 | Atomic::TSelf { .. }
521 | Atomic::TParent { .. }
522 | Atomic::TIntersection { .. }
523 )
524 }
525
526 pub fn is_definitely_non_object(&self) -> bool {
531 matches!(
532 self,
533 Atomic::TString
534 | Atomic::TLiteralString(_)
535 | Atomic::TCallableString
536 | Atomic::TClassString(_)
537 | Atomic::TNonEmptyString
538 | Atomic::TNumericString
539 | Atomic::TInterfaceString(_)
540 | Atomic::TEnumString
541 | Atomic::TTraitString
542 | Atomic::TInt
543 | Atomic::TLiteralInt(_)
544 | Atomic::TIntRange { .. }
545 | Atomic::TPositiveInt
546 | Atomic::TNegativeInt
547 | Atomic::TNonNegativeInt
548 | Atomic::TFloat
549 | Atomic::TIntegralFloat
550 | Atomic::TLiteralFloat(..)
551 | Atomic::TBool
552 | Atomic::TTrue
553 | Atomic::TFalse
554 | Atomic::TNull
555 | Atomic::TVoid
556 | Atomic::TArray { .. }
557 | Atomic::TList { .. }
558 | Atomic::TNonEmptyArray { .. }
559 | Atomic::TNonEmptyList { .. }
560 | Atomic::TKeyedArray { .. }
561 | Atomic::TScalar
562 | Atomic::TNumeric
563 )
564 }
565
566 pub fn is_callable(&self) -> bool {
571 matches!(
572 self,
573 Atomic::TCallable { .. } | Atomic::TClosure { .. } | Atomic::TCallableString
574 )
575 }
576
577 pub fn named_object_fqcn(&self) -> Option<&str> {
579 match self {
580 Atomic::TNamedObject { fqcn, .. }
581 | Atomic::TStaticObject { fqcn }
582 | Atomic::TSelf { fqcn }
583 | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
584 _ => None,
585 }
586 }
587
588 pub fn type_name(&self) -> &'static str {
590 match self {
591 Atomic::TString
592 | Atomic::TLiteralString(_)
593 | Atomic::TNonEmptyString
594 | Atomic::TNumericString => "string",
595 Atomic::TCallableString => "callable-string",
596 Atomic::TClassString(_) => "class-string",
597 Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
598 Atomic::TPositiveInt => "positive-int",
599 Atomic::TNegativeInt => "negative-int",
600 Atomic::TNonNegativeInt => "non-negative-int",
601 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..) => "float",
602 Atomic::TBool => "bool",
603 Atomic::TTrue => "true",
604 Atomic::TFalse => "false",
605 Atomic::TNull => "null",
606 Atomic::TVoid => "void",
607 Atomic::TNever => "never",
608 Atomic::TMixed => "mixed",
609 Atomic::TScalar => "scalar",
610 Atomic::TNumeric => "numeric",
611 Atomic::TObject => "object",
612 Atomic::TNamedObject { .. } => "object",
613 Atomic::TStaticObject { .. } => "static",
614 Atomic::TSelf { .. } => "self",
615 Atomic::TParent { .. } => "parent",
616 Atomic::TCallable { .. } => "callable",
617 Atomic::TClosure { .. } => "Closure",
618 Atomic::TArray { .. } => "array",
619 Atomic::TList { .. } => "list",
620 Atomic::TNonEmptyArray { .. } => "non-empty-array",
621 Atomic::TNonEmptyList { .. } => "non-empty-list",
622 Atomic::TKeyedArray { .. } => "array",
623 Atomic::TTemplateParam { .. } => "template-param",
624 Atomic::TKeyOf { .. } => "key-of",
625 Atomic::TValueOf { .. } => "value-of",
626 Atomic::TConditional { .. } => "conditional-type",
627 Atomic::TInterfaceString(_) => "interface-string",
628 Atomic::TEnumString => "enum-string",
629 Atomic::TTraitString => "trait-string",
630 Atomic::TLiteralEnumCase { .. } => "enum-case",
631 Atomic::TIntersection { .. } => "intersection",
632 }
633 }
634}
635
636impl Hash for FnParam {
641 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
642 self.name.hash(state);
643 self.ty.hash(state);
644 self.out_ty.hash(state);
645 self.default.hash(state);
646 self.is_variadic.hash(state);
647 self.is_byref.hash(state);
648 self.is_optional.hash(state);
649 }
650}
651
652#[allow(non_camel_case_types, clippy::enum_variant_names)]
654#[repr(u8)]
655enum AtomicTag {
656 TString = 0,
657 TLiteralString,
658 TCallableString,
659 TClassString,
660 TNonEmptyString,
661 TNumericString,
662 TInt,
663 TLiteralInt,
664 TIntRange,
665 TPositiveInt,
666 TNegativeInt,
667 TNonNegativeInt,
668 TFloat,
669 TIntegralFloat,
670 TLiteralFloat,
671 TBool,
672 TTrue,
673 TFalse,
674 TNull,
675 TVoid,
676 TNever,
677 TMixed,
678 TScalar,
679 TNumeric,
680 TObject,
681 TNamedObject,
682 TStaticObject,
683 TSelf,
684 TParent,
685 TCallable,
686 TClosure,
687 TArray,
688 TList,
689 TNonEmptyArray,
690 TNonEmptyList,
691 TKeyedArray,
692 TTemplateParam,
693 TKeyOf,
694 TValueOf,
695 TConditional,
696 TInterfaceString,
697 TEnumString,
698 TTraitString,
699 TLiteralEnumCase,
700 TIntersection,
701}
702
703impl Hash for Atomic {
704 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
705 use AtomicTag as T;
706 match self {
707 Atomic::TString => (T::TString as u8).hash(state),
709 Atomic::TCallableString => (T::TCallableString as u8).hash(state),
710 Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
711 Atomic::TNumericString => (T::TNumericString as u8).hash(state),
712 Atomic::TInt => (T::TInt as u8).hash(state),
713 Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
714 Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
715 Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
716 Atomic::TFloat => (T::TFloat as u8).hash(state),
717 Atomic::TIntegralFloat => (T::TIntegralFloat as u8).hash(state),
718 Atomic::TBool => (T::TBool as u8).hash(state),
719 Atomic::TTrue => (T::TTrue as u8).hash(state),
720 Atomic::TFalse => (T::TFalse as u8).hash(state),
721 Atomic::TNull => (T::TNull as u8).hash(state),
722 Atomic::TVoid => (T::TVoid as u8).hash(state),
723 Atomic::TNever => (T::TNever as u8).hash(state),
724 Atomic::TMixed => (T::TMixed as u8).hash(state),
725 Atomic::TScalar => (T::TScalar as u8).hash(state),
726 Atomic::TNumeric => (T::TNumeric as u8).hash(state),
727 Atomic::TObject => (T::TObject as u8).hash(state),
728 Atomic::TEnumString => (T::TEnumString as u8).hash(state),
729 Atomic::TTraitString => (T::TTraitString as u8).hash(state),
730
731 Atomic::TLiteralString(s) => {
733 (T::TLiteralString as u8).hash(state);
734 s.hash(state);
735 }
736 Atomic::TClassString(opt) => {
737 (T::TClassString as u8).hash(state);
738 opt.hash(state);
739 }
740 Atomic::TInterfaceString(opt) => {
741 (T::TInterfaceString as u8).hash(state);
742 opt.hash(state);
743 }
744 Atomic::TLiteralInt(n) => {
745 (T::TLiteralInt as u8).hash(state);
746 n.hash(state);
747 }
748 Atomic::TIntRange { min, max } => {
749 (T::TIntRange as u8).hash(state);
750 min.hash(state);
751 max.hash(state);
752 }
753 Atomic::TLiteralFloat(int_bits, frac_bits) => {
754 (T::TLiteralFloat as u8).hash(state);
755 int_bits.hash(state);
756 frac_bits.hash(state);
757 }
758 Atomic::TNamedObject { fqcn, type_params } => {
759 (T::TNamedObject as u8).hash(state);
760 fqcn.hash(state);
761 type_params.hash(state);
762 }
763 Atomic::TStaticObject { fqcn } => {
764 (T::TStaticObject as u8).hash(state);
765 fqcn.hash(state);
766 }
767 Atomic::TSelf { fqcn } => {
768 (T::TSelf as u8).hash(state);
769 fqcn.hash(state);
770 }
771 Atomic::TParent { fqcn } => {
772 (T::TParent as u8).hash(state);
773 fqcn.hash(state);
774 }
775 Atomic::TCallable {
776 params,
777 return_type,
778 is_pure,
779 } => {
780 (T::TCallable as u8).hash(state);
781 params.hash(state);
782 return_type.hash(state);
783 is_pure.hash(state);
784 }
785 Atomic::TClosure { data } => {
786 (T::TClosure as u8).hash(state);
787 data.hash(state);
788 }
789 Atomic::TArray { key, value } => {
790 (T::TArray as u8).hash(state);
791 key.hash(state);
792 value.hash(state);
793 }
794 Atomic::TList { value } => {
795 (T::TList as u8).hash(state);
796 value.hash(state);
797 }
798 Atomic::TNonEmptyArray { key, value } => {
799 (T::TNonEmptyArray as u8).hash(state);
800 key.hash(state);
801 value.hash(state);
802 }
803 Atomic::TNonEmptyList { value } => {
804 (T::TNonEmptyList as u8).hash(state);
805 value.hash(state);
806 }
807 Atomic::TKeyedArray {
808 properties,
809 is_open,
810 is_list,
811 } => {
812 (T::TKeyedArray as u8).hash(state);
813 properties.entries_hash().hash(state);
814 properties.len().hash(state);
815 is_open.hash(state);
816 is_list.hash(state);
817 }
818 Atomic::TTemplateParam {
819 name,
820 as_type,
821 defining_entity,
822 } => {
823 (T::TTemplateParam as u8).hash(state);
824 name.hash(state);
825 as_type.hash(state);
826 defining_entity.hash(state);
827 }
828 Atomic::TKeyOf { target } => {
829 (T::TKeyOf as u8).hash(state);
830 target.hash(state);
831 }
832 Atomic::TValueOf { target } => {
833 (T::TValueOf as u8).hash(state);
834 target.hash(state);
835 }
836 Atomic::TConditional { data } => {
837 (T::TConditional as u8).hash(state);
838 data.hash(state);
839 }
840 Atomic::TLiteralEnumCase {
841 enum_fqcn,
842 case_name,
843 } => {
844 (T::TLiteralEnumCase as u8).hash(state);
845 enum_fqcn.hash(state);
846 case_name.hash(state);
847 }
848 Atomic::TIntersection { parts } => {
849 (T::TIntersection as u8).hash(state);
850 parts.hash(state);
851 }
852 }
853 }
854}