pylint
pylint copied to clipboard
False positive for no-member with property setters
Bug description
"""test_lint.py"""
# pylint: disable=R0903
class CoreSys:
"""CoreSys object."""
def __init__(self) -> None:
"""Initialize CoreSys."""
self._host: "Host" | None = None
@property
def host(self) -> "Host":
"""Get host."""
if not self._host:
raise RuntimeError("Host not set!")
return self._host
@host.setter
def host(self, value: "Host") -> None:
"""Set Host."""
self._host = value
@property
def timezone(self) -> str:
"""Get timezone."""
if self.host.info.timezone:
return self.host.info.timezone
return "UTC"
class CoreSysAttributes:
"""CoreSysAttributes object."""
coresys: CoreSys
@property
def sys_host(self) -> "Host":
"""Get host."""
return self.coresys.host
class Info(CoreSysAttributes):
"""Info object."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize info."""
self.coresys = coresys
self._timezone: str | None = None
@property
def timezone(self) -> str | None:
"""Get timezone."""
return self._timezone
@timezone.setter
def timezone(self, value: str | None) -> None:
"""Set timezone."""
self._timezone = value
class Host(CoreSysAttributes):
"""Host object."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize host object."""
self.coresys = coresys
self._info = Info(coresys)
@property
def info(self) -> Info:
"""Get info."""
return self._info
Configuration
No response
Command used
pylint test_lint.py
Pylint output
************* Module test_lint
test_lint.py:28:11: E1101: Instance of 'CoreSys' has no 'info' member (no-member)
test_lint.py:29:19: E1101: Instance of 'CoreSys' has no 'info' member (no-member)
Expected behavior
This should not show an error, all members referenced are defined. Notably it does not show an error in 3.12.3, appears to be a regression with 3.12.4
Pylint version
pylint 3.2.5
astroid 3.2.3
Python 3.12.4 (main, Jul 17 2024, 10:41:37) [Clang 15.0.0 (clang-1500.3.9.4)]
OS / Environment
MacOS 14.4.1
We also are seeing it in ubuntu containers running in github actions, like this run: https://github.com/home-assistant/supervisor/actions/runs/9904839873/job/27515699355 (same issue, appeared on upgrade to 3.12.4).
Additional dependencies
No response
Sorry for the complexity of the example. It did not appear unless I added in the inheritance model and a multi-level nested reference matching what is used in the project I was working on when I discovered it.
Thanks for the report. Regression in pylint-dev/astroid@0f9dfa6.
I'm doubtful this is the best thing we can do, but a quick-n-dirty might be some variation of:
diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py
index efd5439b..dbe5872e 100644
--- a/astroid/nodes/scoped_nodes/scoped_nodes.py
+++ b/astroid/nodes/scoped_nodes/scoped_nodes.py
@@ -2495,6 +2495,16 @@ class ClassDef( # pylint: disable=too-many-instance-attributes
if attr.parent and attr.parent.scope() == first_scope
]
functions = [attr for attr in attributes if isinstance(attr, FunctionDef)]
+ setter = None
+ for function in functions:
+ dec_names = function.decoratornames(context=context)
+ for dec_name in dec_names:
+ if dec_name is util.Uninferable:
+ continue
+ if dec_name.split(".")[-1] == "setter":
+ setter = function
+ if setter:
+ break
if functions:
# Prefer only the last function, unless a property is involved.
last_function = functions[-1]
@@ -2518,7 +2528,7 @@ class ClassDef( # pylint: disable=too-many-instance-attributes
elif isinstance(inferred, objects.Property):
function = inferred.function
if not class_context:
- if not context.callcontext:
+ if not context.callcontext and not setter:
context.callcontext = CallContext(
args=function.args.arguments, callee=function
)
Slimmer reproducer:
# pylint: disable=missing-module-docstring,missing-class-docstring, missing-function-docstring
class BugReport:
@property
def host(self):
return self._host
@host.setter
def host(self, value: str):
self._host = value
@property
def timezone(self):
return self.host.lower() # self.host should not infer as self
************* Module a
Desktop/a.py:14:15: E1101: Instance of 'BugReport' has no 'lower' member (no-member)