Skip to content

Mypy crashes on async tuple to tuple assignment #19920

Description

@gooverdian

Crash Report

We got a tuple to tuple assignment with asyncio.gather, which crashes mypy, it looks like this:

tab_content: PriceCombinedDBAccessTabContentDto
price_region_stats: PriceRegionStats

(  # < Error points to this line, this is line 70
    price_data,
    tab_content,
    all_price_regions,
    all_prof_role_groups,
    price_region_stats,
    has_civ_service,
) = await asyncio.gather(
    get_price_data(handler, TabName.COMBINED_DBACCESS, price_description, backoffice_employer_id, session),
    get_price_tab_content(http_client, config, TabName.COMBINED_DBACCESS, backoffice_employer_id),
    get_all_price_regions(handler),
    get_all_prof_role_groups(handler),
    get_price_regions_stats(http_client, config),
    check_employer_has_service_no_handler(
        http_client, config, employer_id_or_backoffice_employer_id, ProductItemCode.ContactsView
    ),
)

Traceback

xhh/pages/price/combined_dbaccess.py:70: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 1.19.0+dev.354bea6352ee7a38b05e2f42c874e7d1f7bf557a
Traceback (most recent call last):
  File "/Users/gooverdian/Projects/xhh/.venv/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/main.py", line 127, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/main.py", line 211, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 196, in build
    result = _build(
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 272, in _build
    graph = dispatch(sources, manager, stdout)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 2946, in dispatch
    process_graph(graph, manager)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 3346, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 3447, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/build.py", line 2325, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 524, in check_first_pass
    self.accept(d)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 635, in accept
    stmt.accept(self)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/nodes.py", line 1127, in accept
    return visitor.visit_decorator(self)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 5412, in visit_decorator
    self.visit_decorator_inner(e)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 5419, in visit_decorator_inner
    self.check_func_item(e.func, name=e.func.name, allow_empty=allow_empty)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 1231, in check_func_item
    self.check_func_def(defn, typ, name, allow_empty)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 1504, in check_func_def
    self.accept(item.body)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 635, in accept
    stmt.accept(self)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/nodes.py", line 1542, in accept
    return visitor.visit_block(self)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 3126, in visit_block
    self.accept(s)
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 635, in accept
    stmt.accept(self)
    ~~~~~~~~~~~^^^^^^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/nodes.py", line 1629, in accept
    return visitor.visit_assignment_stmt(self)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 3177, in visit_assignment_stmt
    self.check_assignment(s.lvalues[-1], s.rvalue, s.type is None, s.new_syntax)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 3233, in check_assignment
    self.check_assignment_to_multiple_lvalues(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        lvalue.items, rvalue, rvalue, infer_lvalue_type
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 3984, in check_assignment_to_multiple_lvalues
    self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 4062, in check_multi_assignment
    self.check_multi_assignment_from_tuple(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        lvalues, rvalue, rvalue_type, context, undefined_rvalue, infer_lvalue_type
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/gooverdian/Projects/xhh/.venv/lib/python3.13/site-packages/mypy/checker.py", line 4202, in check_multi_assignment_from_tuple
    assert isinstance(reinferred_rvalue_type, TupleType)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 

To Reproduce

This file is not new, so the diff, which lead to that error is adding types to some members of a left tuple before the assignment:

+ tab_content: PriceCombinedDBAccessTabContentDto
+ price_region_stats: PriceRegionStats

(
    price_data,
    tab_content,
    all_price_regions,
    all_prof_role_groups,
    price_region_stats,
    has_civ_service,
) = await asyncio.gather(
    get_price_data(handler, TabName.COMBINED_DBACCESS, price_description, backoffice_employer_id, session),
    get_price_tab_content(http_client, config, TabName.COMBINED_DBACCESS, backoffice_employer_id),
    get_all_price_regions(handler),
    get_all_prof_role_groups(handler),
    get_price_regions_stats(http_client, config),
    check_employer_has_service_no_handler(
        http_client, config, employer_id_or_backoffice_employer_id, ProductItemCode.ContactsView
    ),
)

UPD: Found a way to reproduce it in playground https://mypy-play.net/?mypy=latest&python=3.13&gist=ca818b7f434ca646dd6373b26f5b58f6, seems like there would be an error if it wouldn't crash.

Your Environment

  • Mypy version used: 1.15.0, 1.18.2, git master
  • Mypy command-line flags: --config-file (path to config), --show-traceback, (path to file)
  • Mypy configuration options from mypy.ini (and other config files):
        [tool.mypy]
      warn_redundant_casts = true
      warn_unused_ignores = true
      warn_unused_configs = true
      show_error_codes = true
      ignore_missing_imports = true
      follow_imports = "silent"
      follow_untyped_imports = true
      disallow_untyped_calls = false
      disallow_incomplete_defs = true
      disallow_untyped_decorators = true
      check_untyped_defs = true
      strict = true
      disallow_untyped_defs = true
      disallow_any_generics = true
      disallow_subclassing_any = true
      warn_return_any = true
      enable_error_code = [
          "deprecated",
          "ignore-without-code",
      ]
      python_version = "3.13"
      exclude = [
          "xhh/pages/employer/documents-management",
          "xhh/pages/employer/financial-documents",
          "xhh/pages/employer/reviews-settings",
          "xhh/page.py",
      ]
    
  • Python version used: 3.13
  • Operating system and version: mac sequoia 15.6.1, alpine ubuntu (docker)

Activity

  1. gooverdian commented on Sep 24, 2025

    @gooverdian
    Author

    I was playing with this for a bit, seems like changing it to list solves the problem:

        tab_content: PriceCombinedDBAccessTabContentDto
        price_region_stats: PriceRegionStats
    
        [
            price_data,
            tab_content,
            all_price_regions,
            all_prof_role_groups,
            price_region_stats,
            has_civ_service,
        ] = list(await asyncio.gather(
            get_price_data(handler, TabName.COMBINED_DBACCESS, price_description, backoffice_employer_id, session),
            get_price_tab_content(http_client, config, TabName.COMBINED_DBACCESS, backoffice_employer_id),
            get_all_price_regions(handler),
            get_all_prof_role_groups(handler),
            get_price_regions_stats(http_client, config),
            check_employer_has_service_no_handler(
                http_client, config, employer_id_or_backoffice_employer_id, ProductItemCode.ContactsView
            ),
        ))

    Didn't manage to reproduce this bug on mypy-play though sadly Got it! https://mypy-play.net/?mypy=latest&python=3.13&gist=ca818b7f434ca646dd6373b26f5b58f6

  2. sterliakov commented on Sep 24, 2025

    @sterliakov
    Collaborator

    This still repro's on master and does not really need asyncio (and even async/await at all!). mypy 1.0.0 also crashes on this snippet.

    The following is enough to trigger the crash (or playground):

    from typing import TypeVar, overload
    
    _T = TypeVar("_T")
    
    @overload  # type: ignore[no-overload-impl]
    def gather(f1: _T) -> tuple[_T]: ...
    @overload
    def gather(*fns: object) -> int: ...
    
    def crash() -> None:
        foo: str
        (foo,) = gather(0)

    This happens when all following conditions hold true:

    • unpack a result of an overloaded call
    • at least one of unpack targets has an explicit type (from annotation, maybe from other sources too)
    • some suitable overload candidates produce a tuple type
    • some suitable overload candidates produce a non-tuple type
    • a tuple candidate is rejected due to the outer context (target annotation)
  3. ilevkivskyi commented on Sep 27, 2025

    @ilevkivskyi
    Member

    @sterliakov I think simply removing the assert as you do in your PR in wrong. I think outer type context should never affect overload selection, since in Python overload selection happens at runtime, and we must respect this semantics. We still can (and should) use the outer context while type-checking the call to selected overload.

    This however may be a bigger change (with potential performance implications), so we need to be careful, but FWIW I think this is the right thing to do. Also cc @JukkaL for visibility.

  4. sterliakov commented on Sep 27, 2025

    @sterliakov
    Collaborator
  5. ilevkivskyi commented on Sep 27, 2025

    @ilevkivskyi
    Member

    @sterliakov

    Changing this behavior will affect many users

    Completely ignoring outer context in infer_overload_return_type() (a two line change that also fixes this crash) causes only 5 tests to fail, out of those:

    • 4 fails because of slightly different error on the same line.
    • 1 fail for relatively niche use case.

    And in all 5 cases using the context during checking, but not during selection would fix the errors. This part is however tricky without a major performance penalty.

    So I would not be so dramatic. However, as I said before, this is something that requires care.

  6. added a commit that references this issue on Jan 18, 2026
  7. added a commit that references this issue on Aug 10, 2026
    0221c80
  8. added a commit that references this issue on Aug 15, 2026
    6dfa06d
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions