Skip to content

Incorrect tracing of "if" inside async-for #93061

Closed
@nedbat

Description

@nedbat

Tracing an if-statement in an async-for seems to trace statements that aren't executed. It was correct in 3.9, became wrong in 3.10, and remains wrong in 3.11.

import linecache, sys

def trace(frame, event, arg):
    if frame.f_code.co_filename == globals().get("__file__"):
        lineno = frame.f_lineno
        line = linecache.getline(__file__, lineno).rstrip()
        print("{} {}: {}".format(event[:4], lineno, line))
    return trace

print(sys.version)
sys.settrace(trace)

import asyncio

class CoverMe:
    def __init__(self, number):
        self._number = number

    async def _async_range(self):
        for n in range(self._number):
            yield n

    async def do_something(self):
        accumulated = 0
        async for n in self._async_range():
            accumulated += n
            print(f"{accumulated=}")
            if accumulated > 10:
                break

        return accumulated

async def main():
    print(await CoverMe(10).do_something())

asyncio.run(main())

Running this code with 3.11.0b1 gives:

3.11.0b1 (main, May 12 2022, 06:47:33) [Clang 12.0.0 (clang-1200.0.32.29)]
call 15: class CoverMe:
line 15: class CoverMe:
line 16:     def __init__(self, number):
line 19:     async def _async_range(self):
line 23:     async def do_something(self):
retu 23:     async def do_something(self):
call 33: async def main():
line 34:     print(await CoverMe(10).do_something())
call 16:     def __init__(self, number):
line 17:         self._number = number
retu 17:         self._number = number
call 23:     async def do_something(self):
line 24:         accumulated = 0
line 25:         async for n in self._async_range():
call 19:     async def _async_range(self):
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=0
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=1
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=3
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=6
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=10
line 28:             if accumulated > 10:
line 29:                 break                              <<<<
line 25:         async for n in self._async_range():
call 21:             yield n
line 20:         for n in range(self._number):
line 21:             yield n
retu 21:             yield n
exce 25:         async for n in self._async_range():
line 26:             accumulated += n
line 27:             print(f"{accumulated=}")
accumulated=15
line 28:             if accumulated > 10:
line 29:                 break
line 31:         return accumulated
retu 31:         return accumulated
exce 34:     print(await CoverMe(10).do_something())
15
retu 34:     print(await CoverMe(10).do_something())
call 21:             yield n
exce 21:             yield n
retu 21:             yield n

The lines marked with <<<< show a break statement being traced when it is not executed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions