use std::fmt;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
Unknown,
Never,
Null,
Bool,
Number,
String,
Keyword,
Value,
Vector(Box<Type>),
Record,
Selector,
CaptureSpec,
Tagged(Arc<str>),
TableEvent,
Event,
Fn(Vec<Type>, Box<Type>),
Stream(Box<Type>),
JsonEvents,
Text,
}
impl Type {
pub fn vector(item: Type) -> Type {
Type::Vector(Box::new(item))
}
pub fn stream(item: Type) -> Type {
Type::Stream(Box::new(item))
}
pub fn table_events() -> Type {
Type::stream(Type::TableEvent)
}
pub fn events() -> Type {
Type::stream(Type::Event)
}
pub fn tagged(name: &str) -> Type {
Type::Tagged(Arc::from(name))
}
pub fn func(params: Vec<Type>, result: Type) -> Type {
Type::Fn(params, Box::new(result))
}
pub fn func_of(n: usize) -> Type {
Type::func(vec![Type::Unknown; n], Type::Unknown)
}
pub fn kind_text(&self) -> &'static str {
match self {
Type::Null => "null",
Type::Bool => "a boolean",
Type::Number => "a number",
Type::String => "a string",
Type::Keyword => "a keyword",
Type::Value | Type::Unknown | Type::Never => "data",
Type::Vector(_) => "a vector",
Type::Record => "a record",
Type::Selector => "a selector",
Type::CaptureSpec => "a capture",
Type::Tagged(tag) if &**tag == "missing" => "missing",
Type::Tagged(_) | Type::TableEvent | Type::Event => "a tagged value",
Type::Fn(..) => "a function",
Type::Stream(_) | Type::JsonEvents => "a stream",
Type::Text => "a text",
}
}
pub fn is_affine(&self) -> bool {
matches!(self, Type::Stream(_) | Type::JsonEvents | Type::Text)
}
pub fn is_protocol(&self) -> bool {
self.is_affine()
}
pub fn is_data(&self) -> bool {
match self {
Type::Null
| Type::Bool
| Type::Number
| Type::String
| Type::Record
| Type::Value
| Type::Unknown
| Type::Never => true,
Type::Vector(item) => item.is_data(),
Type::Tagged(tag) => &**tag == "missing",
_ => false,
}
}
pub fn is_stream_or_source(&self) -> bool {
matches!(self, Type::Stream(_) | Type::JsonEvents)
}
pub fn is_stream(&self) -> bool {
matches!(self, Type::Stream(_))
}
pub fn is_textlike(&self) -> bool {
matches!(
self,
Type::String | Type::Text | Type::Value | Type::Unknown | Type::Never
)
}
pub fn accepts(&self, actual: &Type) -> bool {
match (self, actual) {
(Type::Unknown, _) | (_, Type::Unknown) | (_, Type::Never) => true,
(Type::Value, actual) => actual.is_data(),
(expected, Type::Value) => expected.is_data(),
(Type::Vector(a), Type::Vector(b)) => a.accepts(b),
(Type::Stream(a), Type::Stream(b)) => a.accepts(b),
(Type::TableEvent, Type::Tagged(tag)) => {
matches!(&**tag, "schema" | "row" | "table-end")
}
(Type::Event, Type::Tagged(tag)) => matches!(
&**tag,
"object-start" | "object-end" | "array-start" | "array-end" | "key" | "scalar"
),
(Type::Fn(ps, r), Type::Fn(qs, s)) => ps.len() == qs.len() && r.accepts(s),
(Type::JsonEvents, Type::Stream(item)) => Type::Event.accepts(item),
(a, b) => a == b,
}
}
pub fn join(a: &Type, b: &Type) -> Type {
match (a, b) {
(Type::Never, other) | (other, Type::Never) => other.clone(),
(a, b) if a == b => a.clone(),
(Type::Text, Type::String) | (Type::String, Type::Text) => Type::Text,
(Type::Vector(a), Type::Vector(b)) => Type::vector(Type::join(a, b)),
(a, b) if a.is_data() && b.is_data() => Type::Value,
_ => Type::Unknown,
}
}
pub fn item(&self) -> Option<&Type> {
match self {
Type::Vector(t) | Type::Stream(t) => Some(t),
_ => None,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Unknown => f.write_str("Unknown"),
Type::Never => f.write_str("Never"),
Type::Null => f.write_str("Null"),
Type::Bool => f.write_str("Bool"),
Type::Number => f.write_str("Number"),
Type::String => f.write_str("String"),
Type::Keyword => f.write_str("Keyword"),
Type::Value => f.write_str("Value"),
Type::Vector(t) => write!(f, "Vector<{t}>"),
Type::Record => f.write_str("Record"),
Type::Selector => f.write_str("Selector"),
Type::CaptureSpec => f.write_str("CaptureSpec"),
Type::Tagged(tag) => f.write_str(tag),
Type::TableEvent => f.write_str("TableEvent"),
Type::Event => f.write_str("Event"),
Type::Fn(params, result) => {
f.write_str("Fn(")?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{p}")?;
}
write!(f, " -> {result})")
}
Type::Stream(t) if **t == Type::TableEvent => f.write_str("TableEvents"),
Type::Stream(t) => write!(f, "Stream<{t}>"),
Type::JsonEvents => f.write_str("JsonEvents"),
Type::Text => f.write_str("Text"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unknown_and_never_are_accepted_everywhere() {
for t in [
Type::Text,
Type::JsonEvents,
Type::Record,
Type::table_events(),
] {
assert!(t.accepts(&Type::Unknown));
assert!(t.accepts(&Type::Never));
assert!(Type::Unknown.accepts(&t));
}
}
#[test]
fn value_accepts_data_and_nothing_else() {
assert!(Type::Value.accepts(&Type::Number));
assert!(Type::Value.accepts(&Type::vector(Type::String)));
assert!(Type::Value.accepts(&Type::tagged("missing")));
assert!(!Type::Value.accepts(&Type::Selector));
assert!(!Type::Value.accepts(&Type::Text));
assert!(!Type::Value.accepts(&Type::func_of(1)));
}
#[test]
fn a_value_passes_where_particular_data_is_wanted() {
for t in [
Type::Record,
Type::Number,
Type::String,
Type::Null,
Type::vector(Type::Value),
Type::vector(Type::Record),
Type::tagged("missing"),
] {
assert!(t.accepts(&Type::Value), "{t}");
}
assert!(Type::vector(Type::Record).accepts(&Type::vector(Type::Value)));
for t in [
Type::Text,
Type::Selector,
Type::Keyword,
Type::TableEvent,
Type::JsonEvents,
Type::stream(Type::Value),
Type::func_of(1),
Type::tagged("row"),
] {
assert!(!t.accepts(&Type::Value), "{t}");
}
assert!(!Type::Record.accepts(&Type::Number));
assert!(!Type::vector(Type::Value).accepts(&Type::Record));
assert!(!Type::Record.accepts(&Type::vector(Type::Value)));
}
#[test]
fn streams_and_table_events() {
let table = Type::table_events();
assert!(table.accepts(&Type::stream(Type::Unknown)));
assert!(table.accepts(&Type::stream(Type::tagged("row"))));
assert!(!table.accepts(&Type::stream(Type::tagged("selected"))));
assert!(!table.accepts(&Type::JsonEvents));
assert!(!Type::JsonEvents.accepts(&table));
let events = Type::events();
assert!(events.accepts(&Type::stream(Type::tagged("key"))));
assert!(events.accepts(&Type::stream(Type::tagged("object-end"))));
assert!(!events.accepts(&Type::stream(Type::tagged("row"))));
assert!(!table.accepts(&events));
assert!(!events.accepts(&table));
assert!(!events.accepts(&Type::JsonEvents));
assert!(Type::JsonEvents.accepts(&events));
assert!(Type::JsonEvents.accepts(&Type::stream(Type::Unknown)));
assert!(!Type::JsonEvents.accepts(&Type::stream(Type::Value)));
assert!(events.is_affine() && events.is_protocol());
assert_eq!(events.to_string(), "Stream<Event>");
assert!(table.is_affine() && Type::Text.is_affine() && !Type::String.is_affine());
assert_eq!(table.to_string(), "TableEvents");
assert_eq!(Type::stream(Type::Value).to_string(), "Stream<Value>");
assert_eq!(
Type::func(vec![Type::Record, Type::JsonEvents], table).to_string(),
"Fn(Record JsonEvents -> TableEvents)"
);
}
#[test]
fn functions_by_arity_and_result() {
let f = Type::func(vec![Type::Unknown, Type::Unknown], Type::Text);
assert!(f.accepts(&Type::func(vec![Type::Record, Type::Value], Type::Text)));
assert!(!f.accepts(&Type::func_of(1)));
assert!(Type::func_of(2).accepts(&f));
}
#[test]
fn joins() {
assert_eq!(Type::join(&Type::Never, &Type::Text), Type::Text);
assert_eq!(Type::join(&Type::Text, &Type::String), Type::Text);
assert_eq!(Type::join(&Type::Number, &Type::String), Type::Value);
assert_eq!(Type::join(&Type::Text, &Type::Record), Type::Unknown);
assert_eq!(
Type::join(&Type::vector(Type::Number), &Type::vector(Type::Null)),
Type::vector(Type::Value)
);
}
}