From eb7a6ae50f43f52d55e946e71feaa739c1d40825 Mon Sep 17 00:00:00 2001 From: Levi Gruspe Date: Mon, 12 Sep 2022 15:19:29 +0800 Subject: [PATCH] Disambiguate between str and enum member args to typing.Literal (#7414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> --- doc/whatsnew/fragments/3299.bugfix | 4 ++++ pylint/checkers/variables.py | 21 ++++++++----------- ..._name_in_string_literal_type_annotation.py | 5 +++++ ..._in_string_literal_type_annotation_py38.py | 9 +++++++- ...in_string_literal_type_annotation_py38.txt | 5 +++-- 5 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 doc/whatsnew/fragments/3299.bugfix diff --git a/doc/whatsnew/fragments/3299.bugfix b/doc/whatsnew/fragments/3299.bugfix new file mode 100644 index 0000000000..dd45d19789 --- /dev/null +++ b/doc/whatsnew/fragments/3299.bugfix @@ -0,0 +1,4 @@ +Fix bug in scanning of names inside arguments to `typing.Literal`. +See https://peps.python.org/pep-0586/#literals-enums-and-forward-references for details. + +Refs #3299 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 5dad3be2ed..f860251682 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2945,28 +2945,25 @@ def visit_const(self, node: nodes.Const) -> None: return if not utils.is_node_in_type_annotation_context(node): return - if not node.value.isidentifier(): - try: - annotation = extract_node(node.value) - self._store_type_annotation_node(annotation) - except ValueError: - # e.g. node.value is white space - return - except astroid.AstroidSyntaxError: - # e.g. "?" or ":" in typing.Literal["?", ":"] - return # Check if parent's or grandparent's first child is typing.Literal parent = node.parent if isinstance(parent, nodes.Tuple): parent = parent.parent - if isinstance(parent, nodes.Subscript): origin = next(parent.get_children(), None) if origin is not None and utils.is_typing_literal(origin): return - self._type_annotation_names.append(node.value) + try: + annotation = extract_node(node.value) + self._store_type_annotation_node(annotation) + except ValueError: + # e.g. node.value is white space + pass + except astroid.AstroidSyntaxError: + # e.g. "?" or ":" in typing.Literal["?", ":"] + pass def register(linter: PyLinter) -> None: diff --git a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation.py b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation.py index 389647657b..400e7725e0 100644 --- a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation.py +++ b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation.py @@ -21,6 +21,11 @@ def example3(_: "os.PathLike[str]") -> None: def example4(_: "PathLike[str]") -> None: """unused-import shouldn't be emitted for PathLike.""" +# pylint shouldn't crash with the following strings in a type annotation context +example5: Set[""] +example6: Set[" "] +example7: Set["?"] + class Class: """unused-import shouldn't be emitted for Namespace""" cls: "Namespace" diff --git a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.py b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.py index 497f64937b..96658ae369 100644 --- a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.py +++ b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.py @@ -2,8 +2,10 @@ from argparse import ArgumentParser # [unused-import] from argparse import Namespace # [unused-import] -from typing import Literal as Lit +import http # [unused-import] +from http import HTTPStatus import typing as t +from typing import Literal as Lit # str inside Literal shouldn't be treated as names example1: t.Literal["ArgumentParser", Lit["Namespace", "ArgumentParser"]] @@ -18,3 +20,8 @@ def unused_variable_example(): # pylint shouldn't crash with the following strings in a type annotation context example3: Lit["", " ", "?"] = "?" + + +# See https://peps.python.org/pep-0586/#literals-enums-and-forward-references +example4: t.Literal["http.HTTPStatus.OK", "http.HTTPStatus.NOT_FOUND"] +example5: "t.Literal[HTTPStatus.OK, HTTPStatus.NOT_FOUND]" diff --git a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.txt b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.txt index 082595bfff..6f8c709bf2 100644 --- a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.txt +++ b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py38.txt @@ -1,4 +1,5 @@ unused-import:3:0:3:35::Unused ArgumentParser imported from argparse:UNDEFINED unused-import:4:0:4:30::Unused Namespace imported from argparse:UNDEFINED -unused-variable:13:4:13:9:unused_variable_example:Unused variable 'hello':UNDEFINED -unused-variable:14:4:14:9:unused_variable_example:Unused variable 'world':UNDEFINED +unused-import:5:0:5:11::Unused import http:UNDEFINED +unused-variable:15:4:15:9:unused_variable_example:Unused variable 'hello':UNDEFINED +unused-variable:16:4:16:9:unused_variable_example:Unused variable 'world':UNDEFINED