gh-118465: Add __firstlineno__ attribute to class (GH-118475)

It is set by compiler with the line number of the first line of
the class definition.
This commit is contained in:
Serhiy Storchaka 2024-05-06 12:02:37 +03:00 committed by GitHub
parent 716ec4bfcf
commit 153b3f7530
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 61 additions and 89 deletions

View file

@ -1035,79 +1035,6 @@ class ClassFoundException(Exception):
pass
class _ClassFinder(ast.NodeVisitor):
def __init__(self, cls, tree, lines, qualname):
self.stack = []
self.cls = cls
self.tree = tree
self.lines = lines
self.qualname = qualname
self.lineno_found = []
def visit_FunctionDef(self, node):
self.stack.append(node.name)
self.stack.append('<locals>')
self.generic_visit(node)
self.stack.pop()
self.stack.pop()
visit_AsyncFunctionDef = visit_FunctionDef
def visit_ClassDef(self, node):
self.stack.append(node.name)
if self.qualname == '.'.join(self.stack):
# Return the decorator for the class if present
if node.decorator_list:
line_number = node.decorator_list[0].lineno
else:
line_number = node.lineno
# decrement by one since lines starts with indexing by zero
self.lineno_found.append((line_number - 1, node.end_lineno))
self.generic_visit(node)
self.stack.pop()
def get_lineno(self):
self.visit(self.tree)
lineno_found_number = len(self.lineno_found)
if lineno_found_number == 0:
raise OSError('could not find class definition')
elif lineno_found_number == 1:
return self.lineno_found[0][0]
else:
# We have multiple candidates for the class definition.
# Now we have to guess.
# First, let's see if there are any method definitions
for member in self.cls.__dict__.values():
if (isinstance(member, types.FunctionType) and
member.__module__ == self.cls.__module__):
for lineno, end_lineno in self.lineno_found:
if lineno <= member.__code__.co_firstlineno <= end_lineno:
return lineno
class_strings = [(''.join(self.lines[lineno: end_lineno]), lineno)
for lineno, end_lineno in self.lineno_found]
# Maybe the class has a docstring and it's unique?
if self.cls.__doc__:
ret = None
for candidate, lineno in class_strings:
if self.cls.__doc__.strip() in candidate:
if ret is None:
ret = lineno
else:
break
else:
if ret is not None:
return ret
# We are out of ideas, just return the last one found, which is
# slightly better than previous ones
return self.lineno_found[-1][0]
def findsource(object):
"""Return the entire source file and starting line number for an object.
@ -1140,11 +1067,11 @@ def findsource(object):
return lines, 0
if isclass(object):
qualname = object.__qualname__
source = ''.join(lines)
tree = ast.parse(source)
class_finder = _ClassFinder(object, tree, lines, qualname)
return lines, class_finder.get_lineno()
try:
firstlineno = object.__firstlineno__
except AttributeError:
raise OSError('source code not available')
return lines, object.__firstlineno__ - 1
if ismethod(object):
object = object.__func__