Search code examples
python-asynciomypy

Mypy doesn't detect awaiting a function call that returns None


Consider the following code:

import asyncio


def a():
    print("whatever")


async def b():
    await a()


asyncio.run(b())

It does not run: TypeError: object NoneType can't be used in 'await' expression

However it passes mypy (v1.2.0): Success: no issues found in 1 source file.

Is there a setting for mypy, or a way to annotate a() so that mypy detects this type of errors? I tried to annotate the return type of a with def a() -> None:, but it does not help. Am doing something wrong? Is this a known limitation of mypy?


Solution

  • The reason why this does not cause Mypy to report an error is that you did not annotate your functions. Specifically, there is no return type annotation for a. Mypy has no choice but to fall back to Any as the return type. And Any is compatible with, well, anything. This includes await expressions.

    If you run mypy --strict, you'll actually get the following output for your code:

    ...:4: error: Function is missing a return type annotation  [no-untyped-def]
    ...:4: note: Use "-> None" if function does not return a value
    ...:8: error: Function is missing a return type annotation  [no-untyped-def]
    ...:8: note: Use "-> None" if function does not return a value
    ...:9: error: Call to untyped function "a" in typed context  [no-untyped-call]
    ...:12: error: Call to untyped function "b" in typed context  [no-untyped-call]
    

    And if you modify b slightly to reveal the types involved, you'll see what I mean:

    ...
    
    async def b():
        supposed_awaitable = a()
        reveal_type(supposed_awaitable)
        await supposed_awaitable
    

    Mypy will tell you Revealed type is "Any" even though you and me know it is None.

    By contrast, if you just add -> None to your a function, you will at least get an error:

    def a() -> None:
        print("whatever")
    
    
    async def b():
        await a()  # error: "a" does not return a value  [func-returns-value]
    

    While this is not ideal IMO because it is not very precise and to be honest actually incorrect since None is of course a value, that is still enough to point you to the error you made because if something is None (or "not a value" as Mypy calls it here), you obviously cannot use it in an await expression.

    As a side note, I appreciate that PyCharm at least tells me Cannot find reference 'await' in 'None', which I find a bit clearer.