summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--astroid/brain/brain_typing.py43
-rw-r--r--tests/unittest_brain.py13
3 files changed, 63 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 19799af5..7cb0730b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,13 @@ What's New in astroid 2.6.0?
Release Date: TBA
+What's New in astroid 2.5.4?
+============================
+Release Date: TBA
+
+* Added inference tip for ``typing.Tuple`` alias
+
+
What's New in astroid 2.5.3?
============================
Release Date: 2021-04-10
diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py
index 1fb471f1..cd4cd442 100644
--- a/astroid/brain/brain_typing.py
+++ b/astroid/brain/brain_typing.py
@@ -297,6 +297,46 @@ def infer_typing_alias(
return iter([class_def])
+def _looks_like_tuple_alias(node: nodes.Call) -> bool:
+ """Return True if call is for Tuple alias.
+
+ In PY37 and PY38 the call is to '_VariadicGenericAlias' with 'tuple' as
+ first argument. In PY39+ it is replaced by a call to '_TupleType'.
+
+ PY37: Tuple = _VariadicGenericAlias(tuple, (), inst=False, special=True)
+ PY39: Tuple = _TupleType(tuple, -1, inst=False, name='Tuple')
+ """
+ return (
+ isinstance(node, nodes.Call)
+ and isinstance(node.func, nodes.Name)
+ and (
+ not PY39
+ and node.func.name == "_VariadicGenericAlias"
+ and isinstance(node.args[0], nodes.Name)
+ and node.args[0].name == "tuple"
+ or PY39
+ and node.func.name == "_TupleType"
+ and isinstance(node.args[0], nodes.Name)
+ and node.args[0].name == "tuple"
+ )
+ )
+
+
+def infer_tuple_alias(
+ node: nodes.Call, ctx: context.InferenceContext = None
+) -> typing.Iterator[nodes.ClassDef]:
+ """Infer call to tuple alias as new subscriptable class typing.Tuple."""
+ res = next(node.args[0].infer(context=ctx))
+ class_def = nodes.ClassDef(
+ name="Tuple",
+ parent=node.parent,
+ )
+ class_def.postinit(bases=[res], body=[], decorators=None)
+ func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE)
+ class_def.locals["__class_getitem__"] = [func_to_add]
+ return iter([class_def])
+
+
MANAGER.register_transform(
nodes.Call,
inference_tip(infer_typing_typevar_or_newtype),
@@ -315,3 +355,6 @@ if PY37:
MANAGER.register_transform(
nodes.Call, inference_tip(infer_typing_alias), _looks_like_typing_alias
)
+ MANAGER.register_transform(
+ nodes.Call, inference_tip(infer_tuple_alias), _looks_like_tuple_alias
+ )
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 373a67c7..7f81a0c5 100644
--- a/tests/unittest_brain.py
+++ b/tests/unittest_brain.py
@@ -1338,6 +1338,19 @@ class TypingBrain(unittest.TestCase):
self.assertIsInstance(inferred, nodes.ClassDef, node.as_string())
@test_utils.require_version(minver="3.7")
+ def test_tuple_type(self):
+ node = builder.extract_node(
+ """
+ from typing import Tuple
+ Tuple[int, int]
+ """
+ )
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.ClassDef)
+ assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
+ assert inferred.qname() == "typing.Tuple"
+
+ @test_utils.require_version(minver="3.7")
def test_typing_generic_subscriptable(self):
"""Test typing.Generic is subscriptable with __class_getitem__ (added in PY37)"""
node = builder.extract_node(