Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Cannot cast value to TypeVar #6440

Closed
LiraNuna opened this issue Feb 20, 2019 · 3 comments
Closed

Cannot cast value to TypeVar #6440

LiraNuna opened this issue Feb 20, 2019 · 3 comments

Comments

@LiraNuna
Copy link

LiraNuna commented Feb 20, 2019

This is a bug report however it could be that I'm doing it wrong™. This is for mypy version 0.670

Consider this method:

from typing import Callable, Iterable, Mapping, MutableMapping, TypeVar, List, cast

T = TypeVar('T')
Tk = TypeVar('Tk')
Tv = TypeVar('Tv')

def group_by(
    items: Iterable[T],
    *,
    key: Callable[[T], Tk],
    value: Callable[[T], Tv] = lambda x: cast(Tv, x),
) -> Mapping[Tk, Iterable[Tv]]:
    result: MutableMapping[Tk, List[Tv]] = {}
    for item in items:
        result.setdefault(key(item), []).append(value(item))

    return result

Notice that the default parameter for value is an identity callable that casts x to Tv. This produces an error:

error: Invalid type "Tv"

If I remove the call to cast, I get a different error which makes sense to me and was the original reason I added cast in the first place:

error: Incompatible default for argument "value" (default has type "Callable[[T], T]", argument has type "Callable[[T], Tv]")
error: Incompatible return value type (got "T", expected "Tv")

Given that this method is mostly TypeVar based, what do you think would be the right behavior? I find the second error acceptable (because T != Tv) but I find Tv not being a type very confusing and therefor a bug.

@emmatyping
Copy link
Collaborator

Here is a more minimal repro:

from typing import TypeVar, Callable, cast

T = TypeVar('T')

def foo(x: Callable[[], T] = lambda: cast(T, 0)) -> T:
    return x()

This seems inconsistent because this passes type checking:

from typing import TypeVar, Callable, cast, Optional

T = TypeVar('T')

def foo(x: Optional[Callable[[], T]] = None) -> T:
    if x is None:
        x = lambda: cast(T, 0)
    return x()

I'm not sure why the difference in scopes would explain one type checking and the other not...

@ilevkivskyi
Copy link
Member

Troubles with specifying defaults for generic arguments is a known issue #3737. And the fact that the cast doesn't work there is unfortunate. A typical workaround is to use an overload. In your case it will actually make the signature more correct:

@overload
def group_by(items: Iterable[T], *, key: Callable[[T], Tk]) -> Mapping[Tk, List[T]]: ...
@overload
def group_by(items: Iterable[T], *,
             key: Callable[[T], Tk], value: Callable[[T], Tv]) -> Mapping[Tk, List[Tv]]: ...

With your signature it is not possible to infer Tv in the return if value is not given in a call.

@hauntsaninja
Copy link
Collaborator

Closing as a duplicate of #3737

@hauntsaninja hauntsaninja closed this as not planned Won't fix, can't repro, duplicate, stale Aug 13, 2023
# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

5 participants