line number for a return in a with block after 3.10 is that of the with, not the return statement · Issue #93975 · python/cpython (original) (raw)

def foo() -> int: with open('/dev/null') as devnull: return 'a'

In Python 3.10 onwards, the reported line number for the return statement is incorrect.

Python main 3.12-main-ish not far from 3.11:

>>> def foo() -> int:
...   with open('a') as f:
...     return 'b'
... 
>>> dis.dis(foo)
  1           0 RESUME                   0

  2           2 LOAD_GLOBAL              1 (NULL + open)
             14 LOAD_CONST               1 ('a')
             16 CALL                     1
             26 BEFORE_WITH
             28 STORE_FAST               0 (f)

  3          30 NOP

  2          32 LOAD_CONST               0 (None)
             34 LOAD_CONST               0 (None)
             36 LOAD_CONST               0 (None)
             38 CALL                     2
             48 POP_TOP
             50 LOAD_CONST               2 ('b')
             52 RETURN_VALUE
        >>   54 PUSH_EXC_INFO
             56 WITH_EXCEPT_START
             58 POP_JUMP_FORWARD_IF_TRUE     4 (to 68)
             60 RERAISE                  2
        >>   62 COPY                     3
             64 POP_EXCEPT
             66 RERAISE                  1
        >>   68 POP_TOP
             70 POP_EXCEPT
             72 POP_TOP
             74 POP_TOP
             76 LOAD_CONST               0 (None)
             78 RETURN_VALUE
ExceptionTable:
  28 to 30 -> 54 [1] lasti
  54 to 60 -> 62 [3] lasti
  68 to 68 -> 62 [3] lasti
>>> sys.version_info
sys.version_info(major=3, minor=12, micro=0, releaselevel='alpha', serial=0)

The only thing attributed to line 3 is a NOP. The RETURN_VALUE of 'b' is listed as line 2. That is wrong.

Python 3.9:

>>> def foo() -> int:
...   with open('a') as f:
...     return 'b'
... 
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (open)
              2 LOAD_CONST               1 ('a')
              4 CALL_FUNCTION            1
              6 SETUP_WITH              18 (to 26)
              8 STORE_FAST               0 (f)

  3          10 POP_BLOCK
             12 LOAD_CONST               0 (None)
             14 DUP_TOP
             16 DUP_TOP
             18 CALL_FUNCTION            3
             20 POP_TOP
             22 LOAD_CONST               2 ('b')
             24 RETURN_VALUE
        >>   26 WITH_EXCEPT_START
             28 POP_JUMP_IF_TRUE        32
             30 RERAISE
        >>   32 POP_TOP
             34 POP_TOP
             36 POP_TOP
             38 POP_EXCEPT
             40 POP_TOP
             42 LOAD_CONST               0 (None)
             44 RETURN_VALUE
>>> sys.version_info
sys.version_info(major=3, minor=9, micro=12, releaselevel='final', serial=0)

Noticed by @martindemello working on a 3.10 error attribution bug in pytype.