Add ListItem type for Expr::List (#12529)

# Description
This PR adds a `ListItem` enum to our set of AST types. It encodes the
two possible expressions inside of list expression: a singular item or a
spread. This is similar to the existing `RecordItem` enum. Adding
`ListItem` allows us to remove the existing `Expr::Spread` case which
was previously used for list spreads. As a consequence, this guarantees
(via the type system) that spreads can only ever occur inside lists,
records, or as command args.

This PR also does a little bit of cleanup in relevant parser code.
This commit is contained in:
Ian Manske 2024-04-18 11:21:05 +00:00 committed by GitHub
parent 57b0c722c6
commit 6ccd547d81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 179 additions and 133 deletions

View File

@ -360,7 +360,6 @@ fn find_matching_block_end_in_expr(
Expr::MatchBlock(_) => None, Expr::MatchBlock(_) => None,
Expr::Nothing => None, Expr::Nothing => None,
Expr::Garbage => None, Expr::Garbage => None,
Expr::Spread(_) => None,
Expr::Table(hdr, rows) => { Expr::Table(hdr, rows) => {
if expr_last == global_cursor_offset { if expr_last == global_cursor_offset {
@ -468,7 +467,7 @@ fn find_matching_block_end_in_expr(
None None
} }
Expr::List(inner_expr) => { Expr::List(list) => {
if expr_last == global_cursor_offset { if expr_last == global_cursor_offset {
// cursor is at list end // cursor is at list end
Some(expr_first) Some(expr_first)
@ -477,8 +476,9 @@ fn find_matching_block_end_in_expr(
Some(expr_last) Some(expr_last)
} else { } else {
// cursor is inside list // cursor is inside list
for inner_expr in inner_expr { for item in list {
find_in_expr_or_continue!(inner_expr); let expr = item.expr();
find_in_expr_or_continue!(expr);
} }
None None
} }

View File

@ -1,6 +1,6 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Expression, RecordItem}, ast::{Expr, Expression, ListItem, RecordItem},
engine::StateWorkingSet, engine::StateWorkingSet,
Range, Unit, Range, Unit,
}; };
@ -253,8 +253,21 @@ fn convert_to_value(
}), }),
Expr::List(vals) => { Expr::List(vals) => {
let mut output = vec![]; let mut output = vec![];
for val in vals {
output.push(convert_to_value(val, span, original_text)?); for item in vals {
match item {
ListItem::Item(expr) => {
output.push(convert_to_value(expr, span, original_text)?);
}
ListItem::Spread(_, inner) => {
return Err(ShellError::OutsideSpannedLabeledError {
src: original_text.to_string(),
error: "Error when loading".into(),
msg: "spread operator not supported in nuon".into(),
span: inner.span,
});
}
}
} }
Ok(Value::list(output, span)) Ok(Value::list(output, span))
@ -351,12 +364,6 @@ fn convert_to_value(
msg: "signatures not supported in nuon".into(), msg: "signatures not supported in nuon".into(),
span: expr.span, span: expr.span,
}), }),
Expr::Spread(..) => Err(ShellError::OutsideSpannedLabeledError {
src: original_text.to_string(),
error: "Error when loading".into(),
msg: "spread operator not supported in nuon".into(),
span: expr.span,
}),
Expr::String(s) => Ok(Value::string(s, span)), Expr::String(s) => Ok(Value::string(s, span)),
Expr::StringInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError { Expr::StringInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError {
src: original_text.to_string(), src: original_text.to_string(),

View File

@ -1,7 +1,8 @@
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, MatchPattern, Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, ListItem,
PathMember, Pattern, Pipeline, PipelineElement, PipelineRedirection, RecordItem, MatchPattern, PathMember, Pattern, Pipeline, PipelineElement, PipelineRedirection,
RecordItem,
}, },
engine::StateWorkingSet, engine::StateWorkingSet,
DeclId, Span, VarId, DeclId, Span, VarId,
@ -377,20 +378,31 @@ pub fn flatten_expression(
let mut last_end = outer_span.start; let mut last_end = outer_span.start;
let mut output = vec![]; let mut output = vec![];
for l in list { for item in list {
let flattened = flatten_expression(working_set, l); match item {
ListItem::Item(expr) => {
let flattened = flatten_expression(working_set, expr);
if let Some(first) = flattened.first() { if let Some(first) = flattened.first() {
if first.0.start > last_end { if first.0.start > last_end {
output.push((Span::new(last_end, first.0.start), FlatShape::List)); output.push((Span::new(last_end, first.0.start), FlatShape::List));
}
}
if let Some(last) = flattened.last() {
last_end = last.0.end;
}
output.extend(flattened);
}
ListItem::Spread(_, expr) => {
let mut output = vec![(
Span::new(expr.span.start, expr.span.start + 3),
FlatShape::Operator,
)];
output.extend(flatten_expression(working_set, expr));
} }
} }
if let Some(last) = flattened.last() {
last_end = last.0.end;
}
output.extend(flattened);
} }
if last_end < outer_span.end { if last_end < outer_span.end {
@ -545,15 +557,6 @@ pub fn flatten_expression(
Expr::VarDecl(var_id) => { Expr::VarDecl(var_id) => {
vec![(expr.span, FlatShape::VarDecl(*var_id))] vec![(expr.span, FlatShape::VarDecl(*var_id))]
} }
Expr::Spread(inner_expr) => {
let mut output = vec![(
Span::new(expr.span.start, expr.span.start + 3),
FlatShape::Operator,
)];
output.extend(flatten_expression(working_set, inner_expr));
output
}
} }
} }

View File

@ -2807,9 +2807,19 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
.. ..
} = result } = result
{ {
for expr in list { for item in list {
let contents = working_set.get_span_contents(expr.span); match item {
output.push((trim_quotes(contents).to_vec(), expr.span)); ListItem::Item(expr) => {
let contents = working_set.get_span_contents(expr.span);
output.push((trim_quotes(contents).to_vec(), expr.span));
}
ListItem::Spread(_, spread) => {
working_set.error(ParseError::WrongImportPattern(
"cannot spread in an import pattern".into(),
spread.span,
))
}
}
} }
import_pattern import_pattern
@ -3823,13 +3833,7 @@ pub fn parse_list_expression(
_ => Type::Any, _ => Type::Any,
}; };
let span = Span::new(curr_span.start, spread_arg.span.end); let span = Span::new(curr_span.start, spread_arg.span.end);
let spread_expr = Expression { (ListItem::Spread(span, spread_arg), elem_ty)
expr: Expr::Spread(Box::new(spread_arg)),
span,
ty: elem_ty.clone(),
custom_completion: None,
};
(spread_expr, elem_ty)
} else { } else {
let arg = parse_multispan_value( let arg = parse_multispan_value(
working_set, working_set,
@ -3838,7 +3842,7 @@ pub fn parse_list_expression(
element_shape, element_shape,
); );
let ty = arg.ty.clone(); let ty = arg.ty.clone();
(arg, ty) (ListItem::Item(arg), ty)
}; };
if let Some(ref ctype) = contained_type { if let Some(ref ctype) = contained_type {
@ -3868,6 +3872,29 @@ pub fn parse_list_expression(
} }
} }
fn parse_table_row(
working_set: &mut StateWorkingSet,
span: Span,
) -> Result<(Vec<Expression>, Span), Span> {
let list = parse_list_expression(working_set, span, &SyntaxShape::Any);
let Expression {
expr: Expr::List(list),
span,
..
} = list
else {
unreachable!("the item must be a list")
};
list.into_iter()
.map(|item| match item {
ListItem::Item(expr) => Ok(expr),
ListItem::Spread(_, spread) => Err(spread.span),
})
.collect::<Result<_, _>>()
.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) -> Expression {
let bytes = working_set.get_span_contents(span); let bytes = working_set.get_span_contents(span);
let inner_span = { let inner_span = {
@ -3905,71 +3932,75 @@ fn parse_table_expression(working_set: &mut StateWorkingSet, span: Span) -> Expr
{ {
return parse_list_expression(working_set, span, &SyntaxShape::Any); return parse_list_expression(working_set, span, &SyntaxShape::Any);
}; };
let head = parse_list_expression(working_set, first.span, &SyntaxShape::Any); let head = parse_table_row(working_set, first.span);
let head = {
let Expression {
expr: Expr::List(vals),
..
} = head
else {
unreachable!("head must be a list by now")
};
vals
};
let errors = working_set.parse_errors.len(); let errors = working_set.parse_errors.len();
let rows = rest let (head, rows) = match head {
.iter() Ok((head, _)) => {
.fold(Vec::with_capacity(rest.len()), |mut acc, it| { let rows = rest
use std::cmp::Ordering; .iter()
let text = working_set.get_span_contents(it.span).to_vec(); .filter_map(|it| {
match text.as_slice() { use std::cmp::Ordering;
b"," => acc,
_ if !&text.starts_with(b"[") => {
let err = ParseError::LabeledErrorWithHelp {
error: String::from("Table item not list"),
label: String::from("not a list"),
span: it.span,
help: String::from("All table items must be lists"),
};
working_set.error(err);
acc
}
_ => {
let ls = parse_list_expression(working_set, it.span, &SyntaxShape::Any);
let Expression {
expr: Expr::List(item),
span,
..
} = ls
else {
unreachable!("the item must be a list")
};
match item.len().cmp(&head.len()) { match working_set.get_span_contents(it.span) {
Ordering::Less => { b"," => None,
let err = ParseError::MissingColumns(head.len(), span); text if !text.starts_with(b"[") => {
working_set.error(err); let err = ParseError::LabeledErrorWithHelp {
} error: String::from("Table item not list"),
Ordering::Greater => { label: String::from("not a list"),
let span = { span: it.span,
let start = item[head.len()].span.start; help: String::from("All table items must be lists"),
let end = span.end;
Span::new(start, end)
}; };
let err = ParseError::ExtraColumns(head.len(), span);
working_set.error(err); working_set.error(err);
None
} }
Ordering::Equal => {} _ => match parse_table_row(working_set, it.span) {
Ok((list, span)) => {
match list.len().cmp(&head.len()) {
Ordering::Less => {
let err = ParseError::MissingColumns(head.len(), span);
working_set.error(err);
}
Ordering::Greater => {
let span = {
let start = list[head.len()].span.start;
let end = span.end;
Span::new(start, end)
};
let err = ParseError::ExtraColumns(head.len(), span);
working_set.error(err);
}
Ordering::Equal => {}
}
Some(list)
}
Err(span) => {
let err = ParseError::LabeledError(
String::from("Cannot spread in a table row"),
String::from("invalid spread here"),
span,
);
working_set.error(err);
None
}
},
} }
})
.collect();
acc.push(item); (head, rows)
acc }
} Err(span) => {
} let err = ParseError::LabeledError(
}); String::from("Cannot spread in a table row"),
String::from("invalid spread here"),
span,
);
working_set.error(err);
(Vec::new(), Vec::new())
}
};
let ty = if working_set.parse_errors.len() == errors { let ty = if working_set.parse_errors.len() == errors {
let (ty, errs) = table_type(&head, &rows); let (ty, errs) = table_type(&head, &rows);
@ -5950,9 +5981,9 @@ pub fn discover_captures_in_expr(
Expr::Keyword(_, _, expr) => { Expr::Keyword(_, _, expr) => {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
} }
Expr::List(exprs) => { Expr::List(list) => {
for expr in exprs { for item in list {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; discover_captures_in_expr(working_set, item.expr(), seen, seen_blocks, output)?;
} }
} }
Expr::Operator(_) => {} Expr::Operator(_) => {}
@ -6071,9 +6102,6 @@ pub fn discover_captures_in_expr(
Expr::VarDecl(var_id) => { Expr::VarDecl(var_id) => {
seen.push(*var_id); seen.push(*var_id);
} }
Expr::Spread(expr) => {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
}
} }
Ok(()) Ok(())
} }

View File

@ -34,7 +34,7 @@ pub enum Expr {
Block(BlockId), Block(BlockId),
Closure(BlockId), Closure(BlockId),
MatchBlock(Vec<(MatchPattern, Expression)>), MatchBlock(Vec<(MatchPattern, Expression)>),
List(Vec<Expression>), List(Vec<ListItem>),
Table(Vec<Expression>, Vec<Vec<Expression>>), Table(Vec<Expression>, Vec<Vec<Expression>>),
Record(Vec<RecordItem>), Record(Vec<RecordItem>),
Keyword(Vec<u8>, Span, Box<Expression>), Keyword(Vec<u8>, Span, Box<Expression>),
@ -50,7 +50,6 @@ pub enum Expr {
Overlay(Option<BlockId>), // block ID of the overlay's origin module Overlay(Option<BlockId>), // block ID of the overlay's origin module
Signature(Box<Signature>), Signature(Box<Signature>),
StringInterpolation(Vec<Expression>), StringInterpolation(Vec<Expression>),
Spread(Box<Expression>),
Nothing, Nothing,
Garbage, Garbage,
} }
@ -99,7 +98,6 @@ impl Expr {
| Expr::ImportPattern(_) | Expr::ImportPattern(_)
| Expr::Overlay(_) | Expr::Overlay(_)
| Expr::Signature(_) | Expr::Signature(_)
| Expr::Spread(_)
| Expr::Garbage => { | Expr::Garbage => {
// These should be impossible to pipe to, // These should be impossible to pipe to,
// but even it is, the pipeline output is not used in any way. // but even it is, the pipeline output is not used in any way.
@ -129,3 +127,23 @@ pub enum RecordItem {
/// Span for the "..." and the expression that's being spread /// Span for the "..." and the expression that's being spread
Spread(Span, Expression), Spread(Span, Expression),
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ListItem {
/// A normal expression
Item(Expression),
/// Span for the "..." and the expression that's being spread
Spread(Span, Expression),
}
impl ListItem {
pub fn expr(&self) -> &Expression {
let (ListItem::Item(expr) | ListItem::Spread(_, expr)) = self;
expr
}
pub fn expr_mut(&mut self) -> &mut Expression {
let (ListItem::Item(expr) | ListItem::Spread(_, expr)) = self;
expr
}
}

View File

@ -86,13 +86,6 @@ impl Expression {
} }
} }
pub fn as_list(&self) -> Option<Vec<Expression>> {
match &self.expr {
Expr::List(list) => Some(list.clone()),
_ => None,
}
}
pub fn as_keyword(&self) -> Option<&Expression> { pub fn as_keyword(&self) -> Option<&Expression> {
match &self.expr { match &self.expr {
Expr::Keyword(_, _, expr) => Some(expr), Expr::Keyword(_, _, expr) => Some(expr),
@ -213,8 +206,8 @@ impl Expression {
Expr::Int(_) => false, Expr::Int(_) => false,
Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set), Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set),
Expr::List(list) => { Expr::List(list) => {
for l in list { for item in list {
if l.has_in_variable(working_set) { if item.expr().has_in_variable(working_set) {
return true; return true;
} }
} }
@ -304,7 +297,6 @@ impl Expression {
Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set), Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set),
Expr::Var(var_id) => *var_id == IN_VARIABLE_ID, Expr::Var(var_id) => *var_id == IN_VARIABLE_ID,
Expr::VarDecl(_) => false, Expr::VarDecl(_) => false,
Expr::Spread(expr) => expr.has_in_variable(working_set),
} }
} }
@ -394,8 +386,9 @@ impl Expression {
Expr::Int(_) => {} Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span), Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span),
Expr::List(list) => { Expr::List(list) => {
for l in list { for item in list {
l.replace_span(working_set, replaced, new_span) item.expr_mut()
.replace_span(working_set, replaced, new_span);
} }
} }
Expr::Operator(_) => {} Expr::Operator(_) => {}
@ -456,7 +449,6 @@ impl Expression {
Expr::ValueWithUnit(expr, _) => expr.replace_span(working_set, replaced, new_span), Expr::ValueWithUnit(expr, _) => expr.replace_span(working_set, replaced, new_span),
Expr::Var(_) => {} Expr::Var(_) => {}
Expr::VarDecl(_) => {} Expr::VarDecl(_) => {}
Expr::Spread(expr) => expr.replace_span(working_set, replaced, new_span),
} }
} }
} }

View File

@ -253,7 +253,6 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
Expr::Record(_) => "record".to_string(), Expr::Record(_) => "record".to_string(),
Expr::RowCondition(_) => "row condition".to_string(), Expr::RowCondition(_) => "row condition".to_string(),
Expr::Signature(_) => "signature".to_string(), Expr::Signature(_) => "signature".to_string(),
Expr::Spread(_) => "spread".to_string(),
Expr::String(_) => "string".to_string(), Expr::String(_) => "string".to_string(),
Expr::StringInterpolation(_) => "string interpolation".to_string(), Expr::StringInterpolation(_) => "string interpolation".to_string(),
Expr::Subexpression(_) => "subexpression".to_string(), Expr::Subexpression(_) => "subexpression".to_string(),

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
ast::{ ast::{
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression,
ExternalArgument, Math, Operator, RecordItem, ExternalArgument, ListItem, Math, Operator, RecordItem,
}, },
debugger::DebugContext, debugger::DebugContext,
Config, IntoInterruptiblePipelineData, Range, Record, ShellError, Span, Value, VarId, Config, IntoInterruptiblePipelineData, Range, Record, ShellError, Span, Value, VarId,
@ -40,15 +40,15 @@ pub trait Eval {
value.follow_cell_path(&cell_path.tail, false) value.follow_cell_path(&cell_path.tail, false)
} }
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)), Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
Expr::List(x) => { Expr::List(list) => {
let mut output = vec![]; let mut output = vec![];
for expr in x { for item in list {
match &expr.expr { match item {
Expr::Spread(expr) => match Self::eval::<D>(state, mut_state, expr)? { ListItem::Item(expr) => output.push(Self::eval::<D>(state, mut_state, expr)?),
Value::List { mut vals, .. } => output.append(&mut vals), ListItem::Spread(_, expr) => match Self::eval::<D>(state, mut_state, expr)? {
Value::List { vals, .. } => output.extend(vals),
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }), _ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
}, },
_ => output.push(Self::eval::<D>(state, mut_state, expr)?),
} }
} }
Ok(Value::list(output, expr.span)) Ok(Value::list(output, expr.span))
@ -295,7 +295,6 @@ pub trait Eval {
| Expr::VarDecl(_) | Expr::VarDecl(_)
| Expr::ImportPattern(_) | Expr::ImportPattern(_)
| Expr::Signature(_) | Expr::Signature(_)
| Expr::Spread(_)
| Expr::Operator(_) | Expr::Operator(_)
| Expr::Garbage => Self::unreachable(expr), | Expr::Garbage => Self::unreachable(expr),
} }