Skip to content

Commit 87633b3

Browse files
committed
[red-knot] Show related information in diagnostic
1 parent b86960f commit 87633b3

File tree

4 files changed

+157
-5
lines changed

4 files changed

+157
-5
lines changed

crates/ruff_db/src/diagnostic/mod.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,25 @@ impl Diagnostic {
249249
diagnostic: self,
250250
}
251251
}
252+
253+
/// Returns all annotations, skipping the first primary annotation.
254+
pub fn secondary_annotations(&self) -> impl Iterator<Item = &Annotation> {
255+
let mut seen_primary = false;
256+
self.inner.annotations.iter().filter(move |ann| {
257+
if seen_primary {
258+
true
259+
} else if ann.is_primary {
260+
seen_primary = true;
261+
false
262+
} else {
263+
true
264+
}
265+
})
266+
}
267+
268+
pub fn sub_diagnostics(&self) -> &[SubDiagnostic] {
269+
&self.inner.subs
270+
}
252271
}
253272

254273
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -371,6 +390,57 @@ impl SubDiagnostic {
371390
pub fn annotate(&mut self, ann: Annotation) {
372391
self.inner.annotations.push(ann);
373392
}
393+
394+
pub fn annotations(&self) -> &[Annotation] {
395+
&self.inner.annotations
396+
}
397+
398+
/// Returns a shared borrow of the "primary" annotation of this diagnostic
399+
/// if one exists.
400+
///
401+
/// When there are multiple primary annotations, then the first one that
402+
/// was added to this diagnostic is returned.
403+
pub fn primary_annotation(&self) -> Option<&Annotation> {
404+
self.inner.annotations.iter().find(|ann| ann.is_primary)
405+
}
406+
407+
/// Introspects this diagnostic and returns what kind of "primary" message
408+
/// it contains for concise formatting.
409+
///
410+
/// When we concisely format diagnostics, we likely want to not only
411+
/// include the primary diagnostic message but also the message attached
412+
/// to the primary annotation. In particular, the primary annotation often
413+
/// contains *essential* information or context for understanding the
414+
/// diagnostic.
415+
///
416+
/// The reason why we don't just always return both the main diagnostic
417+
/// message and the primary annotation message is because this was written
418+
/// in the midst of an incremental migration of ty over to the new
419+
/// diagnostic data model. At time of writing, diagnostics were still
420+
/// constructed in the old model where the main diagnostic message and the
421+
/// primary annotation message were not distinguished from each other. So
422+
/// for now, we carefully return what kind of messages this diagnostic
423+
/// contains. In effect, if this diagnostic has a non-empty main message
424+
/// *and* a non-empty primary annotation message, then the diagnostic is
425+
/// 100% using the new diagnostic data model and we can format things
426+
/// appropriately.
427+
///
428+
/// The type returned implements the `std::fmt::Display` trait. In most
429+
/// cases, just converting it to a string (or printing it) will do what
430+
/// you want.
431+
pub fn concise_message(&self) -> ConciseMessage {
432+
let main = self.inner.message.as_str();
433+
let annotation = self
434+
.primary_annotation()
435+
.and_then(|ann| ann.get_message())
436+
.unwrap_or_default();
437+
match (main.is_empty(), annotation.is_empty()) {
438+
(false, true) => ConciseMessage::MainDiagnostic(main),
439+
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
440+
(false, false) => ConciseMessage::Both { main, annotation },
441+
(true, true) => ConciseMessage::Empty,
442+
}
443+
}
374444
}
375445

376446
#[derive(Debug, Clone, Eq, PartialEq)]

crates/ruff_db/src/files.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange};
1111
use salsa::plumbing::AsId;
1212
use salsa::{Durability, Setter};
1313

14+
use crate::diagnostic::{Span, UnifiedFile};
1415
use crate::file_revision::FileRevision;
1516
use crate::files::file_root::FileRoots;
1617
use crate::files::private::FileStatus;
@@ -549,6 +550,29 @@ impl Ranged for FileRange {
549550
}
550551
}
551552

553+
impl TryFrom<&Span> for FileRange {
554+
type Error = ();
555+
556+
fn try_from(value: &Span) -> Result<Self, Self::Error> {
557+
let UnifiedFile::Ty(file) = value.file() else {
558+
return Err(());
559+
};
560+
561+
Ok(Self {
562+
file: *file,
563+
range: value.range().ok_or(())?,
564+
})
565+
}
566+
}
567+
568+
impl TryFrom<Span> for FileRange {
569+
type Error = ();
570+
571+
fn try_from(value: Span) -> Result<Self, Self::Error> {
572+
Self::try_from(&value)
573+
}
574+
}
575+
552576
#[cfg(test)]
553577
mod tests {
554578
use crate::file_revision::FileRevision;

crates/ty_server/src/server/api/requests/diagnostic.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use lsp_types::{
77
NumberOrString, Range, RelatedFullDocumentDiagnosticReport,
88
};
99

10-
use crate::document::ToRangeExt;
10+
use crate::PositionEncoding;
11+
use crate::document::{FileRangeExt, ToRangeExt};
1112
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
1213
use crate::server::{Result, client::Notifier};
1314
use crate::session::DocumentSnapshot;
14-
use ruff_db::diagnostic::Severity;
15+
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
16+
use ruff_db::files::FileRange;
1517
use ruff_db::source::{line_index, source_text};
1618
use ty_project::{Db, ProjectDatabase};
1719

@@ -116,6 +118,29 @@ fn to_lsp_diagnostic(
116118
})
117119
.flatten();
118120

121+
let mut related_information = Vec::new();
122+
123+
related_information.extend(
124+
diagnostic
125+
.secondary_annotations()
126+
.chain(
127+
diagnostic
128+
.sub_diagnostics()
129+
.iter()
130+
.flat_map(SubDiagnostic::annotations),
131+
)
132+
.filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)),
133+
);
134+
135+
related_information.extend(
136+
diagnostic
137+
.sub_diagnostics()
138+
.iter()
139+
.filter_map(|diagnostic| {
140+
sub_diagnostic_to_related_information(db, diagnostic, encoding)
141+
}),
142+
);
143+
119144
Diagnostic {
120145
range,
121146
severity: Some(severity),
@@ -124,7 +149,41 @@ fn to_lsp_diagnostic(
124149
code_description,
125150
source: Some("ty".into()),
126151
message: diagnostic.concise_message().to_string(),
127-
related_information: None,
152+
related_information: Some(related_information),
128153
data: None,
129154
}
130155
}
156+
157+
fn annotation_to_related_information(
158+
db: &dyn Db,
159+
annotation: &Annotation,
160+
encoding: PositionEncoding,
161+
) -> Option<lsp_types::DiagnosticRelatedInformation> {
162+
let span = annotation.get_span();
163+
164+
let annotation_message = annotation.get_message()?;
165+
let range = FileRange::try_from(span).ok()?;
166+
let location = range.to_location(db.upcast(), encoding)?;
167+
168+
Some(lsp_types::DiagnosticRelatedInformation {
169+
location,
170+
message: annotation_message.to_string(),
171+
})
172+
}
173+
174+
fn sub_diagnostic_to_related_information(
175+
db: &dyn Db,
176+
diagnostic: &SubDiagnostic,
177+
encoding: PositionEncoding,
178+
) -> Option<lsp_types::DiagnosticRelatedInformation> {
179+
let primary_annotation = diagnostic.primary_annotation()?;
180+
181+
let span = primary_annotation.get_span();
182+
let range = FileRange::try_from(span).ok()?;
183+
let location = range.to_location(db.upcast(), encoding)?;
184+
185+
Some(lsp_types::DiagnosticRelatedInformation {
186+
location,
187+
message: diagnostic.concise_message().to_string(),
188+
})
189+
}

crates/ty_server/src/session/capabilities.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ impl ResolvedClientCapabilities {
3838
let document_changes = client_capabilities
3939
.workspace
4040
.as_ref()
41-
.and_then(|workspace| workspace.workspace_edit.as_ref())
42-
.and_then(|workspace_edit| workspace_edit.document_changes)
41+
.and_then(|workspace| workspace.workspace_edit.as_ref()?.document_changes)
4342
.unwrap_or_default();
4443

4544
let declaration_link_support = client_capabilities

0 commit comments

Comments
 (0)