From 23fba6d2eae01ee5d9b4352010d0f40ac5d42982 Mon Sep 17 00:00:00 2001 From: Solomon Date: Wed, 6 Nov 2024 06:36:56 -0700 Subject: [PATCH] correctly parse table literals as lists (#14226) # User-Facing Changes Table literal arguments to list parameters are now correctly parsed: ```diff def a [l: list] { $l | to nuon }; a [[a]; [2]] -[[a]] +[[a]; [2]] ``` --- crates/nu-parser/src/parser.rs | 16 ++++--- tests/repl/test_signatures.rs | 84 ++++++++++------------------------ 2 files changed, 33 insertions(+), 67 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 62ad19dc79..9202045076 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2312,7 +2312,7 @@ pub fn parse_full_cell_path( } else if bytes.starts_with(b"[") { trace!("parsing: table head of full cell path"); - let output = parse_table_expression(working_set, head.span); + let output = parse_table_expression(working_set, head.span, &SyntaxShape::Any); tokens.next(); @@ -4120,7 +4120,11 @@ fn parse_table_row( .map(|exprs| (exprs, span)) } -fn parse_table_expression(working_set: &mut StateWorkingSet, span: Span) -> Expression { +fn parse_table_expression( + working_set: &mut StateWorkingSet, + span: Span, + list_element_shape: &SyntaxShape, +) -> Expression { let bytes = working_set.get_span_contents(span); let inner_span = { let start = if bytes.starts_with(b"[") { @@ -4149,13 +4153,13 @@ fn parse_table_expression(working_set: &mut StateWorkingSet, span: Span) -> Expr // Check that we have all arguments first, before trying to parse the first // in order to avoid exponential parsing time let [first, second, rest @ ..] = &tokens[..] else { - return parse_list_expression(working_set, span, &SyntaxShape::Any); + return parse_list_expression(working_set, span, list_element_shape); }; if !working_set.get_span_contents(first.span).starts_with(b"[") || second.contents != TokenContents::Semicolon || rest.is_empty() { - return parse_list_expression(working_set, span, &SyntaxShape::Any); + return parse_list_expression(working_set, span, list_element_shape); }; let head = parse_table_row(working_set, first.span); @@ -4771,7 +4775,7 @@ pub fn parse_value( } SyntaxShape::List(elem) => { if bytes.starts_with(b"[") { - parse_list_expression(working_set, span, elem) + parse_table_expression(working_set, span, elem) } else { working_set.error(ParseError::Expected("list", span)); @@ -4780,7 +4784,7 @@ pub fn parse_value( } SyntaxShape::Table(_) => { if bytes.starts_with(b"[") { - parse_table_expression(working_set, span) + parse_table_expression(working_set, span, &SyntaxShape::Any) } else { working_set.error(ParseError::Expected("table", span)); diff --git a/tests/repl/test_signatures.rs b/tests/repl/test_signatures.rs index 25b22ee1be..32de87d6c7 100644 --- a/tests/repl/test_signatures.rs +++ b/tests/repl/test_signatures.rs @@ -271,68 +271,30 @@ fn table_annotations_none() -> TestResult { run_test(input, expected) } -#[test] -fn table_annotations() -> TestResult { - let input = "def run [t: table] { $t }; run [[age]; [3]] | describe"; - let expected = "table"; - run_test(input, expected) -} +#[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; -#[test] -fn table_annotations_two_types() -> TestResult { - let input = "\ -def run [t: table] { $t }; -run [[name, age]; [nushell, 3]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_two_types_comma_sep() -> TestResult { - let input = "\ -def run [t: table] { $t }; -run [[name, age]; [nushell, 3]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_key_with_no_type() -> TestResult { - let input = "def run [t: table] { $t }; run [[name]; [nushell]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_two_types_one_with_no_type() -> TestResult { - let input = "\ -def run [t: table] { $t }; -run [[name, age]; [nushell, 3]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_two_types_both_with_no_types() -> TestResult { - let input = "\ -def run [t: table] { $t }; -run [[name, age]; [nushell, 3]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_type_inference_1() -> TestResult { - let input = "def run [t: table] { $t }; run [[age]; [2wk]] | describe"; - let expected = "table"; - run_test(input, expected) -} - -#[test] -fn table_annotations_type_inference_2() -> TestResult { - let input = "def run [t: table] { $t }; run [[size]; [2mb]] | describe"; - let expected = "table"; - run_test(input, expected) + let type_annotation = match list_annotation { + true => format!("list>"), + 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) } #[test]