Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False positive for no-member with property setters #9811

Open
mdegat01 opened this issue Jul 17, 2024 · 4 comments
Open

False positive for no-member with property setters #9811

mdegat01 opened this issue Jul 17, 2024 · 4 comments
Labels
Astroid Related to astroid Needs astroid update Needs an astroid update (probably a release too) before being mergable Needs PR This issue is accepted, sufficiently specified and now needs an implementation properties Regression
Milestone

Comments

@mdegat01
Copy link

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

@mdegat01 mdegat01 added the Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling label Jul 17, 2024
@mdegat01
Copy link
Author

mdegat01 commented Jul 17, 2024

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.

@jacobtylerwalls
Copy link
Member

Thanks for the report. Regression in pylint-dev/astroid@0f9dfa6.

@jacobtylerwalls jacobtylerwalls added Astroid Related to astroid Regression Needs PR This issue is accepted, sufficiently specified and now needs an implementation and removed Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling labels Jul 23, 2024
@jacobtylerwalls jacobtylerwalls added this to the 3.2.7 milestone Jul 23, 2024
@jacobtylerwalls jacobtylerwalls changed the title False positive for no-member, new in 3.12.4 False positive for no-member with property setters Jul 24, 2024
@jacobtylerwalls jacobtylerwalls self-assigned this Jul 24, 2024
@jacobtylerwalls
Copy link
Member

jacobtylerwalls commented Jul 24, 2024

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
                             )

@jacobtylerwalls jacobtylerwalls added the Needs astroid update Needs an astroid update (probably a release too) before being mergable label Jul 24, 2024
@jacobtylerwalls
Copy link
Member

jacobtylerwalls commented Jul 24, 2024

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)

@jacobtylerwalls jacobtylerwalls removed their assignment Aug 11, 2024
@jacobtylerwalls jacobtylerwalls modified the milestones: 3.2.7, 3.3.0 Aug 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Astroid Related to astroid Needs astroid update Needs an astroid update (probably a release too) before being mergable Needs PR This issue is accepted, sufficiently specified and now needs an implementation properties Regression
Projects
None yet
Development

No branches or pull requests

2 participants