Skip to content

Commit

Permalink
Fixed instrumentation mutating function annotations on py < 3.9
Browse files Browse the repository at this point in the history
Fixes #335.
  • Loading branch information
agronholm committed Apr 11, 2023
1 parent dee8c39 commit 7c7acff
Show file tree
Hide file tree
Showing 4 changed files with 12 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v

- Fixed imports guarded by ``if TYPE_CHECKING:`` when used with subscripts
(``SomeType[...]``) being replaced with ``Any[...]`` instead of just ``Any``
- Fixed instrumentation inadvertently mutating a function's annotations on Python 3.7
and 3.8

**4.0.0rc3** (2023-04-10)

Expand Down
8 changes: 4 additions & 4 deletions src/typeguard/_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from collections import defaultdict
from collections.abc import Generator, Sequence
from contextlib import contextmanager
from copy import copy
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any, ClassVar, cast, overload

Expand Down Expand Up @@ -448,7 +448,7 @@ def _use_memo(

# Extract yield, send and return types where possible from a subscripted
# annotation like Generator[int, str, bool]
return_annotation = copy(node.returns)
return_annotation = deepcopy(node.returns)
if detector.contains_yields and new_memo.name_matches(
return_annotation, *generator_names
):
Expand Down Expand Up @@ -622,7 +622,7 @@ def visit_FunctionDef(
all_args.extend(node.args.posonlyargs)

for arg in all_args:
annotation = self._convert_annotation(copy(arg.annotation))
annotation = self._convert_annotation(deepcopy(arg.annotation))
if annotation and not self._memo.name_matches(
annotation, *anytype_names
):
Expand Down Expand Up @@ -898,7 +898,7 @@ def visit_AnnAssign(self, node: AnnAssign) -> Any:
and node.annotation
and isinstance(node.target, Name)
):
annotation = self._convert_annotation(copy(node.annotation))
annotation = self._convert_annotation(deepcopy(node.annotation))
if annotation:
self._memo.variable_annotations[node.target.id] = annotation
if node.value and not self._memo.name_matches(
Expand Down
5 changes: 5 additions & 0 deletions tests/dummymodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,8 @@ def literal(x: Literal["foo"]) -> Literal["foo"]:
@typechecked
def typevar_forwardref(x: Type[T]) -> T:
return x()


def never_called(x: List["NonExistentType"]) -> List["NonExistentType"]: # noqa: F821
"""Regression test for #337."""
return x
2 changes: 1 addition & 1 deletion tests/test_instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def dummymodule(method: str):
config.debug_instrumentation = True
sys.path.insert(0, str(this_dir))
try:
sys.modules.pop("dummymodule", None)
if cached_module_path.exists():
cached_module_path.unlink()
sys.modules.pop("dummymodule", None)

if method == "typechecked":
return import_module("dummymodule")
Expand Down

0 comments on commit 7c7acff

Please sign in to comment.