ruff_db: add "secondary" messages to Diagnostic trait

This is a small little hack to make the `Diagnostic` trait
capable of supporting attaching multiple spans.

This design should be considered transient. This was just the
quickest way that I could see to pass multiple spans through from
the type checker to the diagnostic renderer.
This commit is contained in:
Andrew Gallant 2025-02-12 14:16:24 -05:00 committed by Andrew Gallant
parent 18a9eddf60
commit 87668e24b1
5 changed files with 85 additions and 7 deletions

View file

@ -325,6 +325,7 @@ impl<'a> CheckSuppressionsContext<'a> {
range, range,
severity, severity,
file: self.file, file: self.file,
secondary_messages: vec![],
}); });
} }
} }

View file

@ -2,7 +2,7 @@ use std::fmt;
use drop_bomb::DebugDropBomb; use drop_bomb::DebugDropBomb;
use ruff_db::{ use ruff_db::{
diagnostic::{DiagnosticId, Severity}, diagnostic::{DiagnosticId, SecondaryDiagnosticMessage, Severity},
files::File, files::File,
}; };
use ruff_python_ast::AnyNodeRef; use ruff_python_ast::AnyNodeRef;
@ -73,6 +73,17 @@ impl<'db> InferContext<'db> {
lint: &'static LintMetadata, lint: &'static LintMetadata,
node: AnyNodeRef, node: AnyNodeRef,
message: fmt::Arguments, message: fmt::Arguments,
) {
self.report_lint_with_secondary_messages(lint, node, message, vec![]);
}
/// Reports a lint located at `node`.
pub(super) fn report_lint_with_secondary_messages(
&self,
lint: &'static LintMetadata,
node: AnyNodeRef,
message: fmt::Arguments,
secondary_messages: Vec<SecondaryDiagnosticMessage>,
) { ) {
if !self.db.is_file_open(self.file) { if !self.db.is_file_open(self.file) {
return; return;
@ -94,7 +105,13 @@ impl<'db> InferContext<'db> {
return; return;
} }
self.report_diagnostic(node, DiagnosticId::Lint(lint.name()), severity, message); self.report_diagnostic(
node,
DiagnosticId::Lint(lint.name()),
severity,
message,
secondary_messages,
);
} }
/// Adds a new diagnostic. /// Adds a new diagnostic.
@ -106,6 +123,7 @@ impl<'db> InferContext<'db> {
id: DiagnosticId, id: DiagnosticId,
severity: Severity, severity: Severity,
message: fmt::Arguments, message: fmt::Arguments,
secondary_messages: Vec<SecondaryDiagnosticMessage>,
) { ) {
if !self.db.is_file_open(self.file) { if !self.db.is_file_open(self.file) {
return; return;
@ -123,6 +141,7 @@ impl<'db> InferContext<'db> {
message: message.to_string(), message: message.to_string(),
range: node.range(), range: node.range(),
severity, severity,
secondary_messages,
}); });
} }

View file

@ -8,7 +8,7 @@ use crate::types::string_annotation::{
}; };
use crate::types::{ClassLiteralType, KnownInstanceType, Type}; use crate::types::{ClassLiteralType, KnownInstanceType, Type};
use crate::{declare_lint, Db}; use crate::{declare_lint, Db};
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity, Span}; use ruff_db::diagnostic::{Diagnostic, DiagnosticId, SecondaryDiagnosticMessage, Severity, Span};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
@ -777,6 +777,7 @@ pub struct TypeCheckDiagnostic {
pub(crate) range: TextRange, pub(crate) range: TextRange,
pub(crate) severity: Severity, pub(crate) severity: Severity,
pub(crate) file: File, pub(crate) file: File,
pub(crate) secondary_messages: Vec<SecondaryDiagnosticMessage>,
} }
impl TypeCheckDiagnostic { impl TypeCheckDiagnostic {
@ -806,6 +807,10 @@ impl Diagnostic for TypeCheckDiagnostic {
Some(Span::from(self.file).with_range(self.range)) Some(Span::from(self.file).with_range(self.range))
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
&self.secondary_messages
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
self.severity self.severity
} }

View file

@ -3272,6 +3272,7 @@ impl<'db> TypeInferenceBuilder<'db> {
"Revealed type is `{}`", "Revealed type is `{}`",
revealed_type.display(self.db()) revealed_type.display(self.db())
), ),
vec![],
); );
} }
} }

View file

@ -173,6 +173,12 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
/// or it applies to the entire file (e.g. the file should be executable but isn't). /// or it applies to the entire file (e.g. the file should be executable but isn't).
fn span(&self) -> Option<Span>; fn span(&self) -> Option<Span>;
/// Returns an optional sequence of "secondary" messages (with spans) to
/// include in the rendering of this diagnostic.
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
&[]
}
fn severity(&self) -> Severity; fn severity(&self) -> Severity;
fn display<'db, 'diag, 'config>( fn display<'db, 'diag, 'config>(
@ -191,6 +197,22 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
} }
} }
/// A single secondary message assigned to a `Diagnostic`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SecondaryDiagnosticMessage {
span: Span,
message: String,
}
impl SecondaryDiagnosticMessage {
pub fn new(span: Span, message: impl Into<String>) -> SecondaryDiagnosticMessage {
SecondaryDiagnosticMessage {
span,
message: message.into(),
}
}
}
/// A span represents the source of a diagnostic. /// A span represents the source of a diagnostic.
/// ///
/// It consists of a `File` and an optional range into that file. When the /// It consists of a `File` and an optional range into that file. When the
@ -219,10 +241,12 @@ impl Span {
/// Returns a new `Span` with the given `range` attached to it. /// Returns a new `Span` with the given `range` attached to it.
pub fn with_range(self, range: TextRange) -> Span { pub fn with_range(self, range: TextRange) -> Span {
Span { self.with_optional_range(Some(range))
range: Some(range),
..self
} }
/// Returns a new `Span` with the given optional `range` attached to it.
pub fn with_optional_range(self, range: Option<TextRange>) -> Span {
Span { range, ..self }
} }
} }
@ -311,6 +335,14 @@ impl std::fmt::Display for DisplayDiagnostic<'_, '_, '_> {
&span, &span,
&self.diagnostic.message(), &self.diagnostic.message(),
)); ));
for secondary_msg in self.diagnostic.secondary_messages() {
message.add_snippet(Snippet::new(
self.db,
Severity::Info,
&secondary_msg.span,
&secondary_msg.message,
));
}
render(f, message.to_annotate()) render(f, message.to_annotate())
} }
} }
@ -445,6 +477,10 @@ where
(**self).span() (**self).span()
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
(**self).secondary_messages()
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
(**self).severity() (**self).severity()
} }
@ -466,6 +502,10 @@ where
(**self).span() (**self).span()
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
(**self).secondary_messages()
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
(**self).severity() (**self).severity()
} }
@ -484,6 +524,10 @@ impl Diagnostic for Box<dyn Diagnostic> {
(**self).span() (**self).span()
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
(**self).secondary_messages()
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
(**self).severity() (**self).severity()
} }
@ -502,6 +546,10 @@ impl Diagnostic for &'_ dyn Diagnostic {
(**self).span() (**self).span()
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
(**self).secondary_messages()
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
(**self).severity() (**self).severity()
} }
@ -520,6 +568,10 @@ impl Diagnostic for std::sync::Arc<dyn Diagnostic> {
(**self).span() (**self).span()
} }
fn secondary_messages(&self) -> &[SecondaryDiagnosticMessage] {
(**self).secondary_messages()
}
fn severity(&self) -> Severity { fn severity(&self) -> Severity {
(**self).severity() (**self).severity()
} }