python / python/mypy

dmypy Suggest assertion error in `node_type_from_base`

Open
#16,708 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

crash topic-daemon
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Crash Report

Mypy Daemon crashed attempting to generate type suggestions for __reduce__ function in class with nonstandard __new__. (re-creating #15823)

Traceback

dmypy suggest crash.py:64: 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.8.0
dmypy suggest crash.py: 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.8.0
Daemon crashed!
Traceback (most recent call last):
  File "mypy/dmypy_server.py", line 236, in serve
  File "mypy/dmypy_server.py", line 285, in run_command
  File "mypy/dmypy_server.py", line 972, in cmd_suggest
  File "mypy/suggestions.py", line 267, in suggest
  File "mypy/suggestions.py", line 502, in get_suggestion
  File "mypy/suggestions.py", line 444, in find_best
  File "mypy/suggestions.py", line 675, in try_type
  File "mypy/server/update.py", line 314, in trigger
  File "mypy/server/update.py", line 881, in propagate_changes_using_dependencies
  File "mypy/server/update.py", line 1010, in reprocess_nodes
  File "mypy/semanal_main.py", line 144, in semantic_analysis_for_targets
  File "mypy/semanal_main.py", line 291, in process_top_level_function
  File "mypy/semanal_main.py", line 349, in semantic_analyze_target
  File "mypy/semanal.py", line 600, in refresh_partial
  File "mypy/semanal.py", line 6541, in accept
  File "mypy/errors.py", line 1261, in report_internal_error
  File "mypy/semanal.py", line 6539, in accept
  File "mypy/nodes.py", line 787, in accept
  File "mypy/semanal.py", line 833, in visit_func_def
  File "mypy/semanal.py", line 865, in analyze_func_def
  File "mypy/typeanal.py", line 1031, in visit_callable_type
  File "mypy/typeanal.py", line 1684, in anal_type
  File "mypy/types.py", line 2400, in accept
  File "mypy/typeanal.py", line 1133, in visit_tuple_type
  File "mypy/typeanal.py", line 1659, in anal_array
  File "mypy/typeanal.py", line 1684, in anal_type
  File "mypy/types.py", line 1970, in accept
  File "mypy/typeanal.py", line 980, in visit_callable_type
  File "mypy/typeanal.py", line 1614, in bind_function_type_variables
AssertionError

To Reproduce

dmypy suggest crash.py:

from __future__ import annotations

from collections.abc import Iterable, Callable
from typing import TYPE_CHECKING, TypeVar, Any, cast
import sys

if sys.version_info < (3, 11):
    from exceptiongroup import ExceptionGroup, BaseExceptionGroup

if TYPE_CHECKING:
    from types import TracebackType
    from typing_extensions import Self

NonBaseMultiError = ExceptionGroup


class MultiError(BaseExceptionGroup[BaseException]):
    def __init__(
        self, exceptions: list[BaseException], *, _collapse: bool = True
    ) -> None:
        self.collapse = _collapse

        # Avoid double initialization when _collapse is True and exceptions[0] returned
        # by __new__() happens to be a MultiError and subsequently __init__() is called.
        if _collapse and getattr(self, "exceptions", None) is not None:
            # This exception was already initialized.
            return

        super().__init__("multiple tasks failed", exceptions)

    def __new__(
        cls, exceptions: Iterable[BaseException], *, _collapse: bool = True
    ) -> MultiError | NonBaseMultiError[Exception] | Self:
        exceptions_union: list[BaseException | Exception] = list(exceptions)
        for exc in exceptions_union:
            if not isinstance(exc, BaseException):
                raise TypeError(f"Expected an exception object, not {exc!r}")
        class_sub: type[Self | NonBaseMultiError[Exception]] = cls
        if _collapse and len(exceptions_union) == 1:
            # If this lone object happens to itself be a MultiError, then
            # Python will implicitly call our __init__ on it again.  See
            # special handling in __init__.
            single = exceptions_union[0]
            assert isinstance(single, MultiError)
            return single
        else:
            # The base class __new__() implicitly invokes our __init__, which
            # is what we want.
            #
            # In an earlier version of the code, we didn't define __init__ and
            # simply set the `exceptions` attribute directly on the new object.
            # However, linters expect attributes to be initialized in __init__.
            if all(isinstance(exc, Exception) for exc in exceptions_union):
                exceptions_arg_one = cast("list[Exception]", exceptions_union)

                return super().__new__(
                    NonBaseMultiError, "multiple tasks failed", exceptions_arg_one
                )
            else:
                exceptions_arg_two = cast("list[BaseException]", exceptions_union)

                return super().__new__(cls, "multiple tasks failed", exceptions_arg_two)

    def __reduce__(self):
        return (
            self.__new__,
            (self.__class__, list(self.exceptions)),
            {"collapse": self.collapse},
        )

Run:

dmypy --status-file="<user path>/dmypy.json" run --timeout=1800 --log-file="<user path>/log.txt" --export-types "<user path>/dmypy suggest crash.py" -- --no-implicit-reexport --disallow-untyped-defs --show-error-codes --strict --warn-redundant-casts --cache-dir="<user path>" --show-error-end --no-color-output --no-error-summary --show-column-numbers --warn-unreachable --disallow-untyped-calls --warn-unused-ignores --cache-fine-grained --soft-error-limit=-1 --show-traceback --hide-error-context --show-absolute-path
dmypy --status-file="<user path>/dmypy.json" suggest "<user path>/dmypy suggest crash.py:65"

Your Environment

  • Mypy version used: dmypy 1.8.0
  • Mypy command-line flags: See above
  • Mypy configuration options from mypy.ini (and other config files): See above
  • Python version used: Python 3.12.0 (main, Oct 4 2023, 06:27:34) [GCC 13.2.0] on linux
  • Operating system and version: 64 bit Linux Ubuntu Budgie 10.8 (Ubuntu 23.10) Linux 6.5.0-14-generic

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

Start with the dmypy suggest reproduction in the issue, then trace the reported call path through mypy/suggestions.py and mypy/typeanal.py, especially bind_function_type_variables. Confirm the behavior on the supplied crash.py and determine how the assertion is reached during type analysis. Done means dmypy suggest handles this case without an INTERNAL ERROR or daemon crash.

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
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.