New GitHub issue #95921 from 15r10nk:<br>
<hr>
<pre>
# Bug report
expressions like
``` python
assert 1==x==1
```
have wrong position information in the bytecode (it has the range of the whole assertion instead only the comparison).
This becomes a problem if `==` is defined for a class and raises an exception.
The traceback does not shows the correct error location in this case.
test script:
``` python
import dis
import ast
class T:
def __eq__(self,other):
# use the other assert here to see how the traceback should look like
# this makes the 5==T() in foo() fail -> we get no error location
assert other==7
# this makes the 7==T() in foo() fail -> we get correct error location
#assert other==5
return True
def foo():
assert 5==T()==5 and 7==T()
print("show an example ast where the positions are correct")
node=ast.parse("assert 5==T()==5 and 7==T")
print(ast.dump(node,indent=2,include_attributes=True))
print("bytecode where the double comparison operations have the positions of the assertion")
for bc in dis.get_instructions(foo.__code__):
print(bc.positions,bc.opname,bc.argval)
print("the error message without error position information (this ^^^^^ lines)")
foo()
```
output:
```
show an example ast where the positions are correct
Module(
body=[
Assert(
test=BoolOp(
op=And(),
values=[
Compare(
left=Constant(
value=5,
lineno=1,
col_offset=7,
end_lineno=1,
end_col_offset=8),
ops=[
Eq(),
Eq()],
comparators=[
Call(
func=Name(
id='T',
ctx=Load(),
lineno=1,
col_offset=10,
end_lineno=1,
end_col_offset=11),
args=[],
keywords=[],
lineno=1,
col_offset=10,
end_lineno=1,
end_col_offset=13),
Constant(
value=5,
lineno=1,
col_offset=15,
end_lineno=1,
end_col_offset=16)],
lineno=1,
col_offset=7,
end_lineno=1,
end_col_offset=16),
Compare(
left=Constant(
value=7,
lineno=1,
col_offset=21,
end_lineno=1,
end_col_offset=22),
ops=[
Eq()],
comparators=[
Name(
id='T',
ctx=Load(),
lineno=1,
col_offset=24,
end_lineno=1,
end_col_offset=25)],
lineno=1,
col_offset=21,
end_lineno=1,
end_col_offset=25)],
lineno=1,
col_offset=7,
end_lineno=1,
end_col_offset=25),
lineno=1,
col_offset=0,
end_lineno=1,
end_col_offset=25)],
type_ignores=[])
bytecode where the double comparison operations have the positions of the assertion
Positions(lineno=17, end_lineno=17, col_offset=0, end_col_offset=0) RESUME 0
Positions(lineno=18, end_lineno=18, col_offset=11, end_col_offset=12) LOAD_CONST 5
Positions(lineno=18, end_lineno=18, col_offset=14, end_col_offset=15) LOAD_GLOBAL T
Positions(lineno=18, end_lineno=18, col_offset=14, end_col_offset=17) CALL 0
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) SWAP 2
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) COPY 2
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) COMPARE_OP ==
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) POP_JUMP_FORWARD_IF_FALSE 50
Positions(lineno=18, end_lineno=18, col_offset=19, end_col_offset=20) LOAD_CONST 5
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) COMPARE_OP ==
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) POP_JUMP_FORWARD_IF_FALSE 86
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) JUMP_FORWARD 54
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) POP_TOP None
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) JUMP_FORWARD 86
Positions(lineno=18, end_lineno=18, col_offset=25, end_col_offset=26) LOAD_CONST 7
Positions(lineno=18, end_lineno=18, col_offset=28, end_col_offset=29) LOAD_GLOBAL T
Positions(lineno=18, end_lineno=18, col_offset=28, end_col_offset=31) CALL 0
Positions(lineno=18, end_lineno=18, col_offset=25, end_col_offset=31) COMPARE_OP ==
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) POP_JUMP_FORWARD_IF_TRUE 90
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) LOAD_ASSERTION_ERROR None
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) RAISE_VARARGS 1
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) LOAD_CONST None
Positions(lineno=18, end_lineno=18, col_offset=4, end_col_offset=31) RETURN_VALUE None
the error message without error position information (this ^^^^^ lines)
Traceback (most recent call last):
File "/home/frank/cpython/../executing/assert_test.py", line 30, in <module>
foo()
File "/home/frank/cpython/../executing/assert_test.py", line 18, in foo
assert 5==T()==5 and 7==T()
File "/home/frank/cpython/../executing/assert_test.py", line 9, in __eq__
assert other==7
AssertionError
```
The ast seems to be correct.
The first two `COMPARE_OP ==` operators have the wrong column range (4-31).
The operator without changing has the correct one (25-31).
traceback with `assert other==5` in `T.__eq__`:
```
Traceback (most recent call last):
File "/home/frank/cpython/../executing/assert_test.py", line 30, in <module>
foo()
File "/home/frank/cpython/../executing/assert_test.py", line 18, in foo
assert 5==T()==5 and 7==T()
^^^^^^
File "/home/frank/cpython/../executing/assert_test.py", line 12, in __eq__
assert other==5
AssertionError
```
This shows the correct source range for 7==T()
I would expect the same for the chained comparison.
I think it would be only possible to mark `5==T()==5` because the ast has only a single node for the comparison.
The bug appears also for other comparisons like `<=` but requires the `assert`.
# Your environment
verified with current cpython main 7da4937748eb588bb0e977839061ce76cab1b252
(on a wsl2 but this should not be important)
</pre>
<hr>
<a href="https://github.com/python/cpython/issues/95921">View on GitHub</a>
<p>Labels: type-bug</p>
<p>Assignee: </p>