Fix false positive missing-attribute on overridden class variables#2626
Fix false positive missing-attribute on overridden class variables#2626grievejia wants to merge 1 commit intofacebook:mainfrom
missing-attribute on overridden class variables#2626Conversation
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
|
@grievejia has exported this pull request. If you are a Meta employee, you can view the originating Diff in D94992218. |
|
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) ...
|
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.
Detailed analysis✅ Improvement (7)apprise (+8, -34)
core (-9)
zope.interface (-7)
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.,
mitmproxy (-11)
httpx-caching (-1)
mypy (-4)
scrapy (+23, -188)
➖ Neutral (1)cloud-init (+1, -1)
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (1 heuristic, 7 LLM) |
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 = Requestand a child overrides it withrequest_class = FormRequest, accessingFormRequest-specific methods likefrom_response()would produce a spuriousmissing-attributeerror because pyrefly stored the type astype[Request]instead oftype[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::Noto skip the now-redundant override check (compatibility has already been validated by theerrors2.is_empty()check). TheIsInheritedenum is documented as "only used for efficiency" — setting it toNojust tells the override checker to skip this field, which is safe.Differential Revision: D94992218