diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index e6ecdd7..38f6fe2 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -3,6 +3,12 @@ Version history This library adheres to `Semantic Versioning 2.0 `_. +**UNRELEASED** + +- Fixed regression where ``Literal`` inside a ``Union`` had quotes stripped from its + contents, thus typically causing ``NameError`` to be raised when run + (`#372 `_) + **4.0.1** (2023-07-27) - Fixed handling of ``typing_extensions.Literal`` on Python 3.8 and 3.9 when diff --git a/src/typeguard/_transformer.py b/src/typeguard/_transformer.py index 32d284e..d9d692d 100644 --- a/src/typeguard/_transformer.py +++ b/src/typeguard/_transformer.py @@ -363,6 +363,12 @@ def visit(self, node: AST) -> Any: return new_node + def generic_visit(self, node: AST) -> AST: + if isinstance(node, expr) and self._memo.name_matches(node, *literal_names): + return node + + return super().generic_visit(node) + def visit_BinOp(self, node: BinOp) -> Any: self.generic_visit(node) @@ -395,7 +401,7 @@ def visit_Subscript(self, node: Subscript) -> Any: # The subscript of typing(_extensions).Literal can be any arbitrary string, so # don't try to evaluate it as code - if not self._memo.name_matches(node.value, *literal_names) and node.slice: + if node.slice: if isinstance(node.slice, Index): # Python 3.7 and 3.8 slice_value = node.slice.value # type: ignore[attr-defined] diff --git a/tests/dummymodule.py b/tests/dummymodule.py index 4f3b7ae..36aa4a4 100644 --- a/tests/dummymodule.py +++ b/tests/dummymodule.py @@ -333,6 +333,12 @@ def literal(x: Literal["foo"]) -> Literal["foo"]: return y +@typechecked +def literal_in_union(x: Union[Literal["foo"],]) -> Literal["foo"]: + y: Literal["foo"] = x + return y + + @typechecked def typevar_forwardref(x: Type[T]) -> T: return x() diff --git a/tests/test_instrumentation.py b/tests/test_instrumentation.py index 27939ad..c75f841 100644 --- a/tests/test_instrumentation.py +++ b/tests/test_instrumentation.py @@ -324,6 +324,11 @@ def test_literal(dummymodule): assert dummymodule.literal("foo") == "foo" +def test_literal_in_union(dummymodule): + """Regression test for #372.""" + assert dummymodule.literal_in_union("foo") == "foo" + + def test_typevar_forwardref(dummymodule): instance = dummymodule.typevar_forwardref(dummymodule.DummyClass) assert isinstance(instance, dummymodule.DummyClass)