use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ty {
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
Bool,
StringUtf8,
Bytes,
Record(String),
RichEnum(String),
Enum(String),
Interface(String),
CallbackInterface(String),
Optional(Box<Ty>),
List(Box<Ty>),
Map(Box<Ty>, Box<Ty>),
Iterator(Box<Ty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Family {
Direct,
String,
Bytes,
Buffer,
Object {
nullable: bool,
},
Callback,
Iterator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Prim {
Bool,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
String,
Bytes,
}
impl Prim {
pub const ALL: [Prim; 13] = [
Prim::Bool,
Prim::I8,
Prim::I16,
Prim::I32,
Prim::I64,
Prim::U8,
Prim::U16,
Prim::U32,
Prim::U64,
Prim::F32,
Prim::F64,
Prim::String,
Prim::Bytes,
];
pub fn snake(self) -> &'static str {
match self {
Prim::Bool => "bool",
Prim::I8 => "i8",
Prim::I16 => "i16",
Prim::I32 => "i32",
Prim::I64 => "i64",
Prim::U8 => "u8",
Prim::U16 => "u16",
Prim::U32 => "u32",
Prim::U64 => "u64",
Prim::F32 => "f32",
Prim::F64 => "f64",
Prim::String => "string",
Prim::Bytes => "bytes",
}
}
pub fn pascal(self) -> &'static str {
match self {
Prim::Bool => "Bool",
Prim::I8 => "I8",
Prim::I16 => "I16",
Prim::I32 => "I32",
Prim::I64 => "I64",
Prim::U8 => "U8",
Prim::U16 => "U16",
Prim::U32 => "U32",
Prim::U64 => "U64",
Prim::F32 => "F32",
Prim::F64 => "F64",
Prim::String => "String",
Prim::Bytes => "Bytes",
}
}
pub fn is_numeric(self) -> bool {
!matches!(self, Prim::Bool | Prim::String | Prim::Bytes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireType<'a> {
Prim(Prim),
Object(&'a str),
Enum(&'a str),
User(&'a str),
Optional(&'a Ty),
List(&'a Ty),
Map(&'a Ty, &'a Ty),
}
impl Ty {
pub fn family(&self) -> Family {
match self {
Ty::StringUtf8 => Family::String,
Ty::Bytes => Family::Bytes,
Ty::Record(_) | Ty::RichEnum(_) | Ty::List(_) | Ty::Map(_, _) => Family::Buffer,
Ty::Interface(_) => Family::Object { nullable: false },
Ty::Optional(inner) if matches!(inner.as_ref(), Ty::Interface(_)) => {
Family::Object { nullable: true }
}
Ty::Optional(_) => Family::Buffer,
Ty::CallbackInterface(_) => Family::Callback,
Ty::Iterator(_) => Family::Iterator,
Ty::I8
| Ty::I16
| Ty::I32
| Ty::I64
| Ty::U8
| Ty::U16
| Ty::U32
| Ty::U64
| Ty::F32
| Ty::F64
| Ty::Bool
| Ty::Enum(_) => Family::Direct,
}
}
pub fn is_buffered(&self) -> bool {
self.family() == Family::Buffer
}
pub fn user_name(&self) -> Option<&str> {
match self {
Ty::Record(n)
| Ty::RichEnum(n)
| Ty::Enum(n)
| Ty::Interface(n)
| Ty::CallbackInterface(n) => Some(n),
_ => None,
}
}
pub fn interface_name(&self) -> Option<&str> {
match self {
Ty::Interface(n) => Some(n),
Ty::Optional(inner) => inner.interface_name(),
_ => None,
}
}
pub fn callback_interface_name(&self) -> Option<&str> {
match self {
Ty::CallbackInterface(n) => Some(n),
_ => None,
}
}
pub fn wire(&self) -> WireType<'_> {
match self {
Ty::Bool => WireType::Prim(Prim::Bool),
Ty::I8 => WireType::Prim(Prim::I8),
Ty::I16 => WireType::Prim(Prim::I16),
Ty::I32 => WireType::Prim(Prim::I32),
Ty::I64 => WireType::Prim(Prim::I64),
Ty::U8 => WireType::Prim(Prim::U8),
Ty::U16 => WireType::Prim(Prim::U16),
Ty::U32 => WireType::Prim(Prim::U32),
Ty::U64 => WireType::Prim(Prim::U64),
Ty::F32 => WireType::Prim(Prim::F32),
Ty::F64 => WireType::Prim(Prim::F64),
Ty::StringUtf8 => WireType::Prim(Prim::String),
Ty::Bytes => WireType::Prim(Prim::Bytes),
Ty::Interface(n) => WireType::Object(n),
Ty::Enum(name) => WireType::Enum(name),
Ty::Record(name) | Ty::RichEnum(name) => WireType::User(name),
Ty::Optional(inner) => WireType::Optional(inner),
Ty::List(inner) => WireType::List(inner),
Ty::Map(k, v) => WireType::Map(k, v),
Ty::CallbackInterface(_) | Ty::Iterator(_) => {
panic!("{self} cannot appear inside value buffers")
}
}
}
pub fn contains_user_type(&self) -> bool {
self.any(&|t| matches!(t, Ty::Record(_) | Ty::RichEnum(_)))
}
pub fn contains_object(&self) -> bool {
self.any(&|t| matches!(t, Ty::Interface(_)))
}
pub fn any(&self, pred: &dyn Fn(&Ty) -> bool) -> bool {
if pred(self) {
return true;
}
match self {
Ty::Optional(inner) | Ty::List(inner) | Ty::Iterator(inner) => inner.any(pred),
Ty::Map(k, v) => k.any(pred) || v.any(pred),
_ => false,
}
}
pub fn iterator_elem(&self) -> Option<&Ty> {
match self {
Ty::Iterator(inner) => Some(inner),
_ => None,
}
}
}
impl fmt::Display for Ty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Ty::I8 => f.write_str("i8"),
Ty::I16 => f.write_str("i16"),
Ty::I32 => f.write_str("i32"),
Ty::I64 => f.write_str("i64"),
Ty::U8 => f.write_str("u8"),
Ty::U16 => f.write_str("u16"),
Ty::U32 => f.write_str("u32"),
Ty::U64 => f.write_str("u64"),
Ty::F32 => f.write_str("f32"),
Ty::F64 => f.write_str("f64"),
Ty::Bool => f.write_str("bool"),
Ty::StringUtf8 => f.write_str("string"),
Ty::Bytes => f.write_str("bytes"),
Ty::Record(n)
| Ty::RichEnum(n)
| Ty::Enum(n)
| Ty::Interface(n)
| Ty::CallbackInterface(n) => f.write_str(n),
Ty::Optional(inner) => write!(f, "{inner}?"),
Ty::List(inner) => write!(f, "[{inner}]"),
Ty::Map(k, v) => write!(f, "{{{k}:{v}}}"),
Ty::Iterator(inner) => write!(f, "iter<{inner}>"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn families_are_total_and_agree_with_the_abi_contract() {
assert_eq!(Ty::I32.family(), Family::Direct);
assert_eq!(Ty::Enum("Color".into()).family(), Family::Direct);
assert_eq!(Ty::StringUtf8.family(), Family::String);
assert_eq!(Ty::Bytes.family(), Family::Bytes);
for ty in [
Ty::Record("C".into()),
Ty::RichEnum("S".into()),
Ty::List(Box::new(Ty::I32)),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Map(Box::new(Ty::StringUtf8), Box::new(Ty::I32)),
Ty::Optional(Box::new(Ty::I32)),
Ty::Optional(Box::new(Ty::StringUtf8)),
Ty::Optional(Box::new(Ty::Record("C".into()))),
] {
assert_eq!(ty.family(), Family::Buffer, "{ty}");
assert!(ty.is_buffered());
}
assert_eq!(
Ty::Interface("Store".into()).family(),
Family::Object { nullable: false }
);
assert_eq!(
Ty::Optional(Box::new(Ty::Interface("Store".into()))).family(),
Family::Object { nullable: true }
);
assert_eq!(
Ty::CallbackInterface("Listener".into()).family(),
Family::Callback
);
assert_eq!(Ty::Iterator(Box::new(Ty::I32)).family(), Family::Iterator);
}
#[test]
fn wire_shapes() {
assert_eq!(Ty::StringUtf8.wire(), WireType::Prim(Prim::String));
assert_eq!(Ty::Bytes.wire(), WireType::Prim(Prim::Bytes));
assert_eq!(
Ty::Interface("kv.Store".into()).wire(),
WireType::Object("kv.Store")
);
assert_eq!(Ty::RichEnum("Shape".into()).wire(), WireType::User("Shape"));
assert_eq!(Ty::Enum("Color".into()).wire(), WireType::Enum("Color"));
let list = Ty::List(Box::new(Ty::I32));
assert_eq!(list.wire(), WireType::List(&Ty::I32));
}
#[test]
#[should_panic(expected = "cannot appear inside value buffers")]
fn callback_interfaces_have_no_wire_shape() {
Ty::CallbackInterface("Listener".into()).wire();
}
#[test]
fn containment_recurses() {
assert!(Ty::Record("C".into()).contains_user_type());
assert!(Ty::Map(
Box::new(Ty::StringUtf8),
Box::new(Ty::Optional(Box::new(Ty::RichEnum("S".into()))))
)
.contains_user_type());
assert!(!Ty::List(Box::new(Ty::Enum("Color".into()))).contains_user_type());
assert!(Ty::List(Box::new(Ty::Interface("Store".into()))).contains_object());
assert!(!Ty::List(Box::new(Ty::Record("C".into()))).contains_object());
}
#[test]
fn display_is_the_idl_spelling() {
let ty = Ty::List(Box::new(Ty::Map(
Box::new(Ty::StringUtf8),
Box::new(Ty::Optional(Box::new(Ty::Record("a.Contact".into())))),
)));
assert_eq!(ty.to_string(), "[{string:a.Contact?}]");
assert_eq!(
Ty::Iterator(Box::new(Ty::Interface("Store".into()))).to_string(),
"iter<Store>"
);
assert_eq!(Prim::String.snake(), "string");
assert_eq!(Prim::I32.pascal(), "I32");
}
}