Skip to content

Commit 8e909e4

Browse files
Fix crash on TypeGuard plus "and" (#10496)
In python/typeshed#5473, I tried to switch a number of `inspect` functions to use the new `TypeGuard` functionality. Unfortunately, mypy-primer found a number of crashes in third-party libraries in places where a TypeGuard function was ANDed together with some other check. Examples: - https://github.com/sphinx-doc/sphinx/blob/4.x/sphinx/util/inspect.py#L252 - https://github.com/sphinx-doc/sphinx/blob/4.x/sphinx/ext/coverage.py#L212 - https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/elements/doc_string.py#L105 The problems trace back to the decision in #9865 to make TypeGuardType not inherit from ProperType: in various conditions that are more complicated than a simple `if` check, mypy wants everything to become a ProperType. Therefore, to fix the crashes I had to make TypeGuardType a ProperType and support it in various visitors.
1 parent de6fd6a commit 8e909e4

17 files changed

+113
-21
lines changed

mypy/constraints.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
CallableType, Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarType, Instance,
88
TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType,
99
UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance, TypeOfAny, LiteralType,
10-
ProperType, get_proper_type, TypeAliasType
10+
ProperType, get_proper_type, TypeAliasType, TypeGuardType
1111
)
1212
from mypy.maptype import map_instance_to_supertype
1313
import mypy.subtypes
@@ -534,6 +534,9 @@ def visit_union_type(self, template: UnionType) -> List[Constraint]:
534534
def visit_type_alias_type(self, template: TypeAliasType) -> List[Constraint]:
535535
assert False, "This should be never called, got {}".format(template)
536536

537+
def visit_type_guard_type(self, template: TypeGuardType) -> List[Constraint]:
538+
assert False, "This should be never called, got {}".format(template)
539+
537540
def infer_against_any(self, types: Iterable[Type], any_type: AnyType) -> List[Constraint]:
538541
res = [] # type: List[Constraint]
539542
for t in types:

mypy/erasetype.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarId, Instance, TypeVarType,
55
CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
66
DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType, ProperType,
7-
get_proper_type, TypeAliasType
7+
get_proper_type, TypeAliasType, TypeGuardType
88
)
99
from mypy.nodes import ARG_STAR, ARG_STAR2
1010

@@ -90,6 +90,9 @@ def visit_union_type(self, t: UnionType) -> ProperType:
9090
from mypy.typeops import make_simplified_union
9191
return make_simplified_union(erased_items)
9292

93+
def visit_type_guard_type(self, t: TypeGuardType) -> ProperType:
94+
return TypeGuardType(t.type_guard.accept(self))
95+
9396
def visit_type_type(self, t: TypeType) -> ProperType:
9497
return TypeType.make_normalized(t.item.accept(self), line=t.line)
9598

mypy/expandtype.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Dict, Iterable, List, TypeVar, Mapping, cast
22

33
from mypy.types import (
4-
Type, Instance, CallableType, TypeVisitor, UnboundType, AnyType,
4+
Type, Instance, CallableType, TypeGuardType, TypeVisitor, UnboundType, AnyType,
55
NoneType, TypeVarType, Overloaded, TupleType, TypedDictType, UnionType,
66
ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId,
77
FunctionLike, TypeVarDef, LiteralType, get_proper_type, ProperType,
@@ -126,6 +126,9 @@ def visit_union_type(self, t: UnionType) -> Type:
126126
from mypy.typeops import make_simplified_union # asdf
127127
return make_simplified_union(self.expand_types(t.items), t.line, t.column)
128128

129+
def visit_type_guard_type(self, t: TypeGuardType) -> ProperType:
130+
return TypeGuardType(t.type_guard.accept(self))
131+
129132
def visit_partial_type(self, t: PartialType) -> Type:
130133
return t
131134

mypy/fixup.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
TypeVarExpr, ClassDef, Block, TypeAlias,
1010
)
1111
from mypy.types import (
12-
CallableType, Instance, Overloaded, TupleType, TypedDictType,
12+
CallableType, Instance, Overloaded, TupleType, TypeGuardType, TypedDictType,
1313
TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType,
1414
TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny, TypeVarDef
1515
)
@@ -254,6 +254,9 @@ def visit_union_type(self, ut: UnionType) -> None:
254254
for it in ut.items:
255255
it.accept(self)
256256

257+
def visit_type_guard_type(self, t: TypeGuardType) -> None:
258+
t.type_guard.accept(self)
259+
257260
def visit_void(self, o: Any) -> None:
258261
pass # Nothing to descend into.
259262

mypy/indirection.py

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def visit_literal_type(self, t: types.LiteralType) -> Set[str]:
9797
def visit_union_type(self, t: types.UnionType) -> Set[str]:
9898
return self._visit(t.items)
9999

100+
def visit_type_guard_type(self, t: types.TypeGuardType) -> Set[str]:
101+
return self._visit(t.type_guard)
102+
100103
def visit_partial_type(self, t: types.PartialType) -> Set[str]:
101104
return set()
102105

mypy/join.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Type, AnyType, NoneType, TypeVisitor, Instance, UnboundType, TypeVarType, CallableType,
88
TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded, LiteralType,
99
PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, get_proper_type,
10-
ProperType, get_proper_types, TypeAliasType, PlaceholderType
10+
ProperType, get_proper_types, TypeAliasType, PlaceholderType, TypeGuardType
1111
)
1212
from mypy.maptype import map_instance_to_supertype
1313
from mypy.subtypes import (
@@ -340,6 +340,9 @@ def visit_type_type(self, t: TypeType) -> ProperType:
340340
def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
341341
assert False, "This should be never called, got {}".format(t)
342342

343+
def visit_type_guard_type(self, t: TypeGuardType) -> ProperType:
344+
assert False, "This should be never called, got {}".format(t)
345+
343346
def join(self, s: Type, t: Type) -> ProperType:
344347
return join_types(s, t)
345348

mypy/meet.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType,
99
TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType,
1010
UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType,
11-
ProperType, get_proper_type, get_proper_types, TypeAliasType
11+
ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType
1212
)
1313
from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype
1414
from mypy.erasetype import erase_type
@@ -648,6 +648,9 @@ def visit_type_type(self, t: TypeType) -> ProperType:
648648
def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
649649
assert False, "This should be never called, got {}".format(t)
650650

651+
def visit_type_guard_type(self, t: TypeGuardType) -> ProperType:
652+
assert False, "This should be never called, got {}".format(t)
653+
651654
def meet(self, s: Type, t: Type) -> ProperType:
652655
return meet_types(s, t)
653656

mypy/sametypes.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Sequence
22

33
from mypy.types import (
4-
Type, UnboundType, AnyType, NoneType, TupleType, TypedDictType,
4+
Type, TypeGuardType, UnboundType, AnyType, NoneType, TupleType, TypedDictType,
55
UnionType, CallableType, TypeVarType, Instance, TypeVisitor, ErasedType,
66
Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType,
77
ProperType, get_proper_type, TypeAliasType)
@@ -10,6 +10,7 @@
1010

1111
def is_same_type(left: Type, right: Type) -> bool:
1212
"""Is 'left' the same type as 'right'?"""
13+
1314
left = get_proper_type(left)
1415
right = get_proper_type(right)
1516

@@ -150,6 +151,12 @@ def visit_union_type(self, left: UnionType) -> bool:
150151
else:
151152
return False
152153

154+
def visit_type_guard_type(self, left: TypeGuardType) -> bool:
155+
if isinstance(self.right, TypeGuardType):
156+
return is_same_type(left.type_guard, self.right.type_guard)
157+
else:
158+
return False
159+
153160
def visit_overloaded(self, left: Overloaded) -> bool:
154161
if isinstance(self.right, Overloaded):
155162
return is_same_types(left.items(), self.right.items())

mypy/server/astdiff.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method'
5757
FuncBase, OverloadedFuncDef, FuncItem, MypyFile, UNBOUND_IMPORTED
5858
)
5959
from mypy.types import (
60-
Type, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType,
60+
Type, TypeGuardType, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType,
6161
ErasedType, DeletedType, Instance, TypeVarType, CallableType, TupleType, TypedDictType,
6262
UnionType, Overloaded, PartialType, TypeType, LiteralType, TypeAliasType
6363
)
@@ -335,6 +335,9 @@ def visit_union_type(self, typ: UnionType) -> SnapshotItem:
335335
normalized = tuple(sorted(items))
336336
return ('UnionType', normalized)
337337

338+
def visit_type_guard_type(self, typ: TypeGuardType) -> SnapshotItem:
339+
return ('TypeGuardType', snapshot_type(typ.type_guard))
340+
338341
def visit_overloaded(self, typ: Overloaded) -> SnapshotItem:
339342
return ('Overloaded', snapshot_types(typ.items()))
340343

mypy/server/astmerge.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, ErasedType, DeletedType,
6060
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
6161
Overloaded, TypeVarDef, TypeList, CallableArgument, EllipsisType, StarType, LiteralType,
62-
RawExpressionType, PartialType, PlaceholderType, TypeAliasType
62+
RawExpressionType, PartialType, PlaceholderType, TypeAliasType, TypeGuardType
6363
)
6464
from mypy.util import get_prefix, replace_object_state
6565
from mypy.typestate import TypeState
@@ -389,6 +389,9 @@ def visit_erased_type(self, t: ErasedType) -> None:
389389
def visit_deleted_type(self, typ: DeletedType) -> None:
390390
pass
391391

392+
def visit_type_guard_type(self, typ: TypeGuardType) -> None:
393+
raise RuntimeError
394+
392395
def visit_partial_type(self, typ: PartialType) -> None:
393396
raise RuntimeError
394397

mypy/server/deps.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a
9797
Type, Instance, AnyType, NoneType, TypeVisitor, CallableType, DeletedType, PartialType,
9898
TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType,
9999
FunctionLike, Overloaded, TypeOfAny, LiteralType, ErasedType, get_proper_type, ProperType,
100-
TypeAliasType)
100+
TypeAliasType, TypeGuardType
101+
)
101102
from mypy.server.trigger import make_trigger, make_wildcard_trigger
102103
from mypy.util import correct_relative_import
103104
from mypy.scope import Scope
@@ -970,6 +971,9 @@ def visit_unbound_type(self, typ: UnboundType) -> List[str]:
970971
def visit_uninhabited_type(self, typ: UninhabitedType) -> List[str]:
971972
return []
972973

974+
def visit_type_guard_type(self, typ: TypeGuardType) -> List[str]:
975+
return typ.type_guard.accept(self)
976+
973977
def visit_union_type(self, typ: UnionType) -> List[str]:
974978
triggers = []
975979
for item in typ.items:

mypy/subtypes.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing_extensions import Final
55

66
from mypy.types import (
7-
Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneType,
7+
Type, AnyType, TypeGuardType, UnboundType, TypeVisitor, FormalArgument, NoneType,
88
Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded,
99
ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance,
1010
FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType
@@ -475,6 +475,9 @@ def visit_overloaded(self, left: Overloaded) -> bool:
475475
def visit_union_type(self, left: UnionType) -> bool:
476476
return all(self._is_subtype(item, self.orig_right) for item in left.items)
477477

478+
def visit_type_guard_type(self, left: TypeGuardType) -> bool:
479+
raise RuntimeError("TypeGuard should not appear here")
480+
478481
def visit_partial_type(self, left: PartialType) -> bool:
479482
# This is indeterminate as we don't really know the complete type yet.
480483
raise RuntimeError
@@ -1374,6 +1377,14 @@ def visit_overloaded(self, left: Overloaded) -> bool:
13741377
def visit_union_type(self, left: UnionType) -> bool:
13751378
return all([self._is_proper_subtype(item, self.orig_right) for item in left.items])
13761379

1380+
def visit_type_guard_type(self, left: TypeGuardType) -> bool:
1381+
if isinstance(self.right, TypeGuardType):
1382+
# TypeGuard[bool] is a subtype of TypeGuard[int]
1383+
return self._is_proper_subtype(left.type_guard, self.right.type_guard)
1384+
else:
1385+
# TypeGuards aren't a subtype of anything else for now (but see #10489)
1386+
return False
1387+
13771388
def visit_partial_type(self, left: PartialType) -> bool:
13781389
# TODO: What's the right thing to do here?
13791390
return False

mypy/type_visitor.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
T = TypeVar('T')
2020

2121
from mypy.types import (
22-
Type, AnyType, CallableType, Overloaded, TupleType, TypedDictType, LiteralType,
22+
Type, AnyType, CallableType, Overloaded, TupleType, TypeGuardType, TypedDictType, LiteralType,
2323
RawExpressionType, Instance, NoneType, TypeType,
2424
UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarLikeDef,
2525
UnboundType, ErasedType, StarType, EllipsisType, TypeList, CallableArgument,
@@ -103,6 +103,10 @@ def visit_type_type(self, t: TypeType) -> T:
103103
def visit_type_alias_type(self, t: TypeAliasType) -> T:
104104
pass
105105

106+
@abstractmethod
107+
def visit_type_guard_type(self, t: TypeGuardType) -> T:
108+
pass
109+
106110

107111
@trait
108112
@mypyc_attr(allow_interpreted_subclasses=True)
@@ -220,6 +224,9 @@ def visit_union_type(self, t: UnionType) -> Type:
220224
def translate_types(self, types: Iterable[Type]) -> List[Type]:
221225
return [t.accept(self) for t in types]
222226

227+
def visit_type_guard_type(self, t: TypeGuardType) -> Type:
228+
return TypeGuardType(t.type_guard.accept(self))
229+
223230
def translate_variables(self,
224231
variables: Sequence[TypeVarLikeDef]) -> Sequence[TypeVarLikeDef]:
225232
return variables
@@ -319,6 +326,9 @@ def visit_star_type(self, t: StarType) -> T:
319326
def visit_union_type(self, t: UnionType) -> T:
320327
return self.query_types(t.items)
321328

329+
def visit_type_guard_type(self, t: TypeGuardType) -> T:
330+
return t.type_guard.accept(self)
331+
322332
def visit_overloaded(self, t: Overloaded) -> T:
323333
return self.query_types(t.items())
324334

mypy/typeanal.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from mypy.types import (
1515
Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType,
1616
CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor,
17-
StarType, PartialType, EllipsisType, UninhabitedType, TypeType,
17+
StarType, PartialType, EllipsisType, UninhabitedType, TypeType, TypeGuardType,
1818
CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType,
1919
PlaceholderType, Overloaded, get_proper_type, TypeAliasType, TypeVarLikeDef, ParamSpecDef
2020
)
@@ -542,6 +542,9 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:
542542
)
543543
return ret
544544

545+
def visit_type_guard_type(self, t: TypeGuardType) -> Type:
546+
return t
547+
545548
def anal_type_guard(self, t: Type) -> Optional[Type]:
546549
if isinstance(t, UnboundType):
547550
sym = self.lookup_qualified(t.name, t)

mypy/types.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,14 @@ def copy_modified(self, *,
270270
self.line, self.column)
271271

272272

273-
class TypeGuardType(Type):
273+
class ProperType(Type):
274+
"""Not a type alias.
275+
276+
Every type except TypeAliasType must inherit from this type.
277+
"""
278+
279+
280+
class TypeGuardType(ProperType):
274281
"""Only used by find_instance_check() etc."""
275282
def __init__(self, type_guard: Type):
276283
super().__init__(line=type_guard.line, column=type_guard.column)
@@ -279,12 +286,8 @@ def __init__(self, type_guard: Type):
279286
def __repr__(self) -> str:
280287
return "TypeGuard({})".format(self.type_guard)
281288

282-
283-
class ProperType(Type):
284-
"""Not a type alias.
285-
286-
Every type except TypeAliasType must inherit from this type.
287-
"""
289+
def accept(self, visitor: 'TypeVisitor[T]') -> T:
290+
return visitor.visit_type_guard_type(self)
288291

289292

290293
class TypeVarId:
@@ -2183,6 +2186,9 @@ def visit_union_type(self, t: UnionType) -> str:
21832186
s = self.list_str(t.items)
21842187
return 'Union[{}]'.format(s)
21852188

2189+
def visit_type_guard_type(self, t: TypeGuardType) -> str:
2190+
return 'TypeGuard[{}]'.format(t.type_guard.accept(self))
2191+
21862192
def visit_partial_type(self, t: PartialType) -> str:
21872193
if t.type is None:
21882194
return '<partial None>'

mypy/typetraverser.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Type, SyntheticTypeVisitor, AnyType, UninhabitedType, NoneType, ErasedType, DeletedType,
77
TypeVarType, LiteralType, Instance, CallableType, TupleType, TypedDictType, UnionType,
88
Overloaded, TypeType, CallableArgument, UnboundType, TypeList, StarType, EllipsisType,
9-
PlaceholderType, PartialType, RawExpressionType, TypeAliasType
9+
PlaceholderType, PartialType, RawExpressionType, TypeAliasType, TypeGuardType
1010
)
1111

1212

@@ -62,6 +62,9 @@ def visit_typeddict_type(self, t: TypedDictType) -> None:
6262
def visit_union_type(self, t: UnionType) -> None:
6363
self.traverse_types(t.items)
6464

65+
def visit_type_guard_type(self, t: TypeGuardType) -> None:
66+
t.type_guard.accept(self)
67+
6568
def visit_overloaded(self, t: Overloaded) -> None:
6669
self.traverse_types(t.items())
6770

test-data/unit/check-typeguard.test

+21
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,24 @@ class C:
294294
class D(C):
295295
def is_float(self, a: object) -> bool: pass # E: Signature of "is_float" incompatible with supertype "C"
296296
[builtins fixtures/tuple.pyi]
297+
298+
[case testTypeGuardInAnd]
299+
from typing import Any
300+
from typing_extensions import TypeGuard
301+
import types
302+
def isclass(a: object) -> bool:
303+
pass
304+
def ismethod(a: object) -> TypeGuard[float]:
305+
pass
306+
def isfunction(a: object) -> TypeGuard[str]:
307+
pass
308+
def isclassmethod(obj: Any) -> bool:
309+
if ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__): # E: "float" has no attribute "__self__"
310+
return True
311+
312+
return False
313+
def coverage(obj: Any) -> bool:
314+
if not (ismethod(obj) or isfunction(obj)):
315+
return True
316+
return False
317+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)