mirror of
https://github.com/python/cpython
synced 2024-07-21 02:05:18 +00:00
[3.12] gh-119698: fix symtable.Class.get_methods
and document its behaviour correctly (#120151) (#120776)
(cherry picked from commit b8a8e04fec
)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
parent
61e37dd4f5
commit
0c6d6ab252
|
@ -127,8 +127,39 @@ Examining Symbol Tables
|
|||
|
||||
.. method:: get_methods()
|
||||
|
||||
Return a tuple containing the names of methods declared in the class.
|
||||
Return a tuple containing the names of method-like functions declared
|
||||
in the class.
|
||||
|
||||
Here, the term 'method' designates *any* function defined in the class
|
||||
body via :keyword:`def` or :keyword:`async def`.
|
||||
|
||||
Functions defined in a deeper scope (e.g., in an inner class) are not
|
||||
picked up by :meth:`get_methods`.
|
||||
|
||||
For example:
|
||||
|
||||
>>> import symtable
|
||||
>>> st = symtable.symtable('''
|
||||
... def outer(): pass
|
||||
...
|
||||
... class A:
|
||||
... def f():
|
||||
... def w(): pass
|
||||
...
|
||||
... def g(self): pass
|
||||
...
|
||||
... @classmethod
|
||||
... async def h(cls): pass
|
||||
...
|
||||
... global outer
|
||||
... def outer(self): pass
|
||||
... ''', 'test', 'exec')
|
||||
>>> class_A = st.get_children()[1]
|
||||
>>> class_A.get_methods()
|
||||
('f', 'g', 'h')
|
||||
|
||||
Although ``A().f()`` raises :exc:`TypeError` at runtime, ``A.f`` is still
|
||||
considered as a method-like function.
|
||||
|
||||
.. class:: Symbol
|
||||
|
||||
|
|
|
@ -217,8 +217,25 @@ def get_methods(self):
|
|||
"""
|
||||
if self.__methods is None:
|
||||
d = {}
|
||||
|
||||
def is_local_symbol(ident):
|
||||
flags = self._table.symbols.get(ident, 0)
|
||||
return ((flags >> SCOPE_OFF) & SCOPE_MASK) == LOCAL
|
||||
|
||||
for st in self._table.children:
|
||||
d[st.name] = 1
|
||||
# pick the function-like symbols that are local identifiers
|
||||
if is_local_symbol(st.name):
|
||||
match st.type:
|
||||
case _symtable.TYPE_FUNCTION:
|
||||
d[st.name] = 1
|
||||
case _symtable.TYPE_TYPE_PARAM:
|
||||
# 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
|
||||
break
|
||||
self.__methods = tuple(d)
|
||||
return self.__methods
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
glob = 42
|
||||
some_var = 12
|
||||
some_non_assigned_global_var = 11
|
||||
some_non_assigned_global_var: int
|
||||
some_assigned_global_var = 11
|
||||
|
||||
class Mine:
|
||||
|
@ -51,6 +51,120 @@ class GenericMine[T: int]:
|
|||
pass
|
||||
"""
|
||||
|
||||
TEST_COMPLEX_CLASS_CODE = """
|
||||
# The following symbols are defined in ComplexClass
|
||||
# without being introduced by a 'global' statement.
|
||||
glob_unassigned_meth: Any
|
||||
glob_unassigned_meth_pep_695: Any
|
||||
|
||||
glob_unassigned_async_meth: Any
|
||||
glob_unassigned_async_meth_pep_695: Any
|
||||
|
||||
def glob_assigned_meth(): pass
|
||||
def glob_assigned_meth_pep_695[T](): pass
|
||||
|
||||
async def glob_assigned_async_meth(): pass
|
||||
async def glob_assigned_async_meth_pep_695[T](): pass
|
||||
|
||||
# The following symbols are defined in ComplexClass after
|
||||
# being introduced by a 'global' statement (and therefore
|
||||
# are not considered as local symbols of ComplexClass).
|
||||
glob_unassigned_meth_ignore: Any
|
||||
glob_unassigned_meth_pep_695_ignore: Any
|
||||
|
||||
glob_unassigned_async_meth_ignore: Any
|
||||
glob_unassigned_async_meth_pep_695_ignore: Any
|
||||
|
||||
def glob_assigned_meth_ignore(): pass
|
||||
def glob_assigned_meth_pep_695_ignore[T](): pass
|
||||
|
||||
async def glob_assigned_async_meth_ignore(): pass
|
||||
async def glob_assigned_async_meth_pep_695_ignore[T](): pass
|
||||
|
||||
class ComplexClass:
|
||||
a_var = 1234
|
||||
a_genexpr = (x for x in [])
|
||||
a_lambda = lambda x: x
|
||||
|
||||
type a_type_alias = int
|
||||
type a_type_alias_pep_695[T] = list[T]
|
||||
|
||||
class a_class: pass
|
||||
class a_class_pep_695[T]: pass
|
||||
|
||||
def a_method(self): pass
|
||||
def a_method_pep_695[T](self): pass
|
||||
|
||||
async def an_async_method(self): pass
|
||||
async def an_async_method_pep_695[T](self): pass
|
||||
|
||||
@classmethod
|
||||
def a_classmethod(cls): pass
|
||||
@classmethod
|
||||
def a_classmethod_pep_695[T](self): pass
|
||||
|
||||
@classmethod
|
||||
async def an_async_classmethod(cls): pass
|
||||
@classmethod
|
||||
async def an_async_classmethod_pep_695[T](self): pass
|
||||
|
||||
@staticmethod
|
||||
def a_staticmethod(): pass
|
||||
@staticmethod
|
||||
def a_staticmethod_pep_695[T](self): pass
|
||||
|
||||
@staticmethod
|
||||
async def an_async_staticmethod(): pass
|
||||
@staticmethod
|
||||
async def an_async_staticmethod_pep_695[T](self): pass
|
||||
|
||||
# These ones will be considered as methods because of the 'def' although
|
||||
# they are *not* valid methods at runtime since they are not decorated
|
||||
# with @staticmethod.
|
||||
def a_fakemethod(): pass
|
||||
def a_fakemethod_pep_695[T](): pass
|
||||
|
||||
async def an_async_fakemethod(): pass
|
||||
async def an_async_fakemethod_pep_695[T](): pass
|
||||
|
||||
# Check that those are still considered as methods
|
||||
# since they are not using the 'global' keyword.
|
||||
def glob_unassigned_meth(): pass
|
||||
def glob_unassigned_meth_pep_695[T](): pass
|
||||
|
||||
async def glob_unassigned_async_meth(): pass
|
||||
async def glob_unassigned_async_meth_pep_695[T](): pass
|
||||
|
||||
def glob_assigned_meth(): pass
|
||||
def glob_assigned_meth_pep_695[T](): pass
|
||||
|
||||
async def glob_assigned_async_meth(): pass
|
||||
async def glob_assigned_async_meth_pep_695[T](): pass
|
||||
|
||||
# The following are not picked as local symbols because they are not
|
||||
# visible by the class at runtime (this is equivalent to having the
|
||||
# definitions outside of the class).
|
||||
global glob_unassigned_meth_ignore
|
||||
def glob_unassigned_meth_ignore(): pass
|
||||
global glob_unassigned_meth_pep_695_ignore
|
||||
def glob_unassigned_meth_pep_695_ignore[T](): pass
|
||||
|
||||
global glob_unassigned_async_meth_ignore
|
||||
async def glob_unassigned_async_meth_ignore(): pass
|
||||
global glob_unassigned_async_meth_pep_695_ignore
|
||||
async def glob_unassigned_async_meth_pep_695_ignore[T](): pass
|
||||
|
||||
global glob_assigned_meth_ignore
|
||||
def glob_assigned_meth_ignore(): pass
|
||||
global glob_assigned_meth_pep_695_ignore
|
||||
def glob_assigned_meth_pep_695_ignore[T](): pass
|
||||
|
||||
global glob_assigned_async_meth_ignore
|
||||
async def glob_assigned_async_meth_ignore(): pass
|
||||
global glob_assigned_async_meth_pep_695_ignore
|
||||
async def glob_assigned_async_meth_pep_695_ignore[T](): pass
|
||||
"""
|
||||
|
||||
|
||||
def find_block(block, name):
|
||||
for ch in block.get_children():
|
||||
|
@ -63,6 +177,7 @@ class SymtableTest(unittest.TestCase):
|
|||
top = symtable.symtable(TEST_CODE, "?", "exec")
|
||||
# These correspond to scopes in TEST_CODE
|
||||
Mine = find_block(top, "Mine")
|
||||
|
||||
a_method = find_block(Mine, "a_method")
|
||||
spam = find_block(top, "spam")
|
||||
internal = find_block(spam, "internal")
|
||||
|
@ -238,6 +353,24 @@ def test_name(self):
|
|||
def test_class_info(self):
|
||||
self.assertEqual(self.Mine.get_methods(), ('a_method',))
|
||||
|
||||
top = symtable.symtable(TEST_COMPLEX_CLASS_CODE, "?", "exec")
|
||||
this = find_block(top, "ComplexClass")
|
||||
|
||||
self.assertEqual(this.get_methods(), (
|
||||
'a_method', 'a_method_pep_695',
|
||||
'an_async_method', 'an_async_method_pep_695',
|
||||
'a_classmethod', 'a_classmethod_pep_695',
|
||||
'an_async_classmethod', 'an_async_classmethod_pep_695',
|
||||
'a_staticmethod', 'a_staticmethod_pep_695',
|
||||
'an_async_staticmethod', 'an_async_staticmethod_pep_695',
|
||||
'a_fakemethod', 'a_fakemethod_pep_695',
|
||||
'an_async_fakemethod', 'an_async_fakemethod_pep_695',
|
||||
'glob_unassigned_meth', 'glob_unassigned_meth_pep_695',
|
||||
'glob_unassigned_async_meth', 'glob_unassigned_async_meth_pep_695',
|
||||
'glob_assigned_meth', 'glob_assigned_meth_pep_695',
|
||||
'glob_assigned_async_meth', 'glob_assigned_async_meth_pep_695',
|
||||
))
|
||||
|
||||
def test_filename_correct(self):
|
||||
### Bug tickler: SyntaxError file name correct whether error raised
|
||||
### while parsing or building symbol table.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Fix :meth:`symtable.Class.get_methods` and document its behaviour. Patch by
|
||||
Bénédikt Tran.
|
Loading…
Reference in a new issue