pyrefly icon indicating copy to clipboard operation
pyrefly copied to clipboard

unexpected unbound-name error on second walrus conditional assignment for same variable

Open andrew-meter opened this issue 4 months ago • 4 comments

Describe the Bug

I have a code sequence that's doing walrus assignments of regex matches within if statements, using the same variable name for the walrus assignment, and then using the walrus-assigned variable within the body of the if statement.

Due to it being part of the if condition itself, once inside the if statement body it is my understanding, there is no way that the walrus-assigned variable could ever be unbound.

Yet, this is an outcome I see with a specific code sequence.

Interestingly I'm doing two walrus assignments, if I remove the first once, the error goes away. And if I keep the first one but slightly tweak the if condition of the second one, it also goes away.

Full details are below.

Python code in question:

from re import compile

interface_re = compile(r"^foo")
ipv4_re = compile(r"bar$")
line = str()

if match := interface_re.match(line):  # unbound-name error gone if this is removed
    pass

# if match := ipv4_re.search(line):
if line and (match := ipv4_re.search(line)):  # unbound-name error also gone if above alternate if is used instead
    print(match)

Relevant software versions:

# pyrefly --version
pyrefly 0.39.1

# python3 --version
Python 3.14.0

Pyrefly error:

# pyrefly check walrus.py
 WARN PYTHONPATH environment variable is set to `/myapp`. Checks in other environments may not include these paths.
ERROR `match` may be uninitialized [unbound-name]
  --> walrus.py:12:11
   |
12 |     print(match)
   |           ^^^^^
   |
 INFO 1 error

Sandbox Link

No response

(Only applicable for extension issues) IDE Information

No response

andrew-meter avatar Oct 27 '25 21:10 andrew-meter

Wonder if it might be the same RCA as #1378 ?

andrew-meter avatar Oct 27 '25 21:10 andrew-meter

I don't know if my is exactly the same issue as OP, but I get unbound-name error even with a single walrus operator use

from typing import Any

def test(thing: Any) -> None:
    if not (items := getattr(thing, "items")):
        return
    if not isinstance(items, tuple|list):
        items = (items,)
    for item in items: # <-- unbound-name error here
        print(item)

While this works:

from typing import Any

def test(thing: Any) -> None:
    items = getattr(thing, "items")
    if not items:
        return
    if not isinstance(items, tuple|list):
        items = (items,)
    for item in items:
        print(item)

sanzoghenzo avatar Oct 29 '25 10:10 sanzoghenzo

cc @stroxler

yangdanny97 avatar Nov 04 '25 18:11 yangdanny97

Here's the most minimal example of walrus causing issues:

def f() -> None:
    if a := True:
        print(a) # no error here
    print(a) # `a` may be uninitialized

But something that may break out of the scope (such as raise, or return) does not seem to cause issues:

def f() -> None:
    if a := False:
        raise
    b = a  # `Literal[False]` assigned if `False`, otherwise `Never`

komodovaran avatar Nov 20 '25 06:11 komodovaran