Skip to content

Overconstrained type variable prevents declaring Protocols with shared invariant type #19068

Closed as not planned
@butlscot

Description

@butlscot

From #5775 I learned that

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions