Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix false positive missing-attribute on overridden class variables#2626

Open
grievejia wants to merge 1 commit intofacebook:mainfrom
grievejia:export-D94992218
Open

Fix false positive missing-attribute on overridden class variables#2626
grievejia wants to merge 1 commit intofacebook:mainfrom
grievejia:export-D94992218

Conversation

@grievejia
Copy link
Contributor

Summary:
When a subclass overrides an unannotated class variable with a compatible narrower type, pyrefly was widening the child's type to the parent's type. This caused false positives when the child's narrower type has methods not present on the parent's type. For example, if a parent class has request_class = Request and a child overrides it with request_class = FormRequest, accessing FormRequest-specific methods like from_response() would produce a spurious missing-attribute error because pyrefly stored the type as type[Request] instead of type[FormRequest].

The root cause was in analyze_class_field_value: when both parent and child lack annotations and the child's value is compatible with the parent's type, the code discarded the child's inferred type and returned the parent's type. This widening was intended to prevent invariant override errors for read-write attributes, but it loses type precision.

The fix changes the compatible branch to use the child's inferred type and sets is_inherited = IsInherited::No to skip the now-redundant override check (compatibility has already been validated by the errors2.is_empty() check). The IsInherited enum is documented as "only used for efficiency" — setting it to No just tells the override checker to skip this field, which is safe.

Differential Revision: D94992218

Summary:
When a subclass overrides an unannotated class variable with a compatible narrower type, pyrefly was widening the child's type to the parent's type. This caused false positives when the child's narrower type has methods not present on the parent's type. For example, if a parent class has `request_class = Request` and a child overrides it with `request_class = FormRequest`, accessing `FormRequest`-specific methods like `from_response()` would produce a spurious `missing-attribute` error because pyrefly stored the type as `type[Request]` instead of `type[FormRequest]`.

The root cause was in `analyze_class_field_value`: when both parent and child lack annotations and the child's value is compatible with the parent's type, the code discarded the child's inferred type and returned the parent's type. This widening was intended to prevent invariant override errors for read-write attributes, but it loses type precision.

The fix changes the compatible branch to use the child's inferred type and sets `is_inherited = IsInherited::No` to skip the now-redundant override check (compatibility has already been validated by the `errors2.is_empty()` check). The `IsInherited` enum is documented as "only used for efficiency" — setting it to `No` just tells the override checker to skip this field, which is safe.

Differential Revision: D94992218
@meta-cla meta-cla bot added the cla signed label Mar 3, 2026
@meta-codesync
Copy link

meta-codesync bot commented Mar 3, 2026

@grievejia has exported this pull request. If you are a Meta employee, you can view the originating Diff in D94992218.

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Diff from mypy_primer, showing the effect of this PR on open source code:

apprise (https://github.com/caronc/apprise)
- ERROR apprise/plugins/bluesky.py:649:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/bluesky.py:666:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/discord.py:693:16-75: Returned type `tuple[Unknown | None, Unknown | None, Unknown | None]` is not assignable to declared return type `tuple[str, str, str]` [bad-return]
+ ERROR apprise/plugins/discord.py:693:16-75: Returned type `tuple[str, Unknown | None, Unknown | None]` is not assignable to declared return type `tuple[str, str, str]` [bad-return]
- ERROR apprise/plugins/fortysixelks.py:281:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/fortysixelks.py:296:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/fortysixelks.py:329:28-59: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:357:18-34: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/irc/base.py:384:16-389:10: Returned type `tuple[Unknown | None, str, Unknown | None, Unknown | None]` is not assignable to declared return type `tuple[str, str | None, str | None, str | None]` [bad-return]
- ERROR apprise/plugins/mastodon.py:369:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/mastodon.py:403:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/mastodon.py:403:61-77: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/notificationapi.py:520:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/notificationapi.py:593:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/office365.py:948:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/office365.py:982:24-47: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/smsmanager.py:351:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/smsmanager.py:370:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/splunk.py:414:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/splunk.py:435:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/splunk.py:519:28-59: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/twitter.py:850:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/twitter.py:873:24-47: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/viber.py:180:16-50: Returned type `tuple[Unknown | None, Unknown | None]` is not assignable to declared return type `str` [bad-return]
+ ERROR apprise/plugins/viber.py:180:16-50: Returned type `tuple[str, Unknown | None]` is not assignable to declared return type `str` [bad-return]
- ERROR apprise/plugins/vonage.py:316:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/vonage.py:330:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/webexteams.py:224:17-40: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/webexteams.py:233:20-43: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/webexteams.py:276:28-63: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/workflows.py:504:13-36: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/workflows.py:542:24-47: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/workflows.py:679:28-62: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/xbmc.py:320:17-59: `<=` is not supported between `None` and `int` [unsupported-operation]
+ ERROR apprise/plugins/xbmc.py:320:17-59: `<=` is not supported between `tuple[Literal['xbmc'], Literal['kodi']]` and `int` [unsupported-operation]
- ERROR apprise/plugins/xbmc.py:368:17-59: `<=` is not supported between `None` and `int` [unsupported-operation]
+ ERROR apprise/plugins/xbmc.py:368:17-59: `<=` is not supported between `tuple[Literal['xbmc'], Literal['kodi']]` and `int` [unsupported-operation]
- ERROR apprise/plugins/xmpp/base.py:252:16-255:10: Returned type `tuple[Unknown | None, str, Unknown | None, Unknown | None, Unknown | None]` is not assignable to declared return type `tuple[str, str, str, str, int | None]` [bad-return]
+ ERROR apprise/plugins/xmpp/base.py:252:16-255:10: Returned type `tuple[str, str, Unknown | None, Unknown | None, Unknown | None]` is not assignable to declared return type `tuple[str, str, str, str, int | None]` [bad-return]
+ ERROR tests/test_apprise_config.py:732:9-21: Class member `ConfigCrossPostAlways.service_name` overrides parent class `ConfigFile` in an inconsistent manner [bad-override]
+ ERROR tests/test_apprise_config.py:743:9-21: Class member `ConfigCrossPostStrict.service_name` overrides parent class `ConfigFile` in an inconsistent manner [bad-override]
+ ERROR tests/test_apprise_config.py:754:9-21: Class member `ConfigCrossPostNever.service_name` overrides parent class `ConfigFile` in an inconsistent manner [bad-override]

core (https://github.com/home-assistant/core)
- ERROR homeassistant/components/hunterdouglas_powerview/cover.py:115:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/hunterdouglas_powerview/cover.py:352:9-38: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/hunterdouglas_powerview/cover.py:358:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/hunterdouglas_powerview/cover.py:929:9-38: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/hunterdouglas_powerview/cover.py:935:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/lcn/cover.py:195:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/velux/cover.py:220:9-38: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/zwave_js/cover.py:111:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]
- ERROR homeassistant/components/zwave_js/cover.py:268:13-42: `CoverEntityFeature | int` is not assignable to attribute `_attr_supported_features` with type `CoverEntityFeature | None` [bad-assignment]

zope.interface (https://github.com/zopefoundation/zope.interface)
- ERROR src/zope/interface/common/collections.py:157:5-8: Class member `ICollection.abc` overrides parent class `IIterable` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:157:5-8: Class member `ICollection.abc` overrides parent class `IContainer` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:166:5-8: Class member `ISequence.abc` overrides parent class `ICollection` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:205:5-8: Class member `ISet.abc` overrides parent class `ICollection` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:213:5-8: Class member `IMapping.abc` overrides parent class `ICollection` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:233:5-8: Class member `IItemsView.abc` overrides parent class `ISet` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/collections.py:237:5-8: Class member `IKeysView.abc` overrides parent class `ISet` in an inconsistent manner [bad-override]
- ERROR src/zope/interface/common/tests/test_collections.py:137:5-20: Class member `TestVerifyObject.UNVERIFIABLE_RO` overrides parent class `VerifyObjectMixin` in an inconsistent manner [bad-override]

cloud-init (https://github.com/canonical/cloud-init)
- ERROR cloudinit/config/cc_ca_certs.py:118:44-120:6: No matching overload found for function `posixpath.join` called with arguments: (list[str] | str | Unknown | None, list[str] | str | Unknown | None) [no-matching-overload]
+ ERROR cloudinit/config/cc_ca_certs.py:118:44-120:6: No matching overload found for function `posixpath.join` called with arguments: (list[str] | str | None, list[str] | str | None) [no-matching-overload]

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- ERROR mitmproxy/proxy/layers/dns.py:145:30-46: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@DNSLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/dns.py:160:38-53: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@DNSLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/dns.py:179:34-49: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@DNSLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/tcp.py:87:38-47: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@TCPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/tcp.py:89:30-49: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@TCPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/tcp.py:128:38-47: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@TCPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/udp.py:86:38-47: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@UDPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/udp.py:88:30-49: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@UDPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/udp.py:122:34-43: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@UDPLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/websocket.py:132:30-49: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@WebsocketLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]
- ERROR mitmproxy/proxy/layers/websocket.py:218:38-47: `((self: Unknown, event: Event) -> Unknown) | Unknown` is not assignable to attribute `_handle_event` with type `(self: Self@WebsocketLayer, event: Event) -> Generator[Command, Any]` [bad-assignment]

httpx-caching (https://github.com/johtso/httpx-caching)
- ERROR tests/_async/test_expires_heuristics.py:40:20-49: Object of class `FunctionType` has no attribute `called` [missing-attribute]

mypy (https://github.com/python/mypy)
- ERROR mypy/typeshed/stdlib/enum.pyi:324:9-16: Class member `IntFlag.__ror__` overrides parent class `Flag` in an inconsistent manner [bad-override]
- ERROR mypy/typeshed/stdlib/enum.pyi:325:9-17: Class member `IntFlag.__rand__` overrides parent class `Flag` in an inconsistent manner [bad-override]
- ERROR mypy/typeshed/stdlib/enum.pyi:326:9-17: Class member `IntFlag.__rxor__` overrides parent class `Flag` in an inconsistent manner [bad-override]
- ERROR mypy/typeshed/stdlib/tkinter/ttk.pyi:759:5-11: Class member `Panedwindow.forget` overrides parent class `Widget` in an inconsistent manner [bad-override]

scrapy (https://github.com/scrapy/scrapy)
+ ERROR tests/test_http_request.py:513:84-88: Argument `tuple[tuple[Literal['a'], Literal['one']], tuple[Literal['a'], Literal['two']], tuple[Literal['b'], Literal['2']]]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:515:65-69: Argument `tuple[tuple[Literal['a'], Literal['one']], tuple[Literal['a'], Literal['two']], tuple[Literal['b'], Literal['2']]]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:522:82-86: Argument `dict[str, str]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:530:68-72: Argument `dict[bytes, bytes]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:539:68-72: Argument `dict[str, str]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:548:68-72: Argument `dict[bytes | str, bytes | str]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:557:48-52: Argument `dict[bytes, bytes]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:567:48-52: Argument `dict[str, str]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
+ ERROR tests/test_http_request.py:575:68-72: Argument `dict[str, list[str] | str]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.__init__` [bad-argument-type]
- ERROR tests/test_http_request.py:507:59-67: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:513:75-83: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:515:56-64: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:522:73-81: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:530:59-67: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:539:59-67: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:548:59-67: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:557:39-47: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:567:39-47: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:575:59-67: Unexpected keyword argument `formdata` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:589:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:611:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:634:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:656:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
+ ERROR tests/test_http_request.py:674:22-54: Argument `tuple[tuple[Literal['foo'], Literal['bar']], tuple[Literal['foo'], Literal['baz']]]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.from_response` [bad-argument-type]
+ ERROR tests/test_http_request.py:687:32-60: Argument `tuple[tuple[Literal['two'], Literal['2']], tuple[Literal['two'], Literal['4']]]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.from_response` [bad-argument-type]
+ ERROR tests/test_http_request.py:750:67-80: Argument `dict[str, None]` is not assignable to parameter `formdata` with type `dict[str, Iterable[str] | str] | list[tuple[str, Iterable[str] | str]] | None` in function `scrapy.http.request.form.FormRequest.from_response` [bad-argument-type]
- ERROR tests/test_http_request.py:671:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:686:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:701:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:719:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:738:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:750:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:787:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:802:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:818:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:835:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:848:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:859:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:872:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:889:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:905:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:923:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:935:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:950:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:969:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:980:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:992:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1007:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1012:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1020:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1033:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1042:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1059:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1073:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1088:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1101:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1117:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1133:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1148:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1178:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1195:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1212:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1226:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1239:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1252:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1274:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1289:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1295:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1302:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1308:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1316:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1327:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1345:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1363:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1381:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1405:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1410:15-47: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1424:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1430:14-46: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1435:13-45: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1439:44-81: Class `Request` has no class attribute `valid_form_methods` [missing-attribute]
- ERROR tests/test_http_request.py:1449:17-49: Class `Request` has no class attribute `from_response` [missing-attribute]
- ERROR tests/test_http_request.py:1563:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1567:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1582:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1586:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1589:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1599:79-83: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1611:78-82: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1622:79-83: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1629:79-83: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1639:63-67: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1654:48-52: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1654:59-71: Unexpected keyword argument `dumps_kwargs` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1667:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1679:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1698:44-48: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1698:56-68: Unexpected keyword argument `dumps_kwargs` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_request.py:1716:64-68: Unexpected keyword argument `data` in function `scrapy.http.request.Request.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:355:44-51: Argument `Literal['hello']` is not assignable to parameter `body` with type `bytes` in function `scrapy.http.response.Response.__init__` [bad-argument-type]
- ERROR tests/test_http_response.py:355:53-61: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:364:16-37: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:369:46-83: Class `Response` has no class attribute `_DEFAULT_ENCODING` [missing-attribute]
- ERROR tests/test_http_response.py:372:67-75: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:376:54-62: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:380:54-62: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:400:64-78: Argument `Literal['unicode body']` is not assignable to parameter `body` with type `bytes` in function `scrapy.http.response.Response.__init__` [bad-argument-type]
- ERROR tests/test_http_response.py:404:61-69: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:418:39-47: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:418:62-68: Argument `Literal['£']` is not assignable to parameter `body` with type `bytes` in function `scrapy.http.response.Response.__init__` [bad-argument-type]
- ERROR tests/test_http_response.py:454:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:455:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:456:16-37: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:458:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:459:16-37: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:460:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:461:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:462:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:463:16-36: Object of class `Response` has no attribute `_headers_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:464:16-37: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:465:16-37: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:469:16-42: Object of class `Response` has no attribute `_body_inferred_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:470:16-42: Object of class `Response` has no attribute `_body_inferred_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:480:64-70: Argument `Literal['£']` is not assignable to parameter `body` with type `bytes` in function `scrapy.http.response.Response.__init__` [bad-argument-type]
- ERROR tests/test_http_response.py:489:16-36: Object of class `Response` has no attribute `_declared_encoding` [missing-attribute]
- ERROR tests/test_http_response.py:497:13-21: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:507:16-27: Object of class `Response` has no attribute `encoding` [missing-attribute]
- ERROR tests/test_http_response.py:525:16-33: Object of class `Response` has no attribute `encoding` [missing-attribute]
- ERROR tests/test_http_response.py:529:16-33: Object of class `Response` has no attribute `encoding` [missing-attribute]
- ERROR tests/test_http_response.py:535:16-33: Object of class `Response` has no attribute `encoding` [missing-attribute]
- ERROR tests/test_http_response.py:539:16-33: Object of class `Response` has no attribute `encoding` [missing-attribute]
- ERROR tests/test_http_response.py:545:13-21: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:557:13-21: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:570:27-44: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:585:16-33: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:589:16-33: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:711:13-21: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:723:13-21: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:811:16-34: Object of class `Response` has no attribute `json` [missing-attribute]
- ERROR tests/test_http_response.py:818:13-31: Object of class `Response` has no attribute `json` [missing-attribute]
- ERROR tests/test_http_response.py:829:21-39: Object of class `Response` has no attribute `json` [missing-attribute]
- ERROR tests/test_http_response.py:880:42-79: Class `Response` has no class attribute `_DEFAULT_ENCODING` [missing-attribute]
- ERROR tests/test_http_response.py:888:71-79: Unexpected keyword argument `encoding` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:908:27-44: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:921:16-33: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:935:16-33: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:940:9-26: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:945:16-33: Object of class `Response` has no attribute `selector` [missing-attribute]
- ERROR tests/test_http_response.py:967:13-16: Unexpected keyword argument `foo` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:968:13-16: Unexpected keyword argument `bar` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:969:13-17: Unexpected keyword argument `lost` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:973:16-22: Object of class `Response` has no attribute `foo` [missing-attribute]
- ERROR tests/test_http_response.py:973:26-32: Object of class `Response` has no attribute `foo` [missing-attribute]
- ERROR tests/test_http_response.py:974:16-22: Object of class `Response` has no attribute `bar` [missing-attribute]
- ERROR tests/test_http_response.py:974:26-32: Object of class `Response` has no attribute `bar` [missing-attribute]
- ERROR tests/test_http_response.py:975:16-23: Object of class `Response` has no attribute `lost` [missing-attribute]
- ERROR tests/test_http_response.py:976:16-23: Object of class `Response` has no attribute `lost` [missing-attribute]
- ERROR tests/test_http_response.py:983:13-16: Unexpected keyword argument `foo` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:984:13-16: Unexpected keyword argument `bar` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:985:13-17: Unexpected keyword argument `lost` in function `scrapy.http.response.Response.__init__` [unexpected-keyword]
- ERROR tests/test_http_response.py:990:16-22: Object of class `Response` has no attribute `foo` [missing-attribute]
- ERROR tests/test_http_response.py:991:16-22: Object of class `Response` has no attribute `bar` [missing-attribute]
- ERROR tests/test_http_response.py:992:16-23: Object of class `Response` has no attribute `lost` [missing-attribute]
- ERROR tests/test_http_response.py:993:16-22: Object of class `Response` has no attribute `foo` [missing-attribute]
- ERROR tests/test_http_response.py:994:16-22: Object of class `Response` has no attribute `bar` [missing-attribute]
- ERROR tests/test_http_response.py:995:16-23: Object of class `Response` has no attribute `lost` [missing-attribute]
- ERROR tests/test_http_response.py:1002:16-22: Object of class `Response` has no attribute `foo` [missing-attribute]
- ERROR tests/test_http_response.py:1003:16-22: Object of class `Response` has no attribute `bar` [missing-attribute]
- ERROR tests/test_http_response.py:1004:16-23: Object of class `Response` has no attribute `lost` [missing-attribute]

... (truncated 42 lines) ...

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Primer Diff Classification

✅ 7 improvement(s) | ➖ 1 neutral | 8 project(s) total

7 improvement(s) across apprise, core, zope.interface, mitmproxy, httpx-caching, mypy, scrapy.

Project Verdict Changes Error Kinds Root Cause
apprise ✅ Improvement +8, -34 bad-return with Unknown types analyze_class_field_value()
core ✅ Improvement -9 bad-assignment analyze_class_field_value()
zope.interface ✅ Improvement -7 bad-override analyze_class_field_value()
cloud-init ➖ Neutral +1, -1 no-matching-overload
mitmproxy ✅ Improvement -11 bad-assignment pyrefly/lib/alt/class/class_field.rs
httpx-caching ✅ Improvement -1 missing-attribute analyze_class_field_value()
mypy ✅ Improvement -4 bad-override analyze_class_field_value()
scrapy ✅ Improvement +23, -188 bad-argument-type, bad-override analyze_class_field_value()
Detailed analysis

✅ Improvement (7)

apprise (+8, -34)

bad-return with Unknown types: These correctly identify functions returning tuples with Unknown/None elements instead of the required string types - genuine type mismatches
bad-override on ConfigFile: These correctly identify inconsistent class member overrides between parent and child classes
unsupported-operation comparisons: The new error correctly identifies an invalid comparison between a tuple of literals and an int
removed None subscript errors: These were false positives - the code actually accesses valid attributes on concrete types like tuples, not None

Overall: This is an improvement. The PR fixed a type inference issue where pyrefly was losing type precision when child classes override parent class variables with compatible but more specific types. The new errors correctly identify real type issues: 1) bad-return errors catch functions returning tuple[str, Unknown | None, Unknown | None] instead of tuple[str, str, str] - these are genuine type mismatches where Unknown values don't satisfy str requirements. 2) bad-override errors catch inconsistent class member overrides. 3) unsupported-operation catches invalid comparisons like tuple[Literal['xbmc'], Literal['kodi']] <= int. The removed errors were false positives claiming None was being subscripted, but the actual code accesses valid attributes on concrete types like self.secure_protocol[0] where secure_protocol is a tuple of strings.

Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs modified the compatible branch to use the child's inferred type instead of the parent's type, and set is_inherited = IsInherited::No to skip the override check. This preserved type precision for child class overrides.

core (-9)

This is an improvement. The PR description explains that pyrefly was incorrectly widening child class types to parent types when both lacked annotations but the child's value was compatible. For example, if a parent has _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE and a child adds | CoverEntityFeature.STOP, the child's type should be preserved as the more specific union, not widened to the parent's type. The removed errors were claiming assignments like CoverEntityFeature | int to CoverEntityFeature | None were invalid, but CoverEntityFeature | int is actually a valid narrowing since CoverEntityFeature is an enum (likely with int values) and the union with int is more specific than the union with None. The fix correctly preserves type precision while maintaining compatibility checking.
Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs modified the compatible branch to use the child's inferred type instead of widening to the parent's type, and set is_inherited = IsInherited::No to skip the override check since compatibility was already validated.

zope.interface (-7)

The removed bad-override errors were false positives caused by pyrefly's previous behavior of widening child class variable types to match parent types, then flagging the apparent type mismatch. Looking at the actual code, these are legitimate class variable assignments:

  1. ICollection.abc = _new_in_ver('Collection', ...) (line 157) - assigns a compatible ABC class
  2. ISequence.abc = abc.Sequence (line 166) - assigns the standard library ABC
  3. Similar patterns for ISet and other interface classes

These assignments are type-safe: each child class assigns an ABC that properly implements the interface hierarchy. The child's more specific type (e.g., abc.Sequence) is compatible with the parent's type but provides better type precision. The PR fix correctly preserves this precision instead of losing it through type widening, eliminating the spurious override errors.

Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs modified the logic for handling compatible class variable overrides. Instead of widening the child's type to match the parent's type (which triggered override errors), it now preserves the child's inferred type and sets is_inherited = IsInherited::No to skip the override check when compatibility has already been validated.

mitmproxy (-11)

These were false positive bad-assignment errors caused by type inference failures. The error messages show Unknown types where concrete method signatures should be inferred. The assignments are actually valid - state_start, state_query, and state_done all have compatible signatures (self, event: Event) -> Generator[Command, Any, None] that match the expected type for _handle_event. The PR fixed the root cause: pyrefly was incorrectly widening child class variable types to their parent types, which broke downstream type inference for method assignments. Removing these false positives is an improvement.
Attribution: The change to analyze_class_field_value in pyrefly/lib/alt/class/class_field.rs fixed the type widening issue. Previously, when a child class had a compatible type, pyrefly would use the parent's type instead of preserving the child's more precise type. This caused downstream inference failures where method signatures couldn't be properly resolved, leading to Unknown types and spurious bad-assignment errors.

httpx-caching (-1)

This is an improvement. The error was a false positive caused by imprecise type inference. In the test code, line 28 creates warning = Mock(), and Mock objects have a called attribute for tracking whether they were invoked. The old pyrefly behavior was widening the child class's precise Mock type to some broader parent type (likely FunctionType based on the error), which lost the Mock-specific attributes. The PR fix preserves the child's inferred type precision, correctly maintaining that warning is a Mock with its called attribute available. This matches the behavior of mypy/pyright and eliminates a false positive in test code that uses standard unittest.mock patterns.
Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs fixed the type inference. Previously, when a child class overrode an unannotated class variable with a compatible type, pyrefly would widen the child's type to the parent's type, losing precision. The fix preserves the child's inferred type (Mock instead of widening to a more general type), allowing pyrefly to correctly see the called attribute.

mypy (-4)

These were false positive bad-override errors. The IntFlag class is intentionally designed to work with integers, so having ror accept int instead of Self is correct standard library behavior, not a type violation. The typeshed project is extensively tested against mypy/pyright, and these patterns would not be flagged by those tools. The PR fix correctly preserves type precision by using the child's inferred type rather than widening to the parent's type, eliminating spurious override errors for intentional API design differences.
Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs modified the logic to use the child's inferred type instead of the parent's type when they are compatible, and set is_inherited = IsInherited::No to skip the override check that was producing these false positives.

scrapy (+23, -188)

This is an improvement. The PR fixed false positive missing-attribute errors by preserving type precision in class variable overrides. The new bad-argument-type errors are catching real bugs where test code passes incorrectly structured data to FormRequest constructors. The removed errors were false positives claiming FormRequest was missing methods it actually has.
Attribution: The change to analyze_class_field_value() in pyrefly/lib/alt/class/class_field.rs now preserves the child's inferred type instead of widening to the parent's type when they're compatible. This gives more precise typing but exposes real type errors that were previously hidden.

➖ Neutral (1)

cloud-init (+1, -1)

Same errors at same locations with same error kinds — message wording changed, no behavioral impact.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (1 heuristic, 7 LLM)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant