Skip to content

Commit ca201f4

Browse files
authored
Avoid infinite recursion in type queries (#5434)
Fixes #5357 Fixes #4780 This might potentially fix similar crashes on recursive types.
1 parent 61d17ff commit ca201f4

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

mypy/types.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,7 @@ class TypeQuery(SyntheticTypeVisitor[T]):
19151915

19161916
def __init__(self, strategy: Callable[[Iterable[T]], T]) -> None:
19171917
self.strategy = strategy
1918+
self.seen = [] # type: List[Type]
19181919

19191920
def visit_unbound_type(self, t: UnboundType) -> T:
19201921
return self.query_types(t.args)
@@ -1984,8 +1985,17 @@ def query_types(self, types: Iterable[Type]) -> T:
19841985
"""Perform a query for a list of types.
19851986
19861987
Use the strategy to combine the results.
1988+
Skip types already visited types to avoid infinite recursion.
1989+
Note: types can be recursive until they are fully analyzed and "unentangled"
1990+
in patches after the semantic analysis.
19871991
"""
1988-
return self.strategy(t.accept(self) for t in types)
1992+
res = [] # type: List[T]
1993+
for t in types:
1994+
if any(t is s for s in self.seen):
1995+
continue
1996+
self.seen.append(t)
1997+
res.append(t.accept(self))
1998+
return self.strategy(res)
19891999

19902000

19912001
def strip_type(typ: Type) -> Type:

test-data/unit/check-flags.test

+26
Original file line numberDiff line numberDiff line change
@@ -1059,3 +1059,29 @@ ignore_missing_imports = True
10591059
always_true = YOLO1, YOLO
10601060
always_false = BLAH, BLAH1
10611061
[builtins fixtures/bool.pyi]
1062+
1063+
[case testCheckDisallowAnyGenericsNamedTuple]
1064+
# flags: --disallow-any-generics
1065+
from typing import NamedTuple
1066+
1067+
N = NamedTuple('N', [('x', N)]) # type: ignore
1068+
n: N
1069+
[out]
1070+
1071+
[case testCheckDisallowAnyGenericsTypedDict]
1072+
# flags: --disallow-any-generics
1073+
from typing import Dict, Any, Optional
1074+
from mypy_extensions import TypedDict
1075+
1076+
VarsDict = Dict[str, Any]
1077+
HostsDict = Dict[str, Optional[VarsDict]]
1078+
1079+
GroupDataDict = TypedDict( # type: ignore
1080+
"GroupDataDict", {"children": "GroupsDict",
1081+
"vars": VarsDict,
1082+
"hosts": HostsDict}, total=False
1083+
)
1084+
1085+
GroupsDict = Dict[str, GroupDataDict] # type: ignore
1086+
[builtins fixtures/dict.pyi]
1087+
[out]

test-data/unit/fixtures/dict.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ class float: pass
4646
class bool: pass
4747

4848
class ellipsis: pass
49-
def isinstance(x: object, t: Union[type, Tuple]) -> bool: pass
49+
def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass
5050
class BaseException: pass

0 commit comments

Comments
 (0)