python / python/mypy

`RecursionError` with recursive type alias and concatenate

Open
#21,123 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

crash topic-paramspec topic-recursive-types
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Crash Report

While trying to make an infinitely expanding function, I kept getting 500 errors on the playground. Trying it on the CLI, mypy crashed with error: INTERNAL ERROR, with the traceback ending in RecursionError: maximum recursion depth exceeded.

Traceback

Collapsed for readability
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "Scripts\mypy.exe\__main__.py", line 10, in <module>
    sys.exit(console_entry())
  File "Lib\site-packages\mypy\__main__.py", line 15, in console_entry
    main()
  File "Lib\site-packages\mypy\main.py", line 141, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "Lib\site-packages\mypy\main.py", line 225, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "Lib\site-packages\mypy\build.py", line 391, in build
    result = build_inner(
  File "Lib\site-packages\mypy\build.py", line 488, in build_inner
    graph = dispatch(sources, manager, stdout)
  File "Lib\site-packages\mypy\build.py", line 3754, in dispatch
    process_graph(graph, manager)
  File "Lib\site-packages\mypy\build.py", line 4206, in process_graph
    done, still_working, results = manager.wait_for_done(graph)
  File "Lib\site-packages\mypy\build.py", line 1208, in wait_for_done
    process_stale_scc(graph, next_scc, self)
  File "Lib\site-packages\mypy\build.py", line 4371, in process_stale_scc
    mypy.semanal_main.semantic_analysis_for_scc(graph, scc, manager.errors)
  File "Lib\site-packages\mypy\semanal_main.py", line 92, in semantic_analysis_for_scc
    process_functions(graph, scc, patches)
  File "Lib\site-packages\mypy\semanal_main.py", line 275, in process_functions
    process_top_level_function(
  File "Lib\site-packages\mypy\semanal_main.py", line 314, in process_top_level_function
    deferred, incomplete, progress = semantic_analyze_target(
  File "Lib\site-packages\mypy\semanal_main.py", line 380, in semantic_analyze_target
    analyzer.refresh_partial(
  File "Lib\site-packages\mypy\semanal.py", line 704, in refresh_partial
    self.accept(node)
  File "Lib\site-packages\mypy\semanal.py", line 7710, in accept
    node.accept(self)
    ~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\nodes.py", line 1089, in accept
    return visitor.visit_func_def(self)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\semanal.py", line 988, in visit_func_def
    self.analyze_func_def(defn)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\semanal.py", line 1032, in analyze_func_def
    if self.found_incomplete_ref(tag) or has_placeholder(result):
                                         ~~~~~~~~~~~~~~~^^^^^^^^
  File "Lib\site-packages\mypy\semanal_shared.py", line 377, in has_placeholder
    return typ.accept(HasPlaceholders())
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\mypy\types.py", line 2324, in accept
    return visitor.visit_callable_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\type_visitor.py", line 554, in visit_callable_type
    args = self.query_types(t.arg_types)
  File "Lib\site-packages\mypy\type_visitor.py", line 605, in query_types
    return any(t.accept(self) for t in types)
           ^^^
  File "Lib\site-packages\mypy\type_visitor.py", line 605, in <genexpr>
    return any(t.accept(self) for t in types)
               ~~~~~~~~^^^^^^


  File "Lib\site-packages\mypy\types.py", line 418, in accept
    return visitor.visit_type_alias_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\type_visitor.py", line 598, in visit_type_alias_type
    return get_proper_type(t).accept(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\types.py", line 2324, in accept
    return visitor.visit_callable_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\type_visitor.py", line 555, in visit_callable_type
    ret = t.ret_type.accept(self)

  ^ Above 4 traceback items repeated 4085 more times (excluded for brevity)


  File "Lib\site-packages\mypy\types.py", line 418, in accept
    return visitor.visit_type_alias_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\type_visitor.py", line 598, in visit_type_alias_type
    return get_proper_type(t).accept(self)
           ~~~~~~~~~~~~~~~^^^
  File "Lib\site-packages\mypy\types.py", line 3649, in get_proper_type
    typ = typ._expand_once()
  File "Lib\site-packages\mypy\types.py", line 382, in _expand_once
    return self.alias.target.accept(InstantiateAliasVisitor(mapping))
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\mypy\types.py", line 2324, in accept
    return visitor.visit_callable_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\expandtype.py", line 495, in visit_callable_type
    arg_types = self.expand_types(t.arg_types)
  File "Lib\site-packages\mypy\expandtype.py", line 616, in expand_types
    a.append(t.accept(self))
             ~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\types.py", line 2324, in accept
    return visitor.visit_callable_type(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "Lib\site-packages\mypy\expandtype.py", line 443, in visit_callable_type
    param_spec = t.param_spec()
  File "Lib\site-packages\mypy\types.py", line 2440, in param_spec
    return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\mypy\types.py", line 827, in copy_modified
    return ParamSpecType(
        self.name,
    ...<7 lines>...
        prefix=prefix if prefix is not _dummy else self.prefix,
    )
  File "Lib\site-packages\mypy\types.py", line 803, in __init__
    super().__init__(name, fullname, id, upper_bound, default, line=line, column=column)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\mypy\types.py", line 609, in __init__
    super().__init__(line, column)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "Lib\site-packages\mypy\types.py", line 267, in __init__
    super().__init__(line, column)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded

To Reproduce

https://mypy-play.net/?gist=2c6fdf9918139e545067e2d5dd443b8f

from typing import Callable, reveal_type, Concatenate

type A[**P] = Callable[[Callable[P, None]], A[Concatenate[int, P]]]

def foo[**P](x: A[P]):
    reveal_type(x)
Powershell command used
PS ~\Downloads> uvx mypy@https://github.com/python/mypy.git --show-traceback -c @'
from typing import Callable, reveal_type, Concatenate

type A[**P] = Callable[[Callable[P, None]], A[Concatenate[int, P]]]

def foo[**P](x: A[P]):
    reveal_type(x)
'@ > output.txt

Your Environment

  • Mypy version used: Current master
  • Mypy command-line flags: --show-traceback -c "code"
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.14? Whatever uvx does.
  • Operating system and version: Windows 11

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the minimal reproducer from the issue with --show-traceback, then inspect mypy/semanal_shared.py, mypy/type_visitor.py, mypy/types.py, and mypy/expandtype.py around has_placeholder, type-alias expansion, and callable traversal. Done means the recursive alias and Concatenate example no longer crashes with RecursionError or reports an INTERNAL ERROR.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.