nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: quora/pyanalyze
Pull request #487 (2022-03-02 23:25:01)

Infer precise signatures for TypedDict types


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 0bd0911..3ba5f92 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Infer precise call signatures for `TypedDict` types (#487)
 - Add mechanism to prevent crashes on objects
   with unusual `__getattr__` methods (#486)
 - Infer callable signatures for objects with a
diff --git a/pyanalyze/arg_spec.py b/pyanalyze/arg_spec.py
index 5f46c52..8ad1b82 100644
--- a/pyanalyze/arg_spec.py
+++ b/pyanalyze/arg_spec.py
@@ -42,6 +42,7 @@ from .value import (
     Extension,
     GenericBases,
     KVPair,
+    TypedDictValue,
     TypedValue,
     GenericValue,
     NewTypeValue,
@@ -77,6 +78,7 @@ from typing import (
     Tuple,
     Union,
 )
+from typing_extensions import is_typeddict
 import typing_inspect
 from unittest import mock
 
@@ -645,6 +647,20 @@ class ArgSpecCache:
         if argspec is not None:
             return argspec
 
+        if is_typeddict(obj) and not is_typing_name(obj, "TypedDict"):
+            td_type = type_from_runtime(obj)
+            if isinstance(td_type, TypedDictValue):
+                params = [
+                    SigParameter(
+                        key,
+                        ParameterKind.KEYWORD_ONLY,
+                        default=None if required else KnownValue(...),
+                        annotation=value,
+                    )
+                    for key, (required, value) in td_type.items.items()
+                ]
+                return Signature.make(params, td_type)
+
         if is_newtype(obj):
             assert hasattr(obj, "__supertype__")
             return Signature.make(


Test patch:
diff --git a/pyanalyze/test_typeddict.py b/pyanalyze/test_typeddict.py
new file mode 100644
index 0000000..781b1e5
--- /dev/null
+++ b/pyanalyze/test_typeddict.py
@@ -0,0 +1,37 @@
+# static analysis: ignore
+from .implementation import assert_is_value
+from .value import TypedDictValue, TypedValue
+from .test_name_check_visitor import TestNameCheckVisitorBase
+from .test_node_visitor import assert_passes
+
+
+class TestTypedDict(TestNameCheckVisitorBase):
+    @assert_passes()
+    def test_constructor(self):
+        from typing_extensions import TypedDict, NotRequired
+
+        class Capybara(TypedDict):
+            x: int
+            y: str
+
+        class MaybeCapybara(TypedDict):
+            x: int
+            y: NotRequired[str]
+
+        def capybara():
+            cap = Capybara(x=1, y="2")
+            assert_is_value(
+                cap,
+                TypedDictValue(
+                    {"x": (True, TypedValue(int)), "y": (True, TypedValue(str))}
+                ),
+            )
+            Capybara(x=1)  # E: incompatible_call
+
+            maybe_cap = MaybeCapybara(x=1)
+            assert_is_value(
+                maybe_cap,
+                TypedDictValue(
+                    {"x": (True, TypedValue(int)), "y": (False, TypedValue(str))}
+                ),
+            )
Repository: quora/pyanalyze
Pull request #488 (2022-03-03 06:08:50)

fix for Enum subclasses


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 3ba5f92..4b87fcd 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -6,7 +6,7 @@
 - Add mechanism to prevent crashes on objects
   with unusual `__getattr__` methods (#486)
 - Infer callable signatures for objects with a
-  `__getattr__` method (#485)
+  `__getattr__` method (#485, #488)
 - Do not treat attributes that raise an exception on access
   as nonexistent (#481)
 - Improve detection of unhashable dict keys and set members (#469)
diff --git a/pyanalyze/arg_spec.py b/pyanalyze/arg_spec.py
index 8ad1b82..1322759 100644
--- a/pyanalyze/arg_spec.py
+++ b/pyanalyze/arg_spec.py
@@ -42,6 +42,7 @@ from .value import (
     Extension,
     GenericBases,
     KVPair,
+    SubclassValue,
     TypedDictValue,
     TypedValue,
     GenericValue,
@@ -65,7 +66,7 @@ import qcore
 import inspect
 import sys
 import textwrap
-from types import FunctionType, ModuleType
+from types import FunctionType, ModuleType, MethodType
 from typing import (
     Any,
     Callable,
@@ -85,7 +86,7 @@ from unittest import mock
 # types.MethodWrapperType in 3.7+
 MethodWrapperType = type(object().__str__)
 
-_ENUM_CALL = enum.Enum.__call__
+_ENUM_CALL = enum.Enum.__call__.__func__
 
 
 @used  # exposed as an API
@@ -628,11 +629,21 @@ class ArgSpecCache:
         if (
             safe_isinstance(obj, type)
             and safe_issubclass(obj, enum.Enum)
-            and safe_equals(obj.__call__, _ENUM_CALL)
+            and safe_isinstance(obj.__call__, MethodType)
+            and safe_equals(obj.__call__.__func__, _ENUM_CALL)
         ):
-            return self._cached_get_argspec(
+            signature = self._cached_get_argspec(
                 _ENUM_CALL, impl, is_asynq, in_overload_resolution
             )
+            bound_sig = make_bound_method(
+                signature, Composite(SubclassValue(TypedValue(obj)))
+            )
+            if bound_sig is None:
+                return None
+            sig = bound_sig.get_signature(preserve_impl=True, ctx=self.ctx)
+            if sig is not None:
+                return sig
+            return bound_sig
 
         allow_call = FunctionsSafeToCall.contains(obj, self.options) or (
             safe_isinstance(obj, type) and safe_issubclass(obj, self.safe_bases)


Test patch:
diff --git a/pyanalyze/test_enum.py b/pyanalyze/test_enum.py
index 800172d..e0ca6a1 100644
--- a/pyanalyze/test_enum.py
+++ b/pyanalyze/test_enum.py
@@ -8,12 +8,15 @@ from .test_node_visitor import assert_passes
 class TestEnum(TestNameCheckVisitorBase):
     @assert_passes()
     def test_functional(self):
-        from enum import Enum
+        from enum import Enum, IntEnum
 
         def capybara():
             X = Enum("X", ["a", "b", "c"])
             assert_is_value(X, SubclassValue(TypedValue(Enum)))
 
+            IE = IntEnum("X", ["a", "b", "c"])
+            assert_is_value(IE, SubclassValue(TypedValue(Enum)))
+
     @assert_passes()
     def test_call(self):
         from enum import Enum
Repository: quora/pyanalyze
Pull request #489 (2022-03-03 18:49:53)

more tests for enum iteration


Patch:
diff --git a/pyanalyze/value.py b/pyanalyze/value.py
index 475c58e..f4a52af 100644
--- a/pyanalyze/value.py
+++ b/pyanalyze/value.py
@@ -2242,6 +2242,15 @@ def concrete_values_from_iterable(
         getitem_tv_map = get_tv_map(GetItemProtoValue, value, ctx)
         if not isinstance(getitem_tv_map, CanAssignError):
             val = getitem_tv_map.get(T, AnyValue(AnySource.generic_argument))
+        # Hack to support iteration over StrEnum. A better solution would have to
+        # handle descriptors better in attribute assignment and Protocol compatibility.
+        elif (
+            isinstance(value, SubclassValue)
+            and isinstance(value.typ, TypedValue)
+            and isinstance(value.typ.typ, type)
+            and safe_issubclass(value.typ.typ, enum.Enum)
+        ):
+            return value.typ
         else:
             # We return the error from the __iter__ check because the __getitem__
             # check is more arcane.


Test patch:
diff --git a/pyanalyze/test_enum.py b/pyanalyze/test_enum.py
index e0ca6a1..bebc1e5 100644
--- a/pyanalyze/test_enum.py
+++ b/pyanalyze/test_enum.py
@@ -30,3 +30,38 @@ class TestEnum(TestNameCheckVisitorBase):
             # This should be an error, but the typeshed
             # stubs are too lenient.
             assert_is_value(X(None), TypedValue(X))
+
+    @assert_passes()
+    def test_iteration(self):
+        from enum import Enum, IntEnum
+        from typing import Type
+
+        class X(Enum):
+            a = 1
+            b = 2
+
+        class MySubclass(str, Enum):
+            pass
+
+        def capybara(
+            enum_t: Type[Enum], int_enum_t: Type[IntEnum], subclass_t: Type[MySubclass]
+        ):
+            for x in X:
+                assert_is_value(x, TypedValue(X))
+
+            for et in enum_t:
+                assert_is_value(et, TypedValue(Enum))
+
+            for iet in int_enum_t:
+                assert_is_value(iet, TypedValue(IntEnum))
+
+            for st in subclass_t:
+                assert_is_value(st, TypedValue(MySubclass))
+
+    @assert_passes()
+    def test_duplicate_enum_member(self):
+        import enum
+
+        class Foo(enum.Enum):
+            a = 1
+            b = 1  # E: duplicate_enum_member
diff --git a/pyanalyze/test_name_check_visitor.py b/pyanalyze/test_name_check_visitor.py
index 23b81a8..1a657ba 100644
--- a/pyanalyze/test_name_check_visitor.py
+++ b/pyanalyze/test_name_check_visitor.py
@@ -201,14 +201,6 @@ class TestNameCheckVisitor(TestNameCheckVisitorBase):
             """,
         )
 
-    @assert_passes()
-    def test_duplicate_enum_member(self):
-        import enum
-
-        class Foo(enum.Enum):
-            a = 1
-            b = 1  # E: duplicate_enum_member
-
     @assert_passes()
     def test_undefined_name_in_return(self):
         def what_is_it():
Repository: quora/pyanalyze
Pull request #490 (2022-03-05 19:13:02)

Fix incorrectly inferred `Never` return type for some function implementations


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 4b87fcd..a3594d3 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Fix incorrectly inferred `Never` return type for some function
+  implementations (#490)
 - Infer precise call signatures for `TypedDict` types (#487)
 - Add mechanism to prevent crashes on objects
   with unusual `__getattr__` methods (#486)
diff --git a/pyanalyze/implementation.py b/pyanalyze/implementation.py
index ee3b8d3..5d84432 100644
--- a/pyanalyze/implementation.py
+++ b/pyanalyze/implementation.py
@@ -24,6 +24,7 @@ from .signature import (
     ParameterKind,
 )
 from .value import (
+    NO_RETURN_VALUE,
     UNINITIALIZED_VALUE,
     AnnotatedValue,
     AnySource,
@@ -98,8 +99,11 @@ def flatten_unions(
     unwrap_annotated: bool = False,
 ) -> ImplReturn:
     value_lists = [
-        flatten_values(val, unwrap_annotated=unwrap_annotated) for val in values
+        list(flatten_values(val, unwrap_annotated=unwrap_annotated)) for val in values
     ]
+    # If the lists are empty, we end up inferring Never as the return type, which
+    # generally isn't right.
+    value_lists = [lst if lst else [NO_RETURN_VALUE] for lst in value_lists]
     results = [
         clean_up_implementation_fn_return(callable(*vals))
         for vals in product(*value_lists)


Test patch:
diff --git a/pyanalyze/test_implementation.py b/pyanalyze/test_implementation.py
index 1d0258c..873bdc3 100644
--- a/pyanalyze/test_implementation.py
+++ b/pyanalyze/test_implementation.py
@@ -3,6 +3,7 @@ from .test_name_check_visitor import TestNameCheckVisitorBase
 from .test_node_visitor import assert_passes
 
 from .value import (
+    NO_RETURN_VALUE,
     AnnotatedValue,
     AnySource,
     AnyValue,
@@ -519,6 +520,18 @@ class TestGenericMutators(TestNameCheckVisitorBase):
             lst += [x]
             assert_is_value(lst, GenericValue(list, [TypedValue(int)]))
 
+    @assert_passes()
+    def test_list_iadd_never(self):
+        def render_feedback_text():
+            z = []
+
+            detail_text = None
+            if detail_text:
+                assert_is_value(detail_text, NO_RETURN_VALUE)
+                z += detail_text
+
+            return z
+
     @assert_passes()
     def test_weak_value(self):
         from typing import List
Repository: quora/pyanalyze
Pull request #495 (2022-03-08 03:41:19)

Fix type inference for `with` assignment targets on Python 3.7 and higher


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index ac76b41..da0d3df 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Fix type inference for `with` assignment targets on
+  Python 3.7 and higher (#495)
 - Fix bug where code after a `while` loop is considered
   unreachable if all `break` statements are inside of `if`
   statements (#494)
diff --git a/pyanalyze/annotations.py b/pyanalyze/annotations.py
index 11d8133..7911dca 100644
--- a/pyanalyze/annotations.py
+++ b/pyanalyze/annotations.py
@@ -23,6 +23,7 @@ These functions all use :class:`Context` objects to resolve names and
 show errors.
 
 """
+import contextlib
 from dataclasses import dataclass, InitVar, field
 import typing
 
@@ -31,6 +32,7 @@ import qcore
 import ast
 import builtins
 from collections.abc import Callable, Iterable, Hashable
+import sys
 from typing import (
     Any,
     Container,
@@ -110,6 +112,18 @@ except ImportError:
         return ()
 
 
+CONTEXT_MANAGER_TYPES = (typing.ContextManager, contextlib.AbstractContextManager)
+if sys.version_info >= (3, 7):
+    ASYNC_CONTEXT_MANAGER_TYPES = (
+        typing.AsyncContextManager,
+        # Doesn't exist on 3.6
+        # static analysis: ignore[undefined_attribute]
+        contextlib.AbstractAsyncContextManager,
+    )
+else:
+    ASYNC_CONTEXT_MANAGER_TYPES = (typing.AsyncContextManager,)
+
+
 @dataclass
 class Context:
     """A context for evaluating annotations.
@@ -785,9 +799,9 @@ def _type_from_subscripted_value(
 def _maybe_get_extra(origin: type) -> Union[type, str]:
     # ContextManager is defined oddly and we lose the Protocol if we don't use
     # synthetic types.
-    if origin is typing.ContextManager:
+    if any(origin is cls for cls in CONTEXT_MANAGER_TYPES):
         return "typing.ContextManager"
-    elif origin is typing.AsyncContextManager:
+    elif any(origin is cls for cls in ASYNC_CONTEXT_MANAGER_TYPES):
         return "typing.AsyncContextManager"
     else:
         # turn typing.List into list in some Python versions


Test patch:
diff --git a/pyanalyze/test_annotations.py b/pyanalyze/test_annotations.py
index cbc3dff..752b8f9 100644
--- a/pyanalyze/test_annotations.py
+++ b/pyanalyze/test_annotations.py
@@ -155,6 +155,20 @@ class TestAnnotations(TestNameCheckVisitorBase):
         def post_capybara() -> Iterator[int]:
             yield 3
 
+    @assert_passes()
+    def test_contextmanager_class(self):
+        from typing import ContextManager
+
+        def f() -> ContextManager[int]:
+            raise NotImplementedError
+
+        def capybara():
+            assert_is_value(
+                f(), GenericValue("typing.ContextManager", [TypedValue(int)])
+            )
+            with f() as x:
+                assert_is_value(x, TypedValue(int))
+
     @assert_passes()
     def test_none_annotations(self):
         def mara() -> None:
Repository: GoogleChrome/lighthouse
Pull request #7671 (2019-03-24 20:51:38)

docs(url-shim): add comment about public suffix lst
back in https://github.com/GoogleChrome/lighthouse/pull/5065#discussion_r190330586 i wanted to add a comment mentioning PSL because it's the name of this thing.


anyway, that's it.

cc @patrickhulce as i noticed you do the same sorta thing in https://github.com/patrickhulce/third-party-web/blob/master/lib/index.js#L5




Patch:
diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js
index 53953b73c..e3496d88a 100644
--- a/lighthouse-core/audits/font-display.js
+++ b/lighthouse-core/audits/font-display.js
@@ -114,6 +114,8 @@ class FontDisplay extends Audit {
       .filter(record => record.resourceType === 'Font')
       // ...that don't have a passing font-display value
       .filter(record => !passingFontURLs.has(record.url))
+      // ...and that aren't data URLs, the blocking concern doesn't really apply
+      .filter(record => !/^data:/.test(record.url))
       .map(record => {
         // In reality the end time should be calculated with paint time included
         // all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
diff --git a/lighthouse-core/lib/url-shim.js b/lighthouse-core/lib/url-shim.js
index a275bd88a..52691ef4b 100644
--- a/lighthouse-core/lib/url-shim.js
+++ b/lighthouse-core/lib/url-shim.js
@@ -17,8 +17,9 @@ const Util = require('../report/html/renderer/util.js');
 const URL = /** @type {!Window["URL"]} */ (typeof self !== 'undefined' && self.URL) ||
     require('url').URL;
 
-// 25 most used tld plus one domains from http archive.
+// 25 most used tld plus one domains (aka public suffixes) from http archive.
 // @see https://github.com/GoogleChrome/lighthouse/pull/5065#discussion_r191926212
+// The canonical list is https://publicsuffix.org/learn/ but we're only using subset to conserve bytes
 const listOfTlds = [
   'com', 'co', 'gov', 'edu', 'ac', 'org', 'go', 'gob', 'or', 'net', 'in', 'ne', 'nic', 'gouv',
   'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on',


Test patch:
diff --git a/lighthouse-core/test/audits/font-display-test.js b/lighthouse-core/test/audits/font-display-test.js
index 0d20f4951..575eb6e06 100644
--- a/lighthouse-core/test/audits/font-display-test.js
+++ b/lighthouse-core/test/audits/font-display-test.js
@@ -170,6 +170,11 @@ describe('Performance: Font Display audit', () => {
         endTime: 2, startTime: 1,
         resourceType: 'Font',
       },
+      {
+        url: 'data:application/font-woff',
+        endTime: 7, startTime: 1,
+        resourceType: 'Font',
+      },
     ];
 
     const result = await FontDisplayAudit.audit(getArtifacts(), context);
Repository: GoogleChrome/lighthouse
Pull request #7684 (2019-03-25 14:10:52)

core(font-display): ignore data URLs

**Summary**
It doesn't really make sense to flag these as our concern doesn't really apply. It's also difficult to find where it's included (inspired by #7674)

**Related Issues/PRs**
#7674 



Patch:
diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js
index 53953b73c..e3496d88a 100644
--- a/lighthouse-core/audits/font-display.js
+++ b/lighthouse-core/audits/font-display.js
@@ -114,6 +114,8 @@ class FontDisplay extends Audit {
       .filter(record => record.resourceType === 'Font')
       // ...that don't have a passing font-display value
       .filter(record => !passingFontURLs.has(record.url))
+      // ...and that aren't data URLs, the blocking concern doesn't really apply
+      .filter(record => !/^data:/.test(record.url))
       .map(record => {
         // In reality the end time should be calculated with paint time included
         // all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time


Test patch:
diff --git a/lighthouse-core/test/audits/font-display-test.js b/lighthouse-core/test/audits/font-display-test.js
index 0d20f4951..575eb6e06 100644
--- a/lighthouse-core/test/audits/font-display-test.js
+++ b/lighthouse-core/test/audits/font-display-test.js
@@ -170,6 +170,11 @@ describe('Performance: Font Display audit', () => {
         endTime: 2, startTime: 1,
         resourceType: 'Font',
       },
+      {
+        url: 'data:application/font-woff',
+        endTime: 7, startTime: 1,
+        resourceType: 'Font',
+      },
     ];
 
     const result = await FontDisplayAudit.audit(getArtifacts(), context);
Repository: GoogleChrome/lighthouse
Pull request #7712 (2019-03-26 14:06:12)

core(font-display): handle carriage returns
**Summary**
Apparently leftover `\r` characters destroy our match statement later on so we should remove them before trying to find our `@font-face` declarations.

**Related Issues/PRs**
#7708


Patch:
diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js
index e3496d88a..a8bdba6b2 100644
--- a/lighthouse-core/audits/font-display.js
+++ b/lighthouse-core/audits/font-display.js
@@ -52,7 +52,7 @@ class FontDisplay extends Audit {
     // Go through all the stylesheets to find all @font-face declarations
     for (const stylesheet of artifacts.CSSUsage.stylesheets) {
       // Eliminate newlines so we can more easily scan through with a regex
-      const newlinesStripped = stylesheet.content.replace(/\n/g, ' ');
+      const newlinesStripped = stylesheet.content.replace(/(\r|\n)+/g, ' ');
       // Find the @font-faces
       const fontFaceDeclarations = newlinesStripped.match(/@font-face\s*{(.*?)}/g) || [];
       // Go through all the @font-face declarations to find a declared `font-display: ` property


Test patch:
diff --git a/lighthouse-core/test/audits/font-display-test.js b/lighthouse-core/test/audits/font-display-test.js
index 575eb6e06..cfe0d68a2 100644
--- a/lighthouse-core/test/audits/font-display-test.js
+++ b/lighthouse-core/test/audits/font-display-test.js
@@ -136,18 +136,20 @@ describe('Performance: Font Display audit', () => {
   it('passes when all fonts have a correct font-display rule', async () => {
     stylesheet.content = `
       @font-face {
+        /* make sure we can handle carriage returns */
+        \r\n
         font-display: block;
         /* try with " */
         src: url("./font-a.woff");
       }
 
-      @font-face {
+      @font-face {\r
         font-display: fallback;
         /* try up a directory with ' */
         src: url('../font-b.woff');
       }
 
-      @font-face {
+      @font-face {\n
         font-display: optional;
         /* try no path with no quotes ' */
         src: url(font.woff);