Mypy raises exception for certain type alias definitions with parameters bounded by a (not-yet-defined) type

Sometime we want to use a class in typing before the class itself is defined, in such cases we can use quotes. Suppose we have a type alias B depending on class A and a class A which (in my code, not in this simplified example) uses B. With mypy 1.17, the following code:


type B[T:"A"] = int

class A:
    pass

produces as mypy output:

$ mypy test.py
Traceback (most recent call last):
  File "/home/janr/local/anaconda3/envs/trumpet/bin/mypy", line 7, in <module>
    sys.exit(console_entry())
             ^^^^^^^^^^^^^^^
  File "/home/janr/local/anaconda3/envs/trumpet/lib/python3.12/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "mypy/main.py", line 127, in main
  File "mypy/main.py", line 211, in run_build
  File "mypy/build.py", line 191, in build
  File "mypy/build.py", line 267, in _build
  File "mypy/build.py", line 2939, in dispatch
  File "mypy/build.py", line 3337, in process_graph
  File "mypy/build.py", line 3464, in process_stale_scc
  File "mypy/build.py", line 2501, in write_cache
  File "mypy/build.py", line 1562, in write_cache
  File "mypy/nodes.py", line 367, in serialize
  File "mypy/nodes.py", line 4138, in serialize
  File "mypy/nodes.py", line 4074, in serialize
  File "mypy/nodes.py", line 3806, in serialize
  File "mypy/types.py", line 696, in serialize
  File "mypy/types.py", line 3191, in serialize
AssertionError: Internal error: unresolved placeholder type None

which is not really what I expect. Maybe my code is illegal, but then maybe mypy should produce a comprehensive error message.

In this minimal example, if I put the definition of class A before the definition of B, mypy runs smoothly (not reporting any errors). (In my own code, I can’t do that because the definition of class A depends on type alias B).

Also, the problem may disappear in different but similar cases:

type B[T:"A"] = "C"

class A:
    pass

class C:
    pass

Mypy runs and doesn’t find errors in this code.

I understand it may be wasteful to define an alias depending on class A (as bound for T) but not using A or T inside the body of its definition, but the documentation doesn’t seem to forbid it (and my last example using “C” doesn’t let the body of the definition of “B” use “A” either). My intention was to later extend the definition of B with something depending on A and to have the parameter T already in the code.

Is this a mypy bug or do I misunderstand what I’m allowed to do with aliases/parameters (or both) ?

It is definitely a bug because mypy should not abort with an “internal error” like that.

I think the type annotation is legal but meaningless as the type parameter T does not appear in the rhs.

2 Likes

It can sometimes be useful for backwards compatibility

In this case it is more “forward compatibility”: I know that in future I’ll extend the definition and need the parameter, but as the rest of the code isn’t yet ready I can’t yet add the part of the definition depending on the parameter. Thanks anyway for confirming the code is legal, this then means that we have here a real mypy bug.

Yea, that’s indeed another valid use-case. But what I meant was something like

type Maybe[T, D=None] = T | D

which someone might change to e.g.

type Maybe[T, D=None] = T | Any

(I’m not saying that’s a good idea or anything; it’s just an example)

So you’d have to keep the D type parameter in order for Maybe to be backwards compatible.

Which python version are you using? Perhaps PEP 649 (py 3.14+) would work for you?

It looks like PEP 649 could indeed be a good improvement. I’m currently using python 3.12, we’ll soon upgrade. I understand the current problem is only a mypy bug which will be solved, that there is a short-term work-around for me, and that in the longer run with python 3.14 things will become easier due to overall improvements to the typing system. For my specific problem here, it seems several people agree that even on lower python versions my code is already legal.

1 Like

If mypy hits an AssertionError, that’s always a bug (even if the code it crashes on is not allowed by the type system) and you should report it.

PEP 649 was brought up, but it only affects the runtime behavior. Maybe it allows you to write this code in a different way that doesn’t make mypy crash, but even so, it’s still a bug for mypy to crash.

1 Like