Skip to content

2.2 Regression with placeholder types in a TypeVar #21640

Closed

Description

@delfick

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

  1. ilevkivskyi commented on Jul 6, 2026

    @ilevkivskyi
    Member

    @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 it has_placeholders(). If it does, you check if final_iteration is True. 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).

  2. delfick commented on Jul 6, 2026

    @delfick
    Author

    @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_deps hook (I could be wrong of course, but that is the funky thing that I'm doing)

  3. ilevkivskyi commented on Jul 6, 2026

    @ilevkivskyi
    Member

    Maybe it's a GitHub bug, but that link gives me 404.

  4. delfick commented on Jul 7, 2026

    @delfick
    Author

    @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)

  5. delfick commented on Jul 9, 2026

    @delfick
    Author

    Though, I might have a solution. Seems if I use the type keyword 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.

  6. delfick commented on Jul 14, 2026

    @delfick
    Author
  7. ilevkivskyi commented on Jul 16, 2026

    @ilevkivskyi
    Member

    Sorry for a delay.

    so something like this?

    Yes, but instead of a raise it 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.

    I resolved this with delfick/extended-mypy-django-plugin#74 :)

    Hm, it is suspicious that the type syntax 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.

  8. delfick commented on Jul 17, 2026

    @delfick
    Author

    @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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    crashtopic-pluginsThe plugin API and ideas for new plugins

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions