Skip to content
11 changes: 11 additions & 0 deletions compiler/rustc_errors/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ impl<'a> Diagnostic<'a, ()>
}
}

/// Type used to emit diagnostic through a closure instead of implementing the `Diagnostic` trait.
pub struct DiagDecorator<F: FnOnce(&mut Diag<'_, ()>)>(pub F);

impl<'a, F: FnOnce(&mut Diag<'_, ()>)> Diagnostic<'a, ()> for DiagDecorator<F> {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
let mut diag = Diag::new(dcx, level, "");
(self.0)(&mut diag);
diag
}
}

/// Trait implemented by error types. This should not be implemented manually. Instead, use
/// `#[derive(Subdiagnostic)]` -- see [rustc_macros::Subdiagnostic].
#[rustc_diagnostic_item = "Subdiagnostic"]
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub use anstyle::{
pub use codes::*;
pub use decorate_diag::{BufferedEarlyLint, DecorateDiagCompat, LintBuffer};
pub use diagnostic::{
BugAbort, Diag, DiagInner, DiagLocation, DiagStyledString, Diagnostic, EmissionGuarantee,
FatalAbort, StringPart, Subdiag, Subdiagnostic,
BugAbort, Diag, DiagDecorator, DiagInner, DiagLocation, DiagStyledString, Diagnostic,
EmissionGuarantee, FatalAbort, StringPart, Subdiag, Subdiagnostic,
};
pub use diagnostic_impls::{
DiagSymbolList, ElidedLifetimeInPathSubdiag, ExpectedLifetimeParameter,
Expand Down
87 changes: 48 additions & 39 deletions compiler/rustc_lint/src/default_could_be_derived.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, Diag};
use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, Level};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -147,50 +147,59 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {

let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id);
let span = cx.tcx.hir_span_with_body(hir_id);
cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, span, |diag| {
mk_lint(cx.tcx, diag, type_def_id, orig_fields, fields, span);
});
cx.tcx.emit_node_span_lint(
DEFAULT_OVERRIDES_DEFAULT_FIELDS,
hir_id,
span,
WrongDefaultImpl { tcx: cx.tcx, type_def_id, orig_fields, fields, impl_span: span },
);
}
}

fn mk_lint(
tcx: TyCtxt<'_>,
diag: &mut Diag<'_, ()>,
struct WrongDefaultImpl<'a, 'hir, 'tcx> {
tcx: TyCtxt<'tcx>,
type_def_id: DefId,
orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>,
fields: &[hir::ExprField<'_>],
orig_fields: FxHashMap<Symbol, &'a hir::FieldDef<'hir>>,
fields: &'a [hir::ExprField<'hir>],
impl_span: Span,
) {
diag.primary_message("`Default` impl doesn't use the declared default field values");

// For each field in the struct expression
// - if the field in the type has a default value, it should be removed
// - elif the field is an expression that could be a default value, it should be used as the
// field's default value (FIXME: not done).
// - else, we wouldn't touch this field, it would remain in the manual impl
let mut removed_all_fields = true;
for field in fields {
if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
diag.span_label(field.expr.span, "this field has a default value");
} else {
removed_all_fields = false;
}

impl<'a, 'b, 'hir, 'tcx> Diagnostic<'a, ()> for WrongDefaultImpl<'b, 'hir, 'tcx> {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
let Self { tcx, type_def_id, orig_fields, fields, impl_span } = self;
let mut diag =
Diag::new(dcx, level, "`Default` impl doesn't use the declared default field values");

// For each field in the struct expression
// - if the field in the type has a default value, it should be removed
// - elif the field is an expression that could be a default value, it should be used as the
// field's default value (FIXME: not done).
// - else, we wouldn't touch this field, it would remain in the manual impl
let mut removed_all_fields = true;
for field in fields {
if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
diag.span_label(field.expr.span, "this field has a default value");
} else {
removed_all_fields = false;
}
}
}

if removed_all_fields {
let msg = "to avoid divergence in behavior between `Struct { .. }` and \
`<Struct as Default>::default()`, derive the `Default`";
diag.multipart_suggestion(
msg,
vec![
(tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()),
(impl_span, String::new()),
],
Applicability::MachineApplicable,
);
} else {
let msg = "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \
avoid them diverging over time";
diag.help(msg);
if removed_all_fields {
diag.multipart_suggestion(
"to avoid divergence in behavior between `Struct { .. }` and \
`<Struct as Default>::default()`, derive the `Default`",
vec![
(tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()),
(impl_span, String::new()),
],
Applicability::MachineApplicable,
);
} else {
diag.help(
"use the default values in the `impl` with `Struct { mandatory_field, .. }` to \
avoid them diverging over time",
);
}
diag
}
}
27 changes: 18 additions & 9 deletions compiler/rustc_lint/src/transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,24 @@ fn check_unnecessary_transmute<'tcx>(
_ => return,
};

cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| {
diag.primary_message("unnecessary transmute");
if let Some(sugg) = sugg {
diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable);
}
if let Some(help) = help {
diag.help(help);
}
});
cx.tcx.emit_node_span_lint(
UNNECESSARY_TRANSMUTES,
expr.hir_id,
expr.span,
rustc_errors::DiagDecorator(|diag| {
diag.primary_message("unnecessary transmute");
if let Some(sugg) = sugg {
diag.multipart_suggestion(
"replace this with",
sugg,
Applicability::MachineApplicable,
);
}
if let Some(help) = help {
diag.help(help);
}
}),
);
}

#[derive(Diagnostic)]
Expand Down
11 changes: 8 additions & 3 deletions compiler/rustc_middle/src/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,14 @@ impl<'tcx> TyCtxt<'tcx> {
unmarked: impl FnOnce(Span, DefId),
) -> bool {
let soft_handler = |lint, span, msg: String| {
self.node_span_lint(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
lint.primary_message(msg);
})
self.emit_node_span_lint(
lint,
id.unwrap_or(hir::CRATE_HIR_ID),
span,
rustc_errors::DiagDecorator(|lint| {
lint.primary_message(msg);
}),
);
};
let eval_result =
self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable);
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_middle/src/mir/interpret/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ impl<'tcx> TyCtxt<'tcx> {
let mir_body = self.mir_for_ctfe(cid.instance.def_id());
if mir_body.is_polymorphic {
let Some(local_def_id) = ct.def.as_local() else { return };
self.node_span_lint(
self.emit_node_span_lint(
lint::builtin::CONST_EVALUATABLE_UNCHECKED,
self.local_def_id_to_hir_id(local_def_id),
self.def_span(ct.def),
|lint| {
rustc_errors::DiagDecorator(|lint| {
lint.primary_message(
"cannot use constants which depend on generic parameters in types",
);
},
}),
)
}
}
Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use rustc_hir::lang_items::LangItem;
use rustc_hir::limit::Limit;
use rustc_hir::{self as hir, CRATE_HIR_ID, HirId, Node, TraitCandidate, find_attr};
use rustc_index::IndexVec;
use rustc_macros::Diagnostic;
use rustc_session::Session;
use rustc_session::config::CrateType;
use rustc_session::cstore::{CrateStoreDyn, Untracked};
Expand Down Expand Up @@ -1685,6 +1686,12 @@ impl<'tcx> TyCtxt<'tcx> {
}

pub fn report_unused_features(self) {
#[derive(Diagnostic)]
#[diag("feature `{$feature}` is declared but not used")]
struct UnusedFeature {
feature: Symbol,
}

// Collect first to avoid holding the lock while linting.
let used_features = self.sess.used_features.lock();
let unused_features = self
Expand All @@ -1703,13 +1710,11 @@ impl<'tcx> TyCtxt<'tcx> {
.collect::<Vec<_>>();

for (feature, span) in unused_features {
self.node_span_lint(
self.emit_node_span_lint(
rustc_session::lint::builtin::UNUSED_FEATURES,
CRATE_HIR_ID,
span,
|lint| {
lint.primary_message(format!("feature `{}` is declared but not used", feature));
},
UnusedFeature { feature },
);
}
}
Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_mir_build/src/thir/pattern/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ impl<'a> PatMigration<'a> {
self.format_subdiagnostics(&mut err);
err.emit();
} else {
tcx.node_span_lint(lint::builtin::RUST_2024_INCOMPATIBLE_PAT, pat_id, spans, |diag| {
diag.primary_message(primary_message);
self.format_subdiagnostics(diag);
});
tcx.emit_node_span_lint(
lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
pat_id,
spans,
rustc_errors::DiagDecorator(|diag| {
diag.primary_message(primary_message);
self.format_subdiagnostics(diag);
}),
);
}
}

Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_mir_transform/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ pub(crate) fn emit_inline_always_target_feature_diagnostic<'a, 'tcx>(
caller_def_id: DefId,
callee_only: &[&'a str],
) {
tcx.node_span_lint(
tcx.emit_node_span_lint(
lint::builtin::INLINE_ALWAYS_MISMATCHING_TARGET_FEATURES,
tcx.local_def_id_to_hir_id(caller_def_id.as_local().unwrap()),
call_span,
|lint| {
rustc_errors::DiagDecorator(|lint| {
let callee = tcx.def_path_str(callee_def_id);
let caller = tcx.def_path_str(caller_def_id);

Expand All @@ -46,7 +46,7 @@ pub(crate) fn emit_inline_always_target_feature_diagnostic<'a, 'tcx>(
format!("#[target_feature(enable = \"{feats}\")]\n"),
lint::Applicability::MaybeIncorrect,
);
},
}),
);
}

Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_trait_selection/src/traits/specialize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,10 +578,15 @@ fn report_conflicting_impls<'tcx>(
let lint = match kind {
FutureCompatOverlapErrorKind::LeakCheck => COHERENCE_LEAK_CHECK,
};
tcx.node_span_lint(lint, tcx.local_def_id_to_hir_id(impl_def_id), impl_span, |err| {
err.primary_message(msg());
decorate(tcx, &overlap, impl_span, err);
});
tcx.emit_node_span_lint(
lint,
tcx.local_def_id_to_hir_id(impl_def_id),
impl_span,
rustc_errors::DiagDecorator(|err| {
err.primary_message(msg());
decorate(tcx, &overlap, impl_span, err);
}),
);
Ok(())
}
}
Expand Down
15 changes: 4 additions & 11 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -799,29 +799,22 @@ impl<'tcx> ExtraInfo<'tcx> {
}

fn error_invalid_codeblock_attr(&self, msg: impl Into<DiagMessage>) {
self.tcx.node_span_lint(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
self.tcx.local_def_id_to_hir_id(self.def_id),
self.sp,
|lint| {
lint.primary_message(msg);
},
);
self.error_invalid_codeblock_attr_with_help(msg, |_| {});
}

fn error_invalid_codeblock_attr_with_help(
&self,
msg: impl Into<DiagMessage>,
f: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
) {
self.tcx.node_span_lint(
self.tcx.emit_node_span_lint(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
self.tcx.local_def_id_to_hir_id(self.def_id),
self.sp,
|lint| {
rustc_errors::DiagDecorator(|lint| {
lint.primary_message(msg);
f(lint);
},
}),
);
}
}
Expand Down
24 changes: 17 additions & 7 deletions src/librustdoc/passes/check_doc_test_visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! - PRIVATE_DOC_TESTS: this lint is **STABLE** and looks for private items with doctests.

use rustc_hir as hir;
use rustc_macros::Diagnostic;
use rustc_middle::lint::{LevelAndSource, LintLevelSource};
use rustc_session::lint;
use tracing::debug;
Expand Down Expand Up @@ -116,6 +117,14 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
}

pub(crate) fn look_for_tests(cx: &DocContext<'_>, dox: &str, item: &Item) {
#[derive(Diagnostic)]
#[diag("missing code example in this documentation")]
struct MissingCodeExample;

#[derive(Diagnostic)]
#[diag("documentation test in private item")]
struct DoctestInPrivateItem;

let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
Expand All @@ -129,20 +138,21 @@ pub(crate) fn look_for_tests(cx: &DocContext<'_>, dox: &str, item: &Item) {
if should_have_doc_example(cx, item) {
debug!("reporting error for {item:?} (hir_id={hir_id:?})");
let sp = item.attr_span(cx.tcx);
cx.tcx.node_span_lint(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id, sp, |lint| {
lint.primary_message("missing code example in this documentation");
});
cx.tcx.emit_node_span_lint(
crate::lint::MISSING_DOC_CODE_EXAMPLES,
hir_id,
sp,
MissingCodeExample,
);
}
} else if tests.found_tests > 0
&& !cx.cache.effective_visibilities.is_exported(cx.tcx, item.item_id.expect_def_id())
{
cx.tcx.node_span_lint(
cx.tcx.emit_node_span_lint(
crate::lint::PRIVATE_DOC_TESTS,
hir_id,
item.attr_span(cx.tcx),
|lint| {
lint.primary_message("documentation test in private item");
},
DoctestInPrivateItem,
);
}
}
Loading
Loading