python / python/mypy

Descriptors misbehave with `NoReturn`/`Never`, with `@overload`s, etc.

Offen
#16,862 4 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug Report

To Reproduce

Gists:
mypy-play.net
pyright-play.net (pyright doesn't handle this well, issuing no warnings at all; it does respond to reveal_type() in the descriptors-used-as-intended case, suggesting it doesn't understand that directive is unreachable)

from __future__ import annotations
from typing_extensions import (
        NoReturn,
        Optional,
        overload,
        Self,
        Type,
        )

# Note: This is a simplified example.
#
#  - obviously, it would be more expedient to simply omit the definition
#    of '__get__()' for this trivial case
#
#  - similar problems affect implicitly calling '__set__()' through a
#    descriptor


class WriteOnlyDescr:
    '''some kind of write-only descriptor (to demonstrate the general issue)
    '''
    @overload  # descriptor accessible through class ('SomeClass')
    def __get__(self, owner: None, objtype: Type[object]) -> Self: ...
    @overload
    def __get__(  # access on an instance of 'SomeClass' (forbidden)
            self, owner: object, objtype: Optional[Type[object]] = None
            ) -> NoReturn: ...

    def __get__(
            self, owner: Optional[object],
            objtype: Optional[Type[object]] = None,
            ) -> Self:
        # allow access to this descriptor via the class:
        if owner is None:
            return self

        raise AttributeError

    # '__set__()' omitted for brevity


class SomeClass:
    '''a class with a write-only descriptor
    '''
    descr = WriteOnlyDescr()


descr = SomeClass.descr
inst = SomeClass()

if bool():

    # First case:
    #
    # First, we'll "spell out" the descriptor mechanics; this should
    # give the same results as using the descriptor in a natural way
    # (the second case)

                                # EXPECTED for the second case:
                                # ──────────────────────────────────────
    get = descr.__get__(inst)   # ← mypy: [var-annotated]
                                #     "Need type annotation for 'get'"
    reveal_type(get)            # ← (no mypy output; this is unreachable)
    _ = True                    # ← mypy: [unreachable]


else:                           # ↑ mypy output _should_ be identical ↑
                                # ↓    above and below this point     ↓
    # Second case:
    #
    # This _should_ give the  same output as above, since the first
    # lines of the two cases are virtually synonymous, while the
    # remaining lines are identical.

                                # ACTUAL for the second case:
                                # ──────────────────────────────────────
    get = inst.descr            # ← (no mypy output)
    reveal_type(get)            # ← mypy: "Revealed type is 'Never'"
    _ = True                    # ← (no mypy output)

Expected Behavior

Descriptors should behave the same way when used as intended and when the mechanics are spelled out (explicit calls to __get__(), etc.).

Actual Behavior

This isn't the case, especially when an argument has type Never or a function is marked NoReturn. (This can come up when @overloads are in use.)

Your Environment

  • Mypy version used: 1.8.0
  • Mypy command-line flags: --warn-unreachable is informative
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.8, 3.12

Further context
...follows in a comment.

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 damit, das vereinfachte Beispiel aus dem verlinkten Fall auf mypy-play.net mit mypy 1.8.0 und --warn-unreachable zu reproduzieren, wobei explizite get-Aufrufe mit dem natürlichen Zugriff auf Deskriptoren verglichen werden. Verfolge die Behandlung von Deskriptoren und Overloads im Zusammenhang mit NoReturn/Never und füge anschließend Regressionstests hinzu, sodass beide Formen gleichwertige Diagnosen und aufgedeckte Typen erzeugen.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
compilers
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.