Limited mutable variables (#7089)

This adds support for (limited) mutable variables. Mutable variables are created with mut much the same way immutable variables are made with let.

Mutable variables allow mutation via the assignment operator (=).

❯ mut x = 100
❯ $x = 200
❯ print $x
200

Mutable variables are limited in that they're only tended to be used in the local code block. Trying to capture a local variable will result in an error:

❯ mut x = 123; {|| $x }
Error: nu::parser::expected_keyword (link)

  × Capture of mutable variable.

The intent of this limitation is to reduce some of the issues with mutable variables in general: namely they make code that's harder to reason about. By reducing the scope that a mutable variable can be used it, we can help create local reasoning about them.

Mutation can occur with fields as well, as in this case:

❯ mut y = {abc: 123}
❯ $y.abc = 456
❯ $y

On a historical note: mutable variables are something that we resisted for quite a long time, leaning as much as we could on the functional style of pipelines and dataflow. That said, we've watched folks struggle to work with reduce as an approximation for patterns that would be trivial to express with local mutation. With that in mind, we're leaning towards the happy path.
This commit is contained in:
JT
2022-11-11 19:51:08 +13:00
committed by GitHub
parent 58d960d914
commit 13515c5eb0
22 changed files with 857 additions and 387 deletions

View File

@ -56,6 +56,10 @@ pub enum ParseError {
Type,
),
#[error("Capture of mutable variable.")]
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
CaptureOfMutableVar(#[label("capture of mutable variable")] Span),
#[error("Expected keyword.")]
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
ExpectedKeyword(String, #[label("expected {0}")] Span),
@ -88,6 +92,16 @@ pub enum ParseError {
)]
LetInPipeline(String, String, #[label("let in pipeline")] Span),
#[error("Mut statement used in pipeline.")]
#[diagnostic(
code(nu::parser::unexpected_keyword),
url(docsrs),
help(
"Assigning '{0}' to '{1}' does not produce a value to be piped. If the pipeline result is meant to be assigned to '{1}', use 'mut {1} = ({0} | ...)'."
)
)]
MutInPipeline(String, String, #[label("let in pipeline")] Span),
#[error("Let used with builtin variable name.")]
#[diagnostic(
code(nu::parser::let_builtin_var),
@ -96,6 +110,14 @@ pub enum ParseError {
)]
LetBuiltinVar(String, #[label("already a builtin variable")] Span),
#[error("Mut used with builtin variable name.")]
#[diagnostic(
code(nu::parser::let_builtin_var),
url(docsrs),
help("'{0}' is the name of a builtin Nushell variable. `mut` cannot assign to it.")
)]
MutBuiltinVar(String, #[label("already a builtin variable")] Span),
#[error("Incorrect value")]
#[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))]
IncorrectValue(String, #[label("unexpected {0}")] Span, String),
@ -343,7 +365,10 @@ impl ParseError {
ParseError::UnexpectedKeyword(_, s) => *s,
ParseError::BuiltinCommandInPipeline(_, s) => *s,
ParseError::LetInPipeline(_, _, s) => *s,
ParseError::MutInPipeline(_, _, s) => *s,
ParseError::LetBuiltinVar(_, s) => *s,
ParseError::MutBuiltinVar(_, s) => *s,
ParseError::CaptureOfMutableVar(s) => *s,
ParseError::IncorrectValue(_, s, _) => *s,
ParseError::MultipleRestParams(s) => *s,
ParseError::VariableNotFound(s) => *s,

View File

@ -2781,8 +2781,12 @@ pub fn parse_let(
}
let mut idx = 0;
let (lvalue, err) =
parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx);
let (lvalue, err) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
false,
);
error = error.or(err);
let var_name =
@ -2858,6 +2862,129 @@ pub fn parse_let(
)
}
pub fn parse_mut(
working_set: &mut StateWorkingSet,
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let name = working_set.get_span_contents(spans[0]);
if name == b"mut" {
if let Some((span, err)) = check_name(working_set, spans) {
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
}
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Any) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
if spans.len() >= 4 {
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
// so that the var-id created by the variable isn't visible in the expression that init it
for span in spans.iter().enumerate() {
let item = working_set.get_span_contents(*span.1);
if item == b"=" && spans.len() > (span.0 + 1) {
let mut error = None;
let mut idx = span.0;
let (rvalue, err) = parse_multispan_value(
working_set,
spans,
&mut idx,
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
expand_aliases_denylist,
);
error = error.or(err);
if idx < (spans.len() - 1) {
error = error.or(Some(ParseError::ExtraPositional(
call_signature,
spans[idx + 1],
)));
}
let mut idx = 0;
let (lvalue, err) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
true,
);
error = error.or(err);
let var_name =
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
.to_string();
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
error =
error.or(Some(ParseError::MutBuiltinVar(var_name, lvalue.span)));
}
let var_id = lvalue.as_var();
let rhs_type = rvalue.ty.clone();
if let Some(var_id) = var_id {
working_set.set_variable_type(var_id, rhs_type);
}
let call = Box::new(Call {
decl_id,
head: spans[0],
arguments: vec![
Argument::Positional(lvalue),
Argument::Positional(rvalue),
],
redirect_stdout: true,
redirect_stderr: false,
});
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: nu_protocol::span(spans),
ty: Type::Any,
custom_completion: None,
}]),
error,
);
}
}
}
let ParsedInternalCall {
call,
error: err,
output,
} = parse_internal_call(
working_set,
spans[0],
&spans[1..],
decl_id,
expand_aliases_denylist,
);
return (
Pipeline {
expressions: vec![Expression {
expr: Expr::Call(call),
span: nu_protocol::span(spans),
ty: output,
custom_completion: None,
}],
},
err,
);
}
}
(
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: mut statement unparseable".into(),
span(spans),
)),
)
}
pub fn parse_source(
working_set: &mut StateWorkingSet,
spans: &[Span],

View File

@ -1,15 +1,16 @@
use crate::{
lex, lite_parse,
lite_parse::LiteCommand,
parse_mut,
type_check::{math_result_type, type_compatible},
LiteBlock, ParseError, Token, TokenContents,
};
use nu_protocol::{
ast::{
Argument, Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern,
ImportPatternHead, ImportPatternMember, Operator, PathMember, Pipeline, RangeInclusion,
RangeOperator,
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator,
PathMember, Pipeline, RangeInclusion, RangeOperator,
},
engine::StateWorkingSet,
span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId,
@ -633,7 +634,7 @@ pub fn parse_multispan_value(
SyntaxShape::VarWithOptType => {
trace!("parsing: var with opt type");
let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx);
let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx, false);
error = error.or(err);
(arg, error)
@ -2919,6 +2920,7 @@ pub fn parse_var_with_opt_type(
working_set: &mut StateWorkingSet,
spans: &[Span],
spans_idx: &mut usize,
mutable: bool,
) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec();
@ -2953,7 +2955,7 @@ pub fn parse_var_with_opt_type(
);
}
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone());
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
(
Expression {
@ -2977,7 +2979,7 @@ pub fn parse_var_with_opt_type(
);
}
let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any);
let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any, mutable);
(
Expression {
expr: Expr::VarDecl(id),
@ -3005,6 +3007,7 @@ pub fn parse_var_with_opt_type(
var_name,
span(&spans[*spans_idx..*spans_idx + 1]),
Type::Any,
mutable,
);
(
@ -3044,7 +3047,7 @@ pub fn parse_row_condition(
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Expression, Option<ParseError>) {
let var_id = working_set.add_variable(b"$it".to_vec(), span(spans), Type::Any);
let var_id = working_set.add_variable(b"$it".to_vec(), span(spans), Type::Any, false);
let (expression, err) =
parse_math_expression(working_set, spans, Some(var_id), expand_aliases_denylist);
let span = span(spans);
@ -3232,7 +3235,7 @@ pub fn parse_signature_helper(
}
let var_id =
working_set.add_variable(variable_name, span, Type::Any);
working_set.add_variable(variable_name, span, Type::Any, false);
if flags.len() == 1 {
args.push(Arg::Flag(Flag {
@ -3282,8 +3285,12 @@ pub fn parse_signature_helper(
})
}
let var_id =
working_set.add_variable(variable_name, span, Type::Any);
let var_id = working_set.add_variable(
variable_name,
span,
Type::Any,
false,
);
if chars.len() == 1 {
args.push(Arg::Flag(Flag {
@ -3327,7 +3334,7 @@ pub fn parse_signature_helper(
}
let var_id =
working_set.add_variable(variable_name, span, Type::Any);
working_set.add_variable(variable_name, span, Type::Any, false);
args.push(Arg::Flag(Flag {
arg: None,
@ -3394,7 +3401,8 @@ pub fn parse_signature_helper(
})
}
let var_id = working_set.add_variable(contents, span, Type::Any);
let var_id =
working_set.add_variable(contents, span, Type::Any, false);
// Positional arg, optional
args.push(Arg::Positional(
@ -3420,7 +3428,7 @@ pub fn parse_signature_helper(
}
let var_id =
working_set.add_variable(contents_vec, span, Type::Any);
working_set.add_variable(contents_vec, span, Type::Any, false);
args.push(Arg::RestPositional(PositionalArg {
desc: String::new(),
@ -3443,7 +3451,7 @@ pub fn parse_signature_helper(
}
let var_id =
working_set.add_variable(contents_vec, span, Type::Any);
working_set.add_variable(contents_vec, span, Type::Any, false);
// Positional arg, required
args.push(Arg::Positional(
@ -4411,33 +4419,34 @@ pub fn parse_operator(
let contents = working_set.get_span_contents(span);
let operator = match contents {
b"==" => Operator::Equal,
b"!=" => Operator::NotEqual,
b"<" => Operator::LessThan,
b"<=" => Operator::LessThanOrEqual,
b">" => Operator::GreaterThan,
b">=" => Operator::GreaterThanOrEqual,
b"=~" => Operator::RegexMatch,
b"!~" => Operator::NotRegexMatch,
b"+" => Operator::Plus,
b"++" => Operator::Append,
b"-" => Operator::Minus,
b"*" => Operator::Multiply,
b"/" => Operator::Divide,
b"//" => Operator::FloorDivision,
b"in" => Operator::In,
b"not-in" => Operator::NotIn,
b"mod" => Operator::Modulo,
b"bit-or" => Operator::BitOr,
b"bit-xor" => Operator::BitXor,
b"bit-and" => Operator::BitAnd,
b"bit-shl" => Operator::ShiftLeft,
b"bit-shr" => Operator::ShiftRight,
b"starts-with" => Operator::StartsWith,
b"ends-with" => Operator::EndsWith,
b"&&" | b"and" => Operator::And,
b"||" | b"or" => Operator::Or,
b"**" => Operator::Pow,
b"=" => Operator::Assignment(Assignment::Assign),
b"==" => Operator::Comparison(Comparison::Equal),
b"!=" => Operator::Comparison(Comparison::NotEqual),
b"<" => Operator::Comparison(Comparison::LessThan),
b"<=" => Operator::Comparison(Comparison::LessThanOrEqual),
b">" => Operator::Comparison(Comparison::GreaterThan),
b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
b"=~" => Operator::Comparison(Comparison::RegexMatch),
b"!~" => Operator::Comparison(Comparison::NotRegexMatch),
b"+" => Operator::Math(Math::Plus),
b"++" => Operator::Math(Math::Append),
b"-" => Operator::Math(Math::Minus),
b"*" => Operator::Math(Math::Multiply),
b"/" => Operator::Math(Math::Divide),
b"//" => Operator::Math(Math::FloorDivision),
b"in" => Operator::Comparison(Comparison::In),
b"not-in" => Operator::Comparison(Comparison::NotIn),
b"mod" => Operator::Math(Math::Modulo),
b"bit-or" => Operator::Bits(Bits::BitOr),
b"bit-xor" => Operator::Bits(Bits::BitXor),
b"bit-and" => Operator::Bits(Bits::BitAnd),
b"bit-shl" => Operator::Bits(Bits::ShiftLeft),
b"bit-shr" => Operator::Bits(Bits::ShiftRight),
b"starts-with" => Operator::Comparison(Comparison::StartsWith),
b"ends-with" => Operator::Comparison(Comparison::EndsWith),
b"&&" | b"and" => Operator::Boolean(Boolean::And),
b"||" | b"or" => Operator::Boolean(Boolean::Or),
b"**" => Operator::Math(Math::Pow),
_ => {
return (
garbage(span),
@ -4764,6 +4773,28 @@ pub fn parse_expression(
spans[0],
)),
),
b"mut" => (
parse_call(
working_set,
&spans[pos..],
spans[0],
expand_aliases_denylist,
)
.0,
Some(ParseError::MutInPipeline(
String::from_utf8_lossy(match spans.len() {
1 | 2 | 3 => b"value",
_ => working_set.get_span_contents(spans[3]),
})
.to_string(),
String::from_utf8_lossy(match spans.len() {
1 => b"variable",
_ => working_set.get_span_contents(spans[1]),
})
.to_string(),
spans[0],
)),
),
b"alias" => (
parse_call(
working_set,
@ -4977,6 +5008,7 @@ pub fn parse_builtin_commands(
b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist),
b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
b"let" => parse_let(working_set, &lite_command.parts, expand_aliases_denylist),
b"mut" => parse_mut(working_set, &lite_command.parts, expand_aliases_denylist),
b"for" => {
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
(Pipeline::from_vec(vec![expr]), err)
@ -5229,8 +5261,8 @@ pub fn discover_captures_in_closure(
working_set: &StateWorkingSet,
block: &Block,
seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>,
) -> Vec<VarId> {
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output = vec![];
for flag in &block.signature.named {
@ -5256,57 +5288,71 @@ pub fn discover_captures_in_closure(
}
for pipeline in &block.pipelines {
let result = discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks);
let result = discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks)?;
output.extend(&result);
}
output
Ok(output)
}
fn discover_captures_in_pipeline(
working_set: &StateWorkingSet,
pipeline: &Pipeline,
seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>,
) -> Vec<VarId> {
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output = vec![];
for expr in &pipeline.expressions {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
output
Ok(output)
}
// Closes over captured variables
pub fn discover_captures_in_expr(
working_set: &StateWorkingSet,
expr: &Expression,
seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>,
) -> Vec<VarId> {
let mut output = vec![];
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output: Vec<(VarId, Span)> = vec![];
match &expr.expr {
Expr::BinaryOp(lhs, _, rhs) => {
let lhs_result = discover_captures_in_expr(working_set, lhs, seen, seen_blocks);
let rhs_result = discover_captures_in_expr(working_set, rhs, seen, seen_blocks);
let lhs_result = discover_captures_in_expr(working_set, lhs, seen, seen_blocks)?;
let rhs_result = discover_captures_in_expr(working_set, rhs, seen, seen_blocks)?;
output.extend(&lhs_result);
output.extend(&rhs_result);
}
Expr::UnaryNot(expr) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id);
let results = {
let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)
let results =
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?;
for (var_id, span) in results.iter() {
if !seen.contains(var_id) {
if let Some(variable) = working_set.get_variable_if_possible(*var_id) {
if variable.mutable {
return Err(ParseError::CaptureOfMutableVar(*span));
}
}
}
}
results
};
seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() {
for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) {
output.push(var_id)
output.push((var_id, span))
}
}
}
@ -5315,12 +5361,12 @@ pub fn discover_captures_in_expr(
// FIXME: is this correct?
let results = {
let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
};
seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() {
for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) {
output.push(var_id)
output.push((var_id, span))
}
}
}
@ -5336,7 +5382,7 @@ pub fn discover_captures_in_expr(
None => {
let block = working_set.get_block(block_id);
if !block.captures.is_empty() {
output.extend(&block.captures);
output.extend(block.captures.iter().map(|var_id| (*var_id, call.head)));
} else {
let mut seen = vec![];
seen_blocks.insert(block_id, output.clone());
@ -5346,7 +5392,7 @@ pub fn discover_captures_in_expr(
block,
&mut seen,
seen_blocks,
);
)?;
output.extend(&result);
seen_blocks.insert(block_id, result);
}
@ -5356,24 +5402,24 @@ pub fn discover_captures_in_expr(
for named in call.named_iter() {
if let Some(arg) = &named.2 {
let result = discover_captures_in_expr(working_set, arg, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, arg, seen, seen_blocks)?;
output.extend(&result);
}
}
for positional in call.positional_iter() {
let result = discover_captures_in_expr(working_set, positional, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, positional, seen, seen_blocks)?;
output.extend(&result);
}
}
Expr::CellPath(_) => {}
Expr::DateTime(_) => {}
Expr::ExternalCall(head, exprs) => {
let result = discover_captures_in_expr(working_set, head, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, head, seen, seen_blocks)?;
output.extend(&result);
for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
}
@ -5381,7 +5427,8 @@ pub fn discover_captures_in_expr(
Expr::Directory(_) => {}
Expr::Float(_) => {}
Expr::FullCellPath(cell_path) => {
let result = discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks);
let result =
discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks)?;
output.extend(&result);
}
Expr::ImportPattern(_) => {}
@ -5391,27 +5438,27 @@ pub fn discover_captures_in_expr(
Expr::GlobPattern(_) => {}
Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
Expr::List(exprs) => {
for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
}
Expr::Operator(_) => {}
Expr::Range(expr1, expr2, expr3, _) => {
if let Some(expr) = expr1 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
if let Some(expr) = expr2 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
if let Some(expr) = expr3 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
}
@ -5422,13 +5469,13 @@ pub fn discover_captures_in_expr(
field_name,
seen,
seen_blocks,
));
)?);
output.extend(&discover_captures_in_expr(
working_set,
field_value,
seen,
seen_blocks,
));
)?);
}
}
Expr::Signature(sig) => {
@ -5457,7 +5504,7 @@ pub fn discover_captures_in_expr(
Expr::String(_) => {}
Expr::StringInterpolation(exprs) => {
for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
}
@ -5465,41 +5512,41 @@ pub fn discover_captures_in_expr(
let block = working_set.get_block(*block_id);
let results = {
let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
};
seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() {
for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) {
output.push(var_id)
output.push((var_id, span))
}
}
}
Expr::Table(headers, values) => {
for header in headers {
let result = discover_captures_in_expr(working_set, header, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, header, seen, seen_blocks)?;
output.extend(&result);
}
for row in values {
for cell in row {
let result = discover_captures_in_expr(working_set, cell, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, cell, seen, seen_blocks)?;
output.extend(&result);
}
}
}
Expr::ValueWithUnit(expr, _) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks);
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result);
}
Expr::Var(var_id) => {
if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) {
output.push(*var_id);
output.push((*var_id, expr.span));
}
}
Expr::VarDecl(var_id) => {
seen.push(*var_id);
}
}
output
Ok(output)
}
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
@ -5604,7 +5651,10 @@ pub fn parse(
let mut seen_blocks = HashMap::new();
let captures = discover_captures_in_closure(working_set, &output, &mut seen, &mut seen_blocks);
output.captures = captures;
match captures {
Ok(captures) => output.captures = captures.into_iter().map(|(var_id, _)| var_id).collect(),
Err(err) => error = Some(err),
}
// Also check other blocks that might have been imported
for (block_idx, block) in working_set.delta.blocks.iter().enumerate() {
@ -5613,7 +5663,12 @@ pub fn parse(
if !seen_blocks.contains_key(&block_id) {
let captures =
discover_captures_in_closure(working_set, block, &mut seen, &mut seen_blocks);
seen_blocks.insert(block_id, captures);
match captures {
Ok(captures) => {
seen_blocks.insert(block_id, captures);
}
Err(err) => error = Some(err),
}
}
}
@ -5626,7 +5681,7 @@ pub fn parse(
let block_captures_empty = block.captures.is_empty();
if !captures.is_empty() && block_captures_empty {
let block = working_set.get_block_mut(block_id);
block.captures = captures;
block.captures = captures.into_iter().map(|(var_id, _)| var_id).collect();
}
}

View File

@ -1,6 +1,6 @@
use crate::ParseError;
use nu_protocol::{
ast::{Expr, Expression, Operator},
ast::{Assignment, Bits, Boolean, Comparison, Expr, Expression, Math, Operator},
engine::StateWorkingSet,
Type,
};
@ -26,7 +26,7 @@ pub fn math_result_type(
//println!("checking: {:?} {:?} {:?}", lhs, op, rhs);
match &op.expr {
Expr::Operator(operator) => match operator {
Operator::Plus => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Plus) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
@ -69,7 +69,7 @@ pub fn math_result_type(
)
}
},
Operator::Append => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Append) => match (&lhs.ty, &rhs.ty) {
(Type::List(a), Type::List(b)) => {
if a == b {
(Type::List(a.clone()), None)
@ -98,7 +98,7 @@ pub fn math_result_type(
)
}
},
Operator::Minus => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Minus) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
@ -126,7 +126,7 @@ pub fn math_result_type(
)
}
},
Operator::Multiply => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Multiply) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
@ -159,7 +159,7 @@ pub fn math_result_type(
)
}
},
Operator::Pow => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Pow) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
@ -184,7 +184,8 @@ pub fn math_result_type(
)
}
},
Operator::Divide | Operator::Modulo => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::Divide) | Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty)
{
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None),
@ -215,7 +216,7 @@ pub fn math_result_type(
)
}
},
Operator::FloorDivision => match (&lhs.ty, &rhs.ty) {
Operator::Math(Math::FloorDivision) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Int, None),
(Type::Int, Type::Float) => (Type::Int, None),
@ -243,33 +244,37 @@ pub fn math_result_type(
)
}
},
Operator::And | Operator::Or => match (&lhs.ty, &rhs.ty) {
(Type::Bool, Type::Bool) => (Type::Bool, None),
Operator::Boolean(Boolean::And) | Operator::Boolean(Boolean::Or) => {
match (&lhs.ty, &rhs.ty) {
(Type::Bool, Type::Bool) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
(Type::Custom(a), Type::Custom(b)) if a == b => {
(Type::Custom(a.to_string()), None)
}
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
(Type::Any, _) => (Type::Any, None),
(_, Type::Any) => (Type::Any, None),
(Type::Any, _) => (Type::Any, None),
(_, Type::Any) => (Type::Any, None),
// FIX ME. This is added because there is no type output for custom function
// definitions. As soon as that syntax is added this should be removed
(a, b) if a == b => (Type::Bool, None),
_ => {
*op = Expression::garbage(op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperation(
op.span,
lhs.span,
lhs.ty.clone(),
rhs.span,
rhs.ty.clone(),
)),
)
// FIX ME. This is added because there is no type output for custom function
// definitions. As soon as that syntax is added this should be removed
(a, b) if a == b => (Type::Bool, None),
_ => {
*op = Expression::garbage(op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperation(
op.span,
lhs.span,
lhs.ty.clone(),
rhs.span,
rhs.ty.clone(),
)),
)
}
}
},
Operator::LessThan => match (&lhs.ty, &rhs.ty) {
}
Operator::Comparison(Comparison::LessThan) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
@ -296,7 +301,7 @@ pub fn math_result_type(
)
}
},
Operator::LessThanOrEqual => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::LessThanOrEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
@ -323,7 +328,7 @@ pub fn math_result_type(
)
}
},
Operator::GreaterThan => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::GreaterThan) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
@ -350,7 +355,7 @@ pub fn math_result_type(
)
}
},
Operator::GreaterThanOrEqual => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::GreaterThanOrEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None),
@ -377,19 +382,19 @@ pub fn math_result_type(
)
}
},
Operator::Equal => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::Equal) => match (&lhs.ty, &rhs.ty) {
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
_ => (Type::Bool, None),
},
Operator::NotEqual => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::NotEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
_ => (Type::Bool, None),
},
Operator::RegexMatch => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::RegexMatch) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
@ -411,7 +416,7 @@ pub fn math_result_type(
)
}
},
Operator::NotRegexMatch => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::NotRegexMatch) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
@ -433,7 +438,7 @@ pub fn math_result_type(
)
}
},
Operator::StartsWith => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::StartsWith) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
@ -455,7 +460,7 @@ pub fn math_result_type(
)
}
},
Operator::EndsWith => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::EndsWith) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
@ -477,7 +482,7 @@ pub fn math_result_type(
)
}
},
Operator::In => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::In) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
@ -502,7 +507,7 @@ pub fn math_result_type(
)
}
},
Operator::NotIn => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
@ -527,11 +532,11 @@ pub fn math_result_type(
)
}
},
Operator::ShiftLeft
| Operator::ShiftRight
| Operator::BitOr
| Operator::BitXor
| Operator::BitAnd => match (&lhs.ty, &rhs.ty) {
Operator::Bits(Bits::ShiftLeft)
| Operator::Bits(Bits::ShiftRight)
| Operator::Bits(Bits::BitOr)
| Operator::Bits(Bits::BitXor)
| Operator::Bits(Bits::BitAnd) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None),
(Type::Any, _) => (Type::Any, None),
@ -550,6 +555,15 @@ pub fn math_result_type(
)
}
},
Operator::Assignment(Assignment::Assign) => match (&lhs.ty, &rhs.ty) {
(x, y) if x == y => (Type::Nothing, None),
(Type::Any, _) => (Type::Nothing, None),
(_, Type::Any) => (Type::Nothing, None),
(x, y) => (
Type::Nothing,
Some(ParseError::Mismatch(x.to_string(), y.to_string(), rhs.span)),
),
},
},
_ => {
*op = Expression::garbage(op.span);