Skip to content

Commit

Permalink
Fixed Any being removed from a subscript that still contains other el…
Browse files Browse the repository at this point in the history
…ements

Fixes #373.
  • Loading branch information
agronholm committed Aug 17, 2023
1 parent ee3e1f3 commit d07fbb4
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 5 deletions.
5 changes: 5 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Version history

This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-versioning-200>`_.

**UNRELEASED**

- Fixed ``Any`` being removed from a subscript that still contains other elements
(`#373 <https://github.com/agronholm/typeguard/issues/373>`_

**4.1.1** (2023-08-16)

- Fixed ``suppress_type_checks()`` causing annotated variable assignments to always
Expand Down
18 changes: 13 additions & 5 deletions src/typeguard/_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,15 +349,21 @@ class AnnotationTransformer(NodeTransformer):
def __init__(self, transformer: TypeguardTransformer):
self.transformer = transformer
self._memo = transformer._memo
self._level = 0

def visit(self, node: AST) -> Any:
self._level += 1
new_node = super().visit(node)
self._level -= 1

if isinstance(new_node, Expression) and not hasattr(new_node, "body"):
return None

# Return None if this new node matches a variation of typing.Any
if isinstance(new_node, expr) and self._memo.name_matches(
new_node, *anytype_names
if (
self._level == 0
and isinstance(new_node, expr)
and self._memo.name_matches(new_node, *anytype_names)
):
return None

Expand All @@ -373,9 +379,11 @@ def visit_BinOp(self, node: BinOp) -> Any:
self.generic_visit(node)

if isinstance(node.op, BitOr):
# If either side of the operation resolved to None, return None
if not hasattr(node, "left") or not hasattr(node, "right"):
return None
# Return Any if either side is Any
if self._memo.name_matches(node.left, *anytype_names):
return node.left
elif self._memo.name_matches(node.right, *anytype_names):
return node.right

if sys.version_info < (3, 10):
union_name = self.transformer._get_import("typing", "Union")
Expand Down
29 changes: 29 additions & 0 deletions tests/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,35 @@ def foo(x, y: Any | None) -> Any | None:
)


def test_any_in_nested_dict() -> None:
# Regression test for #373
node = parse(
dedent(
"""
from typing import Any
def foo(x: dict[str, dict[str, Any]]) -> None:
pass
"""
)
)
TypeguardTransformer().visit(node)
assert (
unparse(node)
== dedent(
"""
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
from typing import Any
def foo(x: dict[str, dict[str, Any]]) -> None:
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, dict[str, dict[str, Any]])}, memo)
"""
).strip()
)


def test_avoid_global_names() -> None:
node = parse(
dedent(
Expand Down

0 comments on commit d07fbb4

Please sign in to comment.