diff --git a/ChangeLog b/ChangeLog index fa6662083e..9e80772bd3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,8 @@ What's New in astroid 2.13.4? ============================= Release date: TBA +* Fix issues with ``typing_extensions.TypeVar``. + What's New in astroid 2.13.3? diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index c7e847f870..f914162960 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -40,7 +40,15 @@ "enum.IntFlag", } ENUM_QNAME: Final[str] = "enum.Enum" -TYPING_NAMEDTUPLE_BASENAMES: Final[set[str]] = {"NamedTuple", "typing.NamedTuple"} +TYPING_NAMEDTUPLE_QUALIFIED: Final = { + "typing.NamedTuple", + "typing_extensions.NamedTuple", +} +TYPING_NAMEDTUPLE_BASENAMES: Final = { + "NamedTuple", + "typing.NamedTuple", + "typing_extensions.NamedTuple", +} def _infer_first(node, context): @@ -542,7 +550,7 @@ def infer_typing_namedtuple( except (InferenceError, StopIteration) as exc: raise UseInferenceDefault from exc - if func.qname() != "typing.NamedTuple": + if func.qname() not in TYPING_NAMEDTUPLE_QUALIFIED: raise UseInferenceDefault if len(node.args) != 2: diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 15059f440d..b11bfa1965 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -6,6 +6,7 @@ from __future__ import annotations +import sys import typing from collections.abc import Iterator from functools import partial @@ -34,9 +35,18 @@ from astroid.nodes.scoped_nodes import ClassDef, FunctionDef from astroid.util import Uninferable -TYPING_NAMEDTUPLE_BASENAMES = {"NamedTuple", "typing.NamedTuple"} +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + TYPING_TYPEVARS = {"TypeVar", "NewType"} -TYPING_TYPEVARS_QUALIFIED = {"typing.TypeVar", "typing.NewType"} +TYPING_TYPEVARS_QUALIFIED: Final = { + "typing.TypeVar", + "typing.NewType", + "typing_extensions.TypeVar", +} +TYPING_TYPEDDICT_QUALIFIED: Final = {"typing.TypedDict", "typing_extensions.TypedDict"} TYPING_TYPE_TEMPLATE = """ class Meta(type): def __getitem__(self, item): @@ -186,7 +196,7 @@ def _looks_like_typedDict( # pylint: disable=invalid-name node: FunctionDef | ClassDef, ) -> bool: """Check if node is TypedDict FunctionDef.""" - return node.qname() in {"typing.TypedDict", "typing_extensions.TypedDict"} + return node.qname() in TYPING_TYPEDDICT_QUALIFIED def infer_old_typedDict( # pylint: disable=invalid-name diff --git a/requirements_test_brain.txt b/requirements_test_brain.txt index b1b31a646e..b4778baea8 100644 --- a/requirements_test_brain.txt +++ b/requirements_test_brain.txt @@ -9,3 +9,4 @@ types-python-dateutil six types-six urllib3 +typing_extensions>=4.4.0 diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py index 9ee0f98f7d..3374556bcf 100644 --- a/tests/unittest_brain.py +++ b/tests/unittest_brain.py @@ -66,6 +66,15 @@ except ImportError: HAS_SIX = False +try: + import typing_extensions # pylint: disable=unused-import + + HAS_TYPING_EXTENSIONS = True + HAS_TYPING_EXTENSIONS_TYPEVAR = hasattr(typing_extensions, "TypeVar") +except ImportError: + HAS_TYPING_EXTENSIONS = False + HAS_TYPING_EXTENSIONS_TYPEVAR = False + def assertEqualMro(klass: ClassDef, expected_mro: list[str]) -> None: """Check mro names.""" @@ -2148,6 +2157,29 @@ class A: assert inferred.value == 42 +@pytest.mark.skipif( + not HAS_TYPING_EXTENSIONS, + reason="These tests require the typing_extensions library", +) +class TestTypingExtensions: + @staticmethod + @pytest.mark.skipif( + not HAS_TYPING_EXTENSIONS_TYPEVAR, + reason="Need typing_extensions>=4.4.0 to test TypeVar", + ) + def test_typing_extensions_types() -> None: + ast_nodes = builder.extract_node( + """ + from typing_extensions import TypeVar + TypeVar('MyTypeVar', int, float, complex) #@ + TypeVar('AnyStr', str, bytes) #@ + """ + ) + for node in ast_nodes: + inferred = next(node.infer()) + assert isinstance(inferred, nodes.ClassDef) + + class ReBrainTest(unittest.TestCase): def test_regex_flags(self) -> None: names = [name for name in dir(re) if name.isupper()]