Skip to content

Make self-cls-assignment consider tuple assign. & ignore typing.cast() #5254

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

Closed
wants to merge 1 commit into from

Conversation

DanielNoord
Copy link
Collaborator

  • Add yourself to CONTRIBUTORS if you are a new contributor.
  • Add a ChangeLog entry describing what your PR does.
  • If it's a new feature, or an important bug fix, add a What's New entry in
    doc/whatsnew/<current release.rst>.
  • Write a good description on what the PR does.

Type of Changes

Type
🐛 Bug fix

Description

Found while working on pylint-dev/astroid#1217

I don't think typing.cast() should raise this warning. While I was at it I thought I might as well fix this checker for tuple assignments as well.

I would like for this to go in 2.12 so we can get pylint-dev/astroid#1217 in astroid 2.8.5 and thereby in 2.12.1.

@DanielNoord DanielNoord added False Positive 🦟 A message is emitted but nothing is wrong with the code False Negative 🦋 No message is emitted but something is wrong with the code labels Nov 3, 2021
@DanielNoord DanielNoord added this to the 2.12.0 milestone Nov 3, 2021
@coveralls
Copy link

Pull Request Test Coverage Report for Build 1417458494

  • 15 of 15 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.005%) to 93.341%

Totals Coverage Status
Change from base Build 1415912636: 0.005%
Covered Lines: 13750
Relevant Lines: 14731

💛 - Coveralls

@cdce8p cdce8p self-requested a review November 3, 2021 15:55
Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ! I only have a small question regarding the test case.

def function(self):
"""This function uses typing.cast to reassign self"""

self = cast(TestTypingCast, self)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we cast to TestTypingCast here ? Wouldn't it be used in a more complex context ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just used self's class again because a similar example caused me to make this change. We can change it to something else, perhaps even a more generic Type if you want!

@DanielNoord
Copy link
Collaborator Author

@cdce8p Said that he thought the this message was actually correct for typing.cast() in pylint-dev/astroid#1217 so I think he will want to look at this as well before merging!

@Pierre-Sassoulas Pierre-Sassoulas added the Needs review 🔍 Needs to be reviewed by one or multiple more persons label Nov 3, 2021
@cdce8p
Copy link
Member

cdce8p commented Nov 4, 2021

I would suggest not to special case cast. pylint-dev/astroid#1217 has already been fixed without it. IMO if you need to reassign self with cast you are probably doing something wrong.

It's much better to return the result directly, as now done in the astroid PR.

def func(self) -> SomeType:
    return cast(SomeType, self)

@DanielNoord
Copy link
Collaborator Author

I agree that you're probably doing something wrong when this is necessary. In our case self.is_statement is another way of doing isinstance(self, nodes.Statement).

However, I can create a contrived example where we would need to use the cast for mypy to work. Look at:

class BaseClass:
    def __init__(self):
        self.advanced = False

    def advanced_attribute(self) -> str:
        if self.advanced:
            # Will need to cast here to make this work for mypy
            attr_type = self.get_attribute_type()
            if attr_type == str:
                return "string"
            elif attr_type == int:
                return "number"
        else:
            return ""


class SimpleClass(BaseClass):
    pass


class AdvancedClass(BaseClass):
    def __init__(self, attribute):
        super().__init__()
        self.advanced = True
        self.attribute = attribute

    def get_attribute_type(self):
        return type(self.attribute)


print(AdvancedClass("string").advanced_attribute())
print(AdvancedClass(1).advanced_attribute())

So, based on the fact that a class's attribute has a certain value (which is only true for certain child classes) we know it has a certain method and we need to do something with that method that disallows immediately returning the cast.
Using such logic (attribute has a value -> method exists) should probably be avoided, but is that a reason to raise a message on a valid re-assign to self? The warning (seems to) intend to warn for changing the first parameter of a method, which typing.cast can't do.

@cdce8p
Copy link
Member

cdce8p commented Nov 6, 2021

I agree that you're probably doing something wrong when this is necessary. In our case self.is_statement is another way of doing isinstance(self, nodes.Statement).

However, I can create a contrived example where we would need to use the cast for mypy to work.
...

You don't need to use cast there. Although I admit that it would certainly be easier. But IMO that's where pylint: disable can be used. The key in your example is to annotate self with a Protocol class.

from typing import Any, Generic, Literal, Protocol, TypeVar

T = TypeVar("T")


class HasAdvancedAttribute(Protocol[T]):
    advanced: Literal[True] = True
    attribute: T

    def get_attribute_type(self) -> type[T]:
        ...


class BaseClass:
    def __init__(self) -> None:
        self.advanced = False

    def advanced_attribute(self: HasAdvancedAttribute[Any]) -> str:
        if self.advanced:
            attr_type = self.get_attribute_type()
            if attr_type == str:
                return "string"
            if attr_type == int:
                return "number"
        return ""


class SimpleClass(BaseClass):
    pass


class AdvancedClass(BaseClass, Generic[T]):
    def __init__(self, attribute: T) -> None:
        super().__init__()
        self.advanced: Literal[True] = True
        self.attribute: T = attribute

    def get_attribute_type(self) -> type[T]:
        return type(self.attribute)


print(AdvancedClass("string").advanced_attribute())
print(AdvancedClass(1).advanced_attribute())

@DanielNoord
Copy link
Collaborator Author

Your example would indeed remove the need for typing.cast but wouldn't it still be in the spirit of this checker to ignore it? As far as I know it doesn't actually change the value it is assigning to, so wouldn't that remove the need for the current message?

@cdce8p
Copy link
Member

cdce8p commented Nov 6, 2021

Your example would indeed remove the need for typing.cast but wouldn't it still be in the spirit of this checker to ignore it? As far as I know it doesn't actually change the value it is assigning to, so wouldn't that remove the need for the current message?

Technically yet, but I wonder how common it really is that we need to special case it. Keep in mind, we start with typing.cast and before long we get requests to allow users to add their own function names. At some point it's just not worth it. I searched for self-cls-assignment in Home Assistant to see if it's disabled somewhere but couldn't find anything.

In the end, I still believe the warning provides valuable feedback. A dev should really know what he/she is doing to assign the return of cast to self and in those cases adding pylint: disable is a good alternative.

@DanielNoord
Copy link
Collaborator Author

Fair enough! I'll remove the special casing and leave the handling of tuple assignments in as it is a nice enhancement of the checker.

@cdce8p
Copy link
Member

cdce8p commented Nov 6, 2021

@DanielNoord I wondering, can you open a new PR for it? This discussion here has been quite extensive so it might be worth preserving it.

@DanielNoord
Copy link
Collaborator Author

Will do!

@DanielNoord DanielNoord closed this Nov 6, 2021
@DanielNoord DanielNoord deleted the self-cls branch November 6, 2021 19:46
@DanielNoord DanielNoord restored the self-cls branch November 6, 2021 19:46
@DanielNoord DanielNoord deleted the self-cls branch November 6, 2021 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
False Negative 🦋 No message is emitted but something is wrong with the code False Positive 🦟 A message is emitted but nothing is wrong with the code Needs review 🔍 Needs to be reviewed by one or multiple more persons
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants