Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -37,8 +37,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
}
}
26 changes: 21 additions & 5 deletions compiler/rustc_lint/src/transmute.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, Level};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_macros::Diagnostic;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint, impl_lint_pass};
use rustc_span::sym;
use rustc_span::{Span, sym};

use crate::lints::{IntegerToPtrTransmutes, IntegerToPtrTransmutesSuggestion};
use crate::{LateContext, LateLintPass};
Expand Down Expand Up @@ -357,15 +357,31 @@ fn check_unnecessary_transmute<'tcx>(
_ => return,
};

cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| {
diag.primary_message("unnecessary transmute");
cx.tcx.emit_node_span_lint(
UNNECESSARY_TRANSMUTES,
expr.hir_id,
expr.span,
UnnecessaryTransmutes { sugg, help },
);
}

struct UnnecessaryTransmutes {
sugg: Option<Vec<(Span, String)>>,
help: Option<&'static str>,
}

impl<'a> Diagnostic<'a, ()> for UnnecessaryTransmutes {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
let Self { sugg, help } = self;
let mut diag = Diag::new(dcx, level, "unnecessary transmute");
if let Some(sugg) = sugg {
diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable);
}
if let Some(help) = help {
diag.help(help);
}
});
diag
}
}

#[derive(Diagnostic)]
Expand Down
28 changes: 24 additions & 4 deletions compiler/rustc_middle/src/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
use std::num::NonZero;

use rustc_ast::NodeId;
use rustc_errors::{Applicability, Diag, EmissionGuarantee, LintBuffer, msg};
use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, LintBuffer, msg,
};
use rustc_feature::GateIssue;
use rustc_hir::attrs::{DeprecatedSince, Deprecation};
use rustc_hir::def_id::{DefId, LocalDefId};
Expand Down Expand Up @@ -563,10 +565,28 @@ impl<'tcx> TyCtxt<'tcx> {
allow_unstable: AllowUnstable,
unmarked: impl FnOnce(Span, DefId),
) -> bool {
struct DiagEmitter {
msg: String,
}

impl<'a> Diagnostic<'a, ()> for DiagEmitter {
fn into_diag(
self,
dcx: DiagCtxtHandle<'a>,
level: rustc_errors::Level,
) -> Diag<'a, ()> {
let Self { msg } = self;
Diag::new(dcx, level, msg)
}
}

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,
DiagEmitter { msg },
);
};
let eval_result =
self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable);
Expand Down
13 changes: 7 additions & 6 deletions compiler/rustc_middle/src/mir/interpret/queries.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_macros::Diagnostic;
use rustc_session::lint;
use rustc_span::{DUMMY_SP, Span};
use tracing::{debug, instrument};
Expand Down Expand Up @@ -93,6 +94,10 @@ impl<'tcx> TyCtxt<'tcx> {
ct: ty::UnevaluatedConst<'tcx>,
span: Span,
) -> ConstToValTreeResult<'tcx> {
#[derive(Diagnostic)]
#[diag("cannot use constants which depend on generic parameters in types")]
struct ConstDependingOnGenericParam;

// Cannot resolve `Unevaluated` constants that contain inference
// variables. We reject those here since `resolve`
// would fail otherwise.
Expand Down Expand Up @@ -139,15 +144,11 @@ 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| {
lint.primary_message(
"cannot use constants which depend on generic parameters in types",
);
},
ConstDependingOnGenericParam,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we said we'd not convert more things to the struct error style?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't remember that. Where did we discuss it? Also, we are removing the node_span_lint method to remove the duplicated lint_level function, so there isn't much of a choice, unless we simply wrap the closure in a type.

Copy link
Contributor

@JonathanBrouwer JonathanBrouwer Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afaik the consensus is that we can use whatever style is prettier. In this case, I think the struct style is quite pretty. I don't like the explicit impl Diagnostic ones in this PR tho, and would like to explore the closure approach for those

)
}
}
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_serialize::opaque::{FileEncodeResult, FileEncoder};
use rustc_session::Session;
use rustc_session::config::CrateType;
Expand Down Expand Up @@ -1690,6 +1691,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 @@ -1708,13 +1715,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 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like how positive the diff of this PR is, the explicit Diagnostic implementation is a bit ugly. Could you:

  • For diagnostics like this one, use a struct diagnostic instead
  • For more complicated ones, see if you can invent a syntax similar to the old one with a closure to make the diagnostics a bit less verbose? If this turns out to be difficult I'm happy to discuss some approaches with you, but I feel like this approach is a bit too verbose

);
}
}
Expand Down
28 changes: 23 additions & 5 deletions compiler/rustc_mir_build/src/thir/pattern/migration.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.

use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, pluralize};
use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, MultiSpan, pluralize,
};
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
use rustc_lint as lint;
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt};
Expand Down Expand Up @@ -41,6 +43,20 @@ impl<'a> PatMigration<'a> {
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
/// future-incompatibility lint `rust_2024_incompatible_pat`.
pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
struct DiagError<'a> {
primary_message: String,
this: PatMigration<'a>,
}

impl<'a, 'b> Diagnostic<'a, ()> for DiagError<'b> {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
let Self { primary_message, this } = self;
let mut diag = Diag::new(dcx, level, primary_message);
this.format_subdiagnostics(&mut diag);
diag
}
}

let mut spans =
MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
for (span, label) in self.info.primary_labels.iter() {
Expand All @@ -55,10 +71,12 @@ 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,
DiagError { primary_message, this: self },
);
}
}

Expand Down
Loading
Loading