10000 Implementation of PEP 673 (typing.Self) by Gobot1234 · Pull Request #11666 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Implementation of PEP 673 (typing.Self) #11666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
acf87a3
Much better initial implementation of typing.Self
Gobot1234 Aug 28, 2021
7a6d771
Some extra stuff
Gobot1234 Oct 2, 2021
b1d67ca
Slight improvements
Gobot1234 Nov 5, 2021
c09d95f
Merge branch 'master' into master
Gobot1234 Dec 5, 2021
6a7a084
Remove removed type
Gobot1234 Dec 5, 2021
2ac45c9
Update meet.py
Gobot1234 Dec 5, 2021
1ebe034
Update mypy/type_visitor.py
Gobot1234 Dec 5, 2021
0e6d128
Fix most of the CI issues and respond to comments
Gobot1234 Dec 6, 2021
f844fbe
Merge branch 'master' of https://github.com/Gobot1234/mypy
Gobot1234 Dec 6, 2021
5904918
Merge remote-tracking branch 'upstream/master'
Gobot1234 Jan 14, 2022
e932f52
Merge branch 'master' into master
Gobot1234 Feb 16, 2022
ff779e8
I think this works properly now
Gobot1234 Feb 18, 2022
f94254f
Merge branch 'master' of https://github.com/Gobot1234/mypy
Gobot1234 Feb 18, 2022
c9b2ac9
Merge branch 'master' into master
Gobot1234 Feb 18, 2022
ad0b9b0
Merge remote-tracking branch 'origin/master' into gobot-master
erikkemperman May 20, 2022
6766132
Make tests pass
erikkemperman May 20, 2022
cada36a
Unit tests
erikkemperman May 20, 2022
68c1339
Merge pull request #1 from erikkemperman/gobot-master
Gobot1234 Jun 22, 2022
46d8b70
Merge remote-tracking branch 'upstream/master'
Gobot1234 Jun 22, 2022
fb6d552
I don't think this is entirely correct but lets see
Gobot1234 Jun 22, 2022
6c71758
Fix tests
Gobot1234 Jun 27, 2022
791c9e3
Fixes for signatures of form (type[Self]/Self) -> Self
Gobot1234 Jul 1, 2022
09e966e
Fix some CI
Gobot1234 Jul 1, 2022
ce2d5fa
Fix some CI failures
Gobot1234 Jul 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
I think this works properly now
  • Loading branch information
Gobot1234 committed Feb 18, 2022
commit ff779e8f30eed84b98c1d374c9d5666ac3b29b73
3 changes: 2 additions & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def _analyze_member_access(name: str,
if isinstance(typ, Instance):
return analyze_instance_member_access(name, typ, mx, override_info)
elif isinstance(typ, SelfType):
mx.self_type = typ.instance
return analyze_instance_member_access(name, typ.instance, mx, override_info)
elif isinstance(typ, AnyType):
# The base object has dynamic type.
Expand Down Expand Up @@ -340,7 +341,7 @@ def analyze_none_member_access(name: str, typ: NoneType, mx: MemberContext) -> T
return _analyze_member_access(name, mx.named_type('builtins.object'), mx)


def analyze_member_var_access(name: str,
def analyze_member_var_access(name: str, # what here?
itype: Instance,
info: TypeInfo,
mx: MemberContext) -> Type:
Expand Down
1 change: 1 addition & 0 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@ def visit_self_type(self, typ: SelfType) -> List[str]:
triggers.append(make_trigger(typ.fullname))
if typ.instance:
triggers.extend(self.get_type_triggers(typ.instance))
return triggers

def visit_typeddict_type(self, typ: TypedDictType) -> List[str]:
triggers = []
Expand Down
79 changes: 79 additions & 0 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,82 @@ def query_types(self, types: Iterable[Type]) -> T:
self.seen_aliases.add(t)
res.append(t.accept(self))
return self.strategy(res)


TypeT = TypeVar("TypeT", bound=Type)


class SelfTypeVisitor(TypeVisitor[Any]):
def __init__(self, self_type: Instance) -> None:
# NOTE this visitor will mutate `func`
self.self_type = self_type

def visit_unbound_type(self, t: UnboundType) -> None:
pass

def visit_any(self, t: AnyType) -> None:
pass

def visit_none_type(self, t: NoneType) -> None:
pass

def visit_uninhabited_type(self, t: UninhabitedType) -> None:
pass

def visit_erased_type(self, t: ErasedType) -> None:
pass

def visit_deleted_type(self, t: DeletedType) -> None:
pass

def visit_type_var(self, t: TypeVarType) -> None:
pass

def visit_self_type(self, t: SelfType) -> None:
pass # should this raise?

def visit_param_spec(self, t: ParamSpecType) -> None:
pass

def visit_instance(self, t: Instance) -> None:
t.args = self.replace(t.args)

def visit_callable_type(self, t: CallableType) -> None:
t.arg_types = self.replace(t.arg_types)
t.ret_type, = self.replace([t.ret_type])

def visit_overloaded(self, t: Overloaded) -> None:
for item in t.items:
item.accept(self)

def visit_tuple_type(self, t: TupleType) -> None:
t.items = self.replace(t.items)

def visit_typeddict_type(self, t: TypedDictType) -> None:
for key, value in zip(t.items, self.replace(t.items.values())):
t.items[key] = value

def visit_literal_type(self, t: LiteralType) -> None:
pass

def visit_union_type(self, t: UnionType) -> None:
t.items = self.replace(t.items)

def visit_partial_type(self, t: PartialType) -> None:
pass

def visit_type_type(self, t: TypeType) -> None:
t.item, = self.replace([t.item])

def visit_type_alias_type(self, t: TypeAliasType) -> None:
pass # TODO this is probably invalid

def replace(self, types: Iterable[TypeT]) -> List[TypeT]:
ret: List[TypeT] = []
for type in types:
if isinstance(type, SelfType):
type = self.self_type
else:
type.accept(self)
ret.append(type) # type: ignore # not sure if this is actually unsafe
return ret
7 changes: 4 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,11 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
return AnyType(TypeOfAny.from_error)
return RequiredType(self.anal_type(t.args[0]), required=False)
elif fullname in ('typing_extensions.Self', 'typing.Self'):
try:
bound = self.named_type(self.api.type.fullname) # type: ignore
except AttributeError:
from mypy.semanal import SemanticAnalyzer # circular import

if not isinstance(self.api, SemanticAnalyzer):
return self.fail("Self is unbound", t)
bound = self.named_type(self.api.type.fullname)
return SelfType(bound, fullname, line=t.line, column=t.column)
elif self.anal_type_guard_arg(t, fullname) is not None:
# In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args)
Expand Down
11 changes: 2 additions & 9 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing_extensions import Type as TypingType
import itertools
import sys
from mypy.type_visitor import SelfTypeVisitor

from mypy.types import (
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded,
Expand Down Expand Up @@ -253,15 +254,7 @@ def expand(target: Type) -> Type:
variables=variables,
ret_type=ret_type,
bound_args=[original_type])
for arg_type in res.arg_types:
if isinstance(arg_type, UnionType):
for idx, item in enumerate(arg_type.items):
if isinstance(item, SelfType):
assert original_type is not None
arg_type.items[idx] = original_type
if isinstance(res.ret_type, SelfType):
assert original_type is not None
res.ret_type = original_type
res.accept(SelfTypeVisitor(original_type))
return cast(F, res)


Expand Down
3 changes: 2 additions & 1 deletion mypy/typeshed/stdlib/typing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ if sys.version_info >= (3, 10):

if sys.version_info >= (3, 11):
# Self is also a (non-subscriptable) special form.
Self: object = ...
...
Self: object = ...

# These type variables are used by the container types.
_S = TypeVar("_S")
Expand Down
2 changes: 1 addition & 1 deletion runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
options = Options()
options.show_traceback = True
options.raise_exceptions = True
options.verbosity = 10
# options.verbosity = 10
result = build([BuildSource("test.py", None, )], options, stderr=sys.stderr, stdout=sys.stdout)
print(*result.errors, sep="\n")
40 changes: 15 additions & 25 deletions test.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
from __future__ import annotations

from typing import Self, TypeVar, Protocol
from typing import Generic, TypeVar
from typing_extensions import Self
from abc import ABC

T = TypeVar("T")
K = TypeVar("K")


class InstanceOf(Protocol[T]):
@property # type: ignore
def __class__(self) -> T: ... # type: ignore
class ItemSet(Generic[T]):
def first(self) -> T: ...


class MyMetaclass(type):
bar: str
class BaseItem(ABC):
@property
def set(self) -> ItemSet[Self]: ...

def __new__(mcs: type[MyMetaclass], *args, **kwargs) -> MyMetaclass:
cls = super().__new__(mcs, *args, **kwargs)
cls.bar = "Hello"
return cls

def __mul__(
cls,
count: int,
) -> list[InstanceOf[Self]]:
print(cls)
return [cls()] * count
class FooItem(BaseItem):
name: str

def __call__(cls, *args, **kwargs) -> InstanceOf[Self]:
return super().__call__(*args, **kwargs)
def test(self) -> None: ...


class Foo(metaclass=MyMetaclass):
THIS: int

reveal_type(Foo)
reveal_type(Foo())
foos = Foo * 3
reveal_type(foos)
reveal_type(FooItem().set.first().name)
reveal_type(BaseItem().set)
reveal_type(FooItem().set.first().test())
0