-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-135700: Fix instructions in __annotate__ have incorrect code positions #135814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
983ae79
dd3bea2
c2f0a4d
b79060e
2201864
aed06b7
d85247e
eca9248
e14cb29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1124,6 +1124,43 @@ def test_bug_46724(self): | |
# Test that negative operargs are handled properly | ||
self.do_disassembly_test(bug46724, dis_bug46724) | ||
|
||
def test_annotate_source_locations(self): | ||
# See gh-135700 | ||
issue_135700 = "1\nx: int" | ||
issue_135700_class = "class A:\n 1\n x: int" | ||
|
||
test_cases = [ | ||
("module", compile(issue_135700, "<string>", "exec").co_consts[1]), | ||
( | ||
"class", | ||
compile(ast.parse(issue_135700_class), "?", "exec") | ||
.co_consts[0] | ||
.co_consts[1], | ||
), | ||
] | ||
|
||
for case_name, annotate_code in test_cases: | ||
with self.subTest(case=case_name): | ||
line_starts_iterator = dis.findlinestarts(annotate_code) | ||
valid_line_starts = [ | ||
item[0] | ||
for item in line_starts_iterator | ||
if item[1] is not None | ||
] # The first item is not RESUME in class case | ||
setup_scope_begin = valid_line_starts[0] | ||
setup_scope_end = valid_line_starts[1] | ||
setup_annotations_scope_positions = { | ||
instr.positions | ||
for instr in dis.get_instructions(annotate_code) | ||
if setup_scope_begin <= instr.offset < setup_scope_end | ||
and instr.positions | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this need to be so complicated? Don't we just need to check that all the instructions in this code object have the same line number (excluding Nones)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the important thing is to make sure the instructions in no fix: Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) RESUME
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) LOAD_FAST_BORROW # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) LOAD_SMALL_INT # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) COMPARE_OP # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) POP_JUMP_IF_FALSE # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) NOT_TAKEN # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) LOAD_COMMON_CONSTANT # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) RAISE_VARARGS # incorrect
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=2) BUILD_MAP # incorrect fix: Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) RESUME
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) LOAD_FAST_BORROW
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) LOAD_SMALL_INT
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) COMPARE_OP
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) POP_JUMP_IF_FALSE
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) NOT_TAKEN
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) LOAD_COMMON_CONSTANT
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) RAISE_VARARGS
Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0) BUILD_MAP And not all annotate lines are the same, because some opcodes need to indicate the "code" where the annotation occurs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so we need to check they all have the same end_col_offset as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The test doesn't need to work for all inputs. Only the two inputs it runs on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Just to be clear, instructions within code object can correspond to different line numbers. So I made an offset restriction here, limiting the check to between RESUME and BUILD_MAP (setup_annotations_scope) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option is to leave dis and the bytecode alone, and write a test that asserts that something which is user-visible is fixed. How did you find this problem? A traceback pointed to the wrong place? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This issue appears to stem from an implementation choice in a downstream repository. For full context, you can refer to the issue #135700 linked in the PR. My understanding is that the downstream project expects the position information for bytecode within Ultimately, this doesn't seem to be a factual bug, but rather a disagreement or ambiguity in the semantic interpretation of this position information. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe @JelleZijlstra has an idea how to write a meaningful test for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have much of an intuition on how line numbers should work for synthetic code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @15r10nk are you able to suggest a meaningful unit test for this? Alternatively, can you confirm that the PR resolved the issue? |
||
self.assertEqual( | ||
len(setup_annotations_scope_positions), | ||
1, | ||
f"{case_name}: Expected uniform positions, found {len(setup_annotations_scope_positions)}: {setup_annotations_scope_positions}", | ||
) | ||
|
||
def test_kw_names(self): | ||
# Test that value is displayed for keyword argument names: | ||
self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix instructions positions in :attr:`~object.__annotate__`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert here that
annotate_code
is a code object and thatannotate_code.co_name
is'__annotate__'
.