https://github.com/python/cpython/commit/c6ef5aca614e3ee1cfe4125148d80e0e0bd77462
commit: c6ef5aca614e3ee1cfe4125148d80e0e0bd77462
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2024-07-17T13:54:40Z
summary:

[3.13] gh-119698: fix a special case in `symtable.Class.get_methods` 
(GH-121802) (#121909)

(cherry picked from commit 6682d916780c1cb305e679a057ee6992b114118e)

Co-authored-by: Bénédikt Tran <[email protected]>

files:
M Lib/symtable.py
M Lib/test/test_symtable.py

diff --git a/Lib/symtable.py b/Lib/symtable.py
index 73e9fb318ad917..672ec0ce1ff8fe 100644
--- a/Lib/symtable.py
+++ b/Lib/symtable.py
@@ -238,6 +238,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
@@ -245,7 +250,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 2443898c981a00..c1b7030d2d250c 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
 
@@ -354,7 +356,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")
@@ -375,6 +377,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]

Reply via email to