[ty_test] Store CheckOutput references in SortedCheckOutputs

Update SortedCheckOutputs to store references to CheckOutput instead
of owned values, matching the design of the previous SortedDiagnostics
implementation. This avoids unnecessary cloning and makes the API more
consistent.

Changes:
- SortedCheckOutputs now stores Vec<&CheckOutput>
- new() takes IntoIterator<Item = &CheckOutput>
- LineCheckOutputs.outputs is now &[&CheckOutput]
- Implement Unmatched and UnmatchedWithColumn for &CheckOutput
- Update match_line to take &[&CheckOutput]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Douglas Creager 2025-10-08 14:57:33 -04:00
parent 180d9de472
commit 41772466d5
2 changed files with 29 additions and 23 deletions

View File

@ -39,18 +39,21 @@ impl CheckOutput {
/// [`LineOutputRange`] has one entry for each contiguous slice of the outputs vector /// [`LineOutputRange`] has one entry for each contiguous slice of the outputs vector
/// containing outputs which all start on the same line. /// containing outputs which all start on the same line.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct SortedCheckOutputs { pub(crate) struct SortedCheckOutputs<'a> {
outputs: Vec<CheckOutput>, outputs: Vec<&'a CheckOutput>,
line_ranges: Vec<LineOutputRange>, line_ranges: Vec<LineOutputRange>,
} }
impl SortedCheckOutputs { impl<'a> SortedCheckOutputs<'a> {
pub(crate) fn new(outputs: &[CheckOutput], line_index: &LineIndex) -> Self { pub(crate) fn new(
outputs: impl IntoIterator<Item = &'a CheckOutput>,
line_index: &LineIndex,
) -> Self {
let mut outputs: Vec<_> = outputs let mut outputs: Vec<_> = outputs
.iter() .into_iter()
.map(|output| OutputWithLine { .map(|output| OutputWithLine {
line_number: output.line_number(line_index), line_number: output.line_number(line_index),
output: output.clone(), output,
}) })
.collect(); .collect();
outputs.sort_unstable_by_key(|output_with_line| output_with_line.line_number); outputs.sort_unstable_by_key(|output_with_line| output_with_line.line_number);
@ -104,9 +107,9 @@ impl SortedCheckOutputs {
} }
#[derive(Debug)] #[derive(Debug)]
struct OutputWithLine { struct OutputWithLine<'a> {
line_number: OneIndexed, line_number: OneIndexed,
output: CheckOutput, output: &'a CheckOutput,
} }
/// Range delineating check outputs in [`SortedCheckOutputs`] that begin on a single line. /// Range delineating check outputs in [`SortedCheckOutputs`] that begin on a single line.
@ -118,7 +121,7 @@ struct LineOutputRange {
/// Iterator to group sorted check outputs by line. /// Iterator to group sorted check outputs by line.
pub(crate) struct LineCheckOutputsIterator<'a> { pub(crate) struct LineCheckOutputsIterator<'a> {
outputs: &'a [CheckOutput], outputs: &'a [&'a CheckOutput],
inner: std::slice::Iter<'a, LineOutputRange>, inner: std::slice::Iter<'a, LineOutputRange>,
} }
@ -146,7 +149,7 @@ pub(crate) struct LineCheckOutputs<'a> {
pub(crate) line_number: OneIndexed, pub(crate) line_number: OneIndexed,
/// Check outputs starting on this line. /// Check outputs starting on this line.
pub(crate) outputs: &'a [CheckOutput], pub(crate) outputs: &'a [&'a CheckOutput],
} }
#[cfg(test)] #[cfg(test)]

View File

@ -13,8 +13,8 @@ use ruff_source_file::{LineIndex, OneIndexed};
use crate::assertion::{InlineFileAssertions, ParsedAssertion, UnparsedAssertion}; use crate::assertion::{InlineFileAssertions, ParsedAssertion, UnparsedAssertion};
use crate::check_output::{CheckOutput, SortedCheckOutputs}; use crate::check_output::{CheckOutput, SortedCheckOutputs};
use crate::hover::HoverOutput;
use crate::db::Db; use crate::db::Db;
use crate::hover::HoverOutput;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(super) struct FailuresByLine { pub(super) struct FailuresByLine {
@ -171,7 +171,7 @@ fn maybe_add_undefined_reveal_clarification(
} }
} }
impl Unmatched for CheckOutput { impl Unmatched for &CheckOutput {
fn unmatched(&self) -> String { fn unmatched(&self) -> String {
match self { match self {
CheckOutput::Diagnostic(diag) => diag.unmatched(), CheckOutput::Diagnostic(diag) => diag.unmatched(),
@ -180,7 +180,7 @@ impl Unmatched for CheckOutput {
} }
} }
impl UnmatchedWithColumn for CheckOutput { impl UnmatchedWithColumn for &CheckOutput {
fn unmatched_with_column(&self, column: OneIndexed) -> String { fn unmatched_with_column(&self, column: OneIndexed) -> String {
match self { match self {
CheckOutput::Diagnostic(diag) => diag.unmatched_with_column(column), CheckOutput::Diagnostic(diag) => diag.unmatched_with_column(column),
@ -191,7 +191,11 @@ impl UnmatchedWithColumn for CheckOutput {
impl Unmatched for &HoverOutput { impl Unmatched for &HoverOutput {
fn unmatched(&self) -> String { fn unmatched(&self) -> String {
format!("{} hover result: {}", "unexpected:".red(), self.inferred_type) format!(
"{} hover result: {}",
"unexpected:".red(),
self.inferred_type
)
} }
} }
@ -267,14 +271,14 @@ impl Matcher {
/// assertions. /// assertions.
fn match_line<'a, 'b>( fn match_line<'a, 'b>(
&self, &self,
outputs: &'a [CheckOutput], outputs: &'a [&'a CheckOutput],
assertions: &'a [UnparsedAssertion<'b>], assertions: &'a [UnparsedAssertion<'b>],
) -> Result<(), Vec<String>> ) -> Result<(), Vec<String>>
where where
'b: 'a, 'b: 'a,
{ {
let mut failures = vec![]; let mut failures = vec![];
let mut unmatched: Vec<&CheckOutput> = outputs.iter().collect(); let mut unmatched = outputs.to_vec();
for assertion in assertions { for assertion in assertions {
match assertion.parse(&self.line_index, &self.source) { match assertion.parse(&self.line_index, &self.source) {
Ok(assertion) => { Ok(assertion) => {
@ -308,10 +312,11 @@ impl Matcher {
.column .column
}) })
.unwrap_or(OneIndexed::from_zero_indexed(0)), .unwrap_or(OneIndexed::from_zero_indexed(0)),
CheckOutput::Hover(hover) => self CheckOutput::Hover(hover) => {
.line_index self.line_index
.line_column(hover.offset, &self.source) .line_column(hover.offset, &self.source)
.column, .column
}
} }
} }
@ -335,9 +340,7 @@ impl Matcher {
let lint_name_matches = !error.rule.is_some_and(|rule| { let lint_name_matches = !error.rule.is_some_and(|rule| {
!(diagnostic.id().is_lint_named(rule) || diagnostic.id().as_str() == rule) !(diagnostic.id().is_lint_named(rule) || diagnostic.id().as_str() == rule)
}); });
let column_matches = error let column_matches = error.column.is_none_or(|col| col == self.column(output));
.column
.is_none_or(|col| col == self.column(output));
let message_matches = error.message_contains.is_none_or(|needle| { let message_matches = error.message_contains.is_none_or(|needle| {
diagnostic.concise_message().to_string().contains(needle) diagnostic.concise_message().to_string().contains(needle)
}); });