Skip to content

Commit 1943f5d

Browse files
authored
Merge pull request #75 from KotlinIsland/actually-fix-reified-generic-for-pyright
actual fix for `ReifiedGeneric` with pyright
2 parents d76500b + f6636f8 commit 1943f5d

File tree

8 files changed

+41
-49
lines changed

8 files changed

+41
-49
lines changed

.github/workflows/check.yaml

Lines changed: 3 additions & 3 deletions
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.8", "3.9", "3.10", "3.11-dev"]
13+
python-version: ["3.8", "3.9", "3.10", "3.11"]
1414

1515
steps:
1616
- uses: actions/checkout@v2
@@ -32,7 +32,7 @@ jobs:
3232
if: steps.cache.outputs.cache-hit == 'true'
3333
run: timeout 10s poetry run pip --version || rm -rf .venv
3434
- run: poetry install
35-
- run: poetry run mypy -p basedtyping -p tests
35+
- run: poetry run mypy -p basedtyping -p tests --python-version ${{ matrix.python-version }}
3636
- run: poetry run pytest tests/
3737

3838
lint:
@@ -41,7 +41,7 @@ jobs:
4141
- uses: actions/checkout@v2
4242
- uses: actions/setup-python@v4
4343
with:
44-
python-version: "3.7"
44+
python-version: "3.8"
4545
- name: Run image
4646
uses: abatilo/[email protected]
4747
with:

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

basedtyping/__init__.py

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

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

2323
from basedtyping.runtime_only import OldUnionType
2424

@@ -109,7 +109,7 @@ class NotEnoughTypeParametersError(ReifiedGenericError):
109109
"""
110110

111111

112-
class _ReifiedGenericMetaclass(type, Generic[T]):
112+
class _ReifiedGenericMetaclass(type):
113113
# 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
114114

115115
__reified_generics__: Tuple[type, ...]
@@ -125,7 +125,7 @@ class _ReifiedGenericMetaclass(type, Generic[T]):
125125
_can_do_instance_and_subclass_checks_without_generics: bool
126126
"""Used internally for ``isinstance`` and ``issubclass`` checks, ``True`` when the class can currenty be used in said checks without generics in them"""
127127

128-
def _orig_class(cls) -> _ReifiedGenericMetaclass[T]:
128+
def _orig_class(cls) -> _ReifiedGenericMetaclass:
129129
"""Gets the original class that ``ReifiedGeneric.__class_getitem__`` copied from
130130
"""
131131
result = cls.__bases__[0]
@@ -175,7 +175,7 @@ def _check_generics_reified(cls) -> None:
175175
if not cls._generics_are_reified() or cls._has_non_reified_type_vars():
176176
cls._raise_generics_not_reified()
177177

178-
def _is_subclass(cls, subclass: object) -> TypeGuard[_ReifiedGenericMetaclass[T]]:
178+
def _is_subclass(cls, subclass: object) -> TypeGuard[_ReifiedGenericMetaclass]:
179179
"""For ``__instancecheck__`` and ``__subclasscheck__``. checks whether the
180180
"origin" type (ie. without the generics) is a subclass of this reified generic
181181
"""
@@ -188,7 +188,7 @@ def _is_subclass(cls, subclass: object) -> TypeGuard[_ReifiedGenericMetaclass[T]
188188
cls._orig_class(),
189189
# https://github.com/python/mypy/issues/11671
190190
cast( # pylint:disable=protected-access
191-
_ReifiedGenericMetaclass[T], subclass
191+
_ReifiedGenericMetaclass, subclass
192192
)._orig_class(),
193193
)
194194

@@ -220,40 +220,35 @@ def __instancecheck__(cls, instance: object) -> bool:
220220
cast(ReifiedGeneric[object], instance).__reified_generics__
221221
)
222222

223-
def __call__(cls, *args: object, **kwargs: object) -> T:
223+
# need the generic here for pyright. see https://github.com/microsoft/pyright/issues/5488
224+
def __call__(cls: type[T], *args: object, **kwargs: object) -> T:
224225
"""A placeholder ``__call__`` method that gets called when the class is
225226
instantiated directly, instead of first supplying the type parameters.
226227
"""
228+
cls_narrowed = cast(Type[ReifiedGeneric[object]], cls)
227229
if (
228230
# instantiating a ReifiedGeneric without specifying any TypeVars
229-
not hasattr(cls, "_orig_type_vars")
231+
not hasattr(cls_narrowed, "_orig_type_vars")
230232
# instantiating a subtype of a ReifiedGeneric without specifying any TypeVars
231-
or cls._orig_type_vars == cls.__type_vars__
233+
or cls_narrowed._orig_type_vars == cls_narrowed.__type_vars__
232234
):
233235
raise NotReifiedError(
234-
f"Cannot instantiate ReifiedGeneric {cls.__name__!r} because its type"
235-
" parameters were not supplied. The type parameters must be explicitly"
236-
" specified in the instantiation so that the type data can be made"
237-
" available at runtime.\n\n"
238-
"For example:\n\n"
239-
"foo: Foo[int] = Foo() #wrong\n"
240-
"foo = Foo[T]() #wrong\n"
241-
"foo = Foo[int]() # correct"
236+
f"Cannot instantiate ReifiedGeneric {cls_narrowed.__name__!r} because"
237+
" its type parameters were not supplied. The type parameters must be"
238+
" explicitly specified in the instantiation so that the type data can"
239+
" be made available at runtime.\n\nFor example:\n\nfoo: Foo[int] ="
240+
" Foo() #wrong\nfoo = Foo[T]() #wrong\nfoo = Foo[int]() # correct"
242241
)
243-
cls._check_generics_reified()
244-
return cast(T, super().__call__(*args, **kwargs))
242+
cls_narrowed._check_generics_reified()
243+
# see comment about cls above
244+
return cast(T, super().__call__(*args, **kwargs)) # type:ignore[misc]
245245

246246

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

250250

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-
):
251+
class ReifiedGeneric(Generic[T], metaclass=_ReifiedGenericMetaclass):
257252
"""A ``Generic`` where the type parameters are available at runtime and is
258253
usable in ``isinstance`` and ``issubclass`` checks.
259254
@@ -316,7 +311,9 @@ def __class_getitem__( # type: ignore[no-any-decorated]
316311
orig_type_vars = (
317312
cls.__type_vars__
318313
if hasattr(cls, "__type_vars__")
319-
else cast(Tuple[TypeVar, ...], cls.__parameters__)
314+
else cast(
315+
Tuple[TypeVar, ...], cls.__parameters__ # type:ignore[attr-defined]
316+
)
320317
)
321318

322319
# add any reified generics from the superclass if there is one

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ build-backend = "poetry.core.masonry.api"
2323
requires = ["poetry-core>=1.0.8"]
2424

2525
[tool.mypy]
26+
python_version = 3.8
2627
pretty = true
2728
show_error_context = true
2829

@@ -32,8 +33,9 @@ multi_line_output = 3
3233
combine_as_imports = true
3334

3435
[tool.black]
35-
target-version = ["py37"]
36+
target-version = ["py38"]
3637
preview = true
38+
skip-magic-trailing-comma = true
3739

3840
[tool.pylint.MASTER]
3941
fail-on = "I"

tests/test_is_subform.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,13 @@ def test_union_first_arg() -> None:
1919

2020
def test_old_union() -> None:
2121
# TODO: fix the mypy error
22-
assert not issubform(
23-
Union[int, str], # type: ignore[arg-type]
24-
int,
25-
)
26-
assert issubform(
27-
Union[int, str], # type: ignore[arg-type]
28-
object,
29-
)
22+
assert not issubform(Union[int, str], int) # type: ignore[arg-type]
23+
assert issubform(Union[int, str], object) # type: ignore[arg-type]
3024
assert issubform(
3125
Union[int, str], # type: ignore[arg-type]
3226
Union[str, int], # type: ignore[arg-type]
3327
)
3428
if sys.version_info >= (3, 10):
3529
assert issubform(
36-
Union[int, str], # type: ignore[unused-ignore, arg-type]
37-
int | str,
30+
Union[int, str], int | str # type: ignore[unused-ignore, arg-type]
3831
)

tests/test_never_type/test_runtime.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
@mark.xfail # https://github.com/KotlinIsland/basedtyping/issues/22
99
def test_isinstance() -> None:
1010
assert not isinstance( # type: ignore[misc]
11-
1,
12-
Never, # type: ignore[arg-type]
11+
1, Never # type: ignore[arg-type]
1312
)
1413

1514

tests/test_reified_generics/test_reified_generic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class Reified(ReifiedGeneric[Tuple[T, T2]]):
1414
pass
1515

1616

17-
class ReifiedList(ReifiedGeneric[Tuple[T]], List[T]):
17+
# TODO: investigate this "metaclass conflict" mypy error
18+
class ReifiedList(ReifiedGeneric[Tuple[T]], List[T]): # type:ignore[misc]
1819
pass
1920

2021

tests/test_reified_generics/test_subclasses.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class Reified(ReifiedGeneric[Tuple[T, T2]]):
1313
pass
1414

1515

16-
class ReifiedList(ReifiedGeneric[Tuple[T]], List[T]):
16+
# TODO: investigate this "metaclass conflict" mypy error
17+
class ReifiedList(ReifiedGeneric[Tuple[T]], List[T]): # type:ignore[misc]
1718
pass
1819

1920

0 commit comments

Comments
 (0)