mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Rollup merge of #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 https://github.com/rust-lang/rust/pull/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
This commit is contained in:
@@ -18,18 +18,18 @@
|
||||
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;
|
||||
@@ -662,7 +662,7 @@ fn read_input(&self, path: &Path) -> std::io::Result<&[u8]> {
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
@@ -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() {
|
||||
|
||||
@@ -109,10 +109,18 @@ pub fn from_spans(mut vec: Vec<Span>) -> 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()
|
||||
@@ -179,6 +187,11 @@ pub fn span_labels(&self) -> Vec<SpanLabel> {
|
||||
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())
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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};
|
||||
@@ -482,3 +482,205 @@ fn lint_level_impl(
|
||||
}
|
||||
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)),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user