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

227 lines
7.9 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 {
Any,
Binary,
2021-09-02 01:29:43 +00:00
Block,
Bool,
2021-09-06 22:02:24 +00:00
CellPath,
Closure,
Custom(String),
2021-10-05 02:27:39 +00:00
Date,
Duration,
Error,
2021-09-02 01:29:43 +00:00
Filesize,
Float,
Int,
2021-09-02 01:29:43 +00:00
List(Box<Type>),
ListStream,
#[default]
2021-09-02 01:29:43 +00:00
Nothing,
Number,
Range,
2021-11-19 04:30:27 +00:00
Record(Vec<(String, Type)>),
Signature,
String,
Table(Vec<(String, Type)>),
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)
}
pub fn is_list(&self) -> bool {
matches!(self, Type::List(_))
}
/// 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,
}
}
Display empty records and lists (#7925) # Description Fix some issues related to #7444 1. Empty lists and records are now displayed as a small notice in a box: ![image](https://user-images.githubusercontent.com/17511668/215832023-3f8d743a-2899-416f-9109-7876ad2bbedf.png) ![image](https://user-images.githubusercontent.com/17511668/215832273-c737b8a4-af33-4c16-8dd3-bd4f0fd19b5a.png) 2. Empty records are now correctly displayed if inside of another record list or table: ![image](https://user-images.githubusercontent.com/17511668/215832597-00f0cebc-a3b6-4ce8-8373-a9340d4c7020.png) ![image](https://user-images.githubusercontent.com/17511668/215832540-ab0e2a14-b8f6-4f47-976c-42003b622ef6.png) 3. Fixed inconsistent coloring of empty list placeholder inside of lists/tables: ![image](https://user-images.githubusercontent.com/17511668/215832924-813ffe17-e04e-4301-97c3-1bdbccf1825c.png) ![image](https://user-images.githubusercontent.com/17511668/215832963-4765c4cf-3036-4bcc-81e1-ced941fa47cb.png) # User-Facing Changes `table` command now displays empty records and lists like a table with text and correctly displays empty records inside tables and lists. New behavior of displaying empty lists and records can be disabled using `table.show_empty` config option. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-02-22 16:18:33 +00:00
/// Get a string representation, without inner type specification of lists,
/// tables and records (get `list` instead of `list<any>`
pub fn get_non_specified_string(&self) -> String {
match self {
Type::Block => String::from("block"),
Type::Closure => String::from("closure"),
Type::Bool => String::from("bool"),
Type::CellPath => String::from("cell path"),
Type::Date => String::from("date"),
Type::Duration => String::from("duration"),
Type::Filesize => String::from("filesize"),
Type::Float => String::from("float"),
Type::Int => String::from("int"),
Type::Range => String::from("range"),
Type::Record(_) => String::from("record"),
Type::Table(_) => String::from("table"),
Type::List(_) => String::from("list"),
Type::Nothing => String::from("nothing"),
Type::Number => String::from("number"),
Type::String => String::from("string"),
Type::ListStream => String::from("list stream"),
Type::Any => String::from("any"),
Type::Error => String::from("error"),
Type::Binary => String::from("binary"),
Type::Custom(_) => String::from("custom"),
Type::Signature => String::from("signature"),
}
}
2022-03-07 20:08:56 +00:00
}
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(", ")
)
}
}
Type::List(l) => write!(f, "list<{l}>"),
2021-09-02 01:29:43 +00:00
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));
}
}
}
}
}