From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:48:40 +0000 (+0200) Subject: [3.14] gh-137477: Fix inspect.getblock() for generator expressions (GH-137488) (... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ed9c0c3cbe9505eca07f2cb6fb38bed63a56b359;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-137477: Fix inspect.getblock() for generator expressions (GH-137488) (#137993) gh-137477: Fix inspect.getblock() for generator expressions (GH-137488) This fixes also inspect.getsourcelines() and inspect.getsource(). (cherry picked from commit eae9d7de1c45a64076a926d53672823e6ae1777d) Co-authored-by: Serhiy Storchaka --- diff --git a/Lib/inspect.py b/Lib/inspect.py index 183e67fabf96..faaddadbb87d 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1056,7 +1056,7 @@ class BlockFinder: """Provide a tokeneater() method to detect the end of a code block.""" def __init__(self): self.indent = 0 - self.islambda = False + self.singleline = False self.started = False self.passline = False self.indecorator = False @@ -1065,19 +1065,22 @@ class BlockFinder: def tokeneater(self, type, token, srowcol, erowcol, line): if not self.started and not self.indecorator: + if type == tokenize.INDENT or token == "async": + pass # skip any decorators - if token == "@": + elif token == "@": self.indecorator = True - # look for the first "def", "class" or "lambda" - elif token in ("def", "class", "lambda"): - if token == "lambda": - self.islambda = True + else: + # For "def" and "class" scan to the end of the block. + # For "lambda" and generator expression scan to + # the end of the logical line. + self.singleline = token not in ("def", "class") self.started = True self.passline = True # skip to the end of the line elif type == tokenize.NEWLINE: self.passline = False # stop skipping when a NEWLINE is seen self.last = srowcol[0] - if self.islambda: # lambdas always end at the first NEWLINE + if self.singleline: raise EndOfBlock # hitting a NEWLINE when in a decorator without args # ends the decorator diff --git a/Lib/test/test_inspect/inspect_fodder2.py b/Lib/test/test_inspect/inspect_fodder2.py index 43fda6622537..1de283f672d3 100644 --- a/Lib/test/test_inspect/inspect_fodder2.py +++ b/Lib/test/test_inspect/inspect_fodder2.py @@ -369,3 +369,23 @@ class dc364: # line 369 dc370 = dataclasses.make_dataclass('dc370', (('x', int), ('y', int))) dc371 = dataclasses.make_dataclass('dc370', (('x', int), ('y', int)), module=__name__) + +import inspect +import itertools + +# line 376 +ge377 = ( + inspect.currentframe() + for i in itertools.count() +) + +# line 382 +def func383(): + # line 384 + ge385 = ( + inspect.currentframe() + for i in itertools.count() + ) + return ge385 + +pass # end of file diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 0a8926e00cf8..50eb1703125c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1189,12 +1189,18 @@ class TestBuggyCases(GetSourceBase): self.assertSourceEqual(run(mod2.func225), 226, 227) self.assertSourceEqual(mod2.cls226, 231, 235) + self.assertSourceEqual(mod2.cls226.func232, 232, 235) self.assertSourceEqual(run(mod2.cls226().func232), 233, 234) def test_class_definition_same_name_diff_methods(self): self.assertSourceEqual(mod2.cls296, 296, 298) self.assertSourceEqual(mod2.cls310, 310, 312) + def test_generator_expression(self): + self.assertSourceEqual(next(mod2.ge377), 377, 380) + self.assertSourceEqual(next(mod2.func383()), 385, 388) + + class TestNoEOL(GetSourceBase): def setUp(self): self.tempdir = TESTFN + '_dir' diff --git a/Misc/NEWS.d/next/Library/2025-08-06-23-16-42.gh-issue-137477.bk6BDV.rst b/Misc/NEWS.d/next/Library/2025-08-06-23-16-42.gh-issue-137477.bk6BDV.rst new file mode 100644 index 000000000000..a6e097ea0262 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-06-23-16-42.gh-issue-137477.bk6BDV.rst @@ -0,0 +1,2 @@ +Fix :func:`!inspect.getblock`, :func:`inspect.getsourcelines` and +:func:`inspect.getsource` for generator expressions.