Skip to content

Commit 4968b6b

Browse files
authored
fix error when exporting consts with type signatures in modules (#14118)
Fixes #14023 # Description - Prevents "failed to find added variable" when modules export constants with type signatures: ```nushell > module foo { export const bar: int = 2 } Error: nu::parser::unknown_state × Internal error. ╭─[entry #1:1:21] 1 │ module foo { export const bar: int = 2 } · ─────────┬──────── · ╰── failed to find added variable ``` - Returns `name_is_builtin_var` errors for names with type signatures: ```nushell > let env: string = ""; Error: nu::parser::name_is_builtin_var × `env` used as variable name. ╭─[entry #1:1:5] 1 │ let env: string = ""; · ─┬─ · ╰── already a builtin variable ```
1 parent ee97c00 commit 4968b6b

File tree

5 files changed

+53
-43
lines changed

5 files changed

+53
-43
lines changed

crates/nu-command/tests/commands/let_.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use nu_test_support::nu;
2+
use rstest::rstest;
23

3-
#[test]
4-
fn let_name_builtin_var() {
5-
let actual = nu!("let in = 3");
6-
7-
assert!(actual
4+
#[rstest]
5+
#[case("let in = 3")]
6+
#[case("let in: int = 3")]
7+
fn let_name_builtin_var(#[case] assignment: &str) {
8+
assert!(nu!(assignment)
89
.err
910
.contains("'in' is the name of a builtin Nushell variable"));
1011
}

crates/nu-command/tests/commands/mut_.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use nu_test_support::nu;
2+
use rstest::rstest;
23

34
#[test]
45
fn mut_variable() {
@@ -7,11 +8,11 @@ fn mut_variable() {
78
assert_eq!(actual.out, "4");
89
}
910

10-
#[test]
11-
fn mut_name_builtin_var() {
12-
let actual = nu!("mut in = 3");
13-
14-
assert!(actual
11+
#[rstest]
12+
#[case("mut in = 3")]
13+
#[case("mut in: int = 3")]
14+
fn mut_name_builtin_var(#[case] assignment: &str) {
15+
assert!(nu!(assignment)
1516
.err
1617
.contains("'in' is the name of a builtin Nushell variable"));
1718
}

crates/nu-parser/src/parse_keywords.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,7 +1204,7 @@ pub fn parse_export_in_block(
12041204
match full_name {
12051205
"export alias" => parse_alias(working_set, lite_command, None),
12061206
"export def" => parse_def(working_set, lite_command, None).0,
1207-
"export const" => parse_const(working_set, &lite_command.parts[1..]),
1207+
"export const" => parse_const(working_set, &lite_command.parts[1..]).0,
12081208
"export use" => parse_use(working_set, lite_command, None).0,
12091209
"export module" => parse_module(working_set, lite_command, None).0,
12101210
"export extern" => parse_extern(working_set, lite_command, None),
@@ -1514,7 +1514,7 @@ pub fn parse_export_in_module(
15141514
result
15151515
}
15161516
b"const" => {
1517-
let pipeline = parse_const(working_set, &spans[1..]);
1517+
let (pipeline, var_name_span) = parse_const(working_set, &spans[1..]);
15181518
let export_const_decl_id = if let Some(id) = working_set.find_decl(b"export const")
15191519
{
15201520
id
@@ -1542,8 +1542,8 @@ pub fn parse_export_in_module(
15421542

15431543
let mut result = vec![];
15441544

1545-
if let Some(var_name_span) = spans.get(2) {
1546-
let var_name = working_set.get_span_contents(*var_name_span);
1545+
if let Some(var_name_span) = var_name_span {
1546+
let var_name = working_set.get_span_contents(var_name_span);
15471547
let var_name = trim_quotes(var_name);
15481548

15491549
if let Some(var_id) = working_set.find_variable(var_name) {
@@ -1762,7 +1762,7 @@ pub fn parse_module_block(
17621762
}
17631763
b"const" => block
17641764
.pipelines
1765-
.push(parse_const(working_set, &command.parts)),
1765+
.push(parse_const(working_set, &command.parts).0),
17661766
b"extern" => block
17671767
.pipelines
17681768
.push(parse_extern(working_set, command, None)),
@@ -3154,7 +3154,8 @@ pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
31543154
garbage_pipeline(working_set, spans)
31553155
}
31563156

3157-
pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
3157+
/// Additionally returns a span encompassing the variable name, if successful.
3158+
pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline, Option<Span>) {
31583159
trace!("parsing: const");
31593160

31603161
// JT: Disabling check_name because it doesn't work with optional types in the declaration
@@ -3271,28 +3272,37 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
32713272
let call = Box::new(Call {
32723273
decl_id,
32733274
head: spans[0],
3274-
arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)],
3275+
arguments: vec![
3276+
Argument::Positional(lvalue.clone()),
3277+
Argument::Positional(rvalue),
3278+
],
32753279
parser_info: HashMap::new(),
32763280
});
32773281

3278-
return Pipeline::from_vec(vec![Expression::new(
3279-
working_set,
3280-
Expr::Call(call),
3281-
Span::concat(spans),
3282-
Type::Any,
3283-
)]);
3282+
return (
3283+
Pipeline::from_vec(vec![Expression::new(
3284+
working_set,
3285+
Expr::Call(call),
3286+
Span::concat(spans),
3287+
Type::Any,
3288+
)]),
3289+
Some(lvalue.span),
3290+
);
32843291
}
32853292
}
32863293
}
32873294
let ParsedInternalCall { call, output } =
32883295
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
32893296

3290-
return Pipeline::from_vec(vec![Expression::new(
3291-
working_set,
3292-
Expr::Call(call),
3293-
Span::concat(spans),
3294-
output,
3295-
)]);
3297+
return (
3298+
Pipeline::from_vec(vec![Expression::new(
3299+
working_set,
3300+
Expr::Call(call),
3301+
Span::concat(spans),
3302+
output,
3303+
)]),
3304+
None,
3305+
);
32963306
} else {
32973307
working_set.error(ParseError::UnknownState(
32983308
"internal error: let or const statements not found in core language".into(),
@@ -3305,7 +3315,7 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
33053315
Span::concat(spans),
33063316
));
33073317

3308-
garbage_pipeline(working_set, spans)
3318+
(garbage_pipeline(working_set, spans), None)
33093319
}
33103320

33113321
pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {

crates/nu-parser/src/parser.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3113,7 +3113,8 @@ pub fn parse_var_with_opt_type(
31133113
spans_idx: &mut usize,
31143114
mutable: bool,
31153115
) -> (Expression, Option<Type>) {
3116-
let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec();
3116+
let name_span = spans[*spans_idx];
3117+
let bytes = working_set.get_span_contents(name_span).to_vec();
31173118

31183119
if bytes.contains(&b' ')
31193120
|| bytes.contains(&b'"')
@@ -3125,9 +3126,11 @@ pub fn parse_var_with_opt_type(
31253126
}
31263127

31273128
if bytes.ends_with(b":") {
3129+
let name_span = Span::new(name_span.start, name_span.end - 1);
3130+
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
3131+
31283132
// We end with colon, so the next span should be the type
31293133
if *spans_idx + 1 < spans.len() {
3130-
let span_beginning = *spans_idx;
31313134
*spans_idx += 1;
31323135
// signature like record<a: int b: int> is broken into multiple spans due to
31333136
// whitespaces. Collect the rest into one span and work on it
@@ -3144,8 +3147,6 @@ pub fn parse_var_with_opt_type(
31443147
let ty = parse_type(working_set, &type_bytes, tokens[0].span);
31453148
*spans_idx = spans.len() - 1;
31463149

3147-
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
3148-
31493150
if !is_variable(&var_name) {
31503151
working_set.error(ParseError::Expected(
31513152
"valid variable name",
@@ -3157,17 +3158,10 @@ pub fn parse_var_with_opt_type(
31573158
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
31583159

31593160
(
3160-
Expression::new(
3161-
working_set,
3162-
Expr::VarDecl(id),
3163-
Span::concat(&spans[span_beginning..*spans_idx + 1]),
3164-
ty.clone(),
3165-
),
3161+
Expression::new(working_set, Expr::VarDecl(id), name_span, ty.clone()),
31663162
Some(ty),
31673163
)
31683164
} else {
3169-
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
3170-
31713165
if !is_variable(&var_name) {
31723166
working_set.error(ParseError::Expected(
31733167
"valid variable name",
@@ -5615,7 +5609,7 @@ pub fn parse_builtin_commands(
56155609
.parts_including_redirection()
56165610
.collect::<Vec<Span>>(),
56175611
),
5618-
b"const" => parse_const(working_set, &lite_command.parts),
5612+
b"const" => parse_const(working_set, &lite_command.parts).0,
56195613
b"mut" => parse_mut(
56205614
working_set,
56215615
&lite_command

tests/repl/test_modules.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ fn export_consts() -> TestResult {
117117
run_test(
118118
r#"module spam { export const b = 3; }; use spam b; $b"#,
119119
"3",
120+
)?;
121+
run_test(
122+
r#"module spam { export const b: int = 3; }; use spam b; $b"#,
123+
"3",
120124
)
121125
}
122126

0 commit comments

Comments
 (0)