Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pass to removed blocks of code #386

Merged
merged 7 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.pydevproject
.idea
.tox
.coverage
.coverage*
.cache
.eggs/
*.egg-info/
Expand Down
7 changes: 5 additions & 2 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v
**UNRELEASED**

- Dropped Python 3.7 support
- Fixed ``@typechecked`` optimization causing compilation of instrumented code to fail
when any block was left empty by the AST transformer (eg `if` or `try` / `except` blocks)
(`#352 <https://github.com/agronholm/typeguard/issues/352>`_)

**4.1.2** (2023-08-18)

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

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

Expand All @@ -21,7 +24,7 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v

- Added support for passing a tuple as ``expected_type`` to ``check_type()``, making it
more of a drop-in replacement for ``isinstance()``
(`#371 <https://github.com/agronholm/typeguard/issues/371>`_
(`#371 <https://github.com/agronholm/typeguard/issues/371>`_)
- Fixed regression where ``Literal`` inside a ``Union`` had quotes stripped from its
contents, thus typically causing ``NameError`` to be raised when run
(`#372 <https://github.com/agronholm/typeguard/issues/372>`_)
Expand Down
21 changes: 16 additions & 5 deletions src/typeguard/_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
copy_location,
expr,
fix_missing_locations,
iter_fields,
keyword,
walk,
)
Expand Down Expand Up @@ -498,6 +499,21 @@ def __init__(
self.target_node: FunctionDef | AsyncFunctionDef | None = None
self.target_lineno = target_lineno

def generic_visit(self, node: AST) -> AST:
non_empty_list_fields = []
for field_name, val in iter_fields(node):
if isinstance(val, list) and len(val) > 0:
non_empty_list_fields.append(field_name)

node = super().generic_visit(node)

# Add `pass` to list fields that were optimised away
for field_name in non_empty_list_fields:
if not getattr(node, field_name, None):
setattr(node, field_name, [Pass()])

return node

@contextmanager
def _use_memo(
self, node: ClassDef | FunctionDef | AsyncFunctionDef
Expand Down Expand Up @@ -1175,11 +1191,6 @@ def visit_If(self, node: If) -> Any:
"""
self.generic_visit(node)

# Fix empty node body (caused by removal of classes/functions not on the target
# path)
if not node.body:
node.body.append(Pass())

if (
self._memo is self._module_memo
and isinstance(node.test, Name)
Expand Down
47 changes: 47 additions & 0 deletions tests/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,53 @@ def foo(x: str) -> None:
)


def test_dont_leave_empty_ast_container_nodes_2() -> None:
# Regression test for #352
node = parse(
dedent(
"""
try:

class A:
...

def func():
...

except:

class A:
...

def func():
...


def foo(x: str) -> None:
pass
"""
)
)
TypeguardTransformer(["foo"]).visit(node)
assert (
unparse(node)
== dedent(
"""
try:
pass
except:
pass

def foo(x: str) -> None:
from typeguard import TypeCheckMemo
from typeguard._functions import check_argument_types
memo = TypeCheckMemo(globals(), locals())
check_argument_types('foo', {'x': (x, str)}, memo)
"""
).strip()
)


def test_dont_parse_annotated_2nd_arg() -> None:
# Regression test for #352
node = parse(
Expand Down