diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | astroid/brain/brain_typing.py | 43 | ||||
-rw-r--r-- | tests/unittest_brain.py | 13 |
3 files changed, 63 insertions, 0 deletions
@@ -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( |