diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index beb2f29735..7ec39482f8 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -246,23 +246,26 @@ pub(crate) enum UnparsedAssertion<'a> { /// An `# error:` assertion. Error(&'a str), - /// A `# hover:` assertion, with the full comment text including the down arrow. - Hover(&'a str), + /// A `# hover:` assertion. + /// + /// The first string is the expected type (body after `hover:`). + /// The second string is the full comment text (including the down arrow). + Hover(&'a str, &'a str), } impl<'a> UnparsedAssertion<'a> { /// Returns `Some(_)` if the comment starts with `# error:`, `# revealed:`, or `# hover:`, /// indicating that it is an assertion comment. fn from_comment(comment: &'a str) -> Option { - let comment = comment.trim().strip_prefix('#')?.trim(); - let (keyword, body) = comment.split_once(':')?; + let trimmed = comment.trim().strip_prefix('#')?.trim(); + let (keyword, body) = trimmed.split_once(':')?; let keyword = keyword.trim(); let body = body.trim(); match keyword { "revealed" => Some(Self::Revealed(body)), "error" => Some(Self::Error(body)), - "hover" | "↓ hover" => Some(Self::Hover(body)), + "hover" | "↓ hover" => Some(Self::Hover(body, comment)), _ => None, } } @@ -280,9 +283,11 @@ impl<'a> UnparsedAssertion<'a> { Self::Error(error) => ErrorAssertion::from_str(error) .map(ParsedAssertion::Error) .map_err(PragmaParseError::ErrorAssertionParseError), - Self::Hover(hover) => HoverAssertion::from_str(hover) - .map(ParsedAssertion::Hover) - .map_err(PragmaParseError::HoverAssertionParseError), + Self::Hover(expected_type, full_comment) => { + HoverAssertion::from_str(expected_type, full_comment) + .map(ParsedAssertion::Hover) + .map_err(PragmaParseError::HoverAssertionParseError) + } } } } @@ -292,7 +297,7 @@ impl std::fmt::Display for UnparsedAssertion<'_> { match self { Self::Revealed(expected_type) => write!(f, "revealed: {expected_type}"), Self::Error(assertion) => write!(f, "error: {assertion}"), - Self::Hover(expected_type) => write!(f, "hover: {expected_type}"), + Self::Hover(expected_type, _) => write!(f, "hover: {expected_type}"), } } } @@ -367,16 +372,25 @@ pub(crate) struct HoverAssertion<'a> { } impl<'a> HoverAssertion<'a> { - fn from_str(source: &'a str) -> Result { - if source.is_empty() { + fn from_str( + expected_type: &'a str, + full_comment: &'a str, + ) -> Result { + if expected_type.is_empty() { return Err(HoverAssertionParseError::EmptyType); } - // Column will be computed from the comment position in the matcher - // For now, we just validate and store the expected type + // Find the down arrow position in the full comment to determine the column + let arrow_position = full_comment + .find('↓') + .ok_or(HoverAssertionParseError::MissingDownArrow)?; + + // Column is 1-indexed, and the arrow position is 0-indexed + let column = OneIndexed::from_zero_indexed(arrow_position); + Ok(Self { - column: OneIndexed::from_zero_indexed(0), // Placeholder, will be set by matcher - expected_type: source, + column, + expected_type, }) } } diff --git a/crates/ty_test/src/hover.rs b/crates/ty_test/src/hover.rs index 4022a33e63..85508f0926 100644 --- a/crates/ty_test/src/hover.rs +++ b/crates/ty_test/src/hover.rs @@ -95,21 +95,23 @@ pub(crate) fn generate_hover_outputs( // Look for hover assertions in this line's assertions for assertion in line_assertions.iter() { - let crate::assertion::UnparsedAssertion::Hover(hover_text) = assertion else { + let crate::assertion::UnparsedAssertion::Hover(_, _) = assertion else { continue; }; - // Find the down arrow position in the comment text to determine the column - let Some(arrow_position) = hover_text.find('↓') else { - // No down arrow - skip this hover assertion (will be caught as error by matcher) + // Parse the assertion to get the column + let Ok(crate::assertion::ParsedAssertion::Hover(hover)) = assertion.parse() else { + // Invalid hover assertion - will be caught as error by matcher 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 + arrow column (0-indexed) - let hover_offset = target_line_start + TextSize::try_from(arrow_position).unwrap(); + // Calculate the hover position from the column in the parsed assertion + // Column is 1-indexed, so convert to 0-indexed for TextSize + let hover_offset = + target_line_start + TextSize::try_from(hover.column.get() - 1).unwrap(); // Get the inferred type at that position let Some(inferred_type) = infer_type_at_position(db, file, hover_offset) else {