Skip to content

Commit 2a1cea4

Browse files
ilevkivskyiIvan Levkivskyi
andauthored
A sketchy hack to fix TypeGuard crash in logical expressions (#11015)
Support or expressions involving type guard checks. Co-authored-by: Ivan Levkivskyi <[email protected]>
1 parent 7576f65 commit 2a1cea4

File tree

5 files changed

+70
-6
lines changed

5 files changed

+70
-6
lines changed

mypy/binder.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def update_from_options(self, frames: List[Frame]) -> bool:
203203
for other in resulting_values[1:]:
204204
assert other is not None
205205
# Ignore the error about using get_proper_type().
206-
if not isinstance(other, TypeGuardType): # type: ignore[misc]
206+
if not contains_type_guard(other):
207207
type = join_simple(self.declarations[key], type, other)
208208
if current_value is None or not is_same_type(type, current_value):
209209
self._put(key, type)
@@ -431,3 +431,13 @@ def get_declaration(expr: BindableExpression) -> Optional[Type]:
431431
if not isinstance(type, PartialType):
432432
return type
433433
return None
434+
435+
436+
def contains_type_guard(other: Type) -> bool:
437+
# Ignore the error about using get_proper_type().
438+
if isinstance(other, TypeGuardType): # type: ignore[misc]
439+
return True
440+
other = get_proper_type(other)
441+
if isinstance(other, UnionType):
442+
return any(contains_type_guard(item) for item in other.relevant_items())
443+
return False

mypy/checker.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4167,6 +4167,11 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM
41674167
self.fail("Type guard requires positional argument", node)
41684168
return {}, {}
41694169
if literal(expr) == LITERAL_TYPE:
4170+
# Note: we wrap the target type, so that we can special case later.
4171+
# Namely, for isinstance() we use a normal meet, while TypeGuard is
4172+
# considered "always right" (i.e. even if the types are not overlapping).
4173+
# Also note that a care must be taken to unwrap this back at read places
4174+
# where we use this to narrow down declared type.
41704175
return {expr: TypeGuardType(node.callee.type_guard)}, {}
41714176
elif isinstance(node, ComparisonExpr):
41724177
# Step 1: Obtain the types of each operand and whether or not we can

mypy/checkexpr.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4179,10 +4179,6 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type,
41794179
"""
41804180
if literal(expr) >= LITERAL_TYPE:
41814181
restriction = self.chk.binder.get(expr)
4182-
# Ignore the error about using get_proper_type().
4183-
if isinstance(restriction, TypeGuardType): # type: ignore[misc]
4184-
# A type guard forces the new type even if it doesn't overlap the old.
4185-
return restriction.type_guard
41864182
# If the current node is deferred, some variables may get Any types that they
41874183
# otherwise wouldn't have. We don't want to narrow down these since it may
41884184
# produce invalid inferred Optional[Any] types, at least.

mypy/meet.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
5656

5757
if declared == narrowed:
5858
return declared
59-
if isinstance(declared, UnionType):
59+
# Ignore the error about using get_proper_type().
60+
if isinstance(narrowed, TypeGuardType): # type: ignore[misc]
61+
# A type guard forces the new type even if it doesn't overlap the old.
62+
return narrowed.type_guard
63+
elif isinstance(declared, UnionType):
6064
return make_simplified_union([narrow_declared_type(x, narrowed)
6165
for x in declared.relevant_items()])
6266
elif not is_overlapping_types(declared, narrowed,
@@ -157,6 +161,11 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
157161
if isinstance(left, PartialType) or isinstance(right, PartialType):
158162
assert False, "Unexpectedly encountered partial type"
159163

164+
# Ignore the error about using get_proper_type().
165+
if isinstance(left, TypeGuardType) or isinstance(right, TypeGuardType): # type: ignore[misc]
166+
# A type guard forces the new type even if it doesn't overlap the old.
167+
return True
168+
160169
# We should also never encounter these types, but it's possible a few
161170
# have snuck through due to unrelated bugs. For now, we handle these
162171
# in the same way we handle 'Any'.

test-data/unit/check-typeguard.test

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,47 @@ if guard(a):
370370
reveal_type(a) # N: Revealed type is "__main__.A"
371371
reveal_type(a) # N: Revealed type is "__main__.A"
372372
[builtins fixtures/tuple.pyi]
373+
374+
[case testTypeGuardNestedRestrictionAny]
375+
from typing_extensions import TypeGuard
376+
from typing import Any
377+
378+
class A: ...
379+
def f(x: object) -> TypeGuard[A]: ...
380+
def g(x: object) -> None: ...
381+
382+
def test(x: Any) -> None:
383+
if not(f(x) or x):
384+
return
385+
g(reveal_type(x)) # N: Revealed type is "Union[__main__.A, Any]"
386+
[builtins fixtures/tuple.pyi]
387+
388+
[case testTypeGuardNestedRestrictionUnionOther]
389+
from typing_extensions import TypeGuard
390+
from typing import Any
391+
392+
class A: ...
393+
class B: ...
394+
def f(x: object) -> TypeGuard[A]: ...
395+
def f2(x: object) -> TypeGuard[B]: ...
396+
def g(x: object) -> None: ...
397+
398+
def test(x: object) -> None:
399+
if not(f(x) or f2(x)):
400+
return
401+
g(reveal_type(x)) # N: Revealed type is "Union[__main__.A, __main__.B]"
402+
[builtins fixtures/tuple.pyi]
403+
404+
[case testTypeGuardNestedRestrictionUnionIsInstance]
405+
from typing_extensions import TypeGuard
406+
from typing import Any, List
407+
408+
class A: ...
409+
def f(x: List[object]) -> TypeGuard[List[str]]: ...
410+
def g(x: object) -> None: ...
411+
412+
def test(x: List[object]) -> None:
413+
if not(f(x) or isinstance(x, A)):
414+
return
415+
g(reveal_type(x)) # N: Revealed type is "Union[builtins.list[builtins.str], __main__.<subclass of "list" and "A">]"
416+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)