Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dab5b29

Browse files
authored
Fix binding of self-types on unions (#7152)
Fixes #7149 The fix is quite straightforward, self-types should be bound to each individual component, so I just added only basic tests. Please let me know if you want better test coverage (properties, class-methods) etc.
1 parent 5002277 commit dab5b29

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

mypy/checkexpr.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,8 @@ def check_union_call_expr(self, e: CallExpr, object_type: UnionType, member: str
677677
self.msg.disable_errors()
678678
item = analyze_member_access(member, typ, e, False, False, False,
679679
self.msg, original_type=object_type, chk=self.chk,
680-
in_literal_context=self.is_literal_context())
680+
in_literal_context=self.is_literal_context(),
681+
self_type=typ)
681682
self.msg.enable_errors()
682683
narrowed = self.narrow_type_from_binder(e.callee, item, skip_non_overlapping=True)
683684
if narrowed is None:

mypy/checkmember.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ def __init__(self,
4545
original_type: Type,
4646
context: Context,
4747
msg: MessageBuilder,
48-
chk: 'mypy.checker.TypeChecker') -> None:
48+
chk: 'mypy.checker.TypeChecker',
49+
self_type: Optional[Type]) -> None:
4950
self.is_lvalue = is_lvalue
5051
self.is_super = is_super
5152
self.is_operator = is_operator
5253
self.original_type = original_type
54+
self.self_type = self_type or original_type
5355
self.context = context # Error context
5456
self.msg = msg
5557
self.chk = chk
@@ -60,9 +62,16 @@ def builtin_type(self, name: str) -> Instance:
6062
def not_ready_callback(self, name: str, context: Context) -> None:
6163
self.chk.handle_cannot_determine_type(name, context)
6264

63-
def copy_modified(self, messages: MessageBuilder) -> 'MemberContext':
64-
return MemberContext(self.is_lvalue, self.is_super, self.is_operator,
65-
self.original_type, self.context, messages, self.chk)
65+
def copy_modified(self, *, messages: Optional[MessageBuilder] = None,
66+
self_type: Optional[Type] = None) -> 'MemberContext':
67+
mx = MemberContext(self.is_lvalue, self.is_super, self.is_operator,
68+
self.original_type, self.context, self.msg, self.chk,
69+
self.self_type)
70+
if messages is not None:
71+
mx.msg = messages
72+
if self_type is not None:
73+
mx.self_type = self_type
74+
return mx
6675

6776

6877
def analyze_member_access(name: str,
@@ -75,7 +84,8 @@ def analyze_member_access(name: str,
7584
original_type: Type,
7685
chk: 'mypy.checker.TypeChecker',
7786
override_info: Optional[TypeInfo] = None,
78-
in_literal_context: bool = False) -> Type:
87+
in_literal_context: bool = False,
88+
self_type: Optional[Type] = None) -> Type:
7989
"""Return the type of attribute 'name' of 'typ'.
8090
8191
The actual implementation is in '_analyze_member_access' and this docstring
@@ -91,15 +101,18 @@ def analyze_member_access(name: str,
91101
that we have available. When looking for an attribute of 'typ', we may perform
92102
recursive calls targeting the fallback type, and 'typ' may become some supertype
93103
of 'original_type'. 'original_type' is always preserved as the 'typ' type used in
94-
the initial, non-recursive call.
104+
the initial, non-recursive call. The 'self_type' is a component of 'original_type'
105+
to which generic self should be bound (a narrower type that has a fallback to instance).
106+
Currently this is used only for union types.
95107
"""
96108
mx = MemberContext(is_lvalue,
97109
is_super,
98110
is_operator,
99111
original_type,
100112
context,
101113
msg,
102-
chk=chk)
114+
chk=chk,
115+
self_type=self_type)
103116
result = _analyze_member_access(name, typ, mx, override_info)
104117
if in_literal_context and isinstance(result, Instance) and result.last_known_value is not None:
105118
return result.last_known_value
@@ -183,7 +196,7 @@ def analyze_instance_member_access(name: str,
183196
# the first argument.
184197
pass
185198
else:
186-
signature = bind_self(signature, mx.original_type)
199+
signature = bind_self(signature, mx.self_type)
187200
typ = map_instance_to_supertype(typ, method.info)
188201
member_type = expand_type_by_instance(signature, typ)
189202
freeze_type_vars(member_type)
@@ -263,8 +276,11 @@ def analyze_type_type_member_access(name: str, typ: TypeType, mx: MemberContext)
263276

264277
def analyze_union_member_access(name: str, typ: UnionType, mx: MemberContext) -> Type:
265278
mx.msg.disable_type_names += 1
266-
results = [_analyze_member_access(name, subtype, mx)
267-
for subtype in typ.relevant_items()]
279+
results = []
280+
for subtype in typ.relevant_items():
281+
# Self types should be bound to every individual item of a union.
282+
item_mx = mx.copy_modified(self_type=subtype)
283+
results.append(_analyze_member_access(name, subtype, item_mx))
268284
mx.msg.disable_type_names -= 1
269285
return UnionType.make_simplified_union(results)
270286

@@ -342,7 +358,7 @@ def analyze_member_var_access(name: str,
342358
# that the attribute exists
343359
if method and method.info.fullname() != 'builtins.object':
344360
function = function_type(method, mx.builtin_type('builtins.function'))
345-
bound_method = bind_self(function, mx.original_type)
361+
bound_method = bind_self(function, mx.self_type)
346362
typ = map_instance_to_supertype(itype, method.info)
347363
getattr_type = expand_type_by_instance(bound_method, typ)
348364
if isinstance(getattr_type, CallableType):
@@ -359,7 +375,7 @@ def analyze_member_var_access(name: str,
359375
setattr_meth = info.get_method('__setattr__')
360376
if setattr_meth and setattr_meth.info.fullname() != 'builtins.object':
361377
setattr_func = function_type(setattr_meth, mx.builtin_type('builtins.function'))
362-
bound_type = bind_self(setattr_func, mx.original_type)
378+
bound_type = bind_self(setattr_func, mx.self_type)
363379
typ = map_instance_to_supertype(itype, setattr_meth.info)
364380
setattr_type = expand_type_by_instance(bound_type, typ)
365381
if isinstance(setattr_type, CallableType) and len(setattr_type.arg_types) > 0:
@@ -515,7 +531,7 @@ def analyze_var(name: str,
515531
dispatched_type = meet.meet_types(mx.original_type, itype)
516532
check_self_arg(functype, dispatched_type, var.is_classmethod, mx.context, name,
517533
mx.msg)
518-
signature = bind_self(functype, mx.original_type, var.is_classmethod)
534+
signature = bind_self(functype, mx.self_type, var.is_classmethod)
519535
if var.is_property:
520536
# A property cannot have an overloaded type => the cast is fine.
521537
assert isinstance(signature, CallableType)

test-data/unit/check-selftype.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,33 @@ class C:
503503
ab: Union[A, B, C]
504504
reveal_type(ab.x) # N: Revealed type is 'builtins.int'
505505
[builtins fixtures/property.pyi]
506+
507+
[case testSelfTypeOnUnion]
508+
from typing import TypeVar, Union
509+
510+
T = TypeVar('T')
511+
512+
class A:
513+
same: int
514+
515+
class C:
516+
def same(self: T) -> T: ...
517+
518+
x: Union[A, C]
519+
reveal_type(x.same) # N: Revealed type is 'Union[builtins.int, def () -> __main__.C*]'
520+
521+
[case testSelfTypeOnUnionClassMethod]
522+
from typing import TypeVar, Union, Type
523+
524+
T = TypeVar('T')
525+
526+
class A:
527+
same: int
528+
529+
class C:
530+
@classmethod
531+
def same(cls: Type[T]) -> T: ...
532+
533+
x: Union[A, C]
534+
reveal_type(x.same) # N: Revealed type is 'Union[builtins.int, def () -> __main__.C*]'
535+
[builtins fixtures/classmethod.pyi]

test-data/unit/check-typeddict.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,3 +1662,14 @@ p = Point(x=42, y=1337)
16621662
reveal_type(p) # N: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
16631663
[builtins fixtures/dict.pyi]
16641664
[typing fixtures/typing-full.pyi]
1665+
1666+
[case testTypedDictOptionalUpdate]
1667+
from typing import Union
1668+
from mypy_extensions import TypedDict
1669+
1670+
class A(TypedDict):
1671+
x: int
1672+
1673+
d: Union[A, None]
1674+
d.update({'x': 1})
1675+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)