Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Overconstrained type variable #5775

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
efahl opened this issue Oct 11, 2018 · 10 comments
Closed

Overconstrained type variable #5775

efahl opened this issue Oct 11, 2018 · 10 comments
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code needs discussion topic-protocols topic-type-variables

Comments

@efahl
Copy link

efahl commented Oct 11, 2018

Python 3.6.6 x64
Windows 10 x64
mypy 0.630
mypy-extensions 0.4.1
typing 3.6.6
typing-extensions 3.6.6

https://mail.python.org/pipermail/python-ideas/2018-October/054134.html

Ivan Levkivskyi's suggested solution to handling a "PathLike" protocol:

from typing import AnyStr
from typing_extensions import Protocol
class PathLike(Protocol[AnyStr]):
    def __fspath__(self) -> AnyStr: ...

Produces an error unexpected error:

> mypy fsp.py
fsp.py:3: error: Invariant type variable 'AnyStr' used in protocol where covariant one is expected

Ivan's response

Hm, it looks like mypy overreacts here. I think it should be safe to use a constrained type variable if there are no constraints that are subtypes of other constraints (which is the case for AnyStr on Python 3, where bytes is not a subtype of str).

@emmatyping emmatyping added bug mypy got something wrong needs discussion topic-type-variables topic-protocols false-positive mypy gave an error on correct code labels Oct 12, 2018
@ilevkivskyi
Copy link
Member

(Edited the repro to be closer to what is actually wanted.)

@jmehnle
Copy link

jmehnle commented Oct 7, 2020

Is there a workaround for this? I'm having a similar problem:

from typing import Mapping, Sequence, TypeVar, Union
from typing_extensions import Protocol

Row = TypeVar("Row", bound=Union[Sequence, Mapping])

class C(Protocol[Row]):
    ...

mypy complains:

mypytest.py:6: error: Invariant type variable 'Row' used in protocol where covariant one is expected

Any (perhaps dirty) way to make this work?

@hauntsaninja
Copy link
Collaborator

Make your type variable covariant. (I don't think your case falls under what Ivan's talking about in this issue)

@Prometheus3375
Copy link

Prometheus3375 commented May 5, 2022

I also have such a trouble with mypy 0.950 and Python 3.9.

from collections.abc import Iterable
from typing import Protocol, TypeVar, overload

K = TypeVar('K')
V = TypeVar('V')


class SupportsKeysAndGetItem(Protocol[K, V]):
    def keys(self, /) -> Iterable[K]: ...
    def __getitem__(self, item: K, /) -> V: ...


@overload
def foo() -> dict: ...
@overload
def foo(**kwargs: V) -> dict[str, V]: ...
@overload
def foo(mapping: SupportsKeysAndGetItem[K, V], /) -> dict[K, V]: ...
@overload
def foo(mapping: SupportsKeysAndGetItem[str, V], /, **kwargs: V) -> dict[str, V]: ...
@overload
def foo(iterable: Iterable[tuple[K, V]], /) -> dict[K, V]: ...
@overload
def foo(iterable: Iterable[tuple[str, V]], /, **kwargs: V, ) -> dict[K, V]: ...


def foo(iterable = (), /, **kwargs):
    return dict(iterable, **kwargs)

Produces sandbox.py:8: error: Invariant type variable "V" used in protocol where covariant one is expected

@hauntsaninja
Copy link
Collaborator

Closing as per my previous comment

@hauntsaninja hauntsaninja closed this as not planned Won't fix, can't repro, duplicate, stale Aug 28, 2023
@Prometheus3375
Copy link

@hauntsaninja I am pretty sure that mypy overcontrains Protocol variables.

Check this case in mypy-play.net.

from typing import Protocol, TypeVar

K = TypeVar('K')
V = TypeVar('V')


class Protocol1(Protocol[K, V]):
    def foo1(self, /) -> K: ...
    def foo2(self, value: K, /) -> V: ...
    def foo3(self, value: V, /) -> K: ...

No errors.

If foo3 is removed, then this error is produced:

main.py:7: error: Invariant type variable "V" used in protocol where covariant one is expected  [misc]

If foo3 and foo2 are removed, then two errors are produced.

main.py:7: error: Invariant type variable "K" used in protocol where covariant one is expected  [misc]
main.py:7: error: Invariant type variable "V" used in protocol where covariant one is expected  [misc]

If I make K and V both covariant and remain all methods, then mypy complains about using a covariant type in method arguments:

main.py:9: error: Cannot use a covariant type variable as a parameter  [misc]
main.py:10: error: Cannot use a covariant type variable as a parameter  [misc]

All in all, once a user introduces methods with Protocol variables as argument types, then error about covariance is not emitted.

I personally do not see any sense in constraining Protocol variable to be covariant (unless there are methods with them). I find OK for arbitrary protocol Protocol1[bool] to not be a subtype of Protocol1[int].

@JelleZijlstra
Copy link
Member

Those errors are all correct; I'm not sure what your complaint is.

@Prometheus3375
Copy link

Prometheus3375 commented Aug 28, 2023

I wonder why Protocol type variables must be covariant unless there are methods with them as argument types.

@JelleZijlstra
Copy link
Member

They can be covariant when they are used only in return types, and they can be contravariant when they are used only in argument types; they must be invariant when used in both positions. That's just normal variance rules.

What's perhaps less intuitive is that mypy enforces that when the TypeVar can be covariant, it must be covariant. I didn't make that decision, but I think the reasoning is that usually covariance is what you want if it's possible, and it's helpful to users to point out when they accidentally use invariance.

@butlscot
Copy link

butlscot commented May 10, 2025

mypy enforces that when the TypeVar can be covariant, it must be covariant

This decision seems to prevent users from declaring Protocols that share an invariant generic type.

For a simple practical example, a depth-first search framework:

from typing import TypeVar, Protocol


NodePassthrough = TypeVar("NodePassthrough")


class Node(Protocol):
    def iter(self) -> list["Node"]:
        """Returns a list of child nodes."""
        ...


class DfsEnterNodeFn(Protocol[NodePassthrough]):
    def __call__(self, node: Node) -> NodePassthrough:
        """Called when entering a node during depth-first traversal."""
        ...


class DfsLeaveNodeFn(Protocol[NodePassthrough]):
    def __call__(self, node: Node, node_passthrough: NodePassthrough) -> None:
        """Called before leaving a node during depth-first traversal."""
        ...


def dfs(node: Node, upon_entering: DfsEnterNodeFn[NodePassthrough], before_leaving: DfsLeaveNodeFn[NodePassthrough]) -> None:
    """Depth-first search of a tree of nodes, calling upon_entering and before_leaving for each node encountered."""
    node_passthrough = upon_entering(node)

    for child in node.iter():
        dfs(child, upon_entering, before_leaving)

    before_leaving(node, node_passthrough)

This results in the following mypy errors:

example.py:12: error:
Invariant type variable "NodePassthrough" used in protocol where covariant one is expected  [misc]
    class DfsEnterNodeFn(Protocol[NodePassthrough]):
    ^

example.py:18: error:
Invariant type variable "NodePassthrough" used in protocol where contravariant one is expected  [misc]
    class DfsLeaveNodeFn(Protocol[NodePassthrough]):
    ^

Is there a workaround for this?

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 needs discussion topic-protocols topic-type-variables
Projects
None yet
Development

No branches or pull requests

8 participants