Repository navigation
2.2 Regression with placeholder types in a TypeVar #21640
Description
Crash Report
I was testing the mypy 2.2 branch and I came across some errors around placeholder types.
Traceback
There's kinda two errors and I think they're related
AssertionError: Must not defer during final iteration
Please report a bug at https://github.com/python/mypy/issues
version: 2.2.0+dev.5ef090270579013c337a9bcc79e2c58417f09538
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "mypy/semanal.py", line 7732, in accept
File "mypy/nodes.py", line 1165, in accept
File "mypy/semanal.py", line 991, in visit_func_def
File "mypy/semanal.py", line 1036, in analyze_func_def
File "mypy/semanal.py", line 7333, in defer
AssertionError: Must not defer during final iteration
If I use the source directly so I get more of a traceback
More details
version: 2.2.0+dev.5ef090270579013c337a9bcc79e2c58417f09538
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/Users/stephen.moore/Projects/external/mypy/mypy/__main__.py", line 52, in <module>
console_entry()
File "/Users/stephen.moore/Projects/external/mypy/mypy/__main__.py", line 16, in console_entry
main()
File "/Users/stephen.moore/Projects/external/mypy/mypy/main.py", line 154, in main
res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
File "/Users/stephen.moore/Projects/external/mypy/mypy/main.py", line 244, in run_build
res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 422, in build
result = build_inner(
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 537, in build_inner
graph = dispatch(sources, manager, stdout, connect_threads)
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 4146, in dispatch
process_graph(graph, manager)
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 4614, in process_graph
done, still_working, results = manager.wait_for_done(graph)
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 1484, in wait_for_done
process_stale_scc(graph, next_scc, self)
File "/Users/stephen.moore/Projects/external/mypy/mypy/build.py", line 4782, in process_stale_scc
mypy.semanal_main.semantic_analysis_for_scc(graph, scc, manager.errors)
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal_main.py", line 92, in semantic_analysis_for_scc
process_functions(graph, scc, patches)
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal_main.py", line 275, in process_functions
process_top_level_function(
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal_main.py", line 314, in process_top_level_function
deferred, incomplete, progress = semantic_analyze_target(
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal_main.py", line 380, in semantic_analyze_target
analyzer.refresh_partial(
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal.py", line 709, in refresh_partial
self.accept(node)
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal.py", line 7732, in accept
node.accept(self)
~~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/Projects/external/mypy/mypy/nodes.py", line 1165, in accept
return visitor.visit_func_def(self)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal.py", line 991, in visit_func_def
self.analyze_func_def(defn)
~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal.py", line 1036, in analyze_func_def
self.defer(defn)
~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/Projects/external/mypy/mypy/semanal.py", line 7333, in defer
assert not self.final_iteration, "Must not defer during final iteration"
^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: Must not defer during final iteration
And if I start trying to fill in default values I start to get this happening:
NotImplementedError: Cannot serialize PlaceholderType instance
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/__main__.py", line 52, in <module>
console_entry()
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/__main__.py", line 16, in console_entry
main()
~~~~^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/main.py", line 154, in main
res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/main.py", line 244, in run_build
res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 422, in build
result = build_inner(
sources,
...<9 lines>...
metastore,
)
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 537, in build_inner
graph = dispatch(sources, manager, stdout, connect_threads)
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 4146, in dispatch
process_graph(graph, manager)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 4614, in process_graph
done, still_working, results = manager.wait_for_done(graph)
~~~~~~~~~~~~~~~~~~~~~^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 1484, in wait_for_done
process_stale_scc(graph, next_scc, self)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 4821, in process_stale_scc
meta_tuple = graph[id].write_cache()
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 3604, in write_cache
new_interface_hash, meta_tuple = write_cache(
~~~~~~~~~~~^
self.id,
^^^^^^^^
...<13 lines>...
self.manager,
^^^^^^^^^^^^^
)
^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/build.py", line 2325, in write_cache
tree.write(data_io)
~~~~~~~~~~^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/nodes.py", line 599, in write
self.names.write(data, self._fullname)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/nodes.py", line 5169, in write
value.write(data, fullname, key)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/nodes.py", line 5074, in write
self.node.write(data)
~~~~~~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/nodes.py", line 4721, in write
self.target.write(data)
~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/types.py", line 1756, in write
write_type_list(data, self.args)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/types.py", line 4553, in write_type_list
item.write(data)
~~~~~~~~~~^^^^^^
File "/Users/stephen.moore/.virtualenvs/project/lib/python3.13/site-packages/mypy/types.py", line 318, in write
raise NotImplementedError(f"Cannot serialize {self.__class__.__name__} instance")
NotImplementedError: Cannot serialize PlaceholderType instance
To Reproduce
It appears that ultimately this is an interaction with how my mypy plugin works, which complicates it a little.
Essentially I add dependencies with the get_additional_deps hook that contain aliases (I call these virtual dependencies), and then with a get_type_analyze_hook hook I look for a special class that I replace with the target of an appropriate alias from those "virtual dependencies".
I have created an example with a readme that replicates the problem. Seems I can't upload a git bundle to a github issue, so
here is a repo: https://github.com/delfick/mypy-release-2.2-bug-demonstration
It seems there is one change required to fix it
From 84140edcb76aaf4b6bf3a2fd1f6a5ffc80277810 Mon Sep 17 00:00:00 2001
From: Stephen Moore <stephen.moore@kraken.tech>
Date: Wed, 24 Jun 2026 10:13:32 +1000
Subject: [PATCH] WIP: this makes a difference
---
mypy/semanal.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mypy/semanal.py b/mypy/semanal.py
index e010273b0..324910809 100644
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2009,7 +2009,7 @@ class SemanticAnalyzer(
# re-analyze this class. Note we force progress to handle cases like
# class C[T = C], this matches logic in process_typevar_parameters()
# for "old style" type variables.
- self.defer(force_progress=tvar_defs != defn.type_vars)
+ self.mark_incomplete(defn.name, defn)
self.analyze_class_keywords(defn)
bases_result = self.analyze_base_classes(defn.name, bases)
--
2.50.1 (Apple Git-155)(undoing a change in #21491)
However when I do this mypy unit tests start to fail. I can't figure out how to fix that, or even how to replicate the errors in a mypy unit test.
Your Environment
- Mypy version used: release-2.2 branch (5ef0902)
- Python version used: 3.13.2
- Operating system and version: MacOS Tahoe 26.5.1
AI Disclosure: None: I actively avoid LLMs
Activity
ilevkivskyi commentedon Jul 6, 2026
@delfick I didn't have time to look into this, but most likely you are not calling
.defer()in your plugin somewhere you should. The general rule is like this: Whenever you store some type (that was analyzed/constructed by you, as opposite to mypy internals) as an attribute of a symbol, you should check if ithas_placeholders(). If it does, you check iffinal_iterationisTrue. If it is, you give an error about cyclic definition, otherwise you call.defer(), so that this code will get another pass of semantic analysis (potentially calling your plugin hooks again).We want to add some public API method to handle this more easily, but for now plugins need to do this manually (unfortunately).
delfick commentedon Jul 6, 2026
@ilevkivskyi so something like this?
It seems that doesn't work (and it still has a placeholder on the final iteration). I imagine because I get this type from a file that's only known because I inject it as a dependency in the
get_additional_depshook (I could be wrong of course, but that is the funky thing that I'm doing)ilevkivskyi commentedon Jul 6, 2026
Maybe it's a GitHub bug, but that link gives me 404.
delfick commentedon Jul 7, 2026
@ilevkivskyi oh, the repo is private, it's public now and should work (it's the same example repo I mention in the issue description above)
delfick commentedon Jul 9, 2026
Though, I might have a solution. Seems if I use the
typekeyword to make the alias (something I couldn't do when I made the plugin in the first place) and have the imports after the alias, it seems to fix the problem. I'll close the issue when I've got around to making the complete fix to my plugin and have completely confirmed, but it looks promising that will work.delfick commentedon Jul 14, 2026
I resolved this with delfick/extended-mypy-django-plugin#74 :)
ilevkivskyi commentedon Jul 16, 2026
Sorry for a delay.
Yes, but instead of a
raiseit should give a regular mypy error, i.e.ctx.api.fail("Cannot resolve type, possible cyclic definition"). But IIUC this didn't work, so maybe it should be done somewhere else.Hm, it is suspicious that the
typesyntax behaves differently from an "old-style" type alias. But since I don't have much time to dig deeper, let's keep this closed for now. We can re-open if this will re-surface elsewhere.delfick commentedon Jul 17, 2026
@ilevkivskyi I think it works because the cycle was that the alias needed to be defined before it ends up being used sometimes by the import that is needed to define the alias. And it's just that the type keyword means that I can define the alias before I do the import.
Interestingly (and slightly concerningly) though it does appear that it doesn't work on older versions of both mypy and django-stubs