Skip to content
Merged
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
12 changes: 5 additions & 7 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ use rustc_attr_parsing::eval_config_entry;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
use rustc_errors::DiagCtxtHandle;
use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::attrs::NativeLibKind;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_macros::LintDiagnostic;
use rustc_macros::Diagnostic;
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
use rustc_metadata::{
EncodedMetadata, NativeLibSearchFallback, find_native_static_library,
walk_native_lib_search_dirs,
};
use rustc_middle::bug;
use rustc_middle::lint::lint_level;
use rustc_middle::lint::diag_lint_level;
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Linkage;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
Expand Down Expand Up @@ -662,7 +662,7 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
}
}

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

let level = codegen_results.crate_info.lint_levels.linker_messages;
let lint = |msg| {
lint_level(sess, LINKER_MESSAGES, level, None, |diag| {
LinkerOutput { inner: msg }.decorate_lint(diag)
})
diag_lint_level(sess, LINKER_MESSAGES, level, None, LinkerOutput { inner: msg });
};

if !prog.stderr.is_empty() {
Expand Down
13 changes: 13 additions & 0 deletions compiler/rustc_error_messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,18 @@ impl MultiSpan {
MultiSpan { primary_spans: vec, span_labels: vec![] }
}

pub fn push_primary_span(&mut self, primary_span: Span) {
self.primary_spans.push(primary_span);
}

pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagMessage>) {
self.span_labels.push((span, label.into()));
}

pub fn push_span_diag(&mut self, span: Span, diag: DiagMessage) {
self.span_labels.push((span, diag));
}

/// Selects the first primary span (if any).
pub fn primary_span(&self) -> Option<Span> {
self.primary_spans.first().cloned()
Expand Down Expand Up @@ -386,6 +394,11 @@ impl MultiSpan {
span_labels
}

/// Returns the span labels as contained by `MultiSpan`.
pub fn span_labels_raw(&self) -> &[(Span, DiagMessage)] {
&self.span_labels
}

/// Returns `true` if any of the span labels is displayable.
pub fn has_span_labels(&self) -> bool {
self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())
Expand Down
204 changes: 203 additions & 1 deletion compiler/rustc_middle/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cmp;

use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::sorted_map::SortedMap;
use rustc_errors::{Diag, MultiSpan};
use rustc_errors::{Diag, Diagnostic, MultiSpan};
use rustc_hir::{HirId, ItemLocalId};
use rustc_lint_defs::EditionFcw;
use rustc_macros::{Decodable, Encodable, HashStable};
Expand Down Expand Up @@ -472,3 +472,205 @@ pub fn lint_level(
}
lint_level_impl(sess, lint, level, span, Box::new(decorate))
}

/// The innermost function for emitting lints implementing the [`trait@Diagnostic`] trait.
///
/// If you are looking to implement a lint, look for higher level functions,
/// for example:
///
/// - [`TyCtxt::emit_node_span_lint`]
/// - [`TyCtxt::node_span_lint`]
/// - [`TyCtxt::emit_node_lint`]
/// - [`TyCtxt::node_lint`]
/// - `LintContext::opt_span_lint`
///
/// This function will replace `lint_level` once all `LintDiagnostic` items have been migrated to
/// `Diagnostic`.
#[track_caller]
pub fn diag_lint_level<'a, D: Diagnostic<'a, ()> + 'a>(
sess: &'a Session,
lint: &'static Lint,
level: LevelAndSource,
span: Option<MultiSpan>,
decorate: D,
) {
// Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
// the "real" work.
#[track_caller]
fn diag_lint_level_impl<'a>(
sess: &'a Session,
lint: &'static Lint,
level: LevelAndSource,
span: Option<MultiSpan>,
decorate: Box<
dyn FnOnce(rustc_errors::DiagCtxtHandle<'a>, rustc_errors::Level) -> Diag<'a, ()> + 'a,
>,
) {
let LevelAndSource { level, lint_id, src } = level;

// Check for future incompatibility lints and issue a stronger warning.
let future_incompatible = lint.future_incompatible;

let has_future_breakage = future_incompatible.map_or(
// Default allow lints trigger too often for testing.
sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
|incompat| incompat.report_in_deps,
);

// Convert lint level to error level.
let err_level = match level {
Level::Allow => {
if has_future_breakage {
rustc_errors::Level::Allow
} else {
return;
}
}
Level::Expect => {
// This case is special as we actually allow the lint itself in this context, but
// we can't return early like in the case for `Level::Allow` because we still
// need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`.
//
// We can also not mark the lint expectation as fulfilled here right away, as it
// can still be cancelled in the decorate function. All of this means that we simply
// create a `Diag` and continue as we would for warnings.
rustc_errors::Level::Expect
}
Level::ForceWarn => rustc_errors::Level::ForceWarning,
Level::Warn => rustc_errors::Level::Warning,
Level::Deny | Level::Forbid => rustc_errors::Level::Error,
};
// Finally, run `decorate`. `decorate` can call `trimmed_path_str` (directly or indirectly),
// so we need to make sure when we do call `decorate` that the diagnostic is eventually
// emitted or we'll get a `must_produce_diag` ICE.
//
// When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors:
// 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)`
// or `Error`, then the diagnostic will be emitted regardless of CLI options.
// 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by
// `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic
// will be emitted if `can_emit_warnings` is true.
let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();

let disable_suggestions = if let Some(ref span) = span
// If this code originates in a foreign macro, aka something that this crate
// did not itself author, then it's likely that there's nothing this crate
// can do about it. We probably want to skip the lint entirely.
&& span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map()))
{
true
} else {
false
};

let mut err: Diag<'_, ()> = if !skip {
decorate(sess.dcx(), err_level)
} else {
Diag::new(sess.dcx(), err_level, "")
};
if let Some(span) = span
&& err.span.primary_span().is_none()
{
// We can't use `err.span()` because it overwrites the labels, so we need to do it manually.
for primary in span.primary_spans() {
err.span.push_primary_span(*primary);
}
for (label_span, label) in span.span_labels_raw() {
err.span.push_span_diag(*label_span, label.clone());
}
}
if let Some(lint_id) = lint_id {
err.lint_id(lint_id);
}

if disable_suggestions {
// Any suggestions made here are likely to be incorrect, so anything we
// emit shouldn't be automatically fixed by rustfix.
err.disable_suggestions();

// If this is a future incompatible that is not an edition fixing lint
// it'll become a hard error, so we have to emit *something*. Also,
// if this lint occurs in the expansion of a macro from an external crate,
// allow individual lints to opt-out from being reported.
let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());

if !incompatible && !lint.report_in_external_macro {
err.cancel();

// Don't continue further, since we don't want to have
// `diag_span_note_once` called for a diagnostic that isn't emitted.
return;
}
}

err.is_lint(lint.name_lower(), has_future_breakage);
// Lint diagnostics that are covered by the expect level will not be emitted outside
// the compiler. It is therefore not necessary to add any information for the user.
// This will therefore directly call the decorate function which will in turn emit
// the diagnostic.
if let Level::Expect = level {
err.emit();
return;
}

if let Some(future_incompatible) = future_incompatible {
let explanation = match future_incompatible.reason {
FutureIncompatibilityReason::FutureReleaseError(_) => {
"this was previously accepted by the compiler but is being phased out; \
it will become a hard error in a future release!"
.to_owned()
}
FutureIncompatibilityReason::FutureReleaseSemanticsChange(_) => {
"this will change its meaning in a future release!".to_owned()
}
FutureIncompatibilityReason::EditionError(EditionFcw { edition, .. }) => {
let current_edition = sess.edition();
format!(
"this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
)
}
FutureIncompatibilityReason::EditionSemanticsChange(EditionFcw {
edition, ..
}) => {
format!("this changes meaning in Rust {edition}")
}
FutureIncompatibilityReason::EditionAndFutureReleaseError(EditionFcw {
edition,
..
}) => {
format!(
"this was previously accepted by the compiler but is being phased out; \
it will become a hard error in Rust {edition} and in a future release in all editions!"
)
}
FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(
EditionFcw { edition, .. },
) => {
format!(
"this changes meaning in Rust {edition} and in a future release in all editions!"
)
}
FutureIncompatibilityReason::Custom(reason, _) => reason.to_owned(),
FutureIncompatibilityReason::Unreachable => unreachable!(),
};

if future_incompatible.explain_reason {
err.warn(explanation);
}

let citation =
format!("for more information, see {}", future_incompatible.reason.reference());
err.note(citation);
}

explain_lint_level_source(sess, lint, level, src, &mut err);
err.emit();
}
diag_lint_level_impl(
sess,
lint,
level,
span,
Box::new(move |dcx, level| decorate.into_diag(dcx, level)),
);
}
Loading