DetachHead / basedpyright

pyright fork with various type checking improvements, improved vscode support and pylance features built into the language server
https://docs.basedpyright.com
Other
1.19k stars 21 forks source link

infer types from super #289

Open KotlinIsland opened 7 months ago

KotlinIsland commented 7 months ago
class A:
    def f(self, i: int) -> str:
        ...
class B(A):
    @override
    def f(self, i):
        reveal_type(i)  # int
DetachHead commented 5 months ago

looks like pyright already does this, so all we need to do is make it not report reportMissingParameterType on the overridden method

KotlinIsland commented 4 months ago

even though pyright is working here, there is no inlay hint on the parameter

DetachHead commented 3 months ago

i think there still needs to be an option to still report reportMissingParameterType in such cases, because there are libraries that have decorators that rely on type annotations for runtime checks

from pydantic import validate_call

class Foo:
    @abstractmethod
    def foo(self, value: int):
        ...

class Bar(Foo):
    @override
    @validate_call
    def foo(self, value):
        ...

ideally there would be something in the type system you could put on a decorator to tell the type checker that type annotations get runtime evaluated

jorenham commented 3 months ago

Isn't it the case that because of Barbara's substitution principle (I feel like I'm on a first-name basis with Liskov at this point), value@Var.foo could be any supertype of int in your example, @DetachHead ?

DetachHead commented 3 months ago

yeah it's contravariant because it's in an input position. though i still think it's safe to assume that if you don't specify the type then it should just assume it's the same as in the base class

jorenham commented 3 months ago

Yea that seems reasonable. It's in line with the current behavior:

https://pyright-play.net/?pythonVersion=3.13&strict=true&deprecateTypingAliases=true&code=GYJw9gtgBAhgRgYygSwgBzCALrOBnLEGBLCAUywAswATAKFEiiwE81kA7AcxXUxzAA3MiBDIaZOnQQAbGHjxQAYmDAAuOlC1QAAvAJES5KrU3aJwKMFUAKPGRnAANFEEwZAVzJqUHLAEoNbWCoADpwqVl5RQAhGBAbFTBAsy0dIRExCVSoCytbe0cXN08yQLCIkO0pOHioAF4oOIT-OlqQUOswGwAWACZW9s7bAGZQgEYe1rogA

https://basedpyright.com/?typeCheckingMode=all&code=GYJw9gtgBAhgRgYygSwgBzCALrOBnLEGBLCAUywAswATAKFEiiwE81kA7AcxXUxzAA3MiBDIaZOnQQAbGHjxQAYmDAAuOlC1QAAvAJES5KrU3aJwKMFUAKPGRnAANFEEwZAVzJqUHLAEoNbWCoADpwqVl5RQAhGBAbFTBAsy0dIRExCVSoCytbe0cXN08yQLCIkO0pOHioAF4oOIT-OlqQUOswGwAWACZW9s7bAGZQgEYe1rogA

jorenham commented 3 months ago

Apparantly mypy has a rather liberal interpretation of contravariance, and allows the following (if run with the default flags):

from abc import abstractmethod
from typing import override

class Foo:
    @abstractmethod
    def foo(self, value: int):
        ...

class Bar(Foo):
    @override
    def foo(self, value): ...

bar = Bar()
bar.foo(42)
bar.foo(3.14)
bar.foo('nope')  # mypy doesn't care about your problems
DetachHead commented 3 months ago

yeah that's because mypy types it as Any if there's no annotation instead of inferring it from the base class like pyright does

class Foo:
    @abstractmethod
    def foo(self, value: int):
        ...

class Bar(Foo):
    @override
    def foo(self, value):
        reveal_type(value) # Any
jorenham commented 3 months ago

yeah that's because mypy types it as Any if there's no annotation instead of inferring it from the base class like pyright does

Ah I see.

I suppose that within the valid solution-space, mypy's would lie within the infemum. Or as Wolfgang Pauli once put it:

That is not only not right; it is not even wrong.