Skip to content

Commit 658d3fe

Browse files
committed
Add some checks for edge cases.
1 parent f571ec7 commit 658d3fe

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

Lib/functools.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,32 @@ def _is_valid_dispatch_type(cls):
929929
return (_is_union_type(cls) and
930930
all(isinstance(arg, type) for arg in get_args(cls)))
931931

932+
def _skip_self_type(argname, cls, hints_iter):
933+
# GH-130827: Methods are sometimes annotated with
934+
# typing.Self. We should skip that when it's a valid type.
935+
from typing import Self
936+
if cls is not Self:
937+
return argname, cls
938+
if not is_method:
939+
# typing.Self is not valid in a normal function
940+
raise TypeError(
941+
f"Invalid annotation for {argname!r}. "
942+
"typing.Self can only be used with singledispatchmethod()"
943+
)
944+
try:
945+
argname, cls = next(hints_iter)
946+
return argname, cls
947+
except StopIteration:
948+
# The method is one of some invalid edge cases:
949+
# 1. method(self: Self) -> ...
950+
# 2. method(self, weird: Self) -> ...
951+
# 3. method(self: Self, unannotated) -> ...
952+
raise TypeError(
953+
f"Invalid annotation for {argname!r}. "
954+
"typing.Self must be the first annotation and must "
955+
"have a second parameter with an annotation"
956+
) from None
957+
932958
def register(cls, func=None):
933959
"""generic_func.register(cls, func) -> func
934960
@@ -955,18 +981,11 @@ def register(cls, func=None):
955981
func = cls
956982

957983
# only import typing if annotation parsing is necessary
958-
from typing import get_type_hints, Self
984+
from typing import get_type_hints
959985
from annotationlib import Format, ForwardRef
960986
hints_iter = iter(get_type_hints(func, format=Format.FORWARDREF).items())
961987
argname, cls = next(hints_iter)
962-
if cls is Self:
963-
if not is_method:
964-
raise TypeError(
965-
f"Invalid annotation for {argname!r}. ",
966-
"typing.Self can only be used with singledispatchmethod()"
967-
)
968-
else:
969-
argname, cls = next(hints_iter)
988+
argname, cls = _skip_self_type(argname, cls, hints_iter)
970989

971990
if not _is_valid_dispatch_type(cls):
972991
if _is_union_type(cls):

Lib/test/test_functools.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3376,15 +3376,48 @@ def _(self: typing.Self, arg: int) -> int:
33763376
foo = Foo()
33773377
self.assertEqual(foo.bar(42), 42)
33783378

3379+
# But, it shouldn't work on singledispatch()
33793380
@functools.singledispatch
33803381
def test(self: typing.Self, arg: int | str) -> int | str:
33813382
pass
3382-
# But, it shouldn't work on singledispatch()
33833383
with self.assertRaises(TypeError):
33843384
@test.register
33853385
def silly(self: typing.Self, arg: int | str) -> int | str:
33863386
pass
33873387

3388+
# typing.Self cannot be the only annotation
3389+
with self.assertRaises(TypeError):
3390+
class Foo:
3391+
@functools.singledispatchmethod
3392+
def bar(self: typing.Self, arg: int | str):
3393+
pass
3394+
3395+
@bar.register
3396+
def _(self: typing.Self, arg):
3397+
return arg
3398+
3399+
# typing.Self can only be used in the first parameter
3400+
with self.assertRaises(TypeError):
3401+
class Foo:
3402+
@functools.singledispatchmethod
3403+
def bar(self, arg: int | str):
3404+
pass
3405+
3406+
@bar.register
3407+
def _(self, arg: typing.Self):
3408+
return arg
3409+
3410+
# 'self' cannot be the only parameter
3411+
with self.assertRaises(TypeError):
3412+
class Foo:
3413+
@functools.singledispatchmethod
3414+
def bar(self: typing.Self, arg: int | str):
3415+
pass
3416+
3417+
@bar.register
3418+
def _(self: typing.Self):
3419+
pass
3420+
33883421

33893422
class CachedCostItem:
33903423
_cost = 1

0 commit comments

Comments
 (0)