@@ -1640,6 +1640,26 @@ def test_typing_types(self) -> None:
16401640 inferred = next (node .infer ())
16411641 self .assertIsInstance (inferred , nodes .ClassDef , node .as_string ())
16421642
1643+ def test_typing_typevar_bad_args (self ) -> None :
1644+ ast_nodes = builder .extract_node (
1645+ """
1646+ from typing import TypeVar
1647+
1648+ T = TypeVar()
1649+ T #@
1650+
1651+ U = TypeVar(f"U")
1652+ U #@
1653+ """
1654+ )
1655+ assert isinstance (ast_nodes , list )
1656+
1657+ no_args_node = ast_nodes [0 ]
1658+ assert list (no_args_node .infer ()) == [util .Uninferable ]
1659+
1660+ fstr_node = ast_nodes [1 ]
1661+ assert list (fstr_node .infer ()) == [util .Uninferable ]
1662+
16431663 def test_typing_type_without_tip (self ):
16441664 """Regression test for https://github.com/PyCQA/pylint/issues/5770"""
16451665 node = builder .extract_node (
@@ -1651,7 +1671,171 @@ def make_new_type(t):
16511671 """
16521672 )
16531673 with self .assertRaises (UseInferenceDefault ):
1654- astroid .brain .brain_typing .infer_typing_typevar_or_newtype (node .value )
1674+ astroid .brain .brain_typing .infer_typing_newtype (node .value )
1675+
1676+ def test_typing_newtype_attrs (self ) -> None :
1677+ ast_nodes = builder .extract_node (
1678+ """
1679+ from typing import NewType
1680+ import decimal
1681+ from decimal import Decimal
1682+
1683+ NewType("Foo", str) #@
1684+ NewType("Bar", "int") #@
1685+ NewType("Baz", Decimal) #@
1686+ NewType("Qux", decimal.Decimal) #@
1687+ """
1688+ )
1689+ assert isinstance (ast_nodes , list )
1690+
1691+ # Base type given by reference
1692+ foo_node = ast_nodes [0 ]
1693+
1694+ # Should be unambiguous
1695+ foo_inferred_all = list (foo_node .infer ())
1696+ assert len (foo_inferred_all ) == 1
1697+
1698+ foo_inferred = foo_inferred_all [0 ]
1699+ assert isinstance (foo_inferred , astroid .ClassDef )
1700+
1701+ # Check base type method is inferred by accessing one of its methods
1702+ foo_base_class_method = foo_inferred .getattr ("endswith" )[0 ]
1703+ assert isinstance (foo_base_class_method , astroid .FunctionDef )
1704+ assert foo_base_class_method .qname () == "builtins.str.endswith"
1705+
1706+ # Base type given by string (i.e. "int")
1707+ bar_node = ast_nodes [1 ]
1708+ bar_inferred_all = list (bar_node .infer ())
1709+ assert len (bar_inferred_all ) == 1
1710+ bar_inferred = bar_inferred_all [0 ]
1711+ assert isinstance (bar_inferred , astroid .ClassDef )
1712+
1713+ bar_base_class_method = bar_inferred .getattr ("bit_length" )[0 ]
1714+ assert isinstance (bar_base_class_method , astroid .FunctionDef )
1715+ assert bar_base_class_method .qname () == "builtins.int.bit_length"
1716+
1717+ # Decimal may be reexported from an implementation-defined module. For
1718+ # example, in CPython 3.10 this is _decimal, but in PyPy 7.3 it's
1719+ # _pydecimal. So the expected qname needs to be grabbed dynamically.
1720+ decimal_quant_node = builder .extract_node (
1721+ """
1722+ from decimal import Decimal
1723+ Decimal.quantize #@
1724+ """
1725+ )
1726+ assert isinstance (decimal_quant_node , nodes .NodeNG )
1727+
1728+ # Just grab the first result, since infer() may return values for both
1729+ # _decimal and _pydecimal
1730+ decimal_quant_qname = next (decimal_quant_node .infer ()).qname ()
1731+
1732+ # Base type is from an "import from"
1733+ baz_node = ast_nodes [2 ]
1734+ baz_inferred_all = list (baz_node .infer ())
1735+ assert len (baz_inferred_all ) == 1
1736+ baz_inferred = baz_inferred_all [0 ]
1737+ assert isinstance (baz_inferred , astroid .ClassDef )
1738+
1739+ baz_base_class_method = baz_inferred .getattr ("quantize" )[0 ]
1740+ assert isinstance (baz_base_class_method , astroid .FunctionDef )
1741+ assert decimal_quant_qname == baz_base_class_method .qname ()
1742+
1743+ # Base type is from an import
1744+ qux_node = ast_nodes [3 ]
1745+ qux_inferred_all = list (qux_node .infer ())
1746+ qux_inferred = qux_inferred_all [0 ]
1747+ assert isinstance (qux_inferred , astroid .ClassDef )
1748+
1749+ qux_base_class_method = qux_inferred .getattr ("quantize" )[0 ]
1750+ assert isinstance (qux_base_class_method , astroid .FunctionDef )
1751+ assert decimal_quant_qname == qux_base_class_method .qname ()
1752+
1753+ def test_typing_newtype_bad_args (self ) -> None :
1754+ ast_nodes = builder .extract_node (
1755+ """
1756+ from typing import NewType
1757+
1758+ NoArgs = NewType()
1759+ NoArgs #@
1760+
1761+ OneArg = NewType("OneArg")
1762+ OneArg #@
1763+
1764+ ThreeArgs = NewType("ThreeArgs", int, str)
1765+ ThreeArgs #@
1766+
1767+ DynamicArg = NewType(f"DynamicArg", int)
1768+ DynamicArg #@
1769+
1770+ DynamicBase = NewType("DynamicBase", f"int")
1771+ DynamicBase #@
1772+ """
1773+ )
1774+ assert isinstance (ast_nodes , list )
1775+
1776+ node : nodes .NodeNG
1777+ for node in ast_nodes :
1778+ assert list (node .infer ()) == [util .Uninferable ]
1779+
1780+ def test_typing_newtype_user_defined (self ) -> None :
1781+ ast_nodes = builder .extract_node (
1782+ """
1783+ from typing import NewType
1784+
1785+ class A:
1786+ def __init__(self, value: int):
1787+ self.value = value
1788+
1789+ a = A(5)
1790+ a #@
1791+
1792+ B = NewType("B", A)
1793+ b = B(5)
1794+ b #@
1795+ """
1796+ )
1797+ assert isinstance (ast_nodes , list )
1798+
1799+ for node in ast_nodes :
1800+ self ._verify_node_has_expected_attr (node )
1801+
1802+ def test_typing_newtype_forward_reference (self ) -> None :
1803+ # Similar to the test above, but using a forward reference for "A"
1804+ ast_nodes = builder .extract_node (
1805+ """
1806+ from typing import NewType
1807+
1808+ B = NewType("B", "A")
1809+
1810+ class A:
1811+ def __init__(self, value: int):
1812+ self.value = value
1813+
1814+ a = A(5)
1815+ a #@
1816+
1817+ b = B(5)
1818+ b #@
1819+ """
1820+ )
1821+ assert isinstance (ast_nodes , list )
1822+
1823+ for node in ast_nodes :
1824+ self ._verify_node_has_expected_attr (node )
1825+
1826+ def _verify_node_has_expected_attr (self , node : nodes .NodeNG ) -> None :
1827+ inferred_all = list (node .infer ())
1828+ assert len (inferred_all ) == 1
1829+ inferred = inferred_all [0 ]
1830+ assert isinstance (inferred , astroid .Instance )
1831+
1832+ # Should be able to infer that the "value" attr is present on both types
1833+ val = inferred .getattr ("value" )[0 ]
1834+ assert isinstance (val , astroid .AssignAttr )
1835+
1836+ # Sanity check: nonexistent attr is not inferred
1837+ with self .assertRaises (AttributeInferenceError ):
1838+ inferred .getattr ("bad_attr" )
16551839
16561840 def test_namedtuple_nested_class (self ):
16571841 result = builder .extract_node (
0 commit comments