mypy icon indicating copy to clipboard operation
mypy copied to clipboard

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

Open jwodder opened this issue 2 years ago • 3 comments

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

  • Mypy version used: 1.6.0
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.11.6

jwodder avatar Oct 15 '23 01:10 jwodder

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

AlexWaygood avatar Oct 16 '23 15:10 AlexWaygood

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.

ilevkivskyi avatar Nov 01 '23 22:11 ilevkivskyi

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

tamird avatar Feb 21 '24 18:02 tamird