https://github.com/python/cpython/commit/6682d916780c1cb305e679a057ee6992b114118e
commit: 6682d916780c1cb305e679a057ee6992b114118e
branch: main
author: Bénédikt Tran <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2024-07-17T06:27:35-07:00
summary:
gh-119698: fix a special case in `symtable.Class.get_methods` (#121802)
files:
M Lib/symtable.py
M Lib/test/test_symtable.py
diff --git a/Lib/symtable.py b/Lib/symtable.py
index 221aaf88d41670..8c796b7cc7edb8 100644
--- a/Lib/symtable.py
+++ b/Lib/symtable.py
@@ -249,6 +249,11 @@ 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
@@ -256,7 +261,14 @@ def is_local_symbol(ident):
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
diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py
index 0cc192655931ba..82f667d3687fee 100644
--- a/Lib/test/test_symtable.py
+++ b/Lib/test/test_symtable.py
@@ -1,6 +1,8 @@
"""
Test the API of the symtable module.
"""
+
+import textwrap
import symtable
import unittest
@@ -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")
@@ -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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]