mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00

## Summary Addresses the second follow up to #15565 in #15642. This was easier than expected by using this cool destructuring syntax I hadn't used before, and by assuming [PYI059](https://docs.astral.sh/ruff/rules/generic-not-last-base-class/) (`generic-not-last-base-class`). ## Test Plan Using an existing test, plus two new tests combining multiple base classes and multiple generics. It looks like I deleted a relevant test, which I did, but I meant to rename this in #15565. It looks like instead I copied it and renamed the copy. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
136 lines
2.7 KiB
Python
136 lines
2.7 KiB
Python
from typing import Any, AnyStr, Generic, ParamSpec, TypeVar, TypeVarTuple
|
|
|
|
from somewhere import SupportsRichComparisonT
|
|
|
|
S = TypeVar("S", str, bytes) # constrained type variable
|
|
T = TypeVar("T", bound=float)
|
|
Ts = TypeVarTuple("Ts")
|
|
P = ParamSpec("P")
|
|
|
|
|
|
class A(Generic[T]):
|
|
# Comments in a class body are preserved
|
|
var: T
|
|
|
|
|
|
class B(Generic[*Ts]):
|
|
var: tuple[*Ts]
|
|
|
|
|
|
class C(Generic[P]):
|
|
var: P
|
|
|
|
|
|
class Constrained(Generic[S]):
|
|
var: S
|
|
|
|
|
|
# This case gets a diagnostic but not a fix because we can't look up the bounds
|
|
# or constraints on the TypeVar imported from another module
|
|
class ExternalType(Generic[T, SupportsRichComparisonT]):
|
|
var: T
|
|
compare: SupportsRichComparisonT
|
|
|
|
|
|
# typing.AnyStr is a common external type variable, so treat it specially as a
|
|
# known TypeVar
|
|
class MyStr(Generic[AnyStr]):
|
|
s: AnyStr
|
|
|
|
|
|
class MultipleGenerics(Generic[S, T, *Ts, P]):
|
|
var: S
|
|
typ: T
|
|
tup: tuple[*Ts]
|
|
pep: P
|
|
|
|
|
|
class MultipleBaseClasses(list, Generic[T]):
|
|
var: T
|
|
|
|
|
|
# these are just for the MoreBaseClasses and MultipleBaseAndGenerics cases
|
|
class Base1: ...
|
|
|
|
|
|
class Base2: ...
|
|
|
|
|
|
class Base3: ...
|
|
|
|
|
|
class MoreBaseClasses(Base1, Base2, Base3, Generic[T]):
|
|
var: T
|
|
|
|
|
|
class MultipleBaseAndGenerics(Base1, Base2, Base3, Generic[S, T, *Ts, P]):
|
|
var: S
|
|
typ: T
|
|
tup: tuple[*Ts]
|
|
pep: P
|
|
|
|
|
|
class A(Generic[T]): ...
|
|
|
|
|
|
class B(A[S], Generic[S]):
|
|
var: S
|
|
|
|
|
|
class C(A[S], Generic[S, T]):
|
|
var: tuple[S, T]
|
|
|
|
|
|
class D(A[int], Generic[T]):
|
|
var: T
|
|
|
|
|
|
class NotLast(Generic[T], Base1):
|
|
var: T
|
|
|
|
|
|
class Sandwich(Base1, Generic[T], Base2):
|
|
var: T
|
|
|
|
|
|
# runtime `TypeError` to inherit from `Generic` multiple times, but we still
|
|
# emit a diagnostic
|
|
class TooManyGenerics(Generic[T], Generic[S]):
|
|
var: T
|
|
var: S
|
|
|
|
|
|
# These cases are not handled
|
|
class D(Generic[T, T]): # duplicate generic variable, runtime error
|
|
pass
|
|
|
|
|
|
# TODO(brent) we should also apply the fix to methods, but it will need a
|
|
# little more work. these should be left alone for now but be fixed eventually.
|
|
class NotGeneric:
|
|
# -> generic_method[T: float](t: T)
|
|
def generic_method(t: T) -> T:
|
|
return t
|
|
|
|
|
|
# This one is strange in particular because of the mix of old- and new-style
|
|
# generics, but according to the PEP, this is okay "if the class, function, or
|
|
# type alias does not use the new syntax." `more_generic` doesn't use the new
|
|
# syntax, so it can use T from the module and U from the class scope.
|
|
class MixedGenerics[U]:
|
|
def more_generic(u: U, t: T) -> tuple[U, T]:
|
|
return (u, t)
|
|
|
|
|
|
# TODO(brent) default requires 3.13
|
|
V = TypeVar("V", default=Any, bound=str)
|
|
|
|
|
|
class DefaultTypeVar(Generic[V]): # -> [V: str = Any]
|
|
var: V
|
|
|
|
|
|
# nested classes and functions are skipped
|
|
class Outer:
|
|
class Inner(Generic[T]):
|
|
var: T
|