More specific errors for missing values in records (#11423)

# Description
Currently, when writing a record, if you don't give the value for a
field, the syntax error highlights the entire record instead of
pinpointing the issue. Here's some examples:

```nushell
> { a: 2, 3 } # Missing colon (and value)
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #2:1:1]
 1 │  { a: 2, 3 }
   ·  ─────┬─────
   ·       ╰── expected record
   ╰────

> { a: 2, 3: } # Missing value
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #3:1:1]
 1 │  { a: 2, 3: }
   ·  ──────┬─────
   ·        ╰── expected record
   ╰────

> { a: 2, 3 4 } # Missing colon
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[entry #4:1:1]
 1 │  { a: 2, 3 4 }
   ·  ──────┬──────
   ·        ╰── expected record
   ╰────
```

In all of them, the entire record is highlighted red because an
`Expr::Garbage` is returned covering that whole span:


![image](https://github.com/nushell/nushell/assets/45539777/36660b50-23be-4353-b180-3f84eff3c220)

This PR is for highlighting only the part inside the record that could
not be parsed. If the record literal is big, an error message pointing
to the start of where the parser thinks things went wrong should help
people fix their code.

# User-Facing Changes
Below are screenshots of the new errors:

If there's a stray record key right before the record ends, it
highlights only that key and tells the user it expected a colon after
it:


![image](https://github.com/nushell/nushell/assets/45539777/94503256-8ea2-47dd-b69a-4b520c66f7b6)

If the record ends before the value for the last field was given, it
highlights the key and colon of that field and tells the user it
expected a value after the colon:


![image](https://github.com/nushell/nushell/assets/45539777/2f3837ec-3b35-4b81-8c57-706f8056ac04)

If there are two consecutive expressions without a colon between them,
it highlights everything from the second expression to the end of the
record and tells the user it expected a colon. I was tempted to add a
help message suggesting adding a colon in between, but that may not
always be the right thing to do.


![image](https://github.com/nushell/nushell/assets/45539777/1abaaaa8-1896-4909-bbb7-9a38cece5250)

# Tests + Formatting

# After Submitting
This commit is contained in:
Yash Thakur 2023-12-27 04:15:12 -05:00 committed by GitHub
parent ba880277bf
commit 9522052063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 7 deletions

View File

@ -5272,15 +5272,43 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression
idx += 1;
if idx == tokens.len() {
working_set.error(ParseError::Expected("record", span));
return garbage(span);
working_set.error(ParseError::Expected(
"':'",
Span::new(curr_span.end, curr_span.end),
));
output.push(RecordItem::Pair(
garbage(curr_span),
garbage(Span::new(curr_span.end, curr_span.end)),
));
break;
}
let colon = working_set.get_span_contents(tokens[idx].span);
let colon_span = tokens[idx].span;
let colon = working_set.get_span_contents(colon_span);
idx += 1;
if idx == tokens.len() || colon != b":" {
//FIXME: need better error
working_set.error(ParseError::Expected("record", span));
return garbage(span);
if colon != b":" {
working_set.error(ParseError::Expected(
"':'",
Span::new(colon_span.start, colon_span.start),
));
output.push(RecordItem::Pair(
field,
garbage(Span::new(
colon_span.start,
tokens[tokens.len() - 1].span.end,
)),
));
break;
}
if idx == tokens.len() {
working_set.error(ParseError::Expected(
"value for record field",
Span::new(colon_span.end, colon_span.end),
));
output.push(RecordItem::Pair(
garbage(Span::new(curr_span.start, colon_span.end)),
garbage(Span::new(colon_span.end, tokens[tokens.len() - 1].span.end)),
));
break;
}
let value = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any);
idx += 1;

View File

@ -763,3 +763,14 @@ fn properly_typecheck_rest_param() -> TestResult {
fn implied_collect_has_compatible_type() -> TestResult {
run_test(r#"let idx = 3 | $in; $idx < 1"#, "false")
}
#[test]
fn record_expected_colon() -> TestResult {
fail_test(r#"{ a: 2 b }"#, "expected ':'")?;
fail_test(r#"{ a: 2 b 3 }"#, "expected ':'")
}
#[test]
fn record_missing_value() -> TestResult {
fail_test(r#"{ a: 2 b: }"#, "expected value for record field")
}