Skip to content

Commit 119a3f5

Browse files
committed
fix(parser): pitfall of redefining def
1 parent a473e3e commit 119a3f5

File tree

2 files changed

+33
-39
lines changed

2 files changed

+33
-39
lines changed

crates/nu-parser/src/parse_keywords.rs

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
parser::{compile_block, parse_attribute, parse_redirection, redirecting_builtin_error},
55
type_check::{check_block_input_output, type_compatible},
66
};
7-
use itertools::Itertools;
7+
88
use log::trace;
99
use nu_path::canonicalize_with;
1010
use nu_protocol::{
@@ -533,7 +533,7 @@ fn parse_def_inner(
533533
(spans[0], 1)
534534
};
535535

536-
let def_call = working_set.get_span_contents(name_span).to_vec();
536+
let def_call = working_set.get_span_contents(name_span);
537537
if def_call != b"def" {
538538
working_set.error(ParseError::UnknownState(
539539
"internal error: Wrong call name for def function".into(),
@@ -549,7 +549,11 @@ fn parse_def_inner(
549549
// Parsing the spans and checking that they match the register signature
550550
// Using a parsed call makes more sense than checking for how many spans are in the call
551551
// Also, by creating a call, it can be checked if it matches the declaration signature
552-
let (call, call_span) = match working_set.find_decl(&def_call) {
552+
//
553+
// NOTE: Here we only search for `def` in the permanent state,
554+
// since recursively redefining `def` is dangerous,
555+
// see https://github.com/nushell/nushell/issues/16586
556+
let (call, call_span) = match working_set.permanent_state.find_decl(def_call, &[]) {
553557
None => {
554558
working_set.error(ParseError::UnknownState(
555559
"internal error: def declaration not found".into(),
@@ -573,11 +577,7 @@ fn parse_def_inner(
573577

574578
if let Some(name_span) = decl_name_span {
575579
// Check whether name contains [] or () -- possible missing space error
576-
if let Some(err) = detect_params_in_name(
577-
working_set,
578-
name_span,
579-
String::from_utf8_lossy(&def_call).as_ref(),
580-
) {
580+
if let Some(err) = detect_params_in_name(working_set, name_span, decl_id) {
581581
working_set.error(err);
582582
return (garbage(working_set, Span::concat(spans)), None);
583583
}
@@ -827,11 +827,7 @@ fn parse_extern_inner(
827827
let (command_spans, rest_spans) = spans.split_at(split_id);
828828

829829
if let Some(name_span) = rest_spans.first() {
830-
if let Some(err) = detect_params_in_name(
831-
working_set,
832-
*name_span,
833-
String::from_utf8_lossy(&extern_call).as_ref(),
834-
) {
830+
if let Some(err) = detect_params_in_name(working_set, *name_span, decl_id) {
835831
working_set.error(err);
836832
return garbage(working_set, concat_span);
837833
}
@@ -4216,35 +4212,24 @@ pub fn find_in_dirs(
42164212
fn detect_params_in_name(
42174213
working_set: &StateWorkingSet,
42184214
name_span: Span,
4219-
decl_name: &str,
4215+
decl_id: DeclId,
42204216
) -> Option<ParseError> {
42214217
let name = working_set.get_span_contents(name_span);
4222-
4223-
let extract_span = |delim: u8| {
4224-
// it is okay to unwrap because we know the slice contains the byte
4225-
let (idx, _) = name
4226-
.iter()
4227-
.find_position(|c| **c == delim)
4228-
.unwrap_or((name.len(), &b' '));
4229-
let param_span = Span::new(name_span.start + idx - 1, name_span.start + idx - 1);
4230-
let error = ParseError::LabeledErrorWithHelp {
4231-
error: "no space between name and parameters".into(),
4232-
label: "expected space".into(),
4233-
help: format!(
4234-
"consider adding a space between the `{decl_name}` command's name and its parameters"
4235-
),
4236-
span: param_span,
4237-
};
4238-
Some(error)
4239-
};
4240-
4241-
if name.contains(&b'[') {
4242-
extract_span(b'[')
4243-
} else if name.contains(&b'(') {
4244-
extract_span(b'(')
4245-
} else {
4246-
None
4218+
for (offset, char) in name.iter().enumerate() {
4219+
if *char == b'[' || *char == b'(' {
4220+
return Some(ParseError::LabeledErrorWithHelp {
4221+
error: "no space between name and parameters".into(),
4222+
label: "expected space".into(),
4223+
help: format!(
4224+
"consider adding a space between the `{}` command's name and its parameters",
4225+
working_set.get_decl(decl_id).name()
4226+
),
4227+
span: Span::new(offset + name_span.start - 1, offset + name_span.start - 1),
4228+
});
4229+
}
42474230
}
4231+
4232+
None
42484233
}
42494234

42504235
/// Run has_flag_const and push possible error to working_set

tests/repl/test_parser.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,3 +1042,12 @@ fn external_argument_with_subexpressions() -> TestResult {
10421042
fn quote_escape_but_not_env_shorthand() -> TestResult {
10431043
run_test(r#""\"=foo""#, "\"=foo")
10441044
}
1045+
1046+
// https://github.com/nushell/nushell/issues/16586
1047+
#[test]
1048+
fn redefine_def_should_not_panic() -> TestResult {
1049+
fail_test(
1050+
r#"def def (=a|s)>"#,
1051+
"Missing required positional argument.",
1052+
)
1053+
}

0 commit comments

Comments
 (0)