Skip to content

Commit

Permalink
Forbid reserved variable names for function arguments (nushell#11169)
Browse files Browse the repository at this point in the history
Works for all arguments and flags. Because the signature parsing doesn't
give the spans, it is flags the entire signature.

Also added a constant with reserved variable names.

Fix nushell#11158.
  • Loading branch information
KAAtheWiseGit committed Nov 29, 2023
1 parent e290fa0 commit 0e1322e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 4 deletions.
29 changes: 25 additions & 4 deletions crates/nu-parser/src/parse_keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ use crate::{
/// These parser keywords can be aliased
pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[b"overlay hide", b"overlay new", b"overlay use"];

pub const RESERVED_VARIABLE_NAMES: [&str; 3] = ["in", "nu", "env"];

/// These parser keywords cannot be aliased (either not possible, or support not yet added)
pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
b"export",
Expand Down Expand Up @@ -350,6 +352,13 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
}
}

/// If `name` is a keyword, emit an error.
fn verify_not_reserved_variable_name(working_set: &mut StateWorkingSet, name: &str, span: Span) {
if RESERVED_VARIABLE_NAMES.contains(&name) {
working_set.error(ParseError::NameIsBuiltinVar(name.to_string(), span))
}
}

// Returns also the parsed command name and ID
pub fn parse_def(
working_set: &mut StateWorkingSet,
Expand Down Expand Up @@ -515,6 +524,19 @@ pub fn parse_def(
let mut result = None;

if let (Some(mut signature), Some(block_id)) = (sig.as_signature(), block.as_block()) {
for arg_name in &signature.required_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for arg_name in &signature.optional_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for arg_name in &signature.rest_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for flag_name in &signature.get_names() {
verify_not_reserved_variable_name(working_set, flag_name, sig.span);
}

if has_wrapped {
if let Some(rest) = &signature.rest_positional {
if let Some(var_id) = rest.var_id {
Expand Down Expand Up @@ -2997,7 +3019,7 @@ pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
.trim_start_matches('$')
.to_string();

if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}

Expand Down Expand Up @@ -3104,8 +3126,7 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
.trim_start_matches('$')
.to_string();

// TODO: Remove the hard-coded variables, too error-prone
if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}

Expand Down Expand Up @@ -3246,7 +3267,7 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
.trim_start_matches('$')
.to_string();

if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}

Expand Down
15 changes: 15 additions & 0 deletions tests/parsing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,21 @@ fn parse_function_signature(#[case] phrase: &str) {
assert!(actual.err.is_empty());
}

#[rstest]
#[case("def test [ in ] {}")]
#[case("def test [ in: string ] {}")]
#[case("def test [ nu: int ] {}")]
#[case("def test [ env: record<> ] {}")]
#[case("def test [ --env ] {}")]
#[case("def test [ --nu: int ] {}")]
#[case("def test [ --in (-i): list<any> ] {}")]
#[case("def test [ a: string, b: int, in: table<a: int b: int> ] {}")]
#[case("def test [ env, in, nu ] {}")]
fn parse_function_signature_name_is_builtin_var(#[case] phrase: &str) {
let actual = nu!(phrase);
assert!(actual.err.contains("nu::parser::name_is_builtin_var"))
}

#[rstest]
#[case("let a: int = 1")]
#[case("let a: string = 'qwe'")]
Expand Down

0 comments on commit 0e1322e

Please sign in to comment.