Skip to content

Commit 712af91

Browse files
committed
Fix ast end_location
1 parent 014622a commit 712af91

File tree

2 files changed

+38
-8
lines changed

2 files changed

+38
-8
lines changed

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ def test_invalid_position_information(self):
191191
with self.assertRaises(ValueError):
192192
compile(tree, "<string>", "exec")
193193

194-
@unittest.expectedFailure # TODO: RUSTPYTHON
195194
def test_compilation_of_ast_nodes_with_default_end_position_values(self):
196195
tree = ast.Module(
197196
body=[
@@ -212,7 +211,6 @@ def test_compilation_of_ast_nodes_with_default_end_position_values(self):
212211
# Check that compilation doesn't crash. Note: this may crash explicitly only on debug mode.
213212
compile(tree, "<string>", "exec")
214213

215-
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: required field "end_lineno" missing from alias
216214
def test_negative_locations_for_compile(self):
217215
# See https://github.com/python/cpython/issues/130775
218216
alias = ast.alias(name='traceback', lineno=0, col_offset=0)

crates/vm/src/stdlib/ast.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ fn text_range_to_source_range(source_file: &SourceFile, text_range: TextRange) -
160160
}
161161
}
162162

163+
fn get_opt_int_field(
164+
vm: &VirtualMachine,
165+
obj: &PyObject,
166+
field: &'static str,
167+
) -> PyResult<Option<PyRefExact<PyInt>>> {
168+
match get_node_field_opt(vm, obj, field)? {
169+
Some(val) => val
170+
.downcast_exact(vm)
171+
.map(Some)
172+
.map_err(|_| vm.new_type_error(format!(r#"field "{field}" must have integer type"#))),
173+
None => Ok(None),
174+
}
175+
}
176+
163177
fn range_from_object(
164178
vm: &VirtualMachine,
165179
source_file: &SourceFile,
@@ -168,17 +182,35 @@ fn range_from_object(
168182
) -> PyResult<TextRange> {
169183
let start_row = get_int_field(vm, &object, "lineno", name)?;
170184
let start_column = get_int_field(vm, &object, "col_offset", name)?;
171-
let end_row = get_int_field(vm, &object, "end_lineno", name)?;
172-
let end_column = get_int_field(vm, &object, "end_col_offset", name)?;
185+
// end_lineno and end_col_offset are optional, default to start values
186+
let end_row =
187+
get_opt_int_field(vm, &object, "end_lineno")?.unwrap_or_else(|| start_row.clone());
188+
let end_column =
189+
get_opt_int_field(vm, &object, "end_col_offset")?.unwrap_or_else(|| start_column.clone());
190+
191+
// lineno=0 or negative values as a special case (no location info).
192+
// Use default values (line 1, col 0) when lineno <= 0.
193+
let start_row_val: i32 = start_row.try_to_primitive(vm)?;
194+
let end_row_val: i32 = end_row.try_to_primitive(vm)?;
195+
let start_col_val: i32 = start_column.try_to_primitive(vm)?;
196+
let end_col_val: i32 = end_column.try_to_primitive(vm)?;
173197

174198
let location = PySourceRange {
175199
start: PySourceLocation {
176-
row: Row(OneIndexed::new(start_row.try_to_primitive(vm)?).unwrap()),
177-
column: Column(TextSize::new(start_column.try_to_primitive(vm)?)),
200+
row: Row(if start_row_val > 0 {
201+
OneIndexed::new(start_row_val as usize).unwrap_or(OneIndexed::MIN)
202+
} else {
203+
OneIndexed::MIN
204+
}),
205+
column: Column(TextSize::new(start_col_val.max(0) as u32)),
178206
},
179207
end: PySourceLocation {
180-
row: Row(OneIndexed::new(end_row.try_to_primitive(vm)?).unwrap()),
181-
column: Column(TextSize::new(end_column.try_to_primitive(vm)?)),
208+
row: Row(if end_row_val > 0 {
209+
OneIndexed::new(end_row_val as usize).unwrap_or(OneIndexed::MIN)
210+
} else {
211+
OneIndexed::MIN
212+
}),
213+
column: Column(TextSize::new(end_col_val.max(0) as u32)),
182214
},
183215
};
184216

0 commit comments

Comments
 (0)