Skip to content

Commit fcc9af6

Browse files
committed
Use a bit more principled handling of unions
1 parent e2fadec commit fcc9af6

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

mypy/meet.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,21 +150,23 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
150150
return make_simplified_union(
151151
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
152152
)
153-
elif isinstance(narrowed, UnionType):
154-
return make_simplified_union(
155-
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
156-
)
157153
elif (
158154
isinstance(declared, TypeVarType)
159155
and not has_type_vars(original_narrowed)
160156
and is_subtype(original_narrowed, declared.upper_bound)
161157
):
158+
# We put this branch early to get T(bound=Union[A, B]) instead of
159+
# Union[T(bound=A), T(bound=B)] that will be confusing for users.
162160
return declared.copy_modified(upper_bound=original_narrowed)
163161
elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True):
164162
if state.strict_optional:
165163
return UninhabitedType()
166164
else:
167165
return NoneType()
166+
elif isinstance(narrowed, UnionType):
167+
return make_simplified_union(
168+
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
169+
)
168170
elif isinstance(narrowed, AnyType):
169171
return original_narrowed
170172
elif isinstance(narrowed, TypeVarType) and is_subtype(narrowed.upper_bound, declared):

mypy/subtypes.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,14 @@ def visit_instance(self, left: Instance) -> bool:
632632
def visit_type_var(self, left: TypeVarType) -> bool:
633633
right = self.right
634634
if isinstance(right, TypeVarType) and left.id == right.id:
635-
return True
635+
# Fast path for most common case.
636+
if left.upper_bound == right.upper_bound:
637+
return True
638+
# Corner case for self-types in classes generic in type vars
639+
# with value restrictions.
640+
if left.id.is_self():
641+
return True
642+
return self._is_subtype(left.upper_bound, right.upper_bound)
636643
if left.values and self._is_subtype(UnionType.make_union(left.values), right):
637644
return True
638645
return self._is_subtype(left.upper_bound, self.right)

mypy/types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@ def __init__(self, type_guard: Type) -> None:
458458
def __repr__(self) -> str:
459459
return f"TypeGuard({self.type_guard})"
460460

461+
# This may hide some real bugs, but it is convenient for various "synthetic"
462+
# visitors, similar to RequiredType and ReadOnlyType below.
463+
def accept(self, visitor: TypeVisitor[T]) -> T:
464+
return self.type_guard.accept(visitor)
465+
461466

462467
class RequiredType(Type):
463468
"""Required[T] or NotRequired[T]. Only usable at top-level of a TypedDict definition."""

test-data/unit/check-narrowing.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,3 +2440,26 @@ def test(cls: Type[T]) -> T:
24402440
return cls()
24412441
return cls()
24422442
[builtins fixtures/isinstance.pyi]
2443+
2444+
[case testNarrowTypeVarBoundUnion]
2445+
from typing import TypeVar
2446+
2447+
class A:
2448+
x: int
2449+
class B:
2450+
x: str
2451+
2452+
T = TypeVar("T")
2453+
def test(x: T) -> T:
2454+
if not isinstance(x, (A, B)):
2455+
return x
2456+
reveal_type(x) # N: Revealed type is "T`-1"
2457+
reveal_type(x.x) # N: Revealed type is "Union[builtins.int, builtins.str]"
2458+
if isinstance(x, A):
2459+
reveal_type(x) # N: Revealed type is "T`-1"
2460+
reveal_type(x.x) # N: Revealed type is "builtins.int"
2461+
return x
2462+
reveal_type(x) # N: Revealed type is "T`-1"
2463+
reveal_type(x.x) # N: Revealed type is "builtins.str"
2464+
return x
2465+
[builtins fixtures/isinstance.pyi]

0 commit comments

Comments
 (0)