Skip to content

Commit 77dd4b4

Browse files
authored
Add support for Self type (#14041)
Ref #12840 Fixes #11871 Fixes #14089 This is an alternative implementation to two existing PRs: #11666, #13133. This PR treats `typing.Self` as pure syntactic sugar, and transforms it into a type variable early during semantic analyzis. This way we can re-use all the existing machinery and handled edge cases for self-types. The only new thing is self-type for _attributes_ (as proposed in the PEP). This required handling in several places, since attribute access is duplicated in several places (see #7724), plus special forms (like NamedTuples and TypedDicts) and dataclasses plugin require additional care, since they use attribute annotations in special ways. I don't copy all the existing tests for "old style" self-types, but only some common use cases, possible error conditions, and relevant new edge cases, such as e.g. special forms mentioned above, and implicit type variable binding for callable types.
1 parent e0a37fa commit 77dd4b4

28 files changed

+897
-57
lines changed

docs/source/error_code_list2.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ Example:
8282
# Error: Redundant cast to "int" [redundant-cast]
8383
return cast(int, x)
8484
85+
Check that methods do not have redundant Self annotations [redundant-self]
86+
--------------------------------------------------------------------------
87+
88+
Such annotations are allowed by :pep:`673` but are redundant, so if you want
89+
warnings about them, enable this error code.
90+
91+
Example:
92+
93+
.. code-block:: python
94+
95+
# mypy: enable-error-code="redundant-self"
96+
97+
from typing import Self
98+
99+
class C:
100+
# Error: Redundant Self annotation on method first argument
101+
def copy(self: Self) -> Self:
102+
return type(self)()
103+
85104
Check that comparisons are overlapping [comparison-overlap]
86105
-----------------------------------------------------------
87106

docs/source/generics.rst

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,8 @@ Generic methods and generic self
264264
You can also define generic methods — just use a type variable in the
265265
method signature that is different from class type variables. In particular,
266266
``self`` may also be generic, allowing a method to return the most precise
267-
type known at the point of access.
268-
269-
.. note::
270-
271-
This feature is experimental. Checking code with type annotations for self
272-
arguments is still not fully implemented. Mypy may disallow valid code or
273-
allow unsafe code.
274-
275-
In this way, for example, you can typecheck chaining of setter methods:
267+
type known at the point of access. In this way, for example, you can typecheck
268+
chaining of setter methods:
276269

277270
.. code-block:: python
278271
@@ -333,8 +326,69 @@ or a deserialization method returns the actual type of self. Therefore
333326
you may need to silence mypy inside these methods (but not at the call site),
334327
possibly by making use of the ``Any`` type.
335328

329+
Note that this feature may accept some unsafe code for the purpose of
330+
*practicality*. For example:
331+
332+
.. code-block:: python
333+
334+
from typing import TypeVar
335+
336+
T = TypeVar("T")
337+
class Base:
338+
def compare(self: T, other: T) -> bool:
339+
return False
340+
341+
class Sub(Base):
342+
def __init__(self, x: int) -> None:
343+
self.x = x
344+
345+
# This is unsafe (see below), but allowed because it is
346+
# a common pattern, and rarely causes issues in practice.
347+
def compare(self, other: Sub) -> bool:
348+
return self.x > other.x
349+
350+
b: Base = Sub(42)
351+
b.compare(Base()) # Runtime error here: 'Base' object has no attribute 'x'
352+
336353
For some advanced uses of self-types see :ref:`additional examples <advanced_self>`.
337354

355+
Automatic self types using typing.Self
356+
**************************************
357+
358+
The patterns described above are quite common, so there is a syntactic sugar
359+
for them introduced in :pep:`673`. Instead of defining a type variable and
360+
using an explicit ``self`` annotation, you can import a magic type ``typing.Self``
361+
that is automatically transformed into a type variable with an upper bound of
362+
current class, and you don't need an annotation for ``self`` (or ``cls`` for
363+
class methods). The above example can thus be rewritten as:
364+
365+
.. code-block:: python
366+
367+
from typing import Self
368+
369+
class Friend:
370+
other: Self | None = None
371+
372+
@classmethod
373+
def make_pair(cls) -> tuple[Self, Self]:
374+
a, b = cls(), cls()
375+
a.other = b
376+
b.other = a
377+
return a, b
378+
379+
class SuperFriend(Friend):
380+
pass
381+
382+
a, b = SuperFriend.make_pair()
383+
384+
This is more compact than using explicit type variables, plus additionally
385+
you can use ``Self`` in attribute annotations, not just in methods.
386+
387+
.. note::
388+
389+
To use this feature on versions of Python before 3.11, you will need to
390+
import ``Self`` from ``typing_extensions`` version 4.0 or newer.
391+
338392
.. _variance-of-generics:
339393

340394
Variance of generic types
@@ -548,7 +602,7 @@ Note that class decorators are handled differently than function decorators in
548602
mypy: decorating a class does not erase its type, even if the decorator has
549603
incomplete type annotations.
550604

551-
Suppose we have the following decorator, not type annotated yet,
605+
Suppose we have the following decorator, not type annotated yet,
552606
that preserves the original function's signature and merely prints the decorated function's name:
553607

554608
.. code-block:: python

docs/source/more_types.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,9 +804,10 @@ classes are generic, self-type allows giving them precise signatures:
804804
.. code-block:: python
805805
806806
T = TypeVar('T')
807-
Q = TypeVar('Q', bound='Base[Any]')
808807
809808
class Base(Generic[T]):
809+
Q = TypeVar('Q', bound='Base[T]')
810+
810811
def __init__(self, item: T) -> None:
811812
self.item = item
812813

mypy/checker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from mypy.erasetype import erase_type, erase_typevars, remove_instance_last_known_values
4040
from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode
4141
from mypy.errors import Errors, ErrorWatcher, report_internal_error
42-
from mypy.expandtype import expand_type, expand_type_by_instance
42+
from mypy.expandtype import expand_self_type, expand_type, expand_type_by_instance
4343
from mypy.join import join_types
4444
from mypy.literals import Key, literal, literal_hash
4545
from mypy.maptype import map_instance_to_supertype
@@ -2488,6 +2488,10 @@ class C(B, A[int]): ... # this is unsafe because...
24882488
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
24892489
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
24902490
elif first_type and second_type:
2491+
if isinstance(first.node, Var):
2492+
first_type = expand_self_type(first.node, first_type, fill_typevars(ctx))
2493+
if isinstance(second.node, Var):
2494+
second_type = expand_self_type(second.node, second_type, fill_typevars(ctx))
24912495
ok = is_equivalent(first_type, second_type)
24922496
if not ok:
24932497
second_node = base2[name].node
@@ -3068,6 +3072,8 @@ def lvalue_type_from_base(
30683072
if base_var:
30693073
base_node = base_var.node
30703074
base_type = base_var.type
3075+
if isinstance(base_node, Var) and base_type is not None:
3076+
base_type = expand_self_type(base_node, base_type, fill_typevars(expr_node.info))
30713077
if isinstance(base_node, Decorator):
30723078
base_node = base_node.func
30733079
base_type = base_node.type

mypy/checkexpr.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,10 @@ def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type
26672667

26682668
if isinstance(base, RefExpr) and isinstance(base.node, MypyFile):
26692669
module_symbol_table = base.node.names
2670+
if isinstance(base, RefExpr) and isinstance(base.node, Var):
2671+
is_self = base.node.is_self
2672+
else:
2673+
is_self = False
26702674

26712675
member_type = analyze_member_access(
26722676
e.name,
@@ -2680,6 +2684,7 @@ def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type
26802684
chk=self.chk,
26812685
in_literal_context=self.is_literal_context(),
26822686
module_symbol_table=module_symbol_table,
2687+
is_self=is_self,
26832688
)
26842689

26852690
return member_type

mypy/checkmember.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from mypy import meet, message_registry, subtypes
88
from mypy.erasetype import erase_typevars
9-
from mypy.expandtype import expand_type_by_instance, freshen_function_type_vars
9+
from mypy.expandtype import expand_self_type, expand_type_by_instance, freshen_function_type_vars
1010
from mypy.maptype import map_instance_to_supertype
1111
from mypy.messages import MessageBuilder
1212
from mypy.nodes import (
@@ -37,6 +37,7 @@
3737
erase_to_bound,
3838
function_type,
3939
make_simplified_union,
40+
supported_self_type,
4041
tuple_fallback,
4142
type_object_type_from_function,
4243
)
@@ -90,6 +91,7 @@ def __init__(
9091
self_type: Type | None,
9192
module_symbol_table: SymbolTable | None = None,
9293
no_deferral: bool = False,
94+
is_self: bool = False,
9395
) -> None:
9496
self.is_lvalue = is_lvalue
9597
self.is_super = is_super
@@ -101,6 +103,7 @@ def __init__(
101103
self.chk = chk
102104
self.module_symbol_table = module_symbol_table
103105
self.no_deferral = no_deferral
106+
self.is_self = is_self
104107

105108
def named_type(self, name: str) -> Instance:
106109
return self.chk.named_type(name)
@@ -152,6 +155,7 @@ def analyze_member_access(
152155
self_type: Type | None = None,
153156
module_symbol_table: SymbolTable | None = None,
154157
no_deferral: bool = False,
158+
is_self: bool = False,
155159
) -> Type:
156160
"""Return the type of attribute 'name' of 'typ'.
157161
@@ -187,6 +191,7 @@ def analyze_member_access(
187191
self_type=self_type,
188192
module_symbol_table=module_symbol_table,
189193
no_deferral=no_deferral,
194+
is_self=is_self,
190195
)
191196
result = _analyze_member_access(name, typ, mx, override_info)
192197
possible_literal = get_proper_type(result)
@@ -682,12 +687,12 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
682687
return inferred_dunder_get_type.ret_type
683688

684689

685-
def is_instance_var(var: Var, info: TypeInfo) -> bool:
690+
def is_instance_var(var: Var) -> bool:
686691
"""Return if var is an instance variable according to PEP 526."""
687692
return (
688693
# check the type_info node is the var (not a decorated function, etc.)
689-
var.name in info.names
690-
and info.names[var.name].node is var
694+
var.name in var.info.names
695+
and var.info.names[var.name].node is var
691696
and not var.is_classvar
692697
# variables without annotations are treated as classvar
693698
and not var.is_inferred
@@ -722,12 +727,16 @@ def analyze_var(
722727
mx.msg.read_only_property(name, itype.type, mx.context)
723728
if mx.is_lvalue and var.is_classvar:
724729
mx.msg.cant_assign_to_classvar(name, mx.context)
730+
if not (mx.is_self or mx.is_super) or supported_self_type(
731+
get_proper_type(mx.original_type)
732+
):
733+
typ = expand_self_type(var, typ, mx.original_type)
725734
t = get_proper_type(expand_type_by_instance(typ, itype))
726735
result: Type = t
727736
typ = get_proper_type(typ)
728737
if (
729738
var.is_initialized_in_class
730-
and (not is_instance_var(var, info) or mx.is_operator)
739+
and (not is_instance_var(var) or mx.is_operator)
731740
and isinstance(typ, FunctionLike)
732741
and not typ.is_type_obj()
733742
):
@@ -945,7 +954,12 @@ def analyze_class_attribute_access(
945954
# x: T
946955
# C.x # Error, ambiguous access
947956
# C[int].x # Also an error, since C[int] is same as C at runtime
948-
if isinstance(t, TypeVarType) or has_type_vars(t):
957+
# Exception is Self type wrapped in ClassVar, that is safe.
958+
if node.node.info.self_type is not None and node.node.is_classvar:
959+
exclude = node.node.info.self_type.id
960+
else:
961+
exclude = None
962+
if isinstance(t, TypeVarType) and t.id != exclude or has_type_vars(t, exclude):
949963
# Exception: access on Type[...], including first argument of class methods is OK.
950964
if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit:
951965
if node.node.is_classvar:
@@ -958,6 +972,7 @@ def analyze_class_attribute_access(
958972
# In the above example this means that we infer following types:
959973
# C.x -> Any
960974
# C[int].x -> int
975+
t = get_proper_type(expand_self_type(node.node, t, itype))
961976
t = erase_typevars(expand_type_by_instance(t, isuper))
962977

963978
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (

mypy/errorcodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ def __str__(self) -> str:
186186
"General",
187187
default_enabled=False,
188188
)
189+
REDUNDANT_SELF_TYPE = ErrorCode(
190+
"redundant-self",
191+
"Warn about redundant Self type annotations on method first argument",
192+
"General",
193+
default_enabled=False,
194+
)
189195

190196

191197
# Syntax errors are often blocking.

mypy/expandtype.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Iterable, Mapping, Sequence, TypeVar, cast, overload
44

5-
from mypy.nodes import ARG_STAR
5+
from mypy.nodes import ARG_STAR, Var
66
from mypy.types import (
77
AnyType,
88
CallableType,
@@ -383,3 +383,20 @@ def expand_unpack_with_variables(
383383
raise NotImplementedError(f"Invalid type replacement to expand: {repl}")
384384
else:
385385
raise NotImplementedError(f"Invalid type to expand: {t.type}")
386+
387+
388+
@overload
389+
def expand_self_type(var: Var, typ: ProperType, replacement: ProperType) -> ProperType:
390+
...
391+
392+
393+
@overload
394+
def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type:
395+
...
396+
397+
398+
def expand_self_type(var: Var, typ: Type, replacement: Type) -> Type:
399+
"""Expand appearances of Self type in a variable type."""
400+
if var.info.self_type is not None and not var.is_property:
401+
return expand_type(typ, {var.info.self_type.id: replacement})
402+
return typ

mypy/message_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
238238
"variable"
239239
)
240240
CLASS_VAR_WITH_TYPEVARS: Final = "ClassVar cannot contain type variables"
241+
CLASS_VAR_WITH_GENERIC_SELF: Final = "ClassVar cannot contain Self type in generic classes"
241242
CLASS_VAR_OUTSIDE_OF_CLASS: Final = "ClassVar can only be used for assignments in class body"
242243

243244
# Protocol

mypy/nodes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2813,6 +2813,7 @@ class is generic then it will be a type constructor of higher kind.
28132813
"has_type_var_tuple_type",
28142814
"type_var_tuple_prefix",
28152815
"type_var_tuple_suffix",
2816+
"self_type",
28162817
)
28172818

28182819
_fullname: Bogus[str] # Fully qualified name
@@ -2953,6 +2954,9 @@ class is generic then it will be a type constructor of higher kind.
29532954
# in case we are doing multiple semantic analysis passes.
29542955
special_alias: TypeAlias | None
29552956

2957+
# Shared type variable for typing.Self in this class (if used, otherwise None).
2958+
self_type: mypy.types.TypeVarType | None
2959+
29562960
FLAGS: Final = [
29572961
"is_abstract",
29582962
"is_enum",
@@ -3005,6 +3009,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
30053009
self.is_newtype = False
30063010
self.is_intersection = False
30073011
self.metadata = {}
3012+
self.self_type = None
30083013

30093014
def add_type_vars(self) -> None:
30103015
self.has_type_var_tuple_type = False
@@ -3222,6 +3227,7 @@ def serialize(self) -> JsonDict:
32223227
"metadata": self.metadata,
32233228
"slots": list(sorted(self.slots)) if self.slots is not None else None,
32243229
"deletable_attributes": self.deletable_attributes,
3230+
"self_type": self.self_type.serialize() if self.self_type is not None else None,
32253231
}
32263232
return data
32273233

@@ -3278,6 +3284,8 @@ def deserialize(cls, data: JsonDict) -> TypeInfo:
32783284
ti.slots = set(data["slots"]) if data["slots"] is not None else None
32793285
ti.deletable_attributes = data["deletable_attributes"]
32803286
set_flags(ti, data["flags"])
3287+
st = data["self_type"]
3288+
ti.self_type = mypy.types.TypeVarType.deserialize(st) if st is not None else None
32813289
return ti
32823290

32833291

0 commit comments

Comments
 (0)