python / mypy

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

Error for lambda containing Any even when type can be inferred #5879

Open keis opened 6 years ago

keis commented 6 years ago

Using mypy 0.641 with the --disallow-any-expr flag and this sample using @overload

from typing import Callable, Tuple, TypeVar, overload

T = TypeVar('T')
A0 = TypeVar('A0')
A1 = TypeVar('A1')

@overload
def foo(
    vars: Tuple[A0],
    op: Callable[[A0], T]
) -> T: ...

@overload
def foo(
    vars: Tuple[A0, A1],
    op: Callable[[A0, A1], T]
) -> T: ...

def foo(vars, op):
    pass

def foo_not_overloaded(
    vars: Tuple[A0, A1],
    op: Callable[[A0, A1], T]
) -> T:
    pass

a = 5
b = 3

foo((a, b), lambda a, b: a - b)

produces

typist2.py:36: error: Expression type contains "Any" (has type "Callable[[Any, Any], Any]")
typist2.py:36: error: Expression has type "Any"

However mypy seems to be able to infer the type of the lambda because if I modify the last line to read

foo((a, b), lambda a, b: a - b.bad)

mypy correctly identifies that "int" has no attribute "bad", sprinkling some prints on the mypy source also reveals it figures out it's a (int, int) -> int at some point.

foo_not_overloaded checks without trouble and so does using a plain def instead of the lambda.

ilevkivskyi commented 6 years ago

Unfortunately we received several complains about false positives with this flag recently. My guess is this bug is tightly related to https://github.com/python/mypy/issues/5843, not exactly a duplicate, but the underlying reason is most likely the same.

justinpawela commented 6 years ago

This seems to be a regression between 0.630 and 0.641. I have a few scripts that make liberal use of lambda statements, and it seems to be a problem with all of them now.

Here is my best attempt to isolate an example from my codebase:

import datetime as dt
import typing as t

class DeploymentResponse(t.NamedTuple):
    uid: str
    createdDate: dt.datetime

class DeploymentListResponse(t.NamedTuple):
    items: t.List[DeploymentResponse]

def get_deployments() -> DeploymentListResponse: ...

def prune_deployments(IDs: t.Iterable[str]) -> None: ...

def prune_api_deployments(keep_latest: bool, keep_IDs: t.Iterable[str]) -> None:
    response: DeploymentListResponse = get_deployments()

    obsolete_IDs = { d.uid for d in response.items }.difference(keep_IDs)

    # Works fine with mypy 0.630 but produces errors with 0.641 (see below).
    if keep_latest:
        obsolete_IDs.discard( max(response.items, key=lambda i: i.createdDate).uid )

    # Works fine in both 0.630 and 0.641 because the lambda does not need to be
    # inferred. This code is redundant, just for this example.
    if keep_latest:
        selector: t.Callable[[DeploymentResponse], dt.datetime] = lambda i: i.createdDate
        obsolete_IDs.discard( max(response.items, key=selector).uid )

    prune_deployments(IDs=obsolete_IDs)
    return

This code was tested on Python 3.6.1 using both mypy 0.630 and 0.641 with the --disallow-any-expr flag. There are no errors using 0.630, but 0.641 produces the following output:

test.py:26: error: Expression type contains "Any" (has type "Callable[[Any], Any]")
test.py:26: error: Expression has type "Any"

Of course, the workaround is obvious — don't make mypy infer the type of the lambda, but that gets very verbose and tiresome quickly, especially since lambda is usually quick and concise.

DetachHead commented 2 years ago

what's interesting is that when using reveal_type within the lambda, it activates twice with two different results:

from typing import Callable, NoReturn, overload

@overload
def foo(value: str, predicate: Callable[[str], object]) -> None:
    ...

@overload
def foo(value: int, predicate: Callable[[int], object]) -> None:
    ...

def foo(value: int | str, predicate: Callable[[NoReturn], object]) -> None:
    ...

# error: Expression type contains "Any" (has type "Callable[[Any], Any]")  [misc]
# note: Revealed type is "Any"
# note: Revealed type is "builtins.int"
foo(1, lambda value: reveal_type(value))

https://mypy-play.net/?mypy=master&python=3.10&flags=show-error-codes%2Callow-redefinition%2Cstrict%2Ccheck-untyped-defs%2Cdisallow-any-decorated%2Cdisallow-any-expr%2Cdisallow-any-explicit%2Cdisallow-any-generics%2Cdisallow-any-unimported%2Cdisallow-incomplete-defs%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators%2Cdisallow-untyped-defs%2Cno-implicit-optional%2Cno-implicit-reexport%2Cstrict-equality%2Cwarn-incomplete-stub%2Cwarn-redundant-casts%2Cwarn-return-any%2Cwarn-unreachable%2Cwarn-unused-configs%2Cwarn-unused-ignores&gist=2f59b7264cb0e77c39418bad595ba2e8

KotlinIsland commented 11 months ago

This is resolved in basedmypy https://mypy-play.net/?mypy=basedmypy-2.2.1&python=3.11&gist=66a86e78f129539cd320cd9d1a535a82