From d4a219966c0174bfe6248d07743d36b4d28fb920 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 5 Feb 2026 19:33:31 +0000 Subject: [PATCH] Fix indirect dependencies for protocols --- mypy/indirection.py | 7 ++++ test-data/unit/check-protocols.test | 52 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/mypy/indirection.py b/mypy/indirection.py index 95023e303cbdc..c5f3fa89b8c4a 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -123,6 +123,13 @@ def visit_instance(self, t: types.Instance) -> None: self._visit(t.type.typeddict_type) if t.type.tuple_type: self._visit(t.type.tuple_type) + if t.type.is_protocol: + # For protocols, member types constitute the semantic meaning of the type. + # TODO: this doesn't cover some edge cases, like setter types and exotic nodes. + for m in t.type.protocol_members: + node = t.type.names.get(m) + if node and node.type: + self._visit(node.type) def visit_callable_type(self, t: types.CallableType) -> None: self._visit_type_list(t.arg_types) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index c5136415edd34..4479890820889 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4693,3 +4693,55 @@ class Impl: pass x: Proto = Impl() + +[case testIndirectDependencyOnProtocolIncremental] +import a +[file a.py] +from b import P + +class CNested: + def f(self) -> int: ... +class C: + def f(self) -> CNested: ... + +x: P = C() + +[file b.py] +from typing import Protocol +from c import PNested + +class P(Protocol): + def f(self) -> PNested: ... + +[file c.py] +from typing import Protocol + +class PNested(Protocol): + def f(self) -> str: ... + +[file c.py.2] +from typing import Protocol + +class PNested(Protocol): + def f(self) -> int: ... + +[file c.py.3] +from typing import Protocol + +class PNested(Protocol): + def f(self) -> str: ... +[out] +tmp/a.py:8: error: Incompatible types in assignment (expression has type "C", variable has type "P") +tmp/a.py:8: note: Following member(s) of "C" have conflicts: +tmp/a.py:8: note: Expected: +tmp/a.py:8: note: def f(self) -> PNested +tmp/a.py:8: note: Got: +tmp/a.py:8: note: def f(self) -> CNested +[out2] +[out3] +tmp/a.py:8: error: Incompatible types in assignment (expression has type "C", variable has type "P") +tmp/a.py:8: note: Following member(s) of "C" have conflicts: +tmp/a.py:8: note: Expected: +tmp/a.py:8: note: def f(self) -> PNested +tmp/a.py:8: note: Got: +tmp/a.py:8: note: def f(self) -> CNested