From b7723410a7eafe3d435066157d27460a73c7deb7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 28 Feb 2026 10:40:14 +0300 Subject: [PATCH 1/2] [stubtest] Fix crash on instances with redefined `__class__` --- mypy/stubtest.py | 4 ++++ mypy/test/teststubtest.py | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 9dd630d61d4a..5973d82f2383 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -2049,6 +2049,10 @@ def _named_type(name: str) -> mypy.types.Instance: return mypy.types.TupleType(items, fallback) fallback = mypy.types.Instance(type_info, [anytype() for _ in type_info.type_vars]) + if type(runtime) != runtime.__class__: + # Since `__class__` is redefined for an instance, we can't trust + # its `isinstance` checks, it can be dynamic. See #20919 + return fallback value: bool | int | str if isinstance(runtime, enum.Enum) and isinstance(runtime.name, str): diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 4023a191b548..4c95e7648030 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1564,6 +1564,49 @@ def f(): return 3 error=None, ) + @collect_cases + def test_proxy_object(self) -> Iterator[Case]: + yield Case( + stub=""" + class LazyObject: + def __init__(self, func: object) -> None: ... + def __bool__(self) -> bool: ... + """, + runtime=""" + class LazyObject: + def __init__(self, func): + self.__dict__["_wrapped"] = None + self.__dict__["_setupfunc"] = func + def _setup(self): + self.__dict__["_wrapped"] = self._setupfunc() + @property + def __class__(self): + if self._wrapped is None: + self._setup() + return type(self._wrapped) + def __bool__(self): + if self._wrapped is None: + self._setup() + return bool(self._wrapped) + """, + error='test_module.LazyObject.__class__', + ) + yield Case( + stub=""" + def default_value() -> bool: ... + + DEFAULT_VALUE: bool + """, + runtime=""" + def default_value(): + return True + + DEFAULT_VALUE = LazyObject(default_value) + bool(DEFAULT_VALUE) # evaluate the lazy object + """, + error='test_module.DEFAULT_VALUE', + ) + @collect_cases def test_all_at_runtime_not_stub(self) -> Iterator[Case]: yield Case( From 64af2c6314f991cfeacc9705d683f27c24680c28 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:00:58 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/test/teststubtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 4c95e7648030..d373b1560271 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1589,7 +1589,7 @@ def __bool__(self): self._setup() return bool(self._wrapped) """, - error='test_module.LazyObject.__class__', + error="test_module.LazyObject.__class__", ) yield Case( stub=""" @@ -1604,7 +1604,7 @@ def default_value(): DEFAULT_VALUE = LazyObject(default_value) bool(DEFAULT_VALUE) # evaluate the lazy object """, - error='test_module.DEFAULT_VALUE', + error="test_module.DEFAULT_VALUE", ) @collect_cases