Skip to content

Resolve evaluate/watch variables from the correct scope#2311

Open
andyleejordan wants to merge 1 commit into
mainfrom
andyleejordan/fix-1882-evaluate-scope
Open

Resolve evaluate/watch variables from the correct scope#2311
andyleejordan wants to merge 1 commit into
mainfrom
andyleejordan/fix-1882-evaluate-scope

Conversation

@andyleejordan

@andyleejordan andyleejordan commented Jun 16, 2026

Copy link
Copy Markdown
Member

Problem

Fixes #1882.

While stopped at a breakpoint, a watch / evaluate request for a naked variable could return the value from a parent scope instead of the current one. Given:

$myVarName = 123
& {
    $myVarName = 456
    $myVarName   # break here
}

evaluating $myVarName returned 123 instead of 456, even though the Variables explorer (and PowerShell itself) correctly show 456.

Cause

DebugService.GetVariableFromExpression searched the flat variables list with FirstOrDefault. That list is populated broadest-to-narrowest (global → script → local → stack frames), so the global/parent copy of a shadowed name was always matched first.

Fix

Search the Local, Script, then Global scope containers in that order first, falling back to the full flat list so frame-only names still resolve. This matches PowerShell's own variable resolution and what the Variables explorer displays. Only the name lookup changed; ID-based lookups elsewhere are unaffected.

Testing

  • Added DebuggerResolvesVariableFromMostLocalScope plus a shared VariableScopeTest.ps1 that shadows a variable across scopes and asserts the local value wins.
  • Confirmed it fails without the fix (returns the parent value) and passes with it.
  • All 36 DebugServiceTests pass on net8.0.

When a watch or `evaluate` request resolved a naked variable reference,
`GetVariableFromExpression` searched the flat `variables` list, which is
populated broadest-to-narrowest (global, then script, then local, then
stack frames). `FirstOrDefault` therefore returned the global/parent-scope
copy of a variable whenever the same name also existed in a more local
scope, even though the Variables explorer and PowerShell itself show the
local value.

We now search the local, script, and global scope containers in that order
first, falling back to the full flat list so names that only live in a
frame still resolve. This matches PowerShell's own variable resolution
semantics. Added a regression test (and `VariableScopeTest.ps1`) that
shadows a variable across scopes and asserts the local value wins.

Fixes #1882.

Drafted by Copilot (Claude Opus 4.8).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 16, 2026 03:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a scoping bug (#1882) where the debugger's evaluate/watch feature would return a variable's value from a parent scope instead of the current local scope when variables of the same name exist in multiple scopes.

Changes:

  • Reorder the variable search in GetVariableFromExpression to enumerate scope containers from narrowest to broadest (local → script → global), with a flat variable list fallback, ensuring correct variable shadowing behavior.
  • Add a regression test with a dedicated PowerShell test script that verifies the most-local variable value is returned.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs Replace flat variables list lookup with scope-ordered search (local → script → global → fallback) in GetVariableFromExpression
test/PowerShellEditorServices.Test.Shared/Debugging/VariableScopeTest.ps1 New test script with a parent-scope and local-scope variable of the same name
test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs New regression test asserting the local-scope value is resolved correctly

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@andyleejordan

Copy link
Copy Markdown
Member Author

I'm flabberghasted. That's pretty much the problem and a proper fix for it.

@andyleejordan andyleejordan enabled auto-merge (squash) June 16, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate expression returns variable value from wrong scope

2 participants