Skip to content

Commit 8c1e507

Browse files
Rollup merge of rust-lang#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 rust-lang#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
2 parents 1c88fbe + 4f23c48 commit 8c1e507

File tree

3 files changed

+221
-8
lines changed

3 files changed

+221
-8
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ use rustc_attr_parsing::eval_config_entry;
1818
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
1919
use rustc_data_structures::memmap::Mmap;
2020
use rustc_data_structures::temp_dir::MaybeTempDir;
21-
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
21+
use rustc_errors::DiagCtxtHandle;
2222
use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
2323
use rustc_hir::attrs::NativeLibKind;
2424
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
25-
use rustc_macros::LintDiagnostic;
25+
use rustc_macros::Diagnostic;
2626
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
2727
use rustc_metadata::{
2828
EncodedMetadata, NativeLibSearchFallback, find_native_static_library,
2929
walk_native_lib_search_dirs,
3030
};
3131
use rustc_middle::bug;
32-
use rustc_middle::lint::lint_level;
32+
use rustc_middle::lint::diag_lint_level;
3333
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
3434
use rustc_middle::middle::dependency_format::Linkage;
3535
use rustc_middle::middle::exported_symbols::SymbolExportKind;
@@ -662,7 +662,7 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
662662
}
663663
}
664664

665-
#[derive(LintDiagnostic)]
665+
#[derive(Diagnostic)]
666666
#[diag("{$inner}")]
667667
/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
668668
/// end up with inconsistent languages within the same diagnostic.
@@ -938,9 +938,7 @@ fn link_natively(
938938

939939
let level = codegen_results.crate_info.lint_levels.linker_messages;
940940
let lint = |msg| {
941-
lint_level(sess, LINKER_MESSAGES, level, None, |diag| {
942-
LinkerOutput { inner: msg }.decorate_lint(diag)
943-
})
941+
diag_lint_level(sess, LINKER_MESSAGES, level, None, LinkerOutput { inner: msg });
944942
};
945943

946944
if !prog.stderr.is_empty() {

compiler/rustc_error_messages/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,18 @@ impl MultiSpan {
109109
MultiSpan { primary_spans: vec, span_labels: vec![] }
110110
}
111111

112+
pub fn push_primary_span(&mut self, primary_span: Span) {
113+
self.primary_spans.push(primary_span);
114+
}
115+
112116
pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagMessage>) {
113117
self.span_labels.push((span, label.into()));
114118
}
115119

120+
pub fn push_span_diag(&mut self, span: Span, diag: DiagMessage) {
121+
self.span_labels.push((span, diag));
122+
}
123+
116124
/// Selects the first primary span (if any).
117125
pub fn primary_span(&self) -> Option<Span> {
118126
self.primary_spans.first().cloned()
@@ -179,6 +187,11 @@ impl MultiSpan {
179187
span_labels
180188
}
181189

190+
/// Returns the span labels as contained by `MultiSpan`.
191+
pub fn span_labels_raw(&self) -> &[(Span, DiagMessage)] {
192+
&self.span_labels
193+
}
194+
182195
/// Returns `true` if any of the span labels is displayable.
183196
pub fn has_span_labels(&self) -> bool {
184197
self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())

compiler/rustc_middle/src/lint.rs

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::cmp;
22

33
use rustc_data_structures::fx::FxIndexMap;
44
use rustc_data_structures::sorted_map::SortedMap;
5-
use rustc_errors::{Diag, MultiSpan};
5+
use rustc_errors::{Diag, Diagnostic, MultiSpan};
66
use rustc_hir::{HirId, ItemLocalId};
77
use rustc_lint_defs::EditionFcw;
88
use rustc_macros::{Decodable, Encodable, HashStable};
@@ -482,3 +482,205 @@ pub fn lint_level(
482482
}
483483
lint_level_impl(sess, lint, level, span, Box::new(decorate))
484484
}
485+
486+
/// The innermost function for emitting lints implementing the [`trait@Diagnostic`] trait.
487+
///
488+
/// If you are looking to implement a lint, look for higher level functions,
489+
/// for example:
490+
///
491+
/// - [`TyCtxt::emit_node_span_lint`]
492+
/// - [`TyCtxt::node_span_lint`]
493+
/// - [`TyCtxt::emit_node_lint`]
494+
/// - [`TyCtxt::node_lint`]
495+
/// - `LintContext::opt_span_lint`
496+
///
497+
/// This function will replace `lint_level` once all `LintDiagnostic` items have been migrated to
498+
/// `Diagnostic`.
499+
#[track_caller]
500+
pub fn diag_lint_level<'a, D: Diagnostic<'a, ()> + 'a>(
501+
sess: &'a Session,
502+
lint: &'static Lint,
503+
level: LevelAndSource,
504+
span: Option<MultiSpan>,
505+
decorate: D,
506+
) {
507+
// Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
508+
// the "real" work.
509+
#[track_caller]
510+
fn diag_lint_level_impl<'a>(
511+
sess: &'a Session,
512+
lint: &'static Lint,
513+
level: LevelAndSource,
514+
span: Option<MultiSpan>,
515+
decorate: Box<
516+
dyn FnOnce(rustc_errors::DiagCtxtHandle<'a>, rustc_errors::Level) -> Diag<'a, ()> + 'a,
517+
>,
518+
) {
519+
let LevelAndSource { level, lint_id, src } = level;
520+
521+
// Check for future incompatibility lints and issue a stronger warning.
522+
let future_incompatible = lint.future_incompatible;
523+
524+
let has_future_breakage = future_incompatible.map_or(
525+
// Default allow lints trigger too often for testing.
526+
sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
527+
|incompat| incompat.report_in_deps,
528+
);
529+
530+
// Convert lint level to error level.
531+
let err_level = match level {
532+
Level::Allow => {
533+
if has_future_breakage {
534+
rustc_errors::Level::Allow
535+
} else {
536+
return;
537+
}
538+
}
539+
Level::Expect => {
540+
// This case is special as we actually allow the lint itself in this context, but
541+
// we can't return early like in the case for `Level::Allow` because we still
542+
// need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`.
543+
//
544+
// We can also not mark the lint expectation as fulfilled here right away, as it
545+
// can still be cancelled in the decorate function. All of this means that we simply
546+
// create a `Diag` and continue as we would for warnings.
547+
rustc_errors::Level::Expect
548+
}
549+
Level::ForceWarn => rustc_errors::Level::ForceWarning,
550+
Level::Warn => rustc_errors::Level::Warning,
551+
Level::Deny | Level::Forbid => rustc_errors::Level::Error,
552+
};
553+
// Finally, run `decorate`. `decorate` can call `trimmed_path_str` (directly or indirectly),
554+
// so we need to make sure when we do call `decorate` that the diagnostic is eventually
555+
// emitted or we'll get a `must_produce_diag` ICE.
556+
//
557+
// When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors:
558+
// 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)`
559+
// or `Error`, then the diagnostic will be emitted regardless of CLI options.
560+
// 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by
561+
// `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic
562+
// will be emitted if `can_emit_warnings` is true.
563+
let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
564+
565+
let disable_suggestions = if let Some(ref span) = span
566+
// If this code originates in a foreign macro, aka something that this crate
567+
// did not itself author, then it's likely that there's nothing this crate
568+
// can do about it. We probably want to skip the lint entirely.
569+
&& span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map()))
570+
{
571+
true
572+
} else {
573+
false
574+
};
575+
576+
let mut err: Diag<'_, ()> = if !skip {
577+
decorate(sess.dcx(), err_level)
578+
} else {
579+
Diag::new(sess.dcx(), err_level, "")
580+
};
581+
if let Some(span) = span
582+
&& err.span.primary_span().is_none()
583+
{
584+
// We can't use `err.span()` because it overwrites the labels, so we need to do it manually.
585+
for primary in span.primary_spans() {
586+
err.span.push_primary_span(*primary);
587+
}
588+
for (label_span, label) in span.span_labels_raw() {
589+
err.span.push_span_diag(*label_span, label.clone());
590+
}
591+
}
592+
if let Some(lint_id) = lint_id {
593+
err.lint_id(lint_id);
594+
}
595+
596+
if disable_suggestions {
597+
// Any suggestions made here are likely to be incorrect, so anything we
598+
// emit shouldn't be automatically fixed by rustfix.
599+
err.disable_suggestions();
600+
601+
// If this is a future incompatible that is not an edition fixing lint
602+
// it'll become a hard error, so we have to emit *something*. Also,
603+
// if this lint occurs in the expansion of a macro from an external crate,
604+
// allow individual lints to opt-out from being reported.
605+
let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
606+
607+
if !incompatible && !lint.report_in_external_macro {
608+
err.cancel();
609+
610+
// Don't continue further, since we don't want to have
611+
// `diag_span_note_once` called for a diagnostic that isn't emitted.
612+
return;
613+
}
614+
}
615+
616+
err.is_lint(lint.name_lower(), has_future_breakage);
617+
// Lint diagnostics that are covered by the expect level will not be emitted outside
618+
// the compiler. It is therefore not necessary to add any information for the user.
619+
// This will therefore directly call the decorate function which will in turn emit
620+
// the diagnostic.
621+
if let Level::Expect = level {
622+
err.emit();
623+
return;
624+
}
625+
626+
if let Some(future_incompatible) = future_incompatible {
627+
let explanation = match future_incompatible.reason {
628+
FutureIncompatibilityReason::FutureReleaseError(_) => {
629+
"this was previously accepted by the compiler but is being phased out; \
630+
it will become a hard error in a future release!"
631+
.to_owned()
632+
}
633+
FutureIncompatibilityReason::FutureReleaseSemanticsChange(_) => {
634+
"this will change its meaning in a future release!".to_owned()
635+
}
636+
FutureIncompatibilityReason::EditionError(EditionFcw { edition, .. }) => {
637+
let current_edition = sess.edition();
638+
format!(
639+
"this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
640+
)
641+
}
642+
FutureIncompatibilityReason::EditionSemanticsChange(EditionFcw {
643+
edition, ..
644+
}) => {
645+
format!("this changes meaning in Rust {edition}")
646+
}
647+
FutureIncompatibilityReason::EditionAndFutureReleaseError(EditionFcw {
648+
edition,
649+
..
650+
}) => {
651+
format!(
652+
"this was previously accepted by the compiler but is being phased out; \
653+
it will become a hard error in Rust {edition} and in a future release in all editions!"
654+
)
655+
}
656+
FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(
657+
EditionFcw { edition, .. },
658+
) => {
659+
format!(
660+
"this changes meaning in Rust {edition} and in a future release in all editions!"
661+
)
662+
}
663+
FutureIncompatibilityReason::Custom(reason, _) => reason.to_owned(),
664+
FutureIncompatibilityReason::Unreachable => unreachable!(),
665+
};
666+
667+
if future_incompatible.explain_reason {
668+
err.warn(explanation);
669+
}
670+
671+
let citation =
672+
format!("for more information, see {}", future_incompatible.reason.reference());
673+
err.note(citation);
674+
}
675+
676+
explain_lint_level_source(sess, lint, level, src, &mut err);
677+
err.emit();
678+
}
679+
diag_lint_level_impl(
680+
sess,
681+
lint,
682+
level,
683+
span,
684+
Box::new(move |dcx, level| decorate.into_diag(dcx, level)),
685+
);
686+
}

0 commit comments

Comments
 (0)