preserve variable capture spans in blocks (#15334)

Closes #15160

# User-Facing Changes

Certain "variable not found" errors no longer highlight the surrounding
block.

Before:

```nushell
do {
  match foo {
    _ => $in
  }
}

Error: nu:🐚:variable_not_found

  × Variable not found
   ╭─[entry #1:1:1]
 1 │ ╭─▶ do {
 2 │ │     match foo {
 3 │ │       _ => $in
 4 │ │     }
 5 │ ├─▶ }
   · ╰──── variable not found
```

After:

```nushell
Error: nu:🐚:variable_not_found

  × Variable not found
   ╭─[entry #1:3:10]
 2 │   match foo {
 3 │     _ => $in
   ·          ─┬─
   ·           ╰── variable not found
```
This commit is contained in:
Solomon 2025-03-20 18:20:28 +00:00 committed by GitHub
parent 7a6cfa24fc
commit dd56c813f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 17 additions and 15 deletions

View File

@ -642,17 +642,17 @@ impl Eval for EvalRuntime {
.get_block(block_id)
.captures
.iter()
.map(|&id| {
.map(|(id, span)| {
stack
.get_var(id, span)
.get_var(*id, *span)
.or_else(|_| {
engine_state
.get_var(id)
.get_var(*id)
.const_val
.clone()
.ok_or(ShellError::VariableNotFoundAtRuntime { span })
.ok_or(ShellError::VariableNotFoundAtRuntime { span: *span })
})
.map(|var| (id, var))
.map(|var| (*id, var))
})
.collect::<Result<_, _>>()?;

View File

@ -857,7 +857,7 @@ fn literal_value(
let captures = block
.captures
.iter()
.map(|var_id| get_var(ctx, *var_id, span).map(|val| (*var_id, val)))
.map(|(var_id, span)| get_var(ctx, *var_id, *span).map(|val| (*var_id, val)))
.collect::<Result<Vec<_>, ShellError>>()?;
Value::closure(
Closure {

View File

@ -6589,9 +6589,9 @@ pub fn discover_captures_in_expr(
None => {
let block = working_set.get_block(block_id);
if !block.captures.is_empty() {
for capture in &block.captures {
for (capture, span) in &block.captures {
if !seen.contains(capture) {
output.push((*capture, call.head));
output.push((*capture, *span));
}
}
} else {
@ -6905,8 +6905,7 @@ pub fn parse(
&mut captures,
) {
Ok(_) => {
Arc::make_mut(&mut output).captures =
captures.into_iter().map(|(var_id, _)| var_id).collect();
Arc::make_mut(&mut output).captures = captures;
}
Err(err) => working_set.error(err),
}
@ -6961,7 +6960,7 @@ pub fn parse(
&& block_id.get() >= working_set.permanent_state.num_blocks()
{
let block = working_set.get_block_mut(block_id);
block.captures = captures.into_iter().map(|(var_id, _)| var_id).collect();
block.captures = captures;
}
}

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
pub struct Block {
pub signature: Box<Signature>,
pub pipelines: Vec<Pipeline>,
pub captures: Vec<VarId>,
pub captures: Vec<(VarId, Span)>,
pub redirect_env: bool,
/// The block compiled to IR instructions. Not available for subexpressions.
pub ir_block: Option<IrBlock>,

View File

@ -111,7 +111,10 @@ impl Expression {
Expr::UnaryNot(expr) => expr.has_in_variable(working_set),
Expr::Block(block_id) | Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id);
block.captures.contains(&IN_VARIABLE_ID)
block
.captures
.iter()
.any(|(var_id, _)| var_id == &IN_VARIABLE_ID)
|| block
.pipelines
.iter()

View File

@ -322,12 +322,12 @@ impl Stack {
}
}
pub fn gather_captures(&self, engine_state: &EngineState, captures: &[VarId]) -> Stack {
pub fn gather_captures(&self, engine_state: &EngineState, captures: &[(VarId, Span)]) -> Stack {
let mut vars = Vec::with_capacity(captures.len());
let fake_span = Span::new(0, 0);
for capture in captures {
for (capture, _) in captures {
// Note: this assumes we have calculated captures correctly and that commands
// that take in a var decl will manually set this into scope when running the blocks
if let Ok(value) = self.get_var(*capture, fake_span) {