@@ -1638,3 +1638,217 @@ def b(a: int = B) -> int:
16381638 assert v .refs == set (["int" ])
16391639 # K should have unbounded_refs only for int, not A
16401640 assert v .variable_data ["K" ][0 ].unbounded_refs == set (["int" ])
1641+
1642+
1643+ def test_class_with_typealias_annotation () -> None :
1644+ """Test that TypeAlias annotations in class scope don't create unbounded refs (issue #7089)."""
1645+ code = cleandoc (
1646+ """
1647+ class EmbeddedTypeAlias:
1648+ MyEmbeddedTypeAlias: TypeAlias = int
1649+
1650+ def __init__(self, value: MyEmbeddedTypeAlias):
1651+ self.value = value
1652+ """
1653+ )
1654+ v = visitor .ScopedVisitor ()
1655+ mod = ast .parse (code )
1656+ v .visit (mod )
1657+
1658+ # EmbeddedTypeAlias should be a def
1659+ assert v .defs == set (["EmbeddedTypeAlias" ])
1660+ # MyEmbeddedTypeAlias should NOT be a ref (it's defined in class scope)
1661+ # TypeAlias and int are refs (type annotations)
1662+ assert v .refs == set (["TypeAlias" , "int" ])
1663+ # Should have unbounded_refs only for TypeAlias and int, not MyEmbeddedTypeAlias
1664+ assert v .variable_data ["EmbeddedTypeAlias" ][0 ].unbounded_refs == set (
1665+ ["TypeAlias" , "int" ]
1666+ )
1667+
1668+
1669+ @pytest .mark .skipif ("sys.version_info < (3, 12)" )
1670+ def test_class_with_type_statement () -> None :
1671+ """Test that type statements in class scope don't create unbounded refs (issue #7089)."""
1672+ code = cleandoc (
1673+ """
1674+ class EmbeddedType:
1675+ type MyEmbeddedType = int
1676+
1677+ def __init__(self, value: MyEmbeddedType):
1678+ self.value = value
1679+ """
1680+ )
1681+ v = visitor .ScopedVisitor ()
1682+ mod = ast .parse (code )
1683+ v .visit (mod )
1684+
1685+ # EmbeddedType should be a def
1686+ assert v .defs == set (["EmbeddedType" ])
1687+ # MyEmbeddedType should NOT be a ref (it's defined in class scope via type statement)
1688+ # int is a ref (used in type statement)
1689+ assert v .refs == set (["int" ])
1690+ # Should have unbounded_refs only for int, not MyEmbeddedType
1691+ assert v .variable_data ["EmbeddedType" ][0 ].unbounded_refs == set (["int" ])
1692+
1693+
1694+ def test_class_with_external_var_in_method_default () -> None :
1695+ """Test that external variables in method defaults ARE flagged as unbounded refs."""
1696+ code = cleandoc (
1697+ """
1698+ class K:
1699+ A: int = 1
1700+ def b(a: int = A, c: int = EXTERNAL) -> int:
1701+ return a + c
1702+ """
1703+ )
1704+ v = visitor .ScopedVisitor ()
1705+ mod = ast .parse (code )
1706+ v .visit (mod )
1707+
1708+ # K should be a def
1709+ assert v .defs == set (["K" ])
1710+ # EXTERNAL should be a ref (not defined in class)
1711+ assert v .refs == set (["int" , "EXTERNAL" ])
1712+ # K should have unbounded_refs for EXTERNAL, but not A
1713+ assert v .variable_data ["K" ][0 ].unbounded_refs == set (["int" , "EXTERNAL" ])
1714+
1715+
1716+ def test_class_with_external_var_in_class_var () -> None :
1717+ """Test that external variables used in class variables ARE flagged as unbounded refs."""
1718+ code = cleandoc (
1719+ """
1720+ class K:
1721+ A: int = 1
1722+ B: int = A + EXTERNAL
1723+ def b(a: int = 1) -> int:
1724+ return a
1725+ """
1726+ )
1727+ v = visitor .ScopedVisitor ()
1728+ mod = ast .parse (code )
1729+ v .visit (mod )
1730+
1731+ # K should be a def
1732+ assert v .defs == set (["K" ])
1733+ # EXTERNAL should be a ref (not defined in class)
1734+ assert v .refs == set (["int" , "EXTERNAL" ])
1735+ # K should have unbounded_refs for EXTERNAL, but not A
1736+ assert v .variable_data ["K" ][0 ].unbounded_refs == set (["int" , "EXTERNAL" ])
1737+
1738+
1739+ def test_class_with_external_type_in_typealias () -> None :
1740+ """Test that external types in TypeAlias ARE flagged as unbounded refs."""
1741+ code = cleandoc (
1742+ """
1743+ class K:
1744+ MyType: TypeAlias = ExternalType
1745+
1746+ def __init__(self, value: MyType):
1747+ self.value = value
1748+ """
1749+ )
1750+ v = visitor .ScopedVisitor ()
1751+ mod = ast .parse (code )
1752+ v .visit (mod )
1753+
1754+ # K should be a def
1755+ assert v .defs == set (["K" ])
1756+ # ExternalType and TypeAlias should be refs (not defined in class)
1757+ assert v .refs == set (["TypeAlias" , "ExternalType" ])
1758+ # K should have unbounded_refs for both TypeAlias and ExternalType, but not MyType
1759+ assert v .variable_data ["K" ][0 ].unbounded_refs == set (
1760+ ["TypeAlias" , "ExternalType" ]
1761+ )
1762+
1763+
1764+ @pytest .mark .skipif ("sys.version_info < (3, 12)" )
1765+ def test_class_with_external_type_in_type_statement () -> None :
1766+ """Test that external types in type statement ARE flagged as unbounded refs."""
1767+ code = cleandoc (
1768+ """
1769+ class K:
1770+ type MyType = ExternalType
1771+
1772+ def __init__(self, value: MyType):
1773+ self.value = value
1774+ """
1775+ )
1776+ v = visitor .ScopedVisitor ()
1777+ mod = ast .parse (code )
1778+ v .visit (mod )
1779+
1780+ # K should be a def
1781+ assert v .defs == set (["K" ])
1782+ # ExternalType should be a ref (not defined in class)
1783+ assert v .refs == set (["ExternalType" ])
1784+ # K should have unbounded_refs for ExternalType, but not MyType
1785+ assert v .variable_data ["K" ][0 ].unbounded_refs == set (["ExternalType" ])
1786+
1787+
1788+ def test_class_with_method_using_external_var () -> None :
1789+ """Test that methods using external variables in body track them as required_refs."""
1790+ code = cleandoc (
1791+ """
1792+ class K:
1793+ def method(self) -> int:
1794+ return EXTERNAL_VAR
1795+ """
1796+ )
1797+ v = visitor .ScopedVisitor ()
1798+ mod = ast .parse (code )
1799+ v .visit (mod )
1800+
1801+ # K should be a def
1802+ assert v .defs == set (["K" ])
1803+ # EXTERNAL_VAR should be a ref (used in method body)
1804+ assert v .refs == set (["EXTERNAL_VAR" , "int" ])
1805+ # K should have EXTERNAL_VAR in required_refs (method needs it)
1806+ assert v .variable_data ["K" ][0 ].required_refs == set (
1807+ ["EXTERNAL_VAR" , "int" ]
1808+ )
1809+ # But not in unbounded_refs (it's in method body, not signature)
1810+ assert v .variable_data ["K" ][0 ].unbounded_refs == set (["int" ])
1811+
1812+
1813+ def test_class_with_forward_reference_to_method () -> None :
1814+ """Test that class variables can reference methods defined earlier (valid)."""
1815+ code = cleandoc (
1816+ """
1817+ class A:
1818+ def method(self):
1819+ return 42
1820+ bound = method
1821+ """
1822+ )
1823+ v = visitor .ScopedVisitor ()
1824+ mod = ast .parse (code )
1825+ v .visit (mod )
1826+
1827+ # A should be a def
1828+ assert v .defs == set (["A" ])
1829+ # No external refs - method is defined within class scope
1830+ assert v .refs == set ()
1831+ # A should have no unbounded refs (forward reference is valid)
1832+ assert v .variable_data ["A" ][0 ].unbounded_refs == set ()
1833+
1834+
1835+ def test_class_with_backward_reference_to_method () -> None :
1836+ """Test that class variables cannot reference methods defined later (invalid for top-level)."""
1837+ code = cleandoc (
1838+ """
1839+ class B:
1840+ bound = method
1841+ def method(self):
1842+ return 42
1843+ """
1844+ )
1845+ v = visitor .ScopedVisitor ()
1846+ mod = ast .parse (code )
1847+ v .visit (mod )
1848+
1849+ # B should be a def
1850+ assert v .defs == set (["B" ])
1851+ # method is referenced before it's defined, so it's an external ref
1852+ assert v .refs == set (["method" ])
1853+ # B should have method in unbounded refs (backward reference is invalid)
1854+ assert v .variable_data ["B" ][0 ].unbounded_refs == set (["method" ])
0 commit comments