Skip to content

Commit bebf0ad

Browse files
committed
squashed #14041
1 parent 9eb6a69 commit bebf0ad

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
@@ -2468,6 +2468,10 @@ class C(B, A[int]): ... # this is unsafe because...
24682468
second_sig = self.bind_and_map_method(second, second_type, ctx, base2)
24692469
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
24702470
elif first_type and second_type:
2471+
if isinstance(first.node, Var):
2472+
first_type = expand_self_type(first.node, first_type, fill_typevars(ctx))
2473+
if isinstance(second.node, Var):
2474+
second_type = expand_self_type(second.node, second_type, fill_typevars(ctx))
24712475
ok = is_equivalent(first_type, second_type)
24722476
if not ok:
24732477
second_node = base2[name].node
@@ -3048,6 +3052,8 @@ def lvalue_type_from_base(
30483052
if base_var:
30493053
base_node = base_var.node
30503054
base_type = base_var.type
3055+
if isinstance(base_node, Var) and base_type is not None:
3056+
base_type = expand_self_type(base_node, base_type, fill_typevars(expr_node.info))
30513057
if isinstance(base_node, Decorator):
30523058
base_node = base_node.func
30533059
base_type = base_node.type

mypy/checkexpr.py

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

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

26702674
member_type = analyze_member_access(
26712675
e.name,
@@ -2679,6 +2683,7 @@ def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type
26792683
chk=self.chk,
26802684
in_literal_context=self.is_literal_context(),
26812685
module_symbol_table=module_symbol_table,
2686+
is_self=is_self,
26822687
)
26832688

26842689
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
@@ -180,6 +180,12 @@ def __str__(self) -> str:
180180
"General",
181181
default_enabled=False,
182182
)
183+
REDUNDANT_SELF_TYPE = ErrorCode(
184+
"redundant-self",
185+
"Warn about redundant Self type annotations on method first argument",
186+
"General",
187+
default_enabled=False,
188+
)
183189

184190

185191
# 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
@@ -231,6 +231,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
231231
"variable"
232232
)
233233
CLASS_VAR_WITH_TYPEVARS: Final = "ClassVar cannot contain type variables"
234+
CLASS_VAR_WITH_GENERIC_SELF: Final = "ClassVar cannot contain Self type in generic classes"
234235
CLASS_VAR_OUTSIDE_OF_CLASS: Final = "ClassVar can only be used for assignments in class body"
235236

236237
# Protocol

mypy/nodes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,6 +2810,7 @@ class is generic then it will be a type constructor of higher kind.
28102810
"has_type_var_tuple_type",
28112811
"type_var_tuple_prefix",
28122812
"type_var_tuple_suffix",
2813+
"self_type",
28132814
)
28142815

28152816
_fullname: Bogus[str] # Fully qualified name
@@ -2950,6 +2951,9 @@ class is generic then it will be a type constructor of higher kind.
29502951
# in case we are doing multiple semantic analysis passes.
29512952
special_alias: TypeAlias | None
29522953

2954+
# Shared type variable for typing.Self in this class (if used, otherwise None).
2955+
self_type: mypy.types.TypeVarType | None
2956+
29532957
FLAGS: Final = [
29542958
"is_abstract",
29552959
"is_enum",
@@ -3002,6 +3006,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
30023006
self.is_newtype = False
30033007
self.is_intersection = False
30043008
self.metadata = {}
3009+
self.self_type = None
30053010

30063011
def add_type_vars(self) -> None:
30073012
self.has_type_var_tuple_type = False
@@ -3219,6 +3224,7 @@ def serialize(self) -> JsonDict:
32193224
"metadata": self.metadata,
32203225
"slots": list(sorted(self.slots)) if self.slots is not None else None,
32213226
"deletable_attributes": self.deletable_attributes,
3227+
"self_type": self.self_type.serialize() if self.self_type is not None else None,
32223228
}
32233229
return data
32243230

@@ -3275,6 +3281,8 @@ def deserialize(cls, data: JsonDict) -> TypeInfo:
32753281
ti.slots = set(data["slots"]) if data["slots"] is not None else None
32763282
ti.deletable_attributes = data["deletable_attributes"]
32773283
set_flags(ti, data["flags"])
3284+
st = data["self_type"]
3285+
ti.self_type = mypy.types.TypeVarType.deserialize(st) if st is not None else None
32783286
return ti
32793287

32803288

0 commit comments

Comments
 (0)