python / mypy

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

Type inference for `max()` "tainted" by `default` #16267

Open jwodder opened 1 year ago

jwodder commented 1 year ago

Consider the following code:

from __future__ import annotations
from collections.abc import Iterable
from packaging.version import Version

def best_str(strs: Iterable[str]) -> str | None:
    return max(strs, key=keyfunc, default=None)

def keyfunc(s: str) -> str:
    return s[::-1]

I believe this should type-check without issue; in particular, by my reading of max()'s type annotations in typeshed, the key callable should be expected to take only those types contained in the iterable. However, mypy disagrees:

max-arg2.py:6: error: Argument "key" to "max" has incompatible type "Callable[[str], str]"; expected "Callable[[str | None], SupportsDunderLT[Any] | SupportsDunderGT[Any]]"  [arg-type]

Note that mypy expects the key callable to accept str | None even though strs is an Iterable[str]. Moreover, if the default argument is omitted and the return type of best_str() is changed to str, then mypy accepts the code.

Your Environment

AlexWaygood commented 1 year ago

Thanks for the great bug report! It looks like this still repros if you use --new-type-inference, as well.

ilevkivskyi commented 1 year ago

default is a red herring. This has nothing to do with any fancy inference logic, it is plain old overusing of outer context. I bet if you will rewrite it as temp = max(...); return temp it will work. A while ago I added a special-casing for this for assignments, see https://github.com/python/mypy/pull/14151, I guess we may want to have matching special-casing for return.

tamird commented 8 months ago

@ilevkivskyi confirmed assigning to a local variable first makes this work.