mirror of
https://github.com/nushell/nushell.git
synced 2025-08-15 18:42:42 +02:00
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
This commit is contained in:
@ -318,7 +318,7 @@ fn default_value11() -> TestResult {
|
||||
fn default_value12() -> TestResult {
|
||||
fail_test(
|
||||
r#"def foo [--x:int = "a"] { $x }"#,
|
||||
"default value should be int",
|
||||
"expected default value to be `int`",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -132,3 +132,133 @@ fn list_annotations_with_extra_characters() -> TestResult {
|
||||
let expected = "Extra characters in the parameter name";
|
||||
fail_test(input, expected)
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user