basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

ban calling instance methods on classes

Open DetachHead opened this issue 4 months ago • 2 comments

Description

from typing import override


class Fruit:
    def enjoy(self) -> None: 
        print("tasty fruit")

class Banana(Fruit):
    def __init__(self):
        self.length: int = 42
    @override
    def enjoy(self) -> None:
        if self.length > 10:
            print("mmm banana")

def foo(t: type[Fruit]):
    t.enjoy(Fruit())

foo(Banana) # AttributeError: 'Fruit' object has no attribute 'length'

i think the reason it's unsafe is that because self is a normal argument when the method is called on the class, and its type is Self which is essentially a TypeVar, it needs to be contravariant because it's an input parameter.

this is not an issue when you call the method on the instance because it's impossible to call the method with the wrong type as the self argument:

def foo(t: type[Fruit]):
    t().enjoy() # no error
    Fruit().enjoy() # no error

DetachHead avatar Oct 08 '25 09:10 DetachHead

upstream issue: https://github.com/microsoft/pyright/issues/11007

DetachHead avatar Oct 08 '25 09:10 DetachHead

The only thing that should be done with type[X] is call classmethod or staticmethod or access ClassVar

and a temporary allowance until we have intersections: calling the object to make an instance of it That should normally be done with Callable[[], X], but in some cases someone might need to also access one of the previously mentioned things, like accessing a ClassVar - That should be done with Callable[[], X] & type[X] - but until we have intersections, we have to allow calling the type to make an instance of it.

beauxq avatar Oct 08 '25 14:10 beauxq