use std::cmp::Ordering;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SpecType {
TaggedTuple { tag: String, arity: u8 },
Tuple { arity: u8 },
Union { variants: Vec<SpecType> },
Atom { name: String },
Builtin { name: String },
TypeCall {
module: Option<String>,
name: String,
arity: u8,
},
List { element: Box<SpecType> },
Record { name: String },
Map,
Fun,
Var { name: String },
Integer { value: i64 },
Range { low: i64, high: i64 },
Binary,
Unknown,
}
impl SpecType {
pub fn canonicalise(self) -> Self {
match self {
Self::Union { variants } => {
let mut flat = Vec::new();
flatten_union(variants, &mut flat);
flat.sort_by(canonical_cmp);
flat.dedup();
if flat.len() == 1 {
flat.pop().unwrap()
} else if flat.is_empty() {
Self::Unknown
} else {
Self::Union { variants: flat }
}
}
other => other,
}
}
pub fn matches(&self, expected: &SpecType) -> bool {
if matches!(self, Self::Unknown) || matches!(expected, Self::Unknown) {
return true;
}
match (self, expected) {
(Self::Union { variants }, _) => variants.iter().any(|v| v.matches(expected)),
(_, Self::Union { variants }) => variants.iter().any(|v| self.matches(v)),
(Self::Builtin { name }, _) | (_, Self::Builtin { name })
if matches!(name.as_str(), "term" | "any" | "dynamic") =>
{
true
}
(Self::TaggedTuple { tag: a, arity: aa }, Self::TaggedTuple { tag: b, arity: bb }) => {
a == b && aa == bb
}
(Self::TaggedTuple { arity: a, .. }, Self::Tuple { arity: b })
| (Self::Tuple { arity: a }, Self::TaggedTuple { arity: b, .. }) => a == b,
(Self::Tuple { arity: a }, Self::Tuple { arity: b }) => a == b,
(Self::Atom { name: a }, Self::Atom { name: b }) => a == b,
(Self::Builtin { name: a }, Self::Builtin { name: b }) => a == b,
(Self::Atom { name: a }, Self::Builtin { name: b })
| (Self::Builtin { name: a }, Self::Atom { name: b }) => a == b,
(Self::List { .. }, Self::List { .. }) => true,
(Self::Record { name: a }, Self::Record { name: b }) => a == b,
(Self::Map, Self::Map) => true,
(Self::Fun, Self::Fun) => true,
(Self::Integer { value: a }, Self::Integer { value: b }) => a == b,
(Self::Integer { .. }, Self::Builtin { name }) => {
matches!(name.as_str(), "integer" | "non_neg_integer" | "pos_integer")
}
(Self::Builtin { name }, Self::Integer { .. }) => {
matches!(name.as_str(), "integer" | "non_neg_integer" | "pos_integer")
}
(Self::Range { low: a, high: b }, Self::Range { low: c, high: d }) => a == c && b == d,
(Self::Range { .. }, Self::Builtin { name })
| (Self::Builtin { name }, Self::Range { .. }) => {
matches!(
name.as_str(),
"integer" | "non_neg_integer" | "pos_integer" | "neg_integer" | "number"
)
}
(Self::Integer { value }, Self::Range { low, high })
| (Self::Range { low, high }, Self::Integer { value }) => low <= value && value <= high,
(Self::Binary, Self::Binary) => true,
(Self::Binary, Self::Builtin { name }) | (Self::Builtin { name }, Self::Binary) => {
matches!(
name.as_str(),
"binary" | "bitstring" | "nonempty_binary" | "nonempty_bitstring"
)
}
(
Self::TypeCall {
name: a, arity: aa, ..
},
Self::TypeCall {
name: b, arity: bb, ..
},
) => a == b && aa == bb,
(Self::Var { .. }, _) | (_, Self::Var { .. }) => true,
_ => false,
}
}
}
fn flatten_union(variants: Vec<SpecType>, out: &mut Vec<SpecType>) {
for v in variants {
if let SpecType::Union { variants: inner } = v {
flatten_union(inner, out);
} else {
out.push(v);
}
}
}
fn canonical_cmp(a: &SpecType, b: &SpecType) -> Ordering {
let order = |t: &SpecType| -> (u8, String) {
match t {
SpecType::Atom { name } => (0, name.clone()),
SpecType::Builtin { name } => (1, name.clone()),
SpecType::Integer { value } => (2, value.to_string()),
SpecType::TaggedTuple { tag, arity } => (3, format!("{tag}/{arity}")),
SpecType::Tuple { arity } => (4, arity.to_string()),
SpecType::List { .. } => (5, String::new()),
SpecType::Map => (6, String::new()),
SpecType::Fun => (7, String::new()),
SpecType::Record { name } => (8, name.clone()),
SpecType::TypeCall {
module,
name,
arity,
} => (
9,
format!("{}:{}/{}", module.as_deref().unwrap_or(""), name, arity),
),
SpecType::Var { name } => (10, name.clone()),
SpecType::Union { .. } => (11, String::new()),
SpecType::Unknown => (12, String::new()),
SpecType::Range { low, high } => (13, format!("{low}..{high}")),
SpecType::Binary => (14, String::new()),
}
};
let (ka, va) = order(a);
let (kb, vb) = order(b);
match ka.cmp(&kb) {
Ordering::Equal => va.cmp(&vb),
other => other,
}
}