Skip to content

type alias with typevar + --disallow-any-generic => unexpected error? #5462

New issue

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

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

Already on GitHub? Sign in to your account

Closed
asottile opened this issue Aug 12, 2018 · 8 comments
Closed

Comments

@asottile
Copy link
Contributor

This is a simplified example from a larger project. I've resorted to spamming Any and avoiding the TypeVars for now since type is longer than the code itself 😆

import contextlib
from typing import Callable
from typing import Generator
from typing import Iterable
from typing import TypeVar


T = TypeVar('T')
T2 = TypeVar('T2')
MC = Callable[[Callable[[T2], T], Iterable[T2]], Iterable[T]]


@contextlib.contextmanager
def f() -> Generator[Callable[[Callable[[T2], T], Iterable[T2]], Iterable[T]], None, None]:
    yield map


@contextlib.contextmanager
def f2() -> Generator[MC, None, None]:
    yield map

I expect f and f2 to be identical, however:

$ mypy t.py --disallow-any-generic
t.py:19: error: Missing type parameters for generic type

I can silence the error by doing:

@contextlib.contextmanager
def f2() -> Generator[MC[T2, T], None, None]:
    yield map

or

@contextlib.contextmanager
def f2() -> Generator[MC[T, T2], None, None]:
    yield map

but it's unclear to me what that's doing and/or why it is different

@asottile
Copy link
Contributor Author

Even with the trick with MC[T, T2], the types are just wrong:

import contextlib
from typing import Callable
from typing import Generator
from typing import Iterable
from typing import TypeVar


T = TypeVar('T')
T2 = TypeVar('T2')
MC = Callable[[Callable[[T2], T], Iterable[T2]], Iterable[T]]


@contextlib.contextmanager
def f() -> Generator[Callable[[Callable[[T2], T], Iterable[T2]], Iterable[T]], None, None]:
    yield map


@contextlib.contextmanager
def f2() -> Generator[MC[T2, T], None, None]:
    yield map


with f() as mapper:
    tuple(mapper(print, ('hello', 'there')))


with f2() as mapper:
    tuple(mapper(print, ('hello', 'there')))
$ mypy t.py
t.py:27: error: Incompatible types in assignment (expression has type "Callable[[Callable[[<nothing>], <nothing>], Iterable[<nothing>]], Iterable[<nothing>]]", variable has type "Callable[[Callable[[T2], T], Iterable[T2]], Iterable[T]]")

@ilevkivskyi
Copy link
Member

This is not a bug, this is how generic aliases work. If you define:

T1 = TypeVar('T1')
T2 = TypeVar('T2')
MC = Callable[[Callable[[T2], T1], Iterable[T2]], Iterable[T1]]

then bare MC will be expanded with all type variables set to Any (same as any other generic):

x: MC
reveal_type(x)  # Callable[[Callable[[Any], Any], Iterable[Any]], Iterable[Any]]

Instead, generic aliases could be used in a subscripted form, for example:

y: MC[int, str]
reveal_type(y) # Callable[[Callable[[int], str], Iterable[str]], Iterable[int]]

Note also, that there is one subtlety with Callable, it binds the type variables to itself, but there is unfortunately a bug #3924.

I am closing this since as I explained the first post is as expected, while example in your second post is a duplicate of the mentioned issue.

@asottile
Copy link
Contributor Author

This documentation should probably be updated then: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases

A type alias does not create a new type. It’s just a shorthand notation for another type

At least from my reading aliases should act like a text substitution, but for those containing TypeVars they do not

@ilevkivskyi
Copy link
Member

If you scroll down a bit there is https://mypy.readthedocs.io/en/latest/generics.html#generic-type-aliases

I agree however the wording in the first note is not perfect, would you like to make a PR?

@ilevkivskyi
Copy link
Member

Btw, it looks like the generic part was just moved down (it was previously right after normal aliases). And this move was not done careful enough.

@asottile
Copy link
Contributor Author

I can make a PR, how about something like:

A type alias does not create a new type. It’s just a shorthand notation for another type – it’s equivalent to the target type unless it is a [generic type alias](...)

@ilevkivskyi
Copy link
Member

OK

@asottile
Copy link
Contributor Author

#5478 for that :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants