Skip to content

Commit

Permalink
fix: incorrect returned type of access descriptors on unions of types (
Browse files Browse the repository at this point in the history
…#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`.
  • Loading branch information
md384 authored and svalentin committed Apr 16, 2024
1 parent 5161ac2 commit e1443bb
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 0 deletions.
13 changes: 13 additions & 0 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def copy_modified(
messages: MessageBuilder | None = None,
self_type: Type | None = None,
is_lvalue: bool | None = None,
original_type: Type | None = None,
) -> MemberContext:
mx = MemberContext(
self.is_lvalue,
Expand All @@ -142,6 +143,8 @@ def copy_modified(
mx.self_type = self_type
if is_lvalue is not None:
mx.is_lvalue = is_lvalue
if original_type is not None:
mx.original_type = original_type
return mx


Expand Down Expand Up @@ -644,6 +647,16 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
return make_simplified_union(
[analyze_descriptor_access(typ, mx) for typ in descriptor_type.items]
)
elif isinstance(instance_type, UnionType):
# map over the instance types
return make_simplified_union(
[
analyze_descriptor_access(
descriptor_type, mx.copy_modified(original_type=original_type)
)
for original_type in instance_type.items
]
)
elif not isinstance(descriptor_type, Instance):
return orig_descriptor_type

Expand Down
38 changes: 38 additions & 0 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1220,3 +1220,41 @@ nc: Union[Container[str], int]
'x' in nc # E: Unsupported right operand type for in ("Union[Container[str], int]")
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testDescriptorAccessForUnionOfTypes]
from typing import overload, Generic, Any, TypeVar, List, Optional, Union, Type

_T_co = TypeVar("_T_co", bound=Any, covariant=True)

class Mapped(Generic[_T_co]):
def __init__(self, value: _T_co):
self.value = value

@overload
def __get__(
self, instance: None, owner: Any
) -> List[_T_co]:
...

@overload
def __get__(self, instance: object, owner: Any) -> _T_co:
...

def __get__(
self, instance: Optional[object], owner: Any
) -> Union[List[_T_co], _T_co]:
return self.value

class A:
field_1: Mapped[int] = Mapped(1)
field_2: Mapped[str] = Mapped('1')

class B:
field_1: Mapped[int] = Mapped(2)
field_2: Mapped[str] = Mapped('2')

mix: Union[Type[A], Type[B]] = A
reveal_type(mix) # N: Revealed type is "Union[Type[__main__.A], Type[__main__.B]]"
reveal_type(mix.field_1) # N: Revealed type is "builtins.list[builtins.int]"
reveal_type(mix().field_1) # N: Revealed type is "builtins.int"
[builtins fixtures/list.pyi]

0 comments on commit e1443bb

Please sign in to comment.