Skip to content

Commit e1443bb

Browse files
md384svalentin
authored andcommitted
fix: incorrect returned type of access descriptors on unions of types (#16604)
Fixes #16603 This change maps over union types when determining the types of access descriptors. Previously, the because [this conditional](https://github.com/md384/mypy/blob/c2a55afcef32ecb11a4c76c4c79539f6ba36d55c/mypy/checkmember.py#L697-L701) would fall through to the `else` case because instance type was not a singular `TypeType` (it was a Union), so we'd end up with an instance value being passed to `__get__` instead of `None`.
1 parent 5161ac2 commit e1443bb

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

mypy/checkmember.py

+13
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def copy_modified(
123123
messages: MessageBuilder | None = None,
124124
self_type: Type | None = None,
125125
is_lvalue: bool | None = None,
126+
original_type: Type | None = None,
126127
) -> MemberContext:
127128
mx = MemberContext(
128129
self.is_lvalue,
@@ -142,6 +143,8 @@ def copy_modified(
142143
mx.self_type = self_type
143144
if is_lvalue is not None:
144145
mx.is_lvalue = is_lvalue
146+
if original_type is not None:
147+
mx.original_type = original_type
145148
return mx
146149

147150

@@ -644,6 +647,16 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
644647
return make_simplified_union(
645648
[analyze_descriptor_access(typ, mx) for typ in descriptor_type.items]
646649
)
650+
elif isinstance(instance_type, UnionType):
651+
# map over the instance types
652+
return make_simplified_union(
653+
[
654+
analyze_descriptor_access(
655+
descriptor_type, mx.copy_modified(original_type=original_type)
656+
)
657+
for original_type in instance_type.items
658+
]
659+
)
647660
elif not isinstance(descriptor_type, Instance):
648661
return orig_descriptor_type
649662

test-data/unit/check-unions.test

+38
Original file line numberDiff line numberDiff line change
@@ -1220,3 +1220,41 @@ nc: Union[Container[str], int]
12201220
'x' in nc # E: Unsupported right operand type for in ("Union[Container[str], int]")
12211221
[builtins fixtures/tuple.pyi]
12221222
[typing fixtures/typing-full.pyi]
1223+
1224+
[case testDescriptorAccessForUnionOfTypes]
1225+
from typing import overload, Generic, Any, TypeVar, List, Optional, Union, Type
1226+
1227+
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
1228+
1229+
class Mapped(Generic[_T_co]):
1230+
def __init__(self, value: _T_co):
1231+
self.value = value
1232+
1233+
@overload
1234+
def __get__(
1235+
self, instance: None, owner: Any
1236+
) -> List[_T_co]:
1237+
...
1238+
1239+
@overload
1240+
def __get__(self, instance: object, owner: Any) -> _T_co:
1241+
...
1242+
1243+
def __get__(
1244+
self, instance: Optional[object], owner: Any
1245+
) -> Union[List[_T_co], _T_co]:
1246+
return self.value
1247+
1248+
class A:
1249+
field_1: Mapped[int] = Mapped(1)
1250+
field_2: Mapped[str] = Mapped('1')
1251+
1252+
class B:
1253+
field_1: Mapped[int] = Mapped(2)
1254+
field_2: Mapped[str] = Mapped('2')
1255+
1256+
mix: Union[Type[A], Type[B]] = A
1257+
reveal_type(mix) # N: Revealed type is "Union[Type[__main__.A], Type[__main__.B]]"
1258+
reveal_type(mix.field_1) # N: Revealed type is "builtins.list[builtins.int]"
1259+
reveal_type(mix().field_1) # N: Revealed type is "builtins.int"
1260+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)