diff --git a/crates/nu-command/tests/commands/mut_.rs b/crates/nu-command/tests/commands/mut_.rs index 52aa9fcff1..7c55110f5f 100644 --- a/crates/nu-command/tests/commands/mut_.rs +++ b/crates/nu-command/tests/commands/mut_.rs @@ -148,3 +148,14 @@ fn def_should_not_mutate_mut() { assert!(actual.err.contains("capture of mutable variable")); assert!(!actual.status.success()) } + +#[test] +fn assign_to_non_mut_variable_raises_parse_error() { + let actual = nu!("let x = 3; $x = 4"); + assert!(actual + .err + .contains("parser::assignment_requires_mutable_variable")); + + let actual = nu!("mut x = 3; x = 5"); + assert!(actual.err.contains("parser::assignment_requires_variable")); +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index b2b5756ef0..0f8e699729 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4988,6 +4988,20 @@ pub fn parse_assignment_expression( // Parse the lhs and operator as usual for a math expression let mut lhs = parse_expression(working_set, lhs_spans); + // make sure that lhs is a mutable variable. + match &lhs.expr { + Expr::FullCellPath(p) => { + if let Expr::Var(var_id) = p.head.expr { + if var_id != nu_protocol::ENV_VARIABLE_ID + && !working_set.get_variable(var_id).mutable + { + working_set.error(ParseError::AssignmentRequiresMutableVar(lhs.span)) + } + } + } + _ => working_set.error(ParseError::AssignmentRequiresVar(lhs.span)), + } + let mut operator = parse_assignment_operator(working_set, op_span); // Re-parse the right-hand side as a subexpression diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index ce8522daa1..786ce97509 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -515,6 +515,30 @@ pub enum ParseError { help("To spread arguments, the command needs to define a multi-positional parameter in its signature, such as ...rest") )] UnexpectedSpreadArg(String, #[label = "unexpected spread argument"] Span), + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a mutable variable or cell path. + #[error("Assignment to an immutable variable.")] + #[diagnostic( + code(nu::parser::assignment_requires_mutable_variable), + help("declare the variable with `mut`, or shadow it again with `let`") + )] + AssignmentRequiresMutableVar(#[label("needs to be a mutable variable")] Span), + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a variable or variable cell path. + #[error("Assignment operations require a variable.")] + #[diagnostic( + code(nu::parser::assignment_requires_variable), + help("try assigning to a variable or a cell path of a variable") + )] + AssignmentRequiresVar(#[label("needs to be a variable")] Span), } impl ParseError { @@ -603,6 +627,8 @@ impl ParseError { ParseError::RedirectingBuiltinCommand(_, s, _) => *s, ParseError::UnexpectedSpreadArg(_, s) => *s, ParseError::ExtraTokensAfterClosingDelimiter(s) => *s, + ParseError::AssignmentRequiresVar(s) => *s, + ParseError::AssignmentRequiresMutableVar(s) => *s, } } }