nushell/crates/nu-protocol/src/ty.rs

194 lines
6.5 KiB
Rust
Raw Normal View History

2021-10-01 05:11:49 +00:00
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
2021-10-01 05:11:49 +00:00
2021-09-02 01:29:43 +00:00
use std::fmt::Display;
2022-03-07 20:08:56 +00:00
use crate::SyntaxShape;
#[derive(Clone, Debug, Default, EnumIter, PartialEq, Eq, Serialize, Deserialize, Hash)]
2021-09-02 01:29:43 +00:00
pub enum Type {
Int,
Float,
Range,
2021-09-02 01:29:43 +00:00
Bool,
String,
Block,
Closure,
2021-09-06 22:02:24 +00:00
CellPath,
2021-09-02 01:29:43 +00:00
Duration,
2021-10-05 02:27:39 +00:00
Date,
2021-09-02 01:29:43 +00:00
Filesize,
List(Box<Type>),
Number,
#[default]
2021-09-02 01:29:43 +00:00
Nothing,
2021-11-19 04:30:27 +00:00
Record(Vec<(String, Type)>),
Table(Vec<(String, Type)>),
ListStream,
Any,
2021-09-05 23:16:27 +00:00
Error,
2021-09-23 16:42:03 +00:00
Binary,
Custom(String),
Signature,
2021-09-02 01:29:43 +00:00
}
2022-03-07 20:08:56 +00:00
impl Type {
pub fn is_subtype(&self, other: &Type) -> bool {
match (self, other) {
(t, u) if t == u => true,
(Type::Float, Type::Number) => true,
(Type::Int, Type::Number) => true,
(_, Type::Any) => true,
(Type::List(t), Type::List(u)) if t.is_subtype(u) => true, // List is covariant
// TODO: Currently Record types specify their field types. If we are
// going to continue to do that, then it might make sense to define
// a "structural subtyping" whereby r1 is a subtype of r2 is the
// fields of r1 are a "subset" of the fields of r2 (names are a
// subset and agree on types). However, if we do that, then we need
// a way to specify the supertype of all Records. For now, we define
// any Record to be a subtype of any other Record. This allows
// Record(vec![]) to be used as an ad-hoc supertype of all Records
// in command signatures. This comment applies to Tables also, with
// "columns" in place of "fields".
(Type::Record(_), Type::Record(_)) => true,
(Type::Table(_), Type::Table(_)) => true,
_ => false,
}
}
pub fn is_numeric(&self) -> bool {
matches!(self, Type::Int | Type::Float | Type::Number)
}
/// Does this type represent a data structure containing values that can be addressed using 'cell paths'?
pub fn accepts_cell_paths(&self) -> bool {
matches!(self, Type::List(_) | Type::Record(_) | Type::Table(_))
}
2022-03-07 20:08:56 +00:00
pub fn to_shape(&self) -> SyntaxShape {
match self {
Type::Int => SyntaxShape::Int,
Type::Float => SyntaxShape::Number,
Type::Range => SyntaxShape::Range,
Type::Bool => SyntaxShape::Boolean,
Type::String => SyntaxShape::String,
Type::Block => SyntaxShape::Block, // FIXME needs more accuracy
Type::Closure => SyntaxShape::Closure(None), // FIXME needs more accuracy
2022-03-07 20:08:56 +00:00
Type::CellPath => SyntaxShape::CellPath,
Type::Duration => SyntaxShape::Duration,
Type::Date => SyntaxShape::DateTime,
Type::Filesize => SyntaxShape::Filesize,
Type::List(x) => SyntaxShape::List(Box::new(x.to_shape())),
Type::Number => SyntaxShape::Number,
Type::Nothing => SyntaxShape::Nothing,
2022-03-07 20:08:56 +00:00
Type::Record(_) => SyntaxShape::Record,
Type::Table(_) => SyntaxShape::Table,
2022-03-07 20:08:56 +00:00
Type::ListStream => SyntaxShape::List(Box::new(SyntaxShape::Any)),
Type::Any => SyntaxShape::Any,
2022-03-07 20:08:56 +00:00
Type::Error => SyntaxShape::Any,
Type::Binary => SyntaxShape::Binary,
Type::Custom(_) => SyntaxShape::Any,
2022-03-07 20:08:56 +00:00
Type::Signature => SyntaxShape::Signature,
}
}
}
2021-09-02 01:29:43 +00:00
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Block => write!(f, "block"),
Type::Closure => write!(f, "closure"),
2021-09-02 01:29:43 +00:00
Type::Bool => write!(f, "bool"),
2021-09-06 22:02:24 +00:00
Type::CellPath => write!(f, "cell path"),
2021-10-05 02:27:39 +00:00
Type::Date => write!(f, "date"),
2021-09-02 01:29:43 +00:00
Type::Duration => write!(f, "duration"),
Type::Filesize => write!(f, "filesize"),
Type::Float => write!(f, "float"),
Type::Int => write!(f, "int"),
Type::Range => write!(f, "range"),
Type::Record(fields) => {
if fields.is_empty() {
write!(f, "record")
} else {
write!(
f,
"record<{}>",
fields
.iter()
.map(|(x, y)| format!("{}: {}", x, y))
.collect::<Vec<String>>()
.join(", "),
)
}
}
Type::Table(columns) => {
if columns.is_empty() {
write!(f, "table")
} else {
write!(
f,
"table<{}>",
columns
.iter()
.map(|(x, y)| format!("{}: {}", x, y))
.collect::<Vec<String>>()
.join(", ")
)
}
}
2021-09-02 01:29:43 +00:00
Type::List(l) => write!(f, "list<{}>", l),
Type::Nothing => write!(f, "nothing"),
Type::Number => write!(f, "number"),
Type::String => write!(f, "string"),
Type::ListStream => write!(f, "list stream"),
Type::Any => write!(f, "any"),
2021-09-05 23:16:27 +00:00
Type::Error => write!(f, "error"),
2021-09-23 16:42:03 +00:00
Type::Binary => write!(f, "binary"),
Type::Custom(custom) => write!(f, "{}", custom),
Type::Signature => write!(f, "signature"),
2021-09-02 01:29:43 +00:00
}
}
}
#[cfg(test)]
mod tests {
use super::Type;
use strum::IntoEnumIterator;
mod subtype_relation {
use super::*;
#[test]
fn test_reflexivity() {
for ty in Type::iter() {
assert!(ty.is_subtype(&ty));
}
}
#[test]
fn test_any_is_top_type() {
for ty in Type::iter() {
assert!(ty.is_subtype(&Type::Any));
}
}
#[test]
fn test_number_supertype() {
assert!(Type::Int.is_subtype(&Type::Number));
assert!(Type::Float.is_subtype(&Type::Number));
}
#[test]
fn test_list_covariance() {
for ty1 in Type::iter() {
for ty2 in Type::iter() {
let list_ty1 = Type::List(Box::new(ty1.clone()));
let list_ty2 = Type::List(Box::new(ty2.clone()));
assert_eq!(list_ty1.is_subtype(&list_ty2), ty1.is_subtype(&ty2));
}
}
}
}
}