[ty_test] Fix column units: use character offset not byte offset
The column field was being treated inconsistently - calculated as a character offset but used as a byte offset. This would break on any line with multi-byte UTF-8 characters before the hover position. Fixes: 1. Use chars().position() instead of find() to get character offset 2. Use LineIndex::offset() with PositionEncoding::Utf32 to properly convert character offset to byte offset (TextSize) 3. Document that column is a UTF-32 character offset This ensures hover assertions work correctly with Unicode text. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -377,7 +377,7 @@ impl std::fmt::Display for ErrorAssertion<'_> {
|
||||
/// A parsed and validated `# hover:` assertion comment.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HoverAssertion<'a> {
|
||||
/// The zero-based column in the line where the down arrow appears.
|
||||
/// The zero-based character offset (UTF-32) in the line where the down arrow appears.
|
||||
/// This indicates the character position in the target line where we should hover.
|
||||
pub(crate) column: usize,
|
||||
|
||||
@@ -397,17 +397,18 @@ impl<'a> HoverAssertion<'a> {
|
||||
return Err(HoverAssertionParseError::EmptyType);
|
||||
}
|
||||
|
||||
// Find the down arrow position within the comment text
|
||||
let arrow_offset_in_comment = full_comment
|
||||
.find('↓')
|
||||
// Find the down arrow position within the comment text (as character offset)
|
||||
let arrow_char_offset_in_comment = full_comment
|
||||
.chars()
|
||||
.position(|c| c == '↓')
|
||||
.ok_or(HoverAssertionParseError::MissingDownArrow)?;
|
||||
|
||||
// Calculate the column within the comment's line
|
||||
// First, get the line and column of the comment's start
|
||||
let comment_line_col = line_index.line_column(comment_range.start(), source);
|
||||
|
||||
// The hover column is the comment's column plus the arrow offset within the comment
|
||||
let column = comment_line_col.column.to_zero_indexed() + arrow_offset_in_comment;
|
||||
// The hover column is the comment's column plus the arrow's character offset within the comment
|
||||
let column = comment_line_col.column.to_zero_indexed() + arrow_char_offset_in_comment;
|
||||
|
||||
Ok(Self {
|
||||
column,
|
||||
|
||||
@@ -8,6 +8,7 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_source_file::{OneIndexed, PositionEncoding, SourceLocation};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
|
||||
@@ -106,11 +107,12 @@ pub(crate) fn generate_hover_outputs(
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the start offset of the target line
|
||||
let target_line_start = lines.line_start(target_line, &source);
|
||||
|
||||
// Calculate the hover position: start of target line + column
|
||||
let hover_offset = target_line_start + TextSize::try_from(hover.column).unwrap();
|
||||
// Convert the character column to a byte offset using LineIndex::offset
|
||||
let hover_location = SourceLocation {
|
||||
line: target_line,
|
||||
character_offset: OneIndexed::from_zero_indexed(hover.column),
|
||||
};
|
||||
let hover_offset = lines.offset(hover_location, &source, PositionEncoding::Utf32);
|
||||
|
||||
// Get the inferred type at that position
|
||||
let Some(inferred_type) = infer_type_at_position(db, file, hover_offset) else {
|
||||
|
||||
Reference in New Issue
Block a user