use std::hash::Hash;
use std::sync::Arc;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use crate::Union;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FnParam {
pub name: Arc<str>,
pub ty: Option<crate::compact::SimpleType>,
pub default: Option<crate::compact::SimpleType>,
pub is_variadic: bool,
pub is_byref: bool,
pub is_optional: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum Variance {
#[default]
Invariant,
Covariant,
Contravariant,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TemplateParam {
pub name: Arc<str>,
pub bound: Option<Union>,
pub variance: Variance,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ArrayKey {
String(Arc<str>),
Int(i64),
}
impl PartialOrd for ArrayKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ArrayKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(ArrayKey::Int(a), ArrayKey::Int(b)) => a.cmp(b),
(ArrayKey::String(a), ArrayKey::String(b)) => a.cmp(b),
(ArrayKey::Int(_), ArrayKey::String(_)) => std::cmp::Ordering::Less,
(ArrayKey::String(_), ArrayKey::Int(_)) => std::cmp::Ordering::Greater,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct KeyedProperty {
pub ty: Union,
pub optional: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Atomic {
TString,
TLiteralString(Arc<str>),
TClassString(Option<Arc<str>>),
TNonEmptyString,
TNumericString,
TInt,
TLiteralInt(i64),
TIntRange { min: Option<i64>, max: Option<i64> },
TPositiveInt,
TNegativeInt,
TNonNegativeInt,
TFloat,
TLiteralFloat(i64, i64), TBool,
TTrue,
TFalse,
TNull,
TVoid,
TNever,
TMixed,
TScalar,
TNumeric,
TObject,
TNamedObject {
fqcn: Arc<str>,
type_params: Vec<Union>,
},
TStaticObject { fqcn: Arc<str> },
TSelf { fqcn: Arc<str> },
TParent { fqcn: Arc<str> },
TCallable {
params: Option<Vec<FnParam>>,
return_type: Option<Box<Union>>,
},
TClosure {
params: Vec<FnParam>,
return_type: Box<Union>,
this_type: Option<Box<Union>>,
},
TArray { key: Box<Union>, value: Box<Union> },
TList { value: Box<Union> },
TNonEmptyArray { key: Box<Union>, value: Box<Union> },
TNonEmptyList { value: Box<Union> },
TKeyedArray {
properties: IndexMap<ArrayKey, KeyedProperty>,
is_open: bool,
is_list: bool,
},
TTemplateParam {
name: Arc<str>,
as_type: Box<Union>,
defining_entity: Arc<str>,
},
TConditional {
subject: Box<Union>,
if_true: Box<Union>,
if_false: Box<Union>,
},
TInterfaceString,
TEnumString,
TTraitString,
TLiteralEnumCase {
enum_fqcn: Arc<str>,
case_name: Arc<str>,
},
TIntersection { parts: Vec<Union> },
}
impl Atomic {
pub fn can_be_falsy(&self) -> bool {
match self {
Atomic::TNull
| Atomic::TFalse
| Atomic::TBool
| Atomic::TNever
| Atomic::TLiteralInt(0)
| Atomic::TLiteralFloat(0, 0)
| Atomic::TInt
| Atomic::TFloat
| Atomic::TNumeric
| Atomic::TScalar
| Atomic::TMixed
| Atomic::TString
| Atomic::TNonEmptyString
| Atomic::TArray { .. }
| Atomic::TList { .. }
| Atomic::TNonEmptyArray { .. }
| Atomic::TNonEmptyList { .. } => true,
Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
_ => false,
}
}
pub fn can_be_truthy(&self) -> bool {
match self {
Atomic::TNever
| Atomic::TVoid
| Atomic::TNull
| Atomic::TFalse
| Atomic::TLiteralInt(0)
| Atomic::TLiteralFloat(0, 0) => false,
Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
_ => true,
}
}
pub fn is_numeric(&self) -> bool {
matches!(
self,
Atomic::TInt
| Atomic::TLiteralInt(_)
| Atomic::TIntRange { .. }
| Atomic::TPositiveInt
| Atomic::TNegativeInt
| Atomic::TNonNegativeInt
| Atomic::TFloat
| Atomic::TLiteralFloat(..)
| Atomic::TNumeric
| Atomic::TNumericString
)
}
pub fn is_int(&self) -> bool {
matches!(
self,
Atomic::TInt
| Atomic::TLiteralInt(_)
| Atomic::TIntRange { .. }
| Atomic::TPositiveInt
| Atomic::TNegativeInt
| Atomic::TNonNegativeInt
)
}
pub fn is_string(&self) -> bool {
matches!(
self,
Atomic::TString
| Atomic::TLiteralString(_)
| Atomic::TClassString(_)
| Atomic::TNonEmptyString
| Atomic::TNumericString
| Atomic::TInterfaceString
| Atomic::TEnumString
| Atomic::TTraitString
)
}
pub fn is_array(&self) -> bool {
matches!(
self,
Atomic::TArray { .. }
| Atomic::TList { .. }
| Atomic::TNonEmptyArray { .. }
| Atomic::TNonEmptyList { .. }
| Atomic::TKeyedArray { .. }
)
}
pub fn is_object(&self) -> bool {
matches!(
self,
Atomic::TObject
| Atomic::TNamedObject { .. }
| Atomic::TStaticObject { .. }
| Atomic::TSelf { .. }
| Atomic::TParent { .. }
| Atomic::TIntersection { .. }
)
}
pub fn is_callable(&self) -> bool {
matches!(self, Atomic::TCallable { .. } | Atomic::TClosure { .. })
}
pub fn named_object_fqcn(&self) -> Option<&str> {
match self {
Atomic::TNamedObject { fqcn, .. }
| Atomic::TStaticObject { fqcn }
| Atomic::TSelf { fqcn }
| Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Atomic::TString
| Atomic::TLiteralString(_)
| Atomic::TNonEmptyString
| Atomic::TNumericString => "string",
Atomic::TClassString(_) => "class-string",
Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
Atomic::TPositiveInt => "positive-int",
Atomic::TNegativeInt => "negative-int",
Atomic::TNonNegativeInt => "non-negative-int",
Atomic::TFloat | Atomic::TLiteralFloat(..) => "float",
Atomic::TBool => "bool",
Atomic::TTrue => "true",
Atomic::TFalse => "false",
Atomic::TNull => "null",
Atomic::TVoid => "void",
Atomic::TNever => "never",
Atomic::TMixed => "mixed",
Atomic::TScalar => "scalar",
Atomic::TNumeric => "numeric",
Atomic::TObject => "object",
Atomic::TNamedObject { .. } => "object",
Atomic::TStaticObject { .. } => "static",
Atomic::TSelf { .. } => "self",
Atomic::TParent { .. } => "parent",
Atomic::TCallable { .. } => "callable",
Atomic::TClosure { .. } => "Closure",
Atomic::TArray { .. } => "array",
Atomic::TList { .. } => "list",
Atomic::TNonEmptyArray { .. } => "non-empty-array",
Atomic::TNonEmptyList { .. } => "non-empty-list",
Atomic::TKeyedArray { .. } => "array",
Atomic::TTemplateParam { .. } => "template-param",
Atomic::TConditional { .. } => "conditional-type",
Atomic::TInterfaceString => "interface-string",
Atomic::TEnumString => "enum-string",
Atomic::TTraitString => "trait-string",
Atomic::TLiteralEnumCase { .. } => "enum-case",
Atomic::TIntersection { .. } => "intersection",
}
}
}
impl Hash for FnParam {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.ty.hash(state);
self.default.hash(state);
self.is_variadic.hash(state);
self.is_byref.hash(state);
self.is_optional.hash(state);
}
}
#[allow(non_camel_case_types, clippy::enum_variant_names)]
#[repr(u8)]
enum AtomicTag {
TString = 0,
TLiteralString,
TClassString,
TNonEmptyString,
TNumericString,
TInt,
TLiteralInt,
TIntRange,
TPositiveInt,
TNegativeInt,
TNonNegativeInt,
TFloat,
TLiteralFloat,
TBool,
TTrue,
TFalse,
TNull,
TVoid,
TNever,
TMixed,
TScalar,
TNumeric,
TObject,
TNamedObject,
TStaticObject,
TSelf,
TParent,
TCallable,
TClosure,
TArray,
TList,
TNonEmptyArray,
TNonEmptyList,
TKeyedArray,
TTemplateParam,
TConditional,
TInterfaceString,
TEnumString,
TTraitString,
TLiteralEnumCase,
TIntersection,
}
impl Hash for Atomic {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
use AtomicTag as T;
match self {
Atomic::TString => (T::TString as u8).hash(state),
Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
Atomic::TNumericString => (T::TNumericString as u8).hash(state),
Atomic::TInt => (T::TInt as u8).hash(state),
Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
Atomic::TFloat => (T::TFloat as u8).hash(state),
Atomic::TBool => (T::TBool as u8).hash(state),
Atomic::TTrue => (T::TTrue as u8).hash(state),
Atomic::TFalse => (T::TFalse as u8).hash(state),
Atomic::TNull => (T::TNull as u8).hash(state),
Atomic::TVoid => (T::TVoid as u8).hash(state),
Atomic::TNever => (T::TNever as u8).hash(state),
Atomic::TMixed => (T::TMixed as u8).hash(state),
Atomic::TScalar => (T::TScalar as u8).hash(state),
Atomic::TNumeric => (T::TNumeric as u8).hash(state),
Atomic::TObject => (T::TObject as u8).hash(state),
Atomic::TInterfaceString => (T::TInterfaceString as u8).hash(state),
Atomic::TEnumString => (T::TEnumString as u8).hash(state),
Atomic::TTraitString => (T::TTraitString as u8).hash(state),
Atomic::TLiteralString(s) => {
(T::TLiteralString as u8).hash(state);
s.hash(state);
}
Atomic::TClassString(opt) => {
(T::TClassString as u8).hash(state);
opt.hash(state);
}
Atomic::TLiteralInt(n) => {
(T::TLiteralInt as u8).hash(state);
n.hash(state);
}
Atomic::TIntRange { min, max } => {
(T::TIntRange as u8).hash(state);
min.hash(state);
max.hash(state);
}
Atomic::TLiteralFloat(int_bits, frac_bits) => {
(T::TLiteralFloat as u8).hash(state);
int_bits.hash(state);
frac_bits.hash(state);
}
Atomic::TNamedObject { fqcn, type_params } => {
(T::TNamedObject as u8).hash(state);
fqcn.hash(state);
type_params.hash(state);
}
Atomic::TStaticObject { fqcn } => {
(T::TStaticObject as u8).hash(state);
fqcn.hash(state);
}
Atomic::TSelf { fqcn } => {
(T::TSelf as u8).hash(state);
fqcn.hash(state);
}
Atomic::TParent { fqcn } => {
(T::TParent as u8).hash(state);
fqcn.hash(state);
}
Atomic::TCallable {
params,
return_type,
} => {
(T::TCallable as u8).hash(state);
params.hash(state);
return_type.hash(state);
}
Atomic::TClosure {
params,
return_type,
this_type,
} => {
(T::TClosure as u8).hash(state);
params.hash(state);
return_type.hash(state);
this_type.hash(state);
}
Atomic::TArray { key, value } => {
(T::TArray as u8).hash(state);
key.hash(state);
value.hash(state);
}
Atomic::TList { value } => {
(T::TList as u8).hash(state);
value.hash(state);
}
Atomic::TNonEmptyArray { key, value } => {
(T::TNonEmptyArray as u8).hash(state);
key.hash(state);
value.hash(state);
}
Atomic::TNonEmptyList { value } => {
(T::TNonEmptyList as u8).hash(state);
value.hash(state);
}
Atomic::TKeyedArray {
properties,
is_open,
is_list,
} => {
(T::TKeyedArray as u8).hash(state);
let mut pairs: Vec<_> = properties.iter().collect();
pairs.sort_by_key(|(a, _)| *a);
for (k, v) in pairs {
k.hash(state);
v.hash(state);
}
is_open.hash(state);
is_list.hash(state);
}
Atomic::TTemplateParam {
name,
as_type,
defining_entity,
} => {
(T::TTemplateParam as u8).hash(state);
name.hash(state);
as_type.hash(state);
defining_entity.hash(state);
}
Atomic::TConditional {
subject,
if_true,
if_false,
} => {
(T::TConditional as u8).hash(state);
subject.hash(state);
if_true.hash(state);
if_false.hash(state);
}
Atomic::TLiteralEnumCase {
enum_fqcn,
case_name,
} => {
(T::TLiteralEnumCase as u8).hash(state);
enum_fqcn.hash(state);
case_name.hash(state);
}
Atomic::TIntersection { parts } => {
(T::TIntersection as u8).hash(state);
parts.hash(state);
}
}
}
}