Skip to content

Commit f236e36

Browse files
timmartincdce8p
andauthored
Infer the type of the result of calling typing.cast() (#1076)
* Infer the type of the result of calling typing.cast() Co-authored-by: Marc Mueller <[email protected]>
1 parent e7f4def commit f236e36

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ What's New in astroid 2.6.6?
1111
============================
1212
Release date: TBA
1313

14+
* Added support to infer return type of ``typing.cast()``
1415

1516

1617
What's New in astroid 2.6.5?

astroid/brain/brain_typing.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
Call,
2929
Const,
3030
Name,
31+
NodeNG,
3132
Subscript,
3233
)
3334
from astroid.scoped_nodes import ClassDef, FunctionDef
@@ -356,6 +357,36 @@ def infer_tuple_alias(
356357
return iter([class_def])
357358

358359

360+
def _looks_like_typing_cast(node: Call) -> bool:
361+
return isinstance(node, Call) and (
362+
isinstance(node.func, Name)
363+
and node.func.name == "cast"
364+
or isinstance(node.func, Attribute)
365+
and node.func.attrname == "cast"
366+
)
367+
368+
369+
def infer_typing_cast(
370+
node: Call, ctx: context.InferenceContext = None
371+
) -> typing.Iterator[NodeNG]:
372+
"""Infer call to cast() returning same type as casted-from var"""
373+
if not isinstance(node.func, (Name, Attribute)):
374+
raise UseInferenceDefault
375+
376+
try:
377+
func = next(node.func.infer(context=ctx))
378+
except InferenceError as exc:
379+
raise UseInferenceDefault from exc
380+
if (
381+
not isinstance(func, FunctionDef)
382+
or func.qname() != "typing.cast"
383+
or len(node.args) != 2
384+
):
385+
raise UseInferenceDefault
386+
387+
return node.args[1].infer(context=ctx)
388+
389+
359390
AstroidManager().register_transform(
360391
Call,
361392
inference_tip(infer_typing_typevar_or_newtype),
@@ -364,6 +395,9 @@ def infer_tuple_alias(
364395
AstroidManager().register_transform(
365396
Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript
366397
)
398+
AstroidManager().register_transform(
399+
Call, inference_tip(infer_typing_cast), _looks_like_typing_cast
400+
)
367401

368402
if PY39_PLUS:
369403
AstroidManager().register_transform(

tests/unittest_brain.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,38 @@ def test_typing_object_builtin_subscriptable(self):
18101810
self.assertIsInstance(inferred, nodes.ClassDef)
18111811
self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef)
18121812

1813+
def test_typing_cast(self):
1814+
node = builder.extract_node(
1815+
"""
1816+
from typing import cast
1817+
class A:
1818+
pass
1819+
1820+
b = 42
1821+
a = cast(A, b)
1822+
a
1823+
"""
1824+
)
1825+
inferred = next(node.infer())
1826+
assert isinstance(inferred, nodes.Const)
1827+
assert inferred.value == 42
1828+
1829+
def test_typing_cast_attribute(self):
1830+
node = builder.extract_node(
1831+
"""
1832+
import typing
1833+
class A:
1834+
pass
1835+
1836+
b = 42
1837+
a = typing.cast(A, b)
1838+
a
1839+
"""
1840+
)
1841+
inferred = next(node.infer())
1842+
assert isinstance(inferred, nodes.Const)
1843+
assert inferred.value == 42
1844+
18131845

18141846
class ReBrainTest(unittest.TestCase):
18151847
def test_regex_flags(self):

0 commit comments

Comments
 (0)