python / python/mypy

false positive: classmethod constructor on generic class

Offen
#9,201 3 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

needs discussion topic-type-variables
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

I'm inclined to think this is a bug, but could be argued it's a missing feature.

How to reproduce

# makes_bug.py

from typing import Generic, Type, TypeVar

T = TypeVar('T')

class Foo(Generic[T]):
    def __init__(self, value: T) -> None: ...
    @classmethod
    def construct(cls) -> Foo[str]:
        return cls('bar')

Expected outcome

This code should pass type checks.

Actual outcome

mypy, to my great surprise, fails:

$ python --version
Python 3.8.1
$ mypy --version
mypy 0.790+dev.65186ae1e23fd44f1d7e6aa2c4458bdf55640742
$ mypy src/makes_bug.py
makes_bug.py:11: error: Incompatible return value type (got "Foo[T]", expected "Foo[str]")
makes_bug.py:11: error: Argument "value" to "Foo" has incompatible type "str"; expected "T"
Found 2 errors in 1 file (checked 1 source file)

Workaround

Changing line 10 to def construct(cls: Type[Foo]) -> Foo[str]: fixes it; or, probably more or less equivalently, def construct(cls: Type[Foo[Any]]) -> Foo[str]:.

Investigation / suggested fix

Adding reveal_type(cls) indicates that cls is inferred to have type Foo[T], which cannot return a Foo[str].

It's not clear to me why classmethods should have the lead argument inferred to a bound type. I'd propose that my workaround be made a default: class methods should infer the type of the lead argument type as the unbound class type, or bound with all Anys.

I'd imagine the change would go here: https://github.com/python/mypy/blob/65186ae1e23fd44f1d7e6aa2c4458bdf55640742/mypy/semanal.py#L609,L610
or (more likely) in the referenced method class_type():
https://github.com/python/mypy/blob/65186ae1e23fd44f1d7e6aa2c4458bdf55640742/mypy/semanal.py#L4790,L4791

Happy to take a stab at it, but wanted to solicit some feedback first. This is a complex system and it's possible I'm missing some potential consequence here.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit der generischen classmethod-Reproduktion in makes_bug.py und untersuche mypy/semanal.py an den referenzierten Zeilen, insbesondere class_type(). Verwende reveal_type(cls) und führe mypy für das Beispiel aus, um den abgeleiteten Typ zu bestätigen. Erledigt ist es, wenn das Beispiel die Typprüfung ohne den Type[Foo]-Workaround besteht und das bestehende generische Verhalten erhalten bleibt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
devtools
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.