Rollup merge of #145747 - joshtriplett:builtin-diag-dyn, r=jdonszelmann

Refactor lint buffering to avoid requiring a giant enum

Lint buffering currently relies on a giant enum `BuiltinLintDiag` containing all the lints that might potentially get buffered. In addition to being an unwieldy enum in a central crate, this also makes `rustc_lint_defs` a build bottleneck: it depends on various types from various crates (with a steady pressure to add more), and many crates depend on it.

Having all of these variants in a separate crate also prevents detecting when a variant becomes unused, which we can do with a dedicated type defined and used in the same crate.

Refactor this to use a dyn trait, to allow using `LintDiagnostic` types directly.

Because the existing `BuiltinLintDiag` requires some additional types in order to decorate some variants, which are only available later in `rustc_lint`, use an enum `DecorateDiagCompat` to handle both the `dyn LintDiagnostic` case and the `BuiltinLintDiag` case.

---

With the infrastructure in place, use it to migrate three of the enum variants to use `LintDiagnostic` directly, as a proof of concept and to demonstrate that the net result is a reduction in code size and a removal of a boilerplate-heavy layer of indirection.

Also remove an unused `BuiltinLintDiag` variant.
This commit is contained in:
Jacob Pratt
2025-08-22 22:00:59 -04:00
committed by GitHub
29 changed files with 265 additions and 277 deletions

View File

@@ -8,8 +8,9 @@ use rustc_ast::{Path, Visibility};
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, Subdiagnostic,
SuggestionStyle,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_session::errors::ExprParenthesesNeeded;
use rustc_span::edition::{Edition, LATEST_STABLE_EDITION};
use rustc_span::{Ident, Span, Symbol};
@@ -3601,3 +3602,76 @@ pub(crate) struct ExpectedRegisterClassOrExplicitRegister {
#[primary_span]
pub(crate) span: Span,
}
#[derive(LintDiagnostic)]
#[diag(parse_hidden_unicode_codepoints)]
#[note]
pub(crate) struct HiddenUnicodeCodepointsDiag {
pub label: String,
pub count: usize,
#[label]
pub span_label: Span,
#[subdiagnostic]
pub labels: Option<HiddenUnicodeCodepointsDiagLabels>,
#[subdiagnostic]
pub sub: HiddenUnicodeCodepointsDiagSub,
}
pub(crate) struct HiddenUnicodeCodepointsDiagLabels {
pub spans: Vec<(char, Span)>,
}
impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels {
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
for (c, span) in self.spans {
diag.span_label(span, format!("{c:?}"));
}
}
}
pub(crate) enum HiddenUnicodeCodepointsDiagSub {
Escape { spans: Vec<(char, Span)> },
NoEscape { spans: Vec<(char, Span)> },
}
// Used because of multiple multipart_suggestion and note
impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub {
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
match self {
HiddenUnicodeCodepointsDiagSub::Escape { spans } => {
diag.multipart_suggestion_with_style(
fluent::parse_suggestion_remove,
spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
Applicability::MachineApplicable,
SuggestionStyle::HideCodeAlways,
);
diag.multipart_suggestion(
fluent::parse_suggestion_escape,
spans
.into_iter()
.map(|(c, span)| {
let c = format!("{c:?}");
(span, c[1..c.len() - 1].to_string())
})
.collect(),
Applicability::MachineApplicable,
);
}
HiddenUnicodeCodepointsDiagSub::NoEscape { spans } => {
// FIXME: in other suggestions we've reversed the inner spans of doc comments. We
// should do the same here to provide the same good suggestions as we do for
// literals above.
diag.arg(
"escaped",
spans
.into_iter()
.map(|(c, _)| format!("{c:?}"))
.collect::<Vec<String>>()
.join(", "),
);
diag.note(fluent::parse_suggestion_remove);
diag.note(fluent::parse_no_suggestion_note_escape);
}
}
}
}

View File

@@ -543,21 +543,21 @@ impl<'psess, 'src> Lexer<'psess, 'src> {
})
.collect();
let label = label.to_string();
let count = spans.len();
let labels = point_at_inner_spans.then_some(spans.clone());
let labels = point_at_inner_spans
.then_some(errors::HiddenUnicodeCodepointsDiagLabels { spans: spans.clone() });
let sub = if point_at_inner_spans && !spans.is_empty() {
errors::HiddenUnicodeCodepointsDiagSub::Escape { spans }
} else {
errors::HiddenUnicodeCodepointsDiagSub::NoEscape { spans }
};
self.psess.buffer_lint(
TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
span,
ast::CRATE_NODE_ID,
BuiltinLintDiag::HiddenUnicodeCodepoints {
label: label.to_string(),
count,
span_label: span,
labels,
escape: point_at_inner_spans && !spans.is_empty(),
spans,
},
errors::HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub },
);
}