python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.36k stars 2.81k forks source link

appending `#type:ignore` to an import does more than skipping analyzing the import #14778

Open solstice333 opened 1 year ago

solstice333 commented 1 year ago

Bug Report

I ran into relatively low priority unexpected behavior. I have this code

from anytree import NodeMixin # type:ignore
from typing import *
from abc import abstractmethod

class IFoo(Protocol):
    @abstractmethod
    def foo(self) -> None:
        raise NotImplementedError()

class Foo(IFoo, NodeMixin):
     ...

def main() -> None:
    a = Foo()

if __name__ == '__main__':
    main()

anytree is a third-party package. When I run mypy on it, I expect that static analysis on anytree to be skipped, and static analysis on Foo to fail with something like "Foo needs to implement abstractmethod foo". What actually happens is that mypy exits with success saying 0 errors were found.

What I've tried to do to cause the expected behavior to occur was to use stubgen on the anytree install which generated stubs in the project folder, run mypy --install-types to install type hints for a dependency of anytree, and configure a mypy.ini in project root to point to the stubs. All of which was simple to do, but kind of tedious.

I also read https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports where it mentioned

This can result in mypy failing to warn you about errors in your code. Since operations on Any result in Any, these dynamic types can propagate through your code, making type checking less effective. See Dynamically typed code for more information.

So I guess the suppression of static checking is leaking from anytree.NodeMixin into Foo.

Actual Behavior

mypy exits with success, 0 errors

Your Environment

I'm on Windows.

hauntsaninja commented 1 year ago

Yeah, getting a base class that is Any is one of the relatively dangerous ways for Any to propagate through your code, e.g. mypy will now let you do almost anything with your Foo objects. This behaviour is not going to change; it's how Any and gradual typing work.

However, I agree that even in the presence of Any, mypy might be able to issue an error for class Foo(IFoo, NodeMixin): (but note, note for class Foo(NodeMixin, IFoo):), since given the MRO I'm not sure how a NodeMixin could prevent the error

zachtaira commented 1 year ago

I ran into this as well. Thanks for filing this with a handy link to the docs :)

Leaving this POC script here for future folks who stumble upon this thread, because the behavior is pretty counterintuitive:

from typing import Any
from wario_world import wah  # type: ignore[import]

class KartDriver():
    pass

class mario(KartDriver, Any):
    """mario can be anything"""
    pass

class luigi(KartDriver):
    """luigi can't quite be as many things as mario[0]

    but he can still drive mario's kart, because they're bros

    [0] he has different merits. in this case, type-safety
    """
    pass

class waluigi(wah):
    """wah, wah, waluigi"""
    pass

class MariosKart():
    def __init__(self) -> None:
        self.driver: KartDriver

    def set_driver(self) -> None:
        reveal_type(self.driver)
        # mario, luigi, and waluigi will all pass the following type-check assignment:
        # - mario will pass because he's Any, and even if he weren't Any he's a KartDriver
        # - luigi will pass because he's a KartDriver
        # - waluigi will pass because he's Any
        self.driver = waluigi()

        # furthermore, the self.driver attribute will be redefined to whatever self.driver is set to
        reveal_type(self.driver)

Output:

$ mypy test.py 
test3.py:29: note: Revealed type is "test3.KartDriver"
test3.py:37: note: Revealed type is "test3.waluigi"
Success: no issues found in 1 source file
zachtaira commented 1 year ago

Actually, would it be possible to somehow track that waluigi(wah) is actually a form of Any? This would make debugging your codebase's type system quite a bit easier, and make reveal_type a lot more useful.