Skip to content

Fix #1820#1856

Open
arnaudgelas wants to merge 1 commit intoinformalsystems:mainfrom
arnaudgelas:fix-1820
Open

Fix #1820#1856
arnaudgelas wants to merge 1 commit intoinformalsystems:mainfrom
arnaudgelas:fix-1820

Conversation

@arnaudgelas
Copy link
Contributor

@arnaudgelas arnaudgelas commented Jan 20, 2026

  • I have read and I understand the Note on AI-assisted contributions
  • Changes manually tested locally and confirmed to work as described
    (including screenshots is helpful)
  • Tests added for any new code
  • Documentation added for any new functionality
  • Entries added to the respective CHANGELOG.md for any new functionality

@arnaudgelas arnaudgelas changed the title Fix 1820 Fix #1820 Jan 21, 2026
…wing

Fixes informalsystems#1820

## Problem

When a nondet binding has the same name as a state variable, assignments
to that variable fail with QNT502 "Variable not set":

```quint
module foo {
  var bar: int

  action init = {
    nondet bar = Nat.oneOf()  // shadows state var 'bar'
    bar' = bar                // QNT502: Variable bar not set
  }
}
```

The error occurred because the name resolver treated assignment LHS (`bar'`)
like any other name reference, resolving it to the most recent definition
in scope - the nondet binding instead of the state variable. Since the nondet
binding isn't a state variable, the runtime couldn't find a var to update.

## Root Cause

In Quint, the assignment operator `bar' = expr` has special semantics:
- LHS (`bar`) must always refer to a state variable declaration
- RHS (`expr`) follows normal scoping rules and can reference shadowing bindings

The resolver wasn't distinguishing between these two contexts, applying
standard name resolution to both sides.

## Solution

Introduce special handling for assignment LHS in the name resolver:

1. In `enterApp()`, detect `assign` applications and record the LHS name's ID
2. In `enterName()`, check if the name is an assignment LHS
3. If so, use `resolveAssignmentTarget()` which searches for a `var` definition
   with that name, bypassing any shadowing non-var definitions
4. RHS continues to use standard resolution, correctly picking up shadows

This ensures `bar' = bar` in the example above resolves:
- LHS `bar` → state variable declaration
- RHS `bar` → nondet binding (the shadowing definition)

## Changes

- **quint/src/names/resolver.ts**: Add `assignLhsIds` tracking and
  `resolveAssignmentTarget()` method for special LHS handling
- **quint/test/names/resolver.test.ts**: Add regression tests verifying:
  - All assignment LHS resolve to state vars when shadowed
  - Assignment RHS correctly resolves to shadowing binding

## Testing

- Added unit tests that reproduce the exact issue from informalsystems#1820
- Verified both LHS (state var) and RHS (shadow) resolve correctly
- Ran existing test suite to ensure no regressions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant