Warn when class instance without __bool__ or __len__ is used as boolean condition#3282
Warn when class instance without __bool__ or __len__ is used as boolean condition#3282knQzx wants to merge 5 commits intofacebook:mainfrom
Conversation
This comment has been minimized.
This comment has been minimized.
…heck Variables typed as object, Hashable, Iterable, or any ABC subclass may hold concrete runtime instances that define __bool__ or __len__. Skip the warning for those abstract/protocol types.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- include __getattribute__ and __get__ in dunder check (descriptors, dynamic attribute interception) - skip classes from bundled stubs (typeshed stdlib, third-party stubs): datetime, asyncio.Future/Lock, sqlalchemy Session etc. have runtime behavior not modeled in stubs - skip dataclasses to avoid noise on the common defensive-guard pattern - add regression test covering descriptors, __getattr*__, dataclasses and stdlib stub types
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
extends_abc only matched abc.ABCMeta directly, missing frameworks like HA's ABCCachedProperties(CachedProperties, ABCMeta) where ABCMeta is inherited rather than used as the metaclass directly. Walk the metaclass class's MRO to handle this case
|
Diff from mypy_primer, showing the effect of this PR on open source code: core (https://github.com/home-assistant/core)
+ ERROR homeassistant/components/derivative/sensor.py:430:20-29: Instance of `State` used as condition, but `State` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/gdacs/sensor.py:103:12-25: Instance of `GdacsFeedEntityManager` used as condition, but `GdacsFeedEntityManager` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/home_connect/coordinator.py:165:28-49: Instance of `HomeConnectApplianceCoordinator` used as condition, but `HomeConnectApplianceCoordinator` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/knx/storage/config_store.py:254:12-22: Instance of `KNXModule` used as condition, but `KNXModule` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/media_player/__init__.py:1349:16-31: Instance of `EntityPlatform` used as condition, but `EntityPlatform` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/netatmo/media_source.py:122:20-25: Instance of `BrowseMediaSource` used as condition, but `BrowseMediaSource` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/netatmo/media_source.py:130:20-25: Instance of `BrowseMediaSource` used as condition, but `BrowseMediaSource` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/onvif/device.py:175:12-23: Instance of `EventManager` used as condition, but `EventManager` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/owntracks/device_tracker.py:156:12-21: Instance of `HomeAssistant` used as condition, but `HomeAssistant` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/screenlogic/util.py:40:12-36: Instance of `ConfigEntry` used as condition, but `ConfigEntry` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/shelly/diagnostics.py:35:16-33: Instance of `ShellyBlockCoordinator` used as condition, but `ShellyBlockCoordinator` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/solarman/sensor.py:273:16-40: Instance of `ConfigEntry` used as condition, but `ConfigEntry` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sonos/media_browser.py:324:8-25: Instance of `SonosFavorites` used as condition, but `SonosFavorites` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sql/config_flow.py:121:8-12: Instance of `Session` used as condition, but `Session` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sql/config_flow.py:139:12-16: Instance of `Session` used as condition, but `Session` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sql/config_flow.py:147:16-20: Instance of `Session` used as condition, but `Session` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sql/config_flow.py:155:8-12: Instance of `Session` used as condition, but `Session` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/sql/util.py:51:16-26: Instance of `HomeAssistant` used as condition, but `HomeAssistant` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/stream/__init__.py:582:12-15: Instance of `HlsStreamOutput` used as condition, but `HlsStreamOutput` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/tts/__init__.py:1343:12-27: Instance of `EntityPlatform` used as condition, but `EntityPlatform` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/components/vegehub/__init__.py:98:12-23: Instance of `VegeHubCoordinator` used as condition, but `VegeHubCoordinator` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/helpers/entity.py:1564:12-25: Instance of `EntityPlatform` used as condition, but `EntityPlatform` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
+ ERROR homeassistant/helpers/entity.py:1705:25-89: Instance of `PlatformData` used as condition, but `PlatformData` defines neither `__bool__` nor `__len__`, so instances are always truthy. It's equivalent to `True` [redundant-condition]
|
Primer Diff Classification✅ 1 improvement(s) | 1 project(s) total | +23 errors 1 improvement(s) across core.
Detailed analysis✅ Improvement (1)core (+23)
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (1 LLM) |
Summary
closes #3282 closes #868
adds
ConditionRedundantReason::InstanceAlwaysTruthy- fires when a class instance is used in a boolean context but the class defines neither__bool__nor__len__(so instances are unconditionally truthy)skipped to keep noise down
objectitselfextends_abc) - concrete subclasses can define the dunders at runtimeABCMetavia MRO (covers HA'sABCCachedProperties(CachedProperties, ABCMeta))datetime,asyncio.Future,Lock, sqlalchemySession, etc)__bool__/__len__/__getattr__/__getattribute__/__get__anywhere in MRO (descriptors, dynamic attribute interception)remaining warnings on home-assistant primer
23 warnings, three patterns:
device = random.choice(...)returns non-optional T then code doesif device:. dead by types, lint working as intendedattr: T | None, subclass redeclaresattr: T. after redeclarationif obj.attr:is dead by types# type: ignore[assignment](~5):platform: EntityPlatform = None # type: ignore[assignment]. attr annotated T but assigned None with the assignment error suppressed.if self.platform:is meaningful at runtime but dead by declared types. proper fix isT | None. not suppressed because doing so needs initializer tracking onClassFieldto hide what are arguably real type-annotation bugsnone look like the "incomplete stubs" failure mode the issue author flagged
Test Plan
tests in
flow_branching.rscover plain classes, inheritance, descriptors,__getattr*__, dataclasses, stub-only classes, protocols, ABCs, custom-metaclass-via-MROcargo test -p pyreflyclean