mirror of
https://github.com/nushell/nushell
synced 2025-01-19 00:24:10 +00:00
a9582e1c62
# Description
Fix #9928. When parsing input/output types `nothing` was incorrectly
coerced to `any`. This is addressed by changing how
[to_type](0ad1ad4277/crates/nu-protocol/src/syntax_shape.rs (L121)
)
method handles `nothing` syntax shape. Also `range` syntax shape is
addressed the same way.
# User-Facing Changes
`nothing` and `range` are correctly displayed in help and strictly
processed by type checking. This will break definitions that were not in
fact output nothing or range and incorrect uses of functions which input
nothing or range.
Examples of correctly defined functions:
![image](https://github.com/nushell/nushell/assets/17511668/d9f73438-d8a7-487f-981a-7e791b42766e)
![image](https://github.com/nushell/nushell/assets/17511668/2d5fe3a2-94be-4d25-9522-2ea38e528fe4)
Examples of incorrect definitions and uses of functions:
![image](https://github.com/nushell/nushell/assets/17511668/6a2f9fba-abfa-47fe-8b53-bb348e532cd8)
![image](https://github.com/nushell/nushell/assets/17511668/b1fbf9f6-fd75-4b80-9f38-26cc7c2ecc25)
![image](https://github.com/nushell/nushell/assets/17511668/718ef98b-3d7a-433d-af97-39a225ef34e5)
# 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 -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
# 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.
-->
246 lines
8.3 KiB
Rust
246 lines
8.3 KiB
Rust
use std::fmt::Display;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{DeclId, Type};
|
|
|
|
/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum SyntaxShape {
|
|
/// Any syntactic form is allowed
|
|
Any,
|
|
|
|
/// A binary literal
|
|
Binary,
|
|
|
|
/// A block is allowed, eg `{start this thing}`
|
|
Block,
|
|
|
|
/// A boolean value, eg `true` or `false`
|
|
Boolean,
|
|
|
|
/// A dotted path to navigate the table
|
|
CellPath,
|
|
|
|
/// A closure is allowed, eg `{|| start this thing}`
|
|
Closure(Option<Vec<SyntaxShape>>),
|
|
|
|
/// A custom shape with custom completion logic
|
|
Custom(Box<SyntaxShape>, DeclId),
|
|
|
|
/// A datetime value, eg `2022-02-02` or `2019-10-12T07:20:50.52+00:00`
|
|
DateTime,
|
|
|
|
/// A decimal value, eg `1.0`
|
|
Decimal,
|
|
|
|
/// A directory is allowed
|
|
Directory,
|
|
|
|
/// A duration value is allowed, eg `19day`
|
|
Duration,
|
|
|
|
/// An error value
|
|
Error,
|
|
|
|
/// A general expression, eg `1 + 2` or `foo --bar`
|
|
Expression,
|
|
|
|
/// A filepath is allowed
|
|
Filepath,
|
|
|
|
/// A filesize value is allowed, eg `10kb`
|
|
Filesize,
|
|
|
|
/// A dotted path to navigate the table (including variable)
|
|
FullCellPath,
|
|
|
|
/// A glob pattern is allowed, eg `foo*`
|
|
GlobPattern,
|
|
|
|
/// Only an integer value is allowed
|
|
Int,
|
|
|
|
/// A module path pattern used for imports
|
|
ImportPattern,
|
|
|
|
/// A specific match to a word or symbol
|
|
Keyword(Vec<u8>, Box<SyntaxShape>),
|
|
|
|
/// A list is allowed, eg `[first second]`
|
|
List(Box<SyntaxShape>),
|
|
|
|
/// A general math expression, eg `1 + 2`
|
|
MathExpression,
|
|
|
|
/// A block of matches, used by `match`
|
|
MatchBlock,
|
|
|
|
/// A match pattern, eg `{a: $foo}`
|
|
MatchPattern,
|
|
|
|
/// Nothing
|
|
Nothing,
|
|
|
|
/// Only a numeric (integer or decimal) value is allowed
|
|
Number,
|
|
|
|
/// One of a list of possible items, checked in order
|
|
OneOf(Vec<SyntaxShape>),
|
|
|
|
/// An operator, eg `+`
|
|
Operator,
|
|
|
|
/// A range is allowed (eg, `1..3`)
|
|
Range,
|
|
|
|
/// A record value, eg `{x: 1, y: 2}`
|
|
Record(Vec<(String, SyntaxShape)>),
|
|
|
|
/// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1`
|
|
/// The shorthand allows us to more easily reach columns inside of the row being passed in
|
|
RowCondition,
|
|
|
|
/// A signature for a definition, `[x:int, --foo]`
|
|
Signature,
|
|
|
|
/// Strings and string-like bare words are allowed
|
|
String,
|
|
|
|
/// A table is allowed, eg `[[first, second]; [1, 2]]`
|
|
Table(Vec<(String, SyntaxShape)>),
|
|
|
|
/// A variable name, eg `$foo`
|
|
Variable,
|
|
|
|
/// A variable with optional type, `x` or `x: int`
|
|
VarWithOptType,
|
|
}
|
|
|
|
impl SyntaxShape {
|
|
pub fn to_type(&self) -> Type {
|
|
let mk_ty = |tys: &[(String, SyntaxShape)]| {
|
|
tys.iter()
|
|
.map(|(key, val)| (key.clone(), val.to_type()))
|
|
.collect()
|
|
};
|
|
|
|
match self {
|
|
SyntaxShape::Any => Type::Any,
|
|
SyntaxShape::Block => Type::Block,
|
|
SyntaxShape::Closure(_) => Type::Closure,
|
|
SyntaxShape::Binary => Type::Binary,
|
|
SyntaxShape::CellPath => Type::Any,
|
|
SyntaxShape::Custom(custom, _) => custom.to_type(),
|
|
SyntaxShape::DateTime => Type::Date,
|
|
SyntaxShape::Duration => Type::Duration,
|
|
SyntaxShape::Expression => Type::Any,
|
|
SyntaxShape::Filepath => Type::String,
|
|
SyntaxShape::Directory => Type::String,
|
|
SyntaxShape::Decimal => Type::Float,
|
|
SyntaxShape::Filesize => Type::Filesize,
|
|
SyntaxShape::FullCellPath => Type::Any,
|
|
SyntaxShape::GlobPattern => Type::String,
|
|
SyntaxShape::Error => Type::Error,
|
|
SyntaxShape::ImportPattern => Type::Any,
|
|
SyntaxShape::Int => Type::Int,
|
|
SyntaxShape::List(x) => {
|
|
let contents = x.to_type();
|
|
Type::List(Box::new(contents))
|
|
}
|
|
SyntaxShape::Keyword(_, expr) => expr.to_type(),
|
|
SyntaxShape::MatchBlock => Type::Any,
|
|
SyntaxShape::MatchPattern => Type::Any,
|
|
SyntaxShape::MathExpression => Type::Any,
|
|
SyntaxShape::Nothing => Type::Nothing,
|
|
SyntaxShape::Number => Type::Number,
|
|
SyntaxShape::OneOf(_) => Type::Any,
|
|
SyntaxShape::Operator => Type::Any,
|
|
SyntaxShape::Range => Type::Range,
|
|
SyntaxShape::Record(entries) => Type::Record(mk_ty(entries)),
|
|
SyntaxShape::RowCondition => Type::Bool,
|
|
SyntaxShape::Boolean => Type::Bool,
|
|
SyntaxShape::Signature => Type::Signature,
|
|
SyntaxShape::String => Type::String,
|
|
SyntaxShape::Table(columns) => Type::Table(mk_ty(columns)),
|
|
SyntaxShape::VarWithOptType => Type::Any,
|
|
SyntaxShape::Variable => Type::Any,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for SyntaxShape {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let mk_fmt = |tys: &[(String, SyntaxShape)]| -> String {
|
|
tys.iter()
|
|
.map(|(x, y)| format!("{x}: {y}"))
|
|
.collect::<Vec<String>>()
|
|
.join(", ")
|
|
};
|
|
|
|
match self {
|
|
SyntaxShape::Keyword(kw, shape) => {
|
|
write!(f, "\"{}\" {}", String::from_utf8_lossy(kw), shape)
|
|
}
|
|
SyntaxShape::Any => write!(f, "any"),
|
|
SyntaxShape::String => write!(f, "string"),
|
|
SyntaxShape::CellPath => write!(f, "cellpath"),
|
|
SyntaxShape::FullCellPath => write!(f, "cellpath"),
|
|
SyntaxShape::Number => write!(f, "number"),
|
|
SyntaxShape::Range => write!(f, "range"),
|
|
SyntaxShape::Int => write!(f, "int"),
|
|
SyntaxShape::Decimal => write!(f, "decimal"),
|
|
SyntaxShape::Filepath => write!(f, "path"),
|
|
SyntaxShape::Directory => write!(f, "directory"),
|
|
SyntaxShape::GlobPattern => write!(f, "glob"),
|
|
SyntaxShape::ImportPattern => write!(f, "import"),
|
|
SyntaxShape::Block => write!(f, "block"),
|
|
SyntaxShape::Closure(args) => {
|
|
if let Some(args) = args {
|
|
let arg_vec: Vec<_> = args.iter().map(|x| x.to_string()).collect();
|
|
let arg_string = arg_vec.join(", ");
|
|
write!(f, "closure({arg_string})")
|
|
} else {
|
|
write!(f, "closure()")
|
|
}
|
|
}
|
|
SyntaxShape::Binary => write!(f, "binary"),
|
|
SyntaxShape::List(x) => write!(f, "list<{x}>"),
|
|
SyntaxShape::Table(columns) => {
|
|
if columns.is_empty() {
|
|
write!(f, "table")
|
|
} else {
|
|
write!(f, "table<{}>", mk_fmt(columns))
|
|
}
|
|
}
|
|
SyntaxShape::Record(entries) => {
|
|
if entries.is_empty() {
|
|
write!(f, "record")
|
|
} else {
|
|
write!(f, "record<{}>", mk_fmt(entries))
|
|
}
|
|
}
|
|
SyntaxShape::Filesize => write!(f, "filesize"),
|
|
SyntaxShape::Duration => write!(f, "duration"),
|
|
SyntaxShape::DateTime => write!(f, "datetime"),
|
|
SyntaxShape::Operator => write!(f, "operator"),
|
|
SyntaxShape::RowCondition => write!(f, "condition"),
|
|
SyntaxShape::MathExpression => write!(f, "variable"),
|
|
SyntaxShape::Variable => write!(f, "var"),
|
|
SyntaxShape::VarWithOptType => write!(f, "vardecl"),
|
|
SyntaxShape::Signature => write!(f, "signature"),
|
|
SyntaxShape::MatchPattern => write!(f, "matchpattern"),
|
|
SyntaxShape::MatchBlock => write!(f, "matchblock"),
|
|
SyntaxShape::Expression => write!(f, "expression"),
|
|
SyntaxShape::Boolean => write!(f, "bool"),
|
|
SyntaxShape::Error => write!(f, "error"),
|
|
SyntaxShape::Custom(x, _) => write!(f, "custom<{x}>"),
|
|
SyntaxShape::OneOf(list) => {
|
|
let arg_vec: Vec<_> = list.iter().map(|x| x.to_string()).collect();
|
|
let arg_string = arg_vec.join(", ");
|
|
write!(f, "one_of({arg_string})")
|
|
}
|
|
SyntaxShape::Nothing => write!(f, "nothing"),
|
|
}
|
|
}
|
|
}
|