Skip to content

Commit a85ae24

Browse files
Create new diag_lint_level function, aiming to replace lint_level once all LintDiagnostic items have been removed
1 parent bb2d55e commit a85ae24

1 file changed

Lines changed: 182 additions & 1 deletion

File tree

compiler/rustc_middle/src/lint.rs

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

0 commit comments

Comments
 (0)