2024-05-13 15:37:53 +02:00
|
|
|
use crate::repl::tests::{fail_test, run_test, TestResult};
|
2024-10-22 17:40:45 +02:00
|
|
|
use rstest::rstest;
|
2023-03-24 12:54:06 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations() -> TestResult {
|
|
|
|
let input = "def run [list: list<int>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_unknown_prefix() -> TestResult {
|
|
|
|
let input = "def run [list: listint>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "unknown type";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_empty_1() -> TestResult {
|
|
|
|
let input = "def run [list: list] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_empty_2() -> TestResult {
|
|
|
|
let input = "def run [list: list<>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_empty_3() -> TestResult {
|
|
|
|
let input = "def run [list: list< >] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_empty_4() -> TestResult {
|
|
|
|
let input = "def run [list: list<\n>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_nested() -> TestResult {
|
|
|
|
let input = "def run [list: list<list<float>>] {$list | length}; run [ [2.0] [5.0] [4.0]]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_unknown_inner_type() -> TestResult {
|
|
|
|
let input = "def run [list: list<str>] {$list | length}; run ['nushell' 'nunu' 'nana']";
|
|
|
|
let expected = "unknown type";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_nested_unknown_inner() -> TestResult {
|
|
|
|
let input = "def run [list: list<list<str>>] {$list | length}; run [ [nushell] [nunu] [nana]]";
|
|
|
|
let expected = "unknown type";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_unterminated() -> TestResult {
|
|
|
|
let input = "def run [list: list<string] {$list | length}; run [nu she ll]";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_nested_unterminated() -> TestResult {
|
|
|
|
let input = "def run [list: list<list<>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_space_within_1() -> TestResult {
|
|
|
|
let input = "def run [list: list< range>] {$list | length}; run [2..32 5..<64 4..128]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_space_within_2() -> TestResult {
|
|
|
|
let input = "def run [list: list<number >] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_space_within_3() -> TestResult {
|
|
|
|
let input = "def run [list: list< int >] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_space_before() -> TestResult {
|
|
|
|
let input = "def run [list: list <int>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "expected valid variable name for this parameter";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_unknown_separators() -> TestResult {
|
|
|
|
let input = "def run [list: list<int, string>] {$list | length}; run [2 5 4]";
|
|
|
|
let expected = "unknown type";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
2023-03-24 20:57:18 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_with_default_val_1() -> TestResult {
|
|
|
|
let input = "def run [list: list<int> = [2 5 4]] {$list | length}; run";
|
|
|
|
let expected = "3";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_with_default_val_2() -> TestResult {
|
|
|
|
let input = "def run [list: list<string> = [2 5 4]] {$list | length}; run";
|
|
|
|
let expected = "Default value wrong type";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
2023-03-26 10:59:47 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn list_annotations_with_extra_characters() -> TestResult {
|
|
|
|
let input = "def run [list: list<int>extra] {$list | length}; run [1 2 3]";
|
|
|
|
let expected = "Extra characters in the parameter name";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
allow records to have type annotations (#8914)
# Description
follow up to #8529
cleaned up version of #8892
- the original syntax is okay
```nu
def okay [rec: record] {}
```
- you can now add type annotations for fields if you know
them before hand
```nu
def okay [rec: record<name: string>] {}
```
- you can specify multiple fields
```nu
def okay [person: record<name: string age: int>] {}
# an optional comma is allowed
def okay [person: record<name: string, age: int>] {}
```
- if annotations are specified, any use of the command will be type
checked against the specified type
```nu
def unwrap [result: record<ok: bool, value: any>] {}
unwrap {ok: 2, value: "value"}
# errors with
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #4:1:1]
1 │ unwrap {ok: 2, value: "value"}
· ───────┬─────
· ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string>
╰────
```
> here the error is in the `ok` field, since `any` is coerced into any
type
> as a result `unwrap {ok: true, value: "value"}` is okay
- the key must be a string, either quoted or unquoted
```nu
def err [rec: record<{}: list>] {}
# errors with
Error:
× `record` type annotations key not string
╭─[entry #7:1:1]
1 │ def unwrap [result: record<{}: bool, value: any>] {}
· ─┬
· ╰── must be a string
╰────
```
- a key doesn't have to have a type in which case it is assumed to be
`any`
```nu
def okay [person: record<name age>] {}
def okay [person: record<name: string age>] {}
```
- however, if you put a colon, you have to specify a type
```nu
def err [person: record<name: >] {}
# errors with
Error: nu::parser::parse_mismatch
× Parse mismatch during operation.
╭─[entry #12:1:1]
1 │ def unwrap [res: record<name: >] { $res }
· ┬
· ╰── expected type after colon
╰────
```
# User-Facing Changes
**[BREAKING CHANGES]**
- this change adds a field to `SyntaxShape::Record` so any plugins that
used it will have to update and include the field. though if you are
unsure of the type the record expects, `SyntaxShape::Record(vec![])`
will suffice
2023-04-26 15:16:55 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_none() -> TestResult {
|
|
|
|
let input = "def run [rec: record] { $rec }; run {} | describe";
|
|
|
|
let expected = "record";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations() -> TestResult {
|
|
|
|
let input = "def run [rec: record<age: int>] { $rec }; run {age: 3} | describe";
|
|
|
|
let expected = "record<age: int>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_two_types() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name: string age: int>] { $rec }; run {name: nushell age: 3} | describe";
|
|
|
|
let expected = "record<name: string, age: int>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_two_types_comma_sep() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name: string, age: int>] { $rec }; run {name: nushell age: 3} | describe";
|
|
|
|
let expected = "record<name: string, age: int>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_key_with_no_type() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name>] { $rec }; run {name: nushell} | describe";
|
|
|
|
let expected = "record<name: string>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_two_types_one_with_no_type() -> TestResult {
|
|
|
|
let input =
|
|
|
|
"def run [rec: record<name: string, age>] { $rec }; run {name: nushell age: 3} | describe";
|
|
|
|
let expected = "record<name: string, age: int>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_two_types_both_with_no_types() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name age>] { $rec }; run {name: nushell age: 3} | describe";
|
|
|
|
let expected = "record<name: string, age: int>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_nested() -> TestResult {
|
|
|
|
let input = "def run [
|
|
|
|
err: record<
|
|
|
|
msg: string,
|
|
|
|
label: record<
|
|
|
|
text: string
|
|
|
|
start: int,
|
|
|
|
end: int,
|
|
|
|
>>
|
|
|
|
] {
|
|
|
|
$err
|
|
|
|
}; run {
|
|
|
|
msg: 'error message'
|
|
|
|
label: {
|
|
|
|
text: 'here is the error'
|
|
|
|
start: 0
|
|
|
|
end: 69
|
|
|
|
}
|
|
|
|
} | describe";
|
|
|
|
let expected = "record<msg: string, label: record<text: string, start: int, end: int>>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_type_inference_1() -> TestResult {
|
|
|
|
let input = "def run [rec: record<age: any>] { $rec }; run {age: 2wk} | describe";
|
|
|
|
let expected = "record<age: duration>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_type_inference_2() -> TestResult {
|
|
|
|
let input = "def run [rec: record<size>] { $rec }; run {size: 2mb} | describe";
|
|
|
|
let expected = "record<size: filesize>";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_not_terminated() -> TestResult {
|
|
|
|
let input = "def run [rec: record<age: int] { $rec }";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_not_terminated_inner() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name: string, repos: list<string>] { $rec }";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_no_type_after_colon() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name: >] { $rec }";
|
|
|
|
let expected = "type after colon";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_type_mismatch_key() -> TestResult {
|
|
|
|
let input = "def run [rec: record<name: string>] { $rec }; run {nme: nushell}";
|
|
|
|
let expected = "expected record<name: string>, found record<nme: string>";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_type_mismatch_shape() -> TestResult {
|
|
|
|
let input = "def run [rec: record<age: int>] { $rec }; run {age: 2wk}";
|
|
|
|
let expected = "expected record<age: int>, found record<age: duration>";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_annotations_with_extra_characters() -> TestResult {
|
|
|
|
let input = "def run [list: record<int>extra] {$list | length}; run [1 2 3]";
|
|
|
|
let expected = "Extra characters in the parameter name";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
2023-07-07 11:06:09 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_none() -> TestResult {
|
|
|
|
let input = "def run [t: table] { $t }; run [[]; []] | describe";
|
|
|
|
let expected = "table";
|
|
|
|
run_test(input, expected)
|
|
|
|
}
|
|
|
|
|
2024-11-06 14:36:56 +01:00
|
|
|
#[rstest]
|
|
|
|
fn table_annotations(
|
|
|
|
#[values(true, false)] list_annotation: bool,
|
|
|
|
#[values(
|
|
|
|
("age: int", "age: int", "[[age]; [3]]" ),
|
|
|
|
("name: string age: int", "name: string, age: int", "[[name, age]; [nushell, 3]]" ),
|
|
|
|
("name: string, age: int", "name: string, age: int", "[[name, age]; [nushell, 3]]" ),
|
|
|
|
("name", "name: string", "[[name]; [nushell]]"),
|
|
|
|
("name: string, age", "name: string, age: int", "[[name, age]; [nushell, 3]]"),
|
|
|
|
("name, age", "name: string, age: int", "[[name, age]; [nushell, 3]]"),
|
|
|
|
("age: any", "age: duration", "[[age]; [2wk]]"),
|
|
|
|
("size", "size: filesize", "[[size]; [2mb]]")
|
|
|
|
)]
|
|
|
|
record_annotation_data: (&str, &str, &str),
|
|
|
|
) -> TestResult {
|
|
|
|
let (record_annotation, inferred_type, data) = record_annotation_data;
|
|
|
|
|
|
|
|
let type_annotation = match list_annotation {
|
|
|
|
true => format!("list<record<{record_annotation}>>"),
|
|
|
|
false => format!("table<{record_annotation}>"),
|
|
|
|
};
|
|
|
|
let input = format!("def run [t: {type_annotation}] {{ $t }}; run {data} | describe");
|
|
|
|
let expected = format!("table<{inferred_type}>");
|
|
|
|
run_test(&input, &expected)
|
2023-07-07 11:06:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_not_terminated() -> TestResult {
|
|
|
|
let input = "def run [t: table<age: int] { $t }";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_not_terminated_inner() -> TestResult {
|
|
|
|
let input = "def run [t: table<name: string, repos: list<string>] { $t }";
|
|
|
|
let expected = "expected closing >";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_no_type_after_colon() -> TestResult {
|
|
|
|
let input = "def run [t: table<name: >] { $t }";
|
|
|
|
let expected = "type after colon";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_type_mismatch_column() -> TestResult {
|
|
|
|
let input = "def run [t: table<name: string>] { $t }; run [[nme]; [nushell]]";
|
|
|
|
let expected = "expected table<name: string>, found table<nme: string>";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_type_mismatch_shape() -> TestResult {
|
|
|
|
let input = "def run [t: table<age: int>] { $t }; run [[age]; [2wk]]";
|
|
|
|
let expected = "expected table<age: int>, found table<age: duration>";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn table_annotations_with_extra_characters() -> TestResult {
|
|
|
|
let input = "def run [t: table<int>extra] {$t | length}; run [[int]; [8]]";
|
|
|
|
let expected = "Extra characters in the parameter name";
|
|
|
|
fail_test(input, expected)
|
|
|
|
}
|
2024-10-22 17:40:45 +02:00
|
|
|
|
|
|
|
#[rstest]
|
|
|
|
#[case("{ |a $a }")]
|
|
|
|
#[case("{ |a, b $a + $b }")]
|
|
|
|
#[case("do { |a $a } 1")]
|
|
|
|
#[case("do { |a $a } 1 2")]
|
|
|
|
fn closure_param_list_not_terminated(#[case] input: &str) -> TestResult {
|
|
|
|
fail_test(input, "unclosed |")
|
|
|
|
}
|