You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After upgrading to 1.16.1 we are seeing occasional AssertionErrors being logged:
$ mypymypy 1.16.1 (compiled: yes)Exception ignored in PyObject_HasAttr(); consider using PyObject_HasAttrWithError(), PyObject_GetOptionalAttr() or PyObject_GetAttr():Traceback (most recent call last): File "mypy/nodes.py", line 615, in setterAssertionError: ... (repeated many times)
It seems like the code introduced in #19248 to fix #19224 makes an assert which is not always true. It seems like the worry expressed in a comment on that PR was justified ;).
To Reproduce
We haven't managed to find a reliable reproducer yet. Switching between branches in our repo and invoking mypy, sometimes triggers it, but sometimes doesn't.
Expected Behavior
No errors.
Actual Behavior
Errors.
Your Environment
Mypy version used: 1.6.1
Mypy command-line flags: dmypy run -- --cache-dir .mypy_cache/not_recently_touched <explicitly specified list of .py files>
Mypy configuration options from mypy.ini (and other config files):
Does this ever happen without cache? Your mention of switching between branches suggests some relationship with cache, and it's important to know whether "clean" run without cache is prone to this.
I suspect the astmerge in the dmypy server triggers this error, because OverloadedFuncDef.setter is defined as a @property.
From the error logs above we can see that the AssertionError is triggered during an hasattr call. Searching the codebase for an interesting hasattr, we find a hasattr call in replace_object_state. This results in possibly calling hasattr on OverloadedFuncDef.setter, which evaluates the property unconditionally (since hasattr is implemented using getattr[1]), triggering the assert on mypy/nodes.py:615.
Assuming the above I tried to create a reproducer. Which indeed made it reproducable:
$ mkdir /tmp/hello
$ cd /tmp/hello
$ uv init
$ uv add mypy==1.16.1
$ cat >test.py <<EOLfrom typing import Literal, overload@overloaddef a(x: Literal[1]) -> int: ...@overloaddef a(x: Literal["a"]) -> str: ...@overloaddef a(x: Literal["b"]) -> str: ...def a(x: Literal[1, "a", "b"]) -> int | str: return xEOL
$ uv run dmypy run test.pyDaemon startedSuccess: no issues found in 1 source file
$ cat >test.py <<EOLfrom typing import Literal, overload@overloaddef a(x: Literal[1]) -> int: ...@overloaddef a(x: Literal["a"]) -> str: ...def a(x: Literal[1, "a"]) -> int | str: return xEOL
$ uv run dmypy run test.pyException ignored in PyObject_HasAttr(); consider using PyObject_HasAttrWithError(), PyObject_GetOptionalAttr() or PyObject_GetAttr():Traceback (most recent call last): File "mypy/nodes.py", line 615, in setterAssertionError: Exception ignored in PyObject_HasAttr(); consider using PyObject_HasAttrWithError(), PyObject_GetOptionalAttr() or PyObject_GetAttr():Traceback (most recent call last): File "mypy/nodes.py", line 615, in setterAssertionError: Exception ignored in PyObject_HasAttr(); consider using PyObject_HasAttrWithError(), PyObject_GetOptionalAttr() or PyObject_GetAttr():Traceback (most recent call last): File "mypy/nodes.py", line 615, in setterAssertionError: Exception ignored in PyObject_HasAttr(); consider using PyObject_HasAttrWithError(), PyObject_GetOptionalAttr() or PyObject_GetAttr():Traceback (most recent call last): File "mypy/nodes.py", line 615, in setterAssertionError: Success: no issues found in 1 source file
$ ... cycling between the versions of `test.py` consistently reproduces the error.
We probably want OverloadedFuncDef.setter to not be a @property, s.t. it is not evaluated by hasattr? Or we should prevent it from being evaluated by accident in some other way.
It is probably a good idea to turn PytestUnraisableExceptionWarning (and possibly other warnings) into an error. There is pytest's filterwarnings for that:
Yeah, just global error would be better - IIRC there are no warnings we want to silence deliberately. I only noticed these warnings in CI yesterday by accident and didn't have time to open a ticket...
Activity
sterliakov commentedon Jul 11, 2025
Does this ever happen without cache? Your mention of switching between branches suggests some relationship with cache, and it's important to know whether "clean" run without cache is prone to this.
SemMulder commentedon Jul 11, 2025
I suspect the
astmergein thedmypyserver triggers this error, becauseOverloadedFuncDef.setteris defined as a@property.From the error logs above we can see that the
AssertionErroris triggered during anhasattrcall. Searching the codebase for an interestinghasattr, we find ahasattrcall inreplace_object_state. This results in possibly callinghasattronOverloadedFuncDef.setter, which evaluates the property unconditionally (sincehasattris implemented usinggetattr[1]), triggering theassertonmypy/nodes.py:615.Main uses of
replace_object_stateare inmypy/server/astmerge.py.Assuming the above I tried to create a reproducer. Which indeed made it reproducable:
We probably want
OverloadedFuncDef.setterto not be a@property, s.t. it is not evaluated byhasattr? Or we should prevent it from being evaluated by accident in some other way.Quite a tricky bug ;).
SemMulder commentedon Jul 11, 2025
In addition, we might want to replace the use of
PyObject_HasAttrfor implementinghasattrinmypycwith an alternative that deals with errors better?I suspect the current implementation doesn't have the same semantics as the interpreter?
sterliakov commentedon Jul 11, 2025
Huh, we even have such warnings reported in CI (expand the "Test" step):
https://github.com/python/mypy/actions/runs/16208971103/job/45765332955?pr=19422
SemMulder commentedon Jul 11, 2025
From there:
That also confirms my suspicions around
astmergebeing the triggering factor. :)SemMulder commentedon Jul 11, 2025
It is probably a good idea to turn
PytestUnraisableExceptionWarning(and possibly other warnings) into an error. There ispytest'sfilterwarningsfor that:Or even better:
sterliakov commentedon Jul 11, 2025
Yeah, just global
errorwould be better - IIRC there are no warnings we want to silence deliberately. I only noticed these warnings in CI yesterday by accident and didn't have time to open a ticket...sterliakov commentedon Jul 11, 2025
(for future readers: this is not a crash, exception is logged by dmypy, not raised, label "bug" is correct)
CoolCat467 commentedon Jul 17, 2025
I've been running into this with dmypy as well
Fix "ignored exception in `hasattr`" in dmypy (python#19428)
Fix "ignored exception in `hasattr`" in dmypy (#19428)