python / python/cpython

Inconsistent behavior when validating type parameter substitutions

Offen
#132,100 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

stdlib topic-typing type-bug
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
35.9k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug report

Bug description:

There are currently three different cases (two of them being closely related) where type parameters can be substituted/parameterized with concrete types:

  • On a user-defined generic class:

    class MyGeneric[T1, T2]: ...
    
    alias = MyGeneric[int, str]
    
  • On an already parameterized alias (following the previous example):

    temp_alias = MyGeneric[int, T2]
    alias = temp_alias[str]
    
  • On a PEP 695 type alias:

    type MyAlias[T1, T2] = dict[T1, T2]
    
    gen_alias = MyAlias[int, str]
    

All of these three cases have slightly different behavior. The one that seems to be the most accurate is the second one. As alias is a _GenericAlias instance, parameterizing it will call _GenericAlias.__getitem__. There is a bunch of logic in there, and it seems that both __typing_prepare_subst__ and then __typing_subst__ (which is only doing type check assertions) are being called.

However, the first case is not making the calls to __typing_subst__, meaning the following would unexpectedly work:

class A[T, **P]: ...

A[int, str]
# ok at runtime, should fail as `P` should be substituted with a valid parameter expression
# (another ParamSpec, the ellipsis, a list/tuple of types or a Concatenate form).

If you do the same on a _GenericAlias instance (matches the second case), an error is raised:

alias = A[T, P]

alias[int, str]
# TypeError: Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got <class 'str'>

This leads us to the first point: should we apply the same logic between these two cases? To avoid breaking changes, we might have to consider forward references:

class A[T, **P]: ...

A[int, 'ForwardParamSpec']

ForwardParamSpec = ParamSpec('ForwardParamSpec')

So perhaps we can exclude ForwardRef from the type check in ParamSpec.__typing_subst__ (note that it already doesn't work for the second case).

I'll also note that having __typing_subst__ not called is not the only difference. For instance, the second case also calls _unpack_args() on the passed args, while the first case doesn't [^1]


Onto the last case (PEP 695 type aliases), currently no validation is performed whatsoever:

type MyAlias[T1, T2] = dict[T1, T2]

MyAlias[int]  # no error

So the second point is: should we apply the same logic as in case 1/2? Again, I don't know if applying the same logic here on type aliases is going to introduce any breaking changes concerns?

[^1]: Here is an example of how this manifests: https://gist.github.com/Viicos/db10da58914809a87c0a82c1b2f19162

CPython versions tested on:

3.14

Operating systems tested on:

Linux

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

Beginnen Sie in Lib/typing.py mit _GenericAlias.getitem, ParamSpec.typing_subst und _unpack_args(), und vergleichen Sie diese Pfade anschließend mit der Substitution direkter generischer Klassen und von PEP 695-Typaliasen. Verwenden Sie die Beispiele im Bericht, um zu bestimmen, ob die Validierung konsistent sein sollte, einschließlich Vorwärtsreferenzen, und überprüfen Sie das resultierende Verhalten in allen drei Fällen.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Muss geklärt werden
Anfängerfreundlichkeit
25/100

Neue Issues direkt in Ihr Postfach

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