Rollup merge of #152933 - GuillaumeGomez:start-migrating-lintdiag, r=JonathanBrouwer

Start migration for `LintDiagnostic` items by adding API and migrating `LinkerOutput` lint

This is more or less the same approach as https://github.com/rust-lang/rust/pull/152811, but in a much smaller size to make it reviewable. A lot of PRs will follow though. :)

This PR creates the equivalent of `lint_level` working with `Diagnostic` and add new methods on `MultiSpan` to make it work as well (in particular because we need to copy messages/spans from one context to another).

r? @JonathanBrouwer
This commit is contained in:
Jonathan Brouwer
2026-02-22 11:31:17 +01:00
committed by GitHub
3 changed files with 221 additions and 8 deletions
+5 -7
View File
@@ -18,18 +18,18 @@
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
use rustc_errors::DiagCtxtHandle;
use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::attrs::NativeLibKind;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_macros::LintDiagnostic;
use rustc_macros::Diagnostic;
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
use rustc_metadata::{
EncodedMetadata, NativeLibSearchFallback, find_native_static_library,
walk_native_lib_search_dirs,
};
use rustc_middle::bug;
use rustc_middle::lint::lint_level;
use rustc_middle::lint::diag_lint_level;
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Linkage;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
@@ -662,7 +662,7 @@ fn read_input(&self, path: &Path) -> std::io::Result<&[u8]> {
}
}
#[derive(LintDiagnostic)]
#[derive(Diagnostic)]
#[diag("{$inner}")]
/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
/// end up with inconsistent languages within the same diagnostic.
@@ -938,9 +938,7 @@ fn link_natively(
let level = codegen_results.crate_info.lint_levels.linker_messages;
let lint = |msg| {
lint_level(sess, LINKER_MESSAGES, level, None, |diag| {
LinkerOutput { inner: msg }.decorate_lint(diag)
})
diag_lint_level(sess, LINKER_MESSAGES, level, None, LinkerOutput { inner: msg });
};
if !prog.stderr.is_empty() {
+13
View File
@@ -109,10 +109,18 @@ pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan {
MultiSpan { primary_spans: vec, span_labels: vec![] }
}
pub fn push_primary_span(&mut self, primary_span: Span) {
self.primary_spans.push(primary_span);
}
pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagMessage>) {
self.span_labels.push((span, label.into()));
}
pub fn push_span_diag(&mut self, span: Span, diag: DiagMessage) {
self.span_labels.push((span, diag));
}
/// Selects the first primary span (if any).
pub fn primary_span(&self) -> Option<Span> {
self.primary_spans.first().cloned()
@@ -179,6 +187,11 @@ pub fn span_labels(&self) -> Vec<SpanLabel> {
span_labels
}
/// Returns the span labels as contained by `MultiSpan`.
pub fn span_labels_raw(&self) -> &[(Span, DiagMessage)] {
&self.span_labels
}
/// Returns `true` if any of the span labels is displayable.
pub fn has_span_labels(&self) -> bool {
self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())
+203 -1
View File
@@ -2,7 +2,7 @@
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::sorted_map::SortedMap;
use rustc_errors::{Diag, MultiSpan};
use rustc_errors::{Diag, Diagnostic, MultiSpan};
use rustc_hir::{HirId, ItemLocalId};
use rustc_lint_defs::EditionFcw;
use rustc_macros::{Decodable, Encodable, HashStable};
@@ -482,3 +482,205 @@ fn lint_level_impl(
}
lint_level_impl(sess, lint, level, span, Box::new(decorate))
}
/// The innermost function for emitting lints implementing the [`trait@Diagnostic`] trait.
///
/// If you are looking to implement a lint, look for higher level functions,
/// for example:
///
/// - [`TyCtxt::emit_node_span_lint`]
/// - [`TyCtxt::node_span_lint`]
/// - [`TyCtxt::emit_node_lint`]
/// - [`TyCtxt::node_lint`]
/// - `LintContext::opt_span_lint`
///
/// This function will replace `lint_level` once all `LintDiagnostic` items have been migrated to
/// `Diagnostic`.
#[track_caller]
pub fn diag_lint_level<'a, D: Diagnostic<'a, ()> + 'a>(
sess: &'a Session,
lint: &'static Lint,
level: LevelAndSource,
span: Option<MultiSpan>,
decorate: D,
) {
// Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
// the "real" work.
#[track_caller]
fn diag_lint_level_impl<'a>(
sess: &'a Session,
lint: &'static Lint,
level: LevelAndSource,
span: Option<MultiSpan>,
decorate: Box<
dyn FnOnce(rustc_errors::DiagCtxtHandle<'a>, rustc_errors::Level) -> Diag<'a, ()> + 'a,
>,
) {
let LevelAndSource { level, lint_id, src } = level;
// Check for future incompatibility lints and issue a stronger warning.
let future_incompatible = lint.future_incompatible;
let has_future_breakage = future_incompatible.map_or(
// Default allow lints trigger too often for testing.
sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
|incompat| incompat.report_in_deps,
);
// Convert lint level to error level.
let err_level = match level {
Level::Allow => {
if has_future_breakage {
rustc_errors::Level::Allow
} else {
return;
}
}
Level::Expect => {
// This case is special as we actually allow the lint itself in this context, but
// we can't return early like in the case for `Level::Allow` because we still
// need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`.
//
// We can also not mark the lint expectation as fulfilled here right away, as it
// can still be cancelled in the decorate function. All of this means that we simply
// create a `Diag` and continue as we would for warnings.
rustc_errors::Level::Expect
}
Level::ForceWarn => rustc_errors::Level::ForceWarning,
Level::Warn => rustc_errors::Level::Warning,
Level::Deny | Level::Forbid => rustc_errors::Level::Error,
};
// Finally, run `decorate`. `decorate` can call `trimmed_path_str` (directly or indirectly),
// so we need to make sure when we do call `decorate` that the diagnostic is eventually
// emitted or we'll get a `must_produce_diag` ICE.
//
// When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors:
// 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)`
// or `Error`, then the diagnostic will be emitted regardless of CLI options.
// 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by
// `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic
// will be emitted if `can_emit_warnings` is true.
let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
let disable_suggestions = if let Some(ref span) = span
// If this code originates in a foreign macro, aka something that this crate
// did not itself author, then it's likely that there's nothing this crate
// can do about it. We probably want to skip the lint entirely.
&& span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map()))
{
true
} else {
false
};
let mut err: Diag<'_, ()> = if !skip {
decorate(sess.dcx(), err_level)
} else {
Diag::new(sess.dcx(), err_level, "")
};
if let Some(span) = span
&& err.span.primary_span().is_none()
{
// We can't use `err.span()` because it overwrites the labels, so we need to do it manually.
for primary in span.primary_spans() {
err.span.push_primary_span(*primary);
}
for (label_span, label) in span.span_labels_raw() {
err.span.push_span_diag(*label_span, label.clone());
}
}
if let Some(lint_id) = lint_id {
err.lint_id(lint_id);
}
if disable_suggestions {
// Any suggestions made here are likely to be incorrect, so anything we
// emit shouldn't be automatically fixed by rustfix.
err.disable_suggestions();
// If this is a future incompatible that is not an edition fixing lint
// it'll become a hard error, so we have to emit *something*. Also,
// if this lint occurs in the expansion of a macro from an external crate,
// allow individual lints to opt-out from being reported.
let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
if !incompatible && !lint.report_in_external_macro {
err.cancel();
// Don't continue further, since we don't want to have
// `diag_span_note_once` called for a diagnostic that isn't emitted.
return;
}
}
err.is_lint(lint.name_lower(), has_future_breakage);
// Lint diagnostics that are covered by the expect level will not be emitted outside
// the compiler. It is therefore not necessary to add any information for the user.
// This will therefore directly call the decorate function which will in turn emit
// the diagnostic.
if let Level::Expect = level {
err.emit();
return;
}
if let Some(future_incompatible) = future_incompatible {
let explanation = match future_incompatible.reason {
FutureIncompatibilityReason::FutureReleaseError(_) => {
"this was previously accepted by the compiler but is being phased out; \
it will become a hard error in a future release!"
.to_owned()
}
FutureIncompatibilityReason::FutureReleaseSemanticsChange(_) => {
"this will change its meaning in a future release!".to_owned()
}
FutureIncompatibilityReason::EditionError(EditionFcw { edition, .. }) => {
let current_edition = sess.edition();
format!(
"this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
)
}
FutureIncompatibilityReason::EditionSemanticsChange(EditionFcw {
edition, ..
}) => {
format!("this changes meaning in Rust {edition}")
}
FutureIncompatibilityReason::EditionAndFutureReleaseError(EditionFcw {
edition,
..
}) => {
format!(
"this was previously accepted by the compiler but is being phased out; \
it will become a hard error in Rust {edition} and in a future release in all editions!"
)
}
FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(
EditionFcw { edition, .. },
) => {
format!(
"this changes meaning in Rust {edition} and in a future release in all editions!"
)
}
FutureIncompatibilityReason::Custom(reason, _) => reason.to_owned(),
FutureIncompatibilityReason::Unreachable => unreachable!(),
};
if future_incompatible.explain_reason {
err.warn(explanation);
}
let citation =
format!("for more information, see {}", future_incompatible.reason.reference());
err.note(citation);
}
explain_lint_level_source(sess, lint, level, src, &mut err);
err.emit();
}
diag_lint_level_impl(
sess,
lint,
level,
span,
Box::new(move |dcx, level| decorate.into_diag(dcx, level)),
);
}