Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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