python / python/mypy

[regression] Type narrowing for through `not issubclass(…)` broken for union of TypeVar instance / types in 1.17

Aperta
#19,529 2 commenti 2 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug topic-type-narrowing topic-type-variables
Lingua principale
Python
Stelle
20.6k
Fork
3.3k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

Bug Report

In mypy 1.17.0, type variables for arguments which are defined as the union of a generic TypeVar instance or type are not correctly type-narrowed.

To Reproduce

cf https://mypy-play.net/?mypy=latest&python=3.12&gist=630030506910825d5db3d022cdd4198f

from typing import TypeVar

MyType = TypeVar("MyType")


class HelloMixin:
    a: str


def hello(world: MyType | type[MyType]) -> HelloMixin | None:
    world = world if isinstance(world, type) else type(world)

    reveal_type(world)
    if not issubclass(world, HelloMixin):
        return None

    reveal_type(world)
    res = world()
    reveal_type(res)
    reveal_type(res.a)
    return res

Expected Behavior

In mypy 1.16.1, type checking succeeds, and the variables are correctly type-narrowed, i.e. after the if not issubclass(world, HelloMixin):, world is correctly typed as type[subclass_narrowing.HelloMixin]:

% /tmp/core/venv/bin/mypy --version                 
mypy 1.16.1 (compiled: yes)
% /tmp/core/venv/bin/mypy /tmp/subclass_narrowing.py 
/tmp/subclass_narrowing.py: note: In function "hello":
/tmp/subclass_narrowing.py:13:17: note: Revealed type is "builtins.type"
/tmp/subclass_narrowing.py:17:17: note: Revealed type is "type[subclass_narrowing.HelloMixin]"
/tmp/subclass_narrowing.py:19:17: note: Revealed type is "subclass_narrowing.HelloMixin"
/tmp/subclass_narrowing.py:20:17: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file

Actual Behavior

In mypy 1.17.0:

% ./venv/bin/mypy --version                 
mypy 1.17.0 (compiled: yes)
% ./venv/bin/mypy /tmp/subclass_narrowing.py        
/tmp/subclass_narrowing.py: note: In function "hello":
/tmp/subclass_narrowing.py:13:17: note: Revealed type is "MyType`-1 | type[MyType`-1]"
/tmp/subclass_narrowing.py:17:17: note: Revealed type is "MyType`-1 | type[MyType`-1]"
/tmp/subclass_narrowing.py:19:17: note: Revealed type is "Any | MyType`-1"
/tmp/subclass_narrowing.py:20:17: error: Item "object" of "Any | MyType" has no attribute "a"  [union-attr]
        reveal_type(res.a)
                    ^~~~~
/tmp/subclass_narrowing.py:20:17: note: See https://mypy.rtfd.io/en/stable/_refs.html#code-union-attr for more info
/tmp/subclass_narrowing.py:20:17: note: Revealed type is "Any"
/tmp/subclass_narrowing.py:21:12: error: Incompatible return value type (got "Any | MyType", expected "HelloMixin | None")  [return-value]
        return res
               ^~~
Found 2 errors in 1 file (checked 1 source file)

Note the revealed type of the world argument: in 1.16.1 it starts off as just builtins.type, while 1.17 more precisely types it as MyType-1 | type[MyType-1] before the issubclass() check.

A regular world: type[MyType] does not exhibit this behaviour, nor does concrete types (e.g. using Base | type[Base], with HelloMixin as a Base subclass). This only happens when using an union, world: type[MyType] does not exhibit the bug.

I'm not sure where the problem comes from exactly, but the first reveal_type(world) shows us that the line making sure it's always a type already fails to be narrowed to type[MyType-1]. Using a dedicated variable (i.e. world_type = world if isinstance(world, type) else type(world)) still fails as well.

Your Environment

  • Mypy version used: 1.17
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.12.10

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con il reproducer fornito e confronta i tipi rivelati con mypy 1.16.1 e 1.17.0, quindi traccia la logica di narrowing per i controlli isinstance e issubclass. Il lavoro è concluso quando l’unione di un’istanza di TypeVar e del suo tipo viene ristretta correttamente, i tipi rivelati corrispondono al comportamento previsto e l’esempio supera il controllo dei tipi senza errori.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
devtools
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.