Skip to content

TypeVar in generic union member gets restricted to first constraint #3644

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
JelleZijlstra opened this issue Jul 1, 2017 · 3 comments · Fixed by #14396
Closed

TypeVar in generic union member gets restricted to first constraint #3644

JelleZijlstra opened this issue Jul 1, 2017 · 3 comments · Fixed by #14396
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-0-high topic-type-variables topic-union-types

Comments

@JelleZijlstra
Copy link
Member

In the below:

from typing import TypeVar, Generic, Union

AnyStr = TypeVar('AnyStr', str, bytes)

class PathLike(Generic[AnyStr]):
    def __fspath__(self) -> AnyStr: ...

def readlink(s: Union[AnyStr, PathLike[AnyStr]]) -> AnyStr:
    ...

class BP(PathLike[bytes]):
    def __fspath__(self) -> bytes:
        return b''

class SP(PathLike[str]):
    def __fspath__(self) -> str:
        return ''

reveal_type(readlink(''))
reveal_type(readlink(b''))
reveal_type(readlink(BP()))  # E: Argument 1 to "readlink" has incompatible type "BP"; expected "Union[str, PathLike[str]]"
reveal_type(readlink(SP()))

I ran mypy current master with no extra options.

The second to last line should type check and the type should be bytes, but instead mypy somehow expects Union[str, PathLike[str]]. If I switch around the order of the constraints on the TypeVar, it complains about the last line instead.

The following all make the error go away:

  • Changing the argument type of readlink to Union[str, PathLike[AnyStr]]
  • Changing the argument type to Union[AnyStr, PathLike]
    Changing the order of the Union doesn't make a difference.

Guido found this bug in a comment on python/typeshed#1441. I couldn't find any other reports in the tracker, but wasn't sure what to search for.

@JukkaL
Copy link
Collaborator

JukkaL commented Jul 3, 2017

Here's is a slightly simplified repro:

from typing import TypeVar, Generic, Union

T = TypeVar('T', str, int)

class C(Generic[T]): ...

def f(s: Union[T, C[T]]) -> T:  ...

ci: C[int]
cs: C[str]

reveal_type(f(''))
reveal_type(f(1))
reveal_type(f(ci))  # Argument 1 to "f" has incompatible type C[int]; expected "Union[str, C[str]]"
reveal_type(f(cs))

JelleZijlstra added a commit to JelleZijlstra/typeshed that referenced this issue Apr 14, 2018
Fixes python#1997.

This is tricky because we need to get the return values right (see python#1960 for
prior attempts) and we often run into python/mypy#3644. I found that I
could express most signatures correctly using a series of overloads.

A few other changes in here:
- Added splitunc, which according to https://docs.python.org/3/library/os.path.html
  should exist in both Unix and Windows.
- Made the second argument to os.path.curdir Optional to match the implementation.
- Fixed os.path.split, whose previous Path-aware signature triggered python/mypy#3644.

I used the following test program to make sure mypy accepted the signatures used here:
JelleZijlstra added a commit to python/typeshed that referenced this issue May 15, 2018
Fixes #1997, #2068.

This is tricky because we need to get the return values right (see #1960 for
prior attempts) and we often run into python/mypy#3644. I found that I
could express most signatures correctly using a series of overloads.

A few other changes in here:
- Added splitunc, which according to https://docs.python.org/3/library/os.path.html
  should exist in both Unix and Windows.
- Made the second argument to os.path.curdir Optional to match the implementation.
- Fixed os.path.split, whose previous Path-aware signature triggered python/mypy#3644.
@JukkaL JukkaL added the false-positive mypy gave an error on correct code label May 19, 2018
gwk pushed a commit to gwk/typeshed that referenced this issue May 29, 2018
Fixes python#1997, python#2068.

This is tricky because we need to get the return values right (see python#1960 for
prior attempts) and we often run into python/mypy#3644. I found that I
could express most signatures correctly using a series of overloads.

A few other changes in here:
- Added splitunc, which according to https://docs.python.org/3/library/os.path.html
  should exist in both Unix and Windows.
- Made the second argument to os.path.curdir Optional to match the implementation.
- Fixed os.path.split, whose previous Path-aware signature triggered python/mypy#3644.
yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this issue Jan 23, 2019
Fixes python#1997, python#2068.

This is tricky because we need to get the return values right (see python#1960 for
prior attempts) and we often run into python/mypy#3644. I found that I
could express most signatures correctly using a series of overloads.

A few other changes in here:
- Added splitunc, which according to https://docs.python.org/3/library/os.path.html
  should exist in both Unix and Windows.
- Made the second argument to os.path.curdir Optional to match the implementation.
- Fixed os.path.split, whose previous Path-aware signature triggered python/mypy#3644.
@JukkaL
Copy link
Collaborator

JukkaL commented Apr 23, 2021

This seems like another example of this issue (reported by a coworker):

from typing import Callable, TypeVar, Generic, Union

_U = TypeVar('_U')
_T = TypeVar('_T')

class Future(Generic[_T]):
    
    def __init__(self, t: _T):
        self._t = t

    def then(self, fn: Callable[[_T], Future[_U]]) -> Future[_U]:
        return fn(self._t)
    
    def then2(self, fn: Callable[[_T], _U]) -> Future[_U]:
        return Future(fn(self._t))
        
    def then3(self, fn: Union[Callable[[_T], Future[_U]], Callable[[_T], _U]]) -> Future[_U]:
        ret = fn(self._t)
        if isinstance(ret, Future):
            return ret
        else:
            return Future(ret)

def foo(t: int) -> Future[str]:
    return Future("str")

fut = Future(5)

reveal_type(fut.then(foo)) # is a Future[str], good

def foo2(t: int) -> str:
    return "str"
    
reveal_type(fut.then2(foo2)) # also is a Future[str], good

reveal_type(fut.then3(foo)) # <<==== error, Future[<nothing>]
reveal_type(fut.then3(foo2)) 

@hauntsaninja
Copy link
Collaborator

#14396 fixes this, but Jukka's example in this issue is something different, so I opened #14403 for it

hauntsaninja pushed a commit that referenced this issue Jan 6, 2023
Fixes #3644

Handling of constrained type variables of function `filter_satisfiable`
of module `constraints` was missing (as was indicated by a removed
ToDo).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-0-high topic-type-variables topic-union-types
Projects
None yet
3 participants