Skip to content

Commit

Permalink
Add PEP 673 Self type (#933)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gobot1234 committed Nov 12, 2021
1 parent 8fd49b5 commit 60aa1e2
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 1 deletion.
4 changes: 4 additions & 0 deletions typing_extensions/CHANGELOG
Expand Up @@ -8,6 +8,10 @@ Dropped support for Python versions 3.5 and older.
Simplified backports for Python 3.6.0 and newer.
Patch by Adam Turner (@AA-Turner).

## Added in version 4.0.0

- Runtime support for PEP 673 and `typing_extensions.Self`.

## Removed in version 4.0.0

The following non-exported but non-private names have been removed as they are
Expand Down
38 changes: 37 additions & 1 deletion typing_extensions/src_py3/test_typing_extensions.py
Expand Up @@ -16,7 +16,7 @@
from typing import Generic, NamedTuple
from typing import no_type_check
import typing_extensions
from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict
from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self
from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard
from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager
from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload
Expand Down Expand Up @@ -2075,6 +2075,42 @@ def test_no_isinstance(self):
issubclass(int, TypeGuard)



class SelfTests(BaseTestCase):
def test_basics(self):
class Foo:
def bar(self) -> Self: ...

self.assertEqual(gth(Foo.bar), {'return': Self})

def test_repr(self):
if hasattr(typing, 'Self'):
mod_name = 'typing'
else:
mod_name = 'typing_extensions'
self.assertEqual(repr(Self), '{}.Self'.format(mod_name))

def test_cannot_subscript(self):
with self.assertRaises(TypeError):
Self[int]

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(type(Self)):
pass

def test_cannot_init(self):
with self.assertRaises(TypeError):
Self()
with self.assertRaises(TypeError):
type(Self)()

def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(1, Self)
with self.assertRaises(TypeError):
issubclass(int, Self)

class AllTests(BaseTestCase):

def test_typing_extensions_includes_standard(self):
Expand Down
72 changes: 72 additions & 0 deletions typing_extensions/src_py3/typing_extensions.py
Expand Up @@ -45,6 +45,7 @@ def _check_generic(cls, parameters):
'Concatenate',
'Final',
'ParamSpec',
'Self',
'Type',

# ABCs (from collections.abc).
Expand Down Expand Up @@ -2046,3 +2047,74 @@ def __eq__(self, other):
return self is other

TypeGuard = _TypeGuard(_root=True)


if hasattr(typing, "Self"):
Self = typing.Self

elif sys.version_info[:2] >= (3, 9):
class _SelfForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name

@_SelfForm
def Self(self, params):
"""Used to spell the type of "self" in classes.
Example::
from typing import Self
class ReturnsSelf:
def parse(self, data: bytes) -> Self:
...
return self
"""

raise TypeError(f"{self} is not subscriptable")

elif sys.version_info[:2] >= (3, 7):
class _SelfForm(typing._SpecialForm, _root=True):
def __repr__(self):
return 'typing_extensions.' + self._name

Self = _SelfForm(
"Self",
doc="""Used to spell the type of "self" in classes.
Example::
from typing import Self
class ReturnsSelf:
def parse(self, data: bytes) -> Self:
...
return self
"""
)
else:
class _Self(typing._FinalTypingBase, _root=True):
"""Used to spell the type of "self" in classes.
Example::
from typing import Self
class ReturnsSelf:
def parse(self, data: bytes) -> Self:
...
return self
"""

__slots__ = ()

def __instancecheck__(self, obj):
raise TypeError(f"{self} cannot be used with isinstance().")

def __subclasscheck__(self, cls):
raise TypeError(f"{self} cannot be used with issubclass().")

Self = _Self(_root=True)

0 comments on commit 60aa1e2

Please sign in to comment.