nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: quora/pyanalyze
Pull request #305 (2021-12-03 16:00:09)

fix nested classes


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 704bc74..619bd91 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Fix handling of nested classes (#305)
 - Support Protocols for runtime types that are also defined in stubs (#297)
 - Better detect signatures of methods in stub files (#304)
 - Improve handling of positional-only arguments in stub files (#303)
diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py
index 3240c20..88de71d 100644
--- a/pyanalyze/name_check_visitor.py
+++ b/pyanalyze/name_check_visitor.py
@@ -1188,12 +1188,22 @@ class NameCheckVisitor(
         value = self._visit_class_and_get_value(node)
         return self._set_name_in_scope(node.name, node, value)
 
+    def _get_class_object(self, node: ast.ClassDef) -> Value:
+        if self.scopes.scope_type() == ScopeType.module_scope:
+            return self.scopes.get(node.name, node, self.state)
+        elif (
+            self.scopes.scope_type() == ScopeType.class_scope
+            and self.current_class is not None
+            and hasattr(self.current_class, "__dict__")
+        ):
+            runtime_obj = self.current_class.__dict__.get(node.name)
+            if isinstance(runtime_obj, type):
+                return KnownValue(runtime_obj)
+        return AnyValue(AnySource.inference)
+
     def _visit_class_and_get_value(self, node: ast.ClassDef) -> Value:
         if self._is_checking():
-            if self.scopes.scope_type() == ScopeType.module_scope:
-                cls_obj = self.scopes.get(node.name, node, self.state)
-            else:
-                cls_obj = AnyValue(AnySource.inference)
+            cls_obj = self._get_class_object(node)
 
             if isinstance(cls_obj, MultiValuedValue) and self.module is not None:
                 # if there are multiple, see if there is only one that matches this module


Test patch:
diff --git a/pyanalyze/test_name_check_visitor.py b/pyanalyze/test_name_check_visitor.py
index 4ee6763..88d3ad0 100644
--- a/pyanalyze/test_name_check_visitor.py
+++ b/pyanalyze/test_name_check_visitor.py
@@ -401,12 +401,15 @@ def run():
         def run():
             print({key: 1})
 
-    @assert_fails(ErrorCode.undefined_name)
+    @assert_passes()
     def test_nested_classes(self):
         class Caviids(object):
             class Capybaras(object):
                 if False:
-                    print(neochoerus)
+                    print(neochoerus)  # E: undefined_name
+
+            def method(self, cap: Capybaras):
+                assert_is_value(cap, TypedValue(Caviids.Capybaras))
 
     @assert_fails(ErrorCode.undefined_name)
     def test_class_in_function(self):
Repository: quora/pyanalyze
Pull request #326 (2021-12-08 17:10:20)

support negative ints


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 60d398e..912ed2f 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Support literals over negative numbers in stubs
+  and stringified annotations (#326)
 - Improved overload matching algorithm (#321) (#324)
 - Support runtime overloaded functions with `pyanalyze.extensions.overload` (#318)
 - Internal support for overloaded functions (#316)
diff --git a/pyanalyze/annotations.py b/pyanalyze/annotations.py
index 1f307c1..714ad0c 100644
--- a/pyanalyze/annotations.py
+++ b/pyanalyze/annotations.py
@@ -28,6 +28,7 @@ import typing
 import typing_inspect
 import qcore
 import ast
+from typed_ast import ast3
 import builtins
 from collections.abc import Callable, Iterable
 from typing import (
@@ -752,6 +753,16 @@ class _Visitor(ast.NodeVisitor):
         else:
             return None
 
+    def visit_UnaryOp(self, node: ast.UnaryOp) -> Optional[Value]:
+        # Only int and float negation on literals are supported.
+        if isinstance(node.op, (ast.USub, ast3.USub)):
+            operand = self.visit(node.operand)
+            if isinstance(operand, KnownValue) and isinstance(
+                operand.val, (int, float)
+            ):
+                return KnownValue(-operand.val)
+        return None
+
     def visit_Call(self, node: ast.Call) -> Optional[Value]:
         func = self.visit(node.func)
         if not isinstance(func, KnownValue):


Test patch:
diff --git a/pyanalyze/test_annotations.py b/pyanalyze/test_annotations.py
index 7c7c928..f1540a3 100644
--- a/pyanalyze/test_annotations.py
+++ b/pyanalyze/test_annotations.py
@@ -510,14 +510,22 @@ def f(x: int, y: List[str]):
     def test_pep604(self):
         self.assert_passes(
             """
-from __future__ import annotations
+            from __future__ import annotations
 
-def capybara(x: int | None, y: int | str) -> None:
-    assert_is_value(x, MultiValuedValue([TypedValue(int), KnownValue(None)]))
-    assert_is_value(y, MultiValuedValue([TypedValue(int), TypedValue(str)]))
-"""
+            def capybara(x: int | None, y: int | str) -> None:
+                assert_is_value(x, MultiValuedValue([TypedValue(int), KnownValue(None)]))
+                assert_is_value(y, MultiValuedValue([TypedValue(int), TypedValue(str)]))
+            """
         )
 
+    @assert_passes()
+    def test_stringified_ops(self):
+        from typing_extensions import Literal
+
+        def capybara(x: "int | str", y: "Literal[-1]"):
+            assert_is_value(x, TypedValue(int) | TypedValue(str))
+            assert_is_value(y, KnownValue(-1))
+
     @skip_before((3, 8))
     @assert_fails(ErrorCode.incompatible_argument)
     def test_initvar(self):
Repository: quora/pyanalyze
Pull request #334 (2021-12-12 20:52:52)

Support function calls in annotations


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index f748640..2fdbbbe 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Support function calls in annotations (#334)
 - Better support for `TypeVar` bounds and constraints in stubs (#333)
 - Improve type checking of `dict.update` and `dict.copy` (#328)
 - Improve support for complex type aliases in stubs
diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py
index 83611d9..70c8ee0 100644
--- a/pyanalyze/name_check_visitor.py
+++ b/pyanalyze/name_check_visitor.py
@@ -4014,7 +4014,9 @@ class NameCheckVisitor(
         else:
             keywords = []
 
-        return_value, constraint = self.check_call(node, callee_wrapped, args, keywords)
+        return_value, constraint = self.check_call(
+            node, callee_wrapped, args, keywords, allow_call=self.in_annotation
+        )
 
         if self._is_checking():
             self.yield_checker.record_call(callee_wrapped, node)


Test patch:
diff --git a/pyanalyze/test_annotations.py b/pyanalyze/test_annotations.py
index cf1fd0a..85de6a1 100644
--- a/pyanalyze/test_annotations.py
+++ b/pyanalyze/test_annotations.py
@@ -1349,6 +1349,49 @@ class TestRequired(TestNameCheckVisitorBase):
                 ),
             )
 
+    @assert_passes()
+    def test_typeddict_from_call(self):
+        from typing import Optional, Any
+        from typing_extensions import NotRequired, Required, TypedDict
+
+        class Stringify(TypedDict):
+            a: "int"
+            b: "Required[str]"
+            c: "NotRequired[float]"
+
+        def make_td() -> Any:
+            return Stringify
+
+        def return_optional() -> Optional[Stringify]:
+            return None
+
+        def return_call() -> Optional[make_td()]:
+            return None
+
+        def capybara() -> None:
+            assert_is_value(
+                return_optional(),
+                KnownValue(None)
+                | TypedDictValue(
+                    {
+                        "a": (True, TypedValue(int)),
+                        "b": (True, TypedValue(str)),
+                        "c": (False, TypedValue(float)),
+                    }
+                ),
+            )
+            assert_is_value(
+                return_call(),
+                KnownValue(None)
+                | TypedDictValue(
+                    {
+                        "a": (True, TypedValue(int)),
+                        "b": (True, TypedValue(str)),
+                        "c": (False, TypedValue(float)),
+                    }
+                ),
+            )
+
     @skip_before((3, 8))
     @assert_passes()
     def test_typing(self):
Repository: quora/pyanalyze
Pull request #337 (2021-12-13 01:53:32)

Recognize code following an infinite while loop as unreachable


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 2a27450..215de6a 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Recognize code following an infinite while loop as unreachable (#337)
 - Recognize overloaded functions in stubs (#325)
 - Fix handling of classes in stubs that have an incorrect `__qualname__`
   at runtime (#336)
diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py
index 601a1cc..776a95c 100644
--- a/pyanalyze/name_check_visitor.py
+++ b/pyanalyze/name_check_visitor.py
@@ -3109,7 +3109,7 @@ class NameCheckVisitor(
             Boolability.type_always_true,
         )
         with self.scopes.subscope() as body_scope:
-            with self.scopes.loop_scope():
+            with self.scopes.loop_scope() as loop_scope:
                 # The "node" argument need not be an AST node but must be unique.
                 self.add_constraint((node, 1), constraint)
                 self._generic_visit_list(node.body)
@@ -3121,6 +3121,10 @@ class NameCheckVisitor(
                 self.add_constraint((node, 2), constraint)
                 self._generic_visit_list(node.body)
 
+        if always_entered and LEAVES_LOOP not in loop_scope:
+            # This means the code following the loop is unreachable.
+            self._set_name_in_scope(LEAVES_SCOPE, node, AnyValue(AnySource.marker))
+
     def _handle_loop_else(
         self, orelse: List[ast.stmt], body_scope: SubScope, always_entered: bool
     ) -> None:
diff --git a/pyanalyze/stacked_scopes.py b/pyanalyze/stacked_scopes.py
index f071ce8..7b7468f 100644
--- a/pyanalyze/stacked_scopes.py
+++ b/pyanalyze/stacked_scopes.py
@@ -924,12 +924,12 @@ class FunctionScope(Scope):
             yield new_name_to_nodes
 
     @contextlib.contextmanager
-    def loop_scope(self) -> Iterable[None]:
+    def loop_scope(self) -> Iterable[SubScope]:
         loop_scopes = []
         with self.subscope() as main_scope:
             loop_scopes.append(main_scope)
             with qcore.override(self, "current_loop_scopes", loop_scopes):
-                yield
+                yield main_scope
         self.combine_subscopes(
             [
                 {name: values for name, values in scope.items() if name != LEAVES_LOOP}


Test patch:
diff --git a/pyanalyze/test_name_check_visitor.py b/pyanalyze/test_name_check_visitor.py
index eba77ab..7187682 100644
--- a/pyanalyze/test_name_check_visitor.py
+++ b/pyanalyze/test_name_check_visitor.py
@@ -2550,6 +2550,19 @@ class TestAnnAssign(TestNameCheckVisitorBase):
             print(z)  # E: undefined_name
 
 
+class TestWhile(TestNameCheckVisitorBase):
+    @assert_passes()
+    def test_while_true_reachability(self):
+        def capybara() -> int:  # E: missing_return
+            while True:
+                break
+
+        def pacarana(i: int) -> int:
+            while True:
+                if i == 1:
+                    return 1
+
+
 class HasGetattr(object):
     def __getattr__(self, attr):
         return 42