python / python/mypy

MyPy fails type narrowing on `not issubclass(X, type[T]): raise`

Abierto
#19,917 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

bug
Lenguaje dominante
Python
Estrellas
20.6k
Forks
3.3k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

Bug Report

MyPy fails to narrow types using issubclass, and expects an isinstance check which is wrong.

MyPy fails type narrowing on not issubclass(X, type[T]): raise and complains about returning X() later for the function marked as returning T.

To Reproduce

[Mypy Playground example](https://gist.github.com/mypy-play/dcd17343b289523c197a31f850eca90f)

```python from collections.abc import Iterator from typing import TYPE_CHECKING

class MySentinel:
    pass

# Works as expected
class OptionalSentinel:
    def __init__(self, value: object) -> None:
        self._value = value

    def get_value[T](self, expected: type[T]) -> Iterator[T]:
        if not isinstance(self._value, expected):
            raise TypeError

        yield self._value

# Reveals bug
class MagicIterator:
    def raw[T](self, expected: type[T]) -> Iterator[T]:
        """Get a list of repositories which accept a specific lint to run."""
        if not issubclass(MySentinel, expected):
            raise ValueError

        # if TYPE_CHECKING:
        #     # an ugly workaround to make mypy happy
        #     if not isinstance(expected, MySentinel):
        #         raise ValueError

        for project in [MySentinel(), MySentinel()]:
            yield project  # error: Incompatible types in "yield" (actual type "MySentinel", expected type "T")  [misc]
```

Expected Behavior

I expected mypy to infer that after an if not issubclass(T, X): raise block X must be subclass T and it should be ok to return X in place of T.

Since I pass a type and want to compare it to a type an isinstance check is not appropriate for MagicIterator, neiter o I think the issubclass check is the most appropriate (I would prefer to check using expected is not T to guarantee that expected is actually exactly T but ìs`i not listed in the list of supported narrowing expressions.

Actual Behavior

Mypy is only happy if I add an isinstance check, which I deem wrong since I dont pass an instance but a type to the function. Thus, if I do not put the incorrect code in a if TYPE_CHECKCING block my program crashes

src/typeTest.py:31: error: Incompatible types in "yield" (actual type "MySentinel", expected type "T")  [misc]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: mypy 1.18.2 (compiled: yes)
  • Mypy command-line flags: none (mypy src)
  • Mypy configuration options from pyproject.toml (and other config files):
  • Python version used: 3.13.7 (uv)

** Possibly related **:

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con el ejemplo enlazado de MyPy Playground y compara la ruta de narrowing de issubclass que falla con el ejemplo de isinstance que funciona. Lee los issues relacionados #10680, #19529, #9003 y #17728 antes de localizar las pruebas de narrowing relevantes y la implementación; se considera terminado cuando el ejemplo pasa la comprobación de tipos sin el workaround de ejecución incorrecto.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
devtools
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.