Skip to content

Commit

Permalink
Fix type query for recursive aliases (#14136)
Browse files Browse the repository at this point in the history
See #14130 for context.

Btw it looks like these `Any` reports are quite broken in general. Some
issues I found:
* Many types are reported twice (even non-recursive)
* Explicit `Any` in alias r.h.s are not counted (because of reckless
`res = make_any_non_explicit(res)` in semanal.py)
* For generic aliases we count their r.h.s. as containing `Any` from
omitted generics

I tried to fix these things, but it is not trivial, so maybe we can do
it later in a separate PR.
  • Loading branch information
ilevkivskyi committed Nov 19, 2022
1 parent a2477ff commit 6cd8e00
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 18 deletions.
24 changes: 8 additions & 16 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,24 +404,16 @@ def visit_placeholder_type(self, t: PlaceholderType) -> T:
return self.query_types(t.args)

def visit_type_alias_type(self, t: TypeAliasType) -> T:
# Skip type aliases already visited types to avoid infinite recursion.
# TODO: Ideally we should fire subvisitors here (or use caching) if we care
# about duplicates.
if t in self.seen_aliases:
return self.strategy([])
self.seen_aliases.add(t)
if self.skip_alias_target:
return self.query_types(t.args)
return get_proper_type(t).accept(self)

def query_types(self, types: Iterable[Type]) -> T:
"""Perform a query for a list of types.
Use the strategy to combine the results.
Skip type aliases already visited types to avoid infinite recursion.
"""
res: list[T] = []
for t in types:
if isinstance(t, TypeAliasType):
# Avoid infinite recursion for recursive type aliases.
# TODO: Ideally we should fire subvisitors here (or use caching) if we care
# about duplicates.
if t in self.seen_aliases:
continue
self.seen_aliases.add(t)
res.append(t.accept(self))
return self.strategy(res)
"""Perform a query for a list of types using the strategy to combine the results."""
return self.strategy([t.accept(self) for t in types])
2 changes: 1 addition & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ
if fullname == "builtins.None":
return NoneType()
elif fullname == "typing.Any" or fullname == "builtins.Any":
return AnyType(TypeOfAny.explicit)
return AnyType(TypeOfAny.explicit, line=t.line, column=t.column)
elif fullname in FINAL_TYPE_NAMES:
self.fail(
"Final can be only used as an outermost qualifier in a variable annotation",
Expand Down
24 changes: 23 additions & 1 deletion test-data/unit/reports.test
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ class A(object):
</body>
</html>

[case testNoCrashRecursiveAliasInReport]
# cmd: mypy --any-exprs-report report n.py

[file n.py]
from typing import Union, List, Any, TypeVar

Nested = List[Union[Any, Nested]]
T = TypeVar("T")
NestedGen = List[Union[T, NestedGen[T]]]

x: Nested
y: NestedGen[int]
z: NestedGen[Any]

[file report/any-exprs.txt]
[outfile report/types-of-anys.txt]
Name Unannotated Explicit Unimported Omitted Generics Error Special Form Implementation Artifact
-----------------------------------------------------------------------------------------------------------------
n 0 4 0 8 0 0 0
-----------------------------------------------------------------------------------------------------------------
Total 0 4 0 8 0 0 0

[case testTypeVarTreatedAsEmptyLine]
# cmd: mypy --html-report report n.py

Expand Down Expand Up @@ -480,7 +502,7 @@ namespace_packages = True
<link rel="stylesheet" type="text/css" href="../../../mypy-html.css">
</head>
<body>
<h2>folder.subfolder.something</h2>
<h2>folder.subfolder.something</h2>
<table>
<caption>folder/subfolder/something.py</caption>
<tbody><tr>
Expand Down

0 comments on commit 6cd8e00

Please sign in to comment.