Skip to content

Commit d76500b

Browse files
authored
Merge pull request #69 from KotlinIsland/pyright-ReifiedGeneric-fix
fix `ReifiedGeneric` for pyright
2 parents 121f14c + 5013774 commit d76500b

File tree

11 files changed

+386
-492
lines changed

11 files changed

+386
-492
lines changed

.github/workflows/check.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]
13+
python-version: ["3.8", "3.9", "3.10", "3.11-dev"]
1414

1515
steps:
1616
- uses: actions/checkout@v2

basedtyping/__init__.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
cast,
1919
)
2020

21-
from typing_extensions import Final, TypeAlias, TypeGuard
21+
from typing_extensions import Final, Self, TypeAlias, TypeGuard
2222

2323
from basedtyping.runtime_only import OldUnionType
2424

@@ -42,6 +42,7 @@ def __repr__(self):
4242
def __getitem__(self, item):
4343
if self._name == "Intersection":
4444
return _IntersectionGenericAlias(self, item)
45+
return None
4546

4647

4748
if TYPE_CHECKING:
@@ -60,7 +61,7 @@ def __getitem__(self, item):
6061

6162
T = TypeVar("T")
6263
T_co = TypeVar("T_co", covariant=True)
63-
T_cont = TypeVar("T_cont", contravariant=True)
64+
T_contra = TypeVar("T_contra", contravariant=True)
6465
Fn = TypeVar("Fn", bound=Function)
6566

6667

@@ -108,7 +109,7 @@ class NotEnoughTypeParametersError(ReifiedGenericError):
108109
"""
109110

110111

111-
class _ReifiedGenericMetaclass(type):
112+
class _ReifiedGenericMetaclass(type, Generic[T]):
112113
# these should really only be on the class not the metaclass, but since it needs to be accessible from both instances and the class itself, its duplicated here
113114

114115
__reified_generics__: Tuple[type, ...]
@@ -124,7 +125,7 @@ class _ReifiedGenericMetaclass(type):
124125
_can_do_instance_and_subclass_checks_without_generics: bool
125126
"""Used internally for ``isinstance`` and ``issubclass`` checks, ``True`` when the class can currenty be used in said checks without generics in them"""
126127

127-
def _orig_class(cls) -> _ReifiedGenericMetaclass:
128+
def _orig_class(cls) -> _ReifiedGenericMetaclass[T]:
128129
"""Gets the original class that ``ReifiedGeneric.__class_getitem__`` copied from
129130
"""
130131
result = cls.__bases__[0]
@@ -174,20 +175,20 @@ def _check_generics_reified(cls) -> None:
174175
if not cls._generics_are_reified() or cls._has_non_reified_type_vars():
175176
cls._raise_generics_not_reified()
176177

177-
def _is_subclass(cls, subclass: object) -> TypeGuard[_ReifiedGenericMetaclass]:
178+
def _is_subclass(cls, subclass: object) -> TypeGuard[_ReifiedGenericMetaclass[T]]:
178179
"""For ``__instancecheck__`` and ``__subclasscheck__``. checks whether the
179180
"origin" type (ie. without the generics) is a subclass of this reified generic
180181
"""
181182
# could be any random instance, check it's a reified generic first:
182-
return type.__instancecheck__( # type: ignore[no-any-expr]
183-
_ReifiedGenericMetaclass, # type: ignore[no-any-expr]
183+
return type.__instancecheck__(
184+
_ReifiedGenericMetaclass,
184185
subclass,
185186
# then check that the instance is an instance of this particular reified generic:
186-
) and type.__subclasscheck__( # type: ignore[no-any-expr]
187+
) and type.__subclasscheck__(
187188
cls._orig_class(),
188189
# https://github.com/python/mypy/issues/11671
189190
cast( # pylint:disable=protected-access
190-
_ReifiedGenericMetaclass, subclass
191+
_ReifiedGenericMetaclass[T], subclass
191192
)._orig_class(),
192193
)
193194

@@ -219,7 +220,7 @@ def __instancecheck__(cls, instance: object) -> bool:
219220
cast(ReifiedGeneric[object], instance).__reified_generics__
220221
)
221222

222-
def __call__(cls, *args: object, **kwargs: object) -> object:
223+
def __call__(cls, *args: object, **kwargs: object) -> T:
223224
"""A placeholder ``__call__`` method that gets called when the class is
224225
instantiated directly, instead of first supplying the type parameters.
225226
"""
@@ -240,22 +241,27 @@ def __call__(cls, *args: object, **kwargs: object) -> object:
240241
"foo = Foo[int]() # correct"
241242
)
242243
cls._check_generics_reified()
243-
return super().__call__(*args, **kwargs) # type: ignore[no-any-expr]
244+
return cast(T, super().__call__(*args, **kwargs))
244245

245246

246247
GenericItems: TypeAlias = Union[type, TypeVar, Tuple[Union[type, TypeVar], ...]]
247248
"""The ``items`` argument passed to ``__class_getitem__`` when creating or using a ``Generic``"""
248249

249250

250-
class ReifiedGeneric(Generic[T], metaclass=_ReifiedGenericMetaclass):
251+
class ReifiedGeneric(
252+
Generic[T],
253+
# mypy doesn't support metaclasses with generics but for pyright we need to correctly type the `__call__`
254+
# return type, otherwise all instances of `ReifiedGeneric` will have the wrong type
255+
metaclass=_ReifiedGenericMetaclass[Self], # type:ignore[misc]
256+
):
251257
"""A ``Generic`` where the type parameters are available at runtime and is
252258
usable in ``isinstance`` and ``issubclass`` checks.
253259
254260
For example:
255261
256262
>>> class Foo(ReifiedGeneric[T]):
257263
... def create_instance(self) -> T:
258-
... cls = self.__orig_class__.__args__[0]
264+
... cls = self.__reified_generics__[0]
259265
... return cls()
260266
...
261267
... foo: Foo[int] = Foo() # error: generic cannot be reified
@@ -310,9 +316,7 @@ def __class_getitem__( # type: ignore[no-any-decorated]
310316
orig_type_vars = (
311317
cls.__type_vars__
312318
if hasattr(cls, "__type_vars__")
313-
else cast(
314-
Tuple[TypeVar, ...], cls.__parameters__ # type: ignore[attr-defined]
315-
)
319+
else cast(Tuple[TypeVar, ...], cls.__parameters__)
316320
)
317321

318322
# add any reified generics from the superclass if there is one
@@ -330,13 +334,13 @@ def __class_getitem__( # type: ignore[no-any-decorated]
330334
cls, # make the copied class extend the original so normal instance checks work
331335
),
332336
# TODO: proper type
333-
dict( # type: ignore[no-any-expr]
334-
__reified_generics__=tuple( # type: ignore[no-any-expr]
337+
{ # type: ignore[no-any-expr]
338+
"__reified_generics__": tuple( # type: ignore[no-any-expr]
335339
_type_convert(t) for t in items # type: ignore[unused-ignore, no-any-expr]
336340
),
337-
_orig_type_vars=orig_type_vars,
338-
__type_vars__=_collect_parameters(items), # type: ignore[name-defined]
339-
),
341+
"_orig_type_vars": orig_type_vars,
342+
"__type_vars__": _collect_parameters(items), # type: ignore[name-defined]
343+
},
340344
)
341345
# can't set it in the dict above otherwise __init_subclass__ overwrites it
342346
ReifiedGenericCopy._can_do_instance_and_subclass_checks_without_generics = ( # pylint:disable=protected-access
@@ -358,6 +362,7 @@ def __init_subclass__(cls) -> None: # pylint:disable=arguments-differ
358362
_UnionTypes = (OldUnionType,)
359363
_Forms: TypeAlias = Union[type, _SpecialForm]
360364

365+
361366
# TODO: make this work with any "form", not just unions
362367
# should be (form: TypeForm, forminfo: TypeForm)
363368
# TODO: form/forminfo can include _UnionGenericAlias
@@ -416,7 +421,8 @@ def Untyped(self: _SpecialForm, parameters: object) -> NoReturn:
416421
raise TypeError(f"{self} is not subscriptable")
417422

418423
else:
419-
Untyped: Final = _BasedSpecialForm(
424+
# old version had the doc argument
425+
Untyped: Final = _BasedSpecialForm( # pylint:disable=unexpected-keyword-arg
420426
"Untyped",
421427
doc=(
422428
"Special type indicating that something isn't typed.\nThis is more"
@@ -427,8 +433,8 @@ def Untyped(self: _SpecialForm, parameters: object) -> NoReturn:
427433
if not TYPE_CHECKING:
428434

429435
class _IntersectionGenericAlias(_GenericAlias, _root=True):
430-
def copy_with(self, params):
431-
return Intersection[params]
436+
def copy_with(self, args):
437+
return Intersection[args]
432438

433439
def __eq__(self, other):
434440
if not isinstance(other, _IntersectionGenericAlias):
@@ -445,9 +451,10 @@ def __subclasscheck__(self, cls):
445451
for arg in self.__args__:
446452
if issubclass(cls, arg):
447453
return True
454+
return False
448455

449456
def __reduce__(self):
450-
func, (origin, args) = super().__reduce__()
457+
func, (_, args) = super().__reduce__()
451458
return func, (Intersection, args)
452459

453460
if sys.version_info > (3, 9):
@@ -494,6 +501,9 @@ def Intersection(self, parameters):
494501
return _IntersectionGenericAlias(self, parameters)
495502

496503
else:
497-
Intersection = _BasedSpecialForm("Intersection", doc="")
504+
# old version had the doc argument
505+
Intersection = _BasedSpecialForm( # pylint:disable=unexpected-keyword-arg
506+
"Intersection", doc=""
507+
)
498508
else:
499509
Intersection: _SpecialForm

0 commit comments

Comments
 (0)