Skip to content

Commit b995e16

Browse files
Rebind self-types in subclass methods without Self annotation (#15541)
Fixes #15529 The fix is straightforward, hopefully there will be no fallout. (Note that #14075 would also fix this, but I am still not sure we should do that) --------- Co-authored-by: Shantanu <[email protected]>
1 parent 95dde9d commit b995e16

File tree

5 files changed

+50
-3
lines changed

5 files changed

+50
-3
lines changed

mypy/checkmember.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,12 +735,12 @@ def analyze_var(
735735
"""Analyze access to an attribute via a Var node.
736736
737737
This is conceptually part of analyze_member_access and the arguments are similar.
738-
739-
itype is the class object in which var is defined
738+
itype is the instance type in which attribute should be looked up
740739
original_type is the type of E in the expression E.var
741740
if implicit is True, the original Var was created as an assignment to self
742741
"""
743742
# Found a member variable.
743+
original_itype = itype
744744
itype = map_instance_to_supertype(itype, var.info)
745745
typ = var.type
746746
if typ:
@@ -756,6 +756,16 @@ def analyze_var(
756756
get_proper_type(mx.original_type)
757757
):
758758
t = expand_self_type(var, t, mx.original_type)
759+
elif (
760+
mx.is_self
761+
and original_itype.type != var.info
762+
# If an attribute with Self-type was defined in a supertype, we need to
763+
# rebind the Self type variable to Self type variable of current class...
764+
and original_itype.type.self_type is not None
765+
# ...unless `self` has an explicit non-trivial annotation.
766+
and original_itype == mx.chk.scope.active_self_type()
767+
):
768+
t = expand_self_type(var, t, original_itype.type.self_type)
759769
t = get_proper_type(expand_type_by_instance(t, itype))
760770
freeze_all_type_vars(t)
761771
result: Type = t

mypy/plugins/dataclasses.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ def transform(self) -> bool:
355355
self._add_dataclass_fields_magic_attribute()
356356

357357
if self._spec is _TRANSFORM_SPEC_FOR_DATACLASSES:
358-
self._add_internal_replace_method(attributes)
358+
with state.strict_optional_set(self._api.options.strict_optional):
359+
self._add_internal_replace_method(attributes)
359360
if "__post_init__" in info.names:
360361
self._add_internal_post_init_method(attributes)
361362

mypy/semanal.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,8 @@ def is_core_builtin_class(self, defn: ClassDef) -> bool:
16921692
def analyze_class_body_common(self, defn: ClassDef) -> None:
16931693
"""Parts of class body analysis that are common to all kinds of class defs."""
16941694
self.enter_class(defn.info)
1695+
if any(b.self_type is not None for b in defn.info.mro):
1696+
self.setup_self_type()
16951697
defn.defs.accept(self)
16961698
self.apply_class_plugin_hooks(defn)
16971699
self.leave_class()

test-data/unit/check-selftype.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,3 +1867,20 @@ class B:
18671867
return B() # E: Incompatible return value type (got "B", expected "A")
18681868

18691869
[builtins fixtures/isinstancelist.pyi]
1870+
1871+
[case testAttributeOnSelfAttributeInSubclass]
1872+
from typing import List, Self
1873+
1874+
class A:
1875+
x: Self
1876+
xs: List[Self]
1877+
1878+
class B(A):
1879+
extra: int
1880+
1881+
def meth(self) -> None:
1882+
reveal_type(self.x) # N: Revealed type is "Self`0"
1883+
reveal_type(self.xs[0]) # N: Revealed type is "Self`0"
1884+
reveal_type(self.x.extra) # N: Revealed type is "builtins.int"
1885+
reveal_type(self.xs[0].extra) # N: Revealed type is "builtins.int"
1886+
[builtins fixtures/list.pyi]

test-data/unit/pythoneval.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,3 +2092,20 @@ def fst(kv: Tuple[K, V]) -> K:
20922092
pairs = [(len(s), s) for s in ["one", "two", "three"]]
20932093
grouped = groupby(pairs, key=fst)
20942094
[out]
2095+
2096+
[case testDataclassReplaceOptional]
2097+
# flags: --strict-optional
2098+
from dataclasses import dataclass, replace
2099+
from typing import Optional
2100+
2101+
@dataclass
2102+
class A:
2103+
x: Optional[int]
2104+
2105+
a = A(x=42)
2106+
reveal_type(a)
2107+
a2 = replace(a, x=None) # OK
2108+
reveal_type(a2)
2109+
[out]
2110+
_testDataclassReplaceOptional.py:10: note: Revealed type is "_testDataclassReplaceOptional.A"
2111+
_testDataclassReplaceOptional.py:12: note: Revealed type is "_testDataclassReplaceOptional.A"

0 commit comments

Comments
 (0)