Skip to content

Commit

Permalink
gh-119698: fix a special case in symtable.Class.get_methods (#121802)
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz authored Jul 17, 2024
1 parent cffad5c commit 6682d91
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 2 deletions.
14 changes: 13 additions & 1 deletion Lib/symtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,26 @@ def is_local_symbol(ident):
if is_local_symbol(st.name):
match st.type:
case _symtable.TYPE_FUNCTION:
# generators are of type TYPE_FUNCTION with a ".0"
# parameter as a first parameter (which makes them
# distinguishable from a function named 'genexpr')
if st.name == 'genexpr' and '.0' in st.varnames:
continue
d[st.name] = 1
case _symtable.TYPE_TYPE_PARAMETERS:
# Get the function-def block in the annotation
# scope 'st' with the same identifier, if any.
scope_name = st.name
for c in st.children:
if c.name == scope_name and c.type == _symtable.TYPE_FUNCTION:
d[st.name] = 1
# A generic generator of type TYPE_FUNCTION
# cannot be a direct child of 'st' (but it
# can be a descendant), e.g.:
#
# class A:
# type genexpr[genexpr] = (x for x in [])
assert scope_name != 'genexpr' or '.0' not in c.varnames
d[scope_name] = 1
break
self.__methods = tuple(d)
return self.__methods
Expand Down
56 changes: 55 additions & 1 deletion Lib/test/test_symtable.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Test the API of the symtable module.
"""

import textwrap
import symtable
import unittest

Expand Down Expand Up @@ -356,7 +358,7 @@ def test_name(self):
self.assertEqual(self.spam.lookup("x").get_name(), "x")
self.assertEqual(self.Mine.get_name(), "Mine")

def test_class_info(self):
def test_class_get_methods(self):
self.assertEqual(self.Mine.get_methods(), ('a_method',))

top = symtable.symtable(TEST_COMPLEX_CLASS_CODE, "?", "exec")
Expand All @@ -377,6 +379,58 @@ def test_class_info(self):
'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695',
))

# Test generator expressions that are of type TYPE_FUNCTION
# but will not be reported by get_methods() since they are
# not functions per se.
#
# Other kind of comprehensions such as list, set or dict
# expressions do not have the TYPE_FUNCTION type.

def check_body(body, expected_methods):
indented = textwrap.indent(body, ' ' * 4)
top = symtable.symtable(f"class A:\n{indented}", "?", "exec")
this = find_block(top, "A")
self.assertEqual(this.get_methods(), expected_methods)

# statements with 'genexpr' inside it
GENEXPRS = (
'x = (x for x in [])',
'x = (x async for x in [])',
'type x[genexpr = (x for x in [])] = (x for x in [])',
'type x[genexpr = (x async for x in [])] = (x async for x in [])',
'genexpr = (x for x in [])',
'genexpr = (x async for x in [])',
'type genexpr[genexpr = (x for x in [])] = (x for x in [])',
'type genexpr[genexpr = (x async for x in [])] = (x async for x in [])',
)

for gen in GENEXPRS:
# test generator expression
with self.subTest(gen=gen):
check_body(gen, ())

# test generator expression + variable named 'genexpr'
with self.subTest(gen=gen, isvar=True):
check_body('\n'.join((gen, 'genexpr = 1')), ())
check_body('\n'.join(('genexpr = 1', gen)), ())

for paramlist in ('()', '(x)', '(x, y)', '(z: T)'):
for func in (
f'def genexpr{paramlist}:pass',
f'async def genexpr{paramlist}:pass',
f'def genexpr[T]{paramlist}:pass',
f'async def genexpr[T]{paramlist}:pass',
):
with self.subTest(func=func):
# test function named 'genexpr'
check_body(func, ('genexpr',))

for gen in GENEXPRS:
with self.subTest(gen=gen, func=func):
# test generator expression + function named 'genexpr'
check_body('\n'.join((gen, func)), ('genexpr',))
check_body('\n'.join((func, gen)), ('genexpr',))

def test_filename_correct(self):
### Bug tickler: SyntaxError file name correct whether error raised
### while parsing or building symbol table.
Expand Down

0 comments on commit 6682d91

Please sign in to comment.