Repository: quora/pyanalyze (python)

Issue:
Incorrect "possibly_undefined_name" with walrus operator
```
% cat ~/Desktop/walrus.py 
from typing_extensions import assert_type


def f() -> int:
    return 3


def g(cond: bool) -> None:
    if cond and (x := f()):
        assert_type(x, int)
% python -m pyanalyze ~/Desktop/walrus.py  -e possibly_undefined_name

x may be used uninitialized (code: possibly_undefined_name)
In /Users/jelle/Desktop/walrus.py at line 10
   7: 
   8: def g(cond: bool) -> None:
   9:     if cond and (x := f()):
  10:         assert_type(x, int)
                          ^

Type is Union[Any[error], int] (expected int) (code: inference_failure)
In /Users/jelle/Desktop/walrus.py at line 10
   7: 
   8: def g(cond: bool) -> None:
   9:     if cond and (x := f()):
  10:         assert_type(x, int)
              ^
```

Pull request:
Fix possibly_undefined_name on use of the walrus operator
A suspiciously simple fix but I think it's correct.

Fixes #597

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 81f1782..bab7659 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Fix incorrect `possibly_undefined_name` error on certain uses of the
+  walrus operator (#749)
 - Fix narrowing on `isinstance` calls with arguments that are not
   instance of `typing`, such as unions and certain typing special forms (#747)
 - Detect invalid calls to `isinstance` (#747)
diff --git a/pyanalyze/stacked_scopes.py b/pyanalyze/stacked_scopes.py
index c3cc13c..7efae94 100644
--- a/pyanalyze/stacked_scopes.py
+++ b/pyanalyze/stacked_scopes.py
@@ -342,7 +342,9 @@ class Constraint(AbstractConstraint):
         """
         inner_value = value.value if isinstance(value, AnnotatedValue) else value
         if inner_value is UNINITIALIZED_VALUE:
-            yield UNINITIALIZED_VALUE
+            # If a constraint applies to a value, it must have been initialized,
+            # or at least we must have already tried to read it and gotten a
+            # possibly_undefined_name error.
             return
         if self.constraint_type == ConstraintType.is_instance:
             if isinstance(inner_value, AnyValue):


Test patch:
diff --git a/pyanalyze/test_name_check_visitor.py b/pyanalyze/test_name_check_visitor.py
index fc4fb1f..09f8cbf 100644
--- a/pyanalyze/test_name_check_visitor.py
+++ b/pyanalyze/test_name_check_visitor.py
@@ -2050,6 +2050,17 @@ class TestWalrus(TestNameCheckVisitorBase):
             """
         )
 
+    @assert_passes()
+    def test_and_then_walrus(self):
+        from typing_extensions import Literal, assert_type
+
+        def capybara(cond):
+            if cond and (x := 1):
+                assert_type(x, Literal[1])
+            else:
+                print(x)  # E: possibly_undefined_name
+            print(x)  # E: possibly_undefined_name
+
     def test_if_exp(self):
         self.assert_passes(
             """
diff --git a/pyanalyze/test_stacked_scopes.py b/pyanalyze/test_stacked_scopes.py
index 9d5001f..ee1a86c 100644
--- a/pyanalyze/test_stacked_scopes.py
+++ b/pyanalyze/test_stacked_scopes.py
@@ -818,7 +818,7 @@ class TestLeavesScope(TestNameCheckVisitorBase):
             else:
                 pass
 
-            return x  # static analysis: ignore[possibly_undefined_name]
+            return x  # E: possibly_undefined_name
 
         def kerodon():
             # make sure we don't propagate the UNINITIALIZED_VALUE from
@@ -1705,6 +1705,17 @@ class TestConstraints(TestNameCheckVisitorBase):
             else:
                 assert_is_value(x, AnnotatedValue(KnownValue(None), [KnownValue(1)]))
 
+    @assert_passes()
+    def test_possibly_undefined(self):
+        from typing_extensions import Literal, assert_type
+
+        def capybara(cond):
+            if cond:
+                x = 1
+
+            if x:  # E: possibly_undefined_name
+                assert_type(x, Literal[1])
+
 
 class TestComposite(TestNameCheckVisitorBase):
     @assert_passes()