From 57d969500c2b62f849b2a7a0c6896209a2363a38 Mon Sep 17 00:00:00 2001 From: xizheyin Date: Fri, 9 Oct 2026 18:54:42 +0800 Subject: [PATCH] show span-only suggestions when source is unavailable in ui tests --- .../src/annotate_snippet_emitter_writer.rs | 65 +++++++++++++++++++ compiler/rustc_errors/src/json.rs | 3 + compiler/rustc_session/src/options.rs | 2 + compiler/rustc_session/src/session.rs | 8 ++- src/tools/compiletest/src/runtest.rs | 3 + ...parison_instead_of_pattern_matching.stderr | 2 + .../pin-ergonomics/pinned-drop-check.stderr | 18 +++++ .../ice-line-bounds-issue-148684.stderr | 2 + 8 files changed, 102 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 15a435357544d..6e8d490ce0ea4 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -45,6 +45,7 @@ pub struct AnnotateSnippetEmitter { track_diagnostics: bool, terminal_url: TerminalUrl, theme: OutputTheme, + show_suggestions_with_unavailable_source: bool, } impl Debug for AnnotateSnippetEmitter { @@ -60,6 +61,7 @@ impl Debug for AnnotateSnippetEmitter { track_diagnostics, terminal_url, theme, + show_suggestions_with_unavailable_source, } = self; f.debug_struct("AnnotateSnippetEmitter") @@ -73,6 +75,10 @@ impl Debug for AnnotateSnippetEmitter { .field("track_diagnostics", track_diagnostics) .field("terminal_url", terminal_url) .field("theme", theme) + .field( + "show_suggestions_with_unavailable_source", + show_suggestions_with_unavailable_source, + ) .finish() } } @@ -153,6 +159,7 @@ impl AnnotateSnippetEmitter { track_diagnostics: false, terminal_url: TerminalUrl::No, theme: OutputTheme::Ascii, + show_suggestions_with_unavailable_source: false, } } @@ -319,6 +326,12 @@ impl AnnotateSnippetEmitter { SuggestionStyle::HideCodeInline | SuggestionStyle::ShowCode | SuggestionStyle::ShowAlways => { + // Get the original unavailable spans before `suggestion` is consumed. + let unavailable_source_span = if self.show_suggestions_with_unavailable_source { + self.suggestion_span_with_unavailable_source(sm, &suggestion) + } else { + None + }; let substitutions = suggestion .substitutions .into_iter() @@ -368,6 +381,30 @@ impl AnnotateSnippetEmitter { .collect::>(); if substitutions.is_empty() { + if let Some(span) = unavailable_source_span { + let msg = format_diag_message(&suggestion.msg, args).to_string(); + report.push(std::mem::replace( + &mut group, + Group::with_title( + annotate_snippets::Level::HELP.secondary_title(msg), + ), + )); + + let file_ann = collect_annotations(args, &span, sm); + let level = annotate_snippets::Level::HELP; + for (file_idx, (file, annotations)) in file_ann.into_iter().enumerate() + { + group = self.unannotated_messages( + annotations, + &file.name, + sm, + file_idx, + &mut report, + group, + &level, + ); + } + } continue; } let mut msg = format_diag_message(&suggestion.msg, args).to_string(); @@ -652,6 +689,34 @@ impl AnnotateSnippetEmitter { } group } + + fn suggestion_span_with_unavailable_source( + &self, + sm: &Arc, + suggestion: &CodeSuggestion, + ) -> Option { + // These spans cannot be rendered as source patches because their source + // files are unavailable, but can still be shown as locations. + let spans = suggestion + .substitutions + .iter() + .flat_map(|subst| &subst.parts) + .filter_map(|part| { + if sm.is_valid_span(part.span).is_err() { + debug!("suggestion contains an invalid span: {:?}", part); + return None; + } + let lines = sm.span_to_lines(part.span).ok()?; + if sm.ensure_source_file_source_present(&lines.file) { + None + } else { + Some(part.span) + } + }) + .collect::>(); + + if spans.is_empty() { None } else { Some(MultiSpan::from_spans(spans)) } + } } fn emit_to_destination( diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index 59d79754314c1..29cffb1e3cdda 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -54,6 +54,7 @@ pub struct JsonEmitter { macro_backtrace: bool, track_diagnostics: bool, terminal_url: TerminalUrl, + show_suggestions_with_unavailable_source: bool, } impl JsonEmitter { @@ -76,6 +77,7 @@ impl JsonEmitter { macro_backtrace: false, track_diagnostics: false, terminal_url: TerminalUrl::No, + show_suggestions_with_unavailable_source: false, } } @@ -361,6 +363,7 @@ impl Diagnostic { .terminal_url(je.terminal_url) .ui_testing(je.ui_testing) .ignored_directories_in_source_blocks(je.ignored_directories_in_source_blocks.clone()) + .show_suggestions_with_unavailable_source(je.show_suggestions_with_unavailable_source) .theme(if je.json_rendered.unicode { OutputTheme::Unicode } else { OutputTheme::Ascii }) .emit_diagnostic(diag); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index a29309c71e879..d5bc54ffb1647 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2883,6 +2883,8 @@ written to standard error output)"), "make the current crate share its generic instantiations"), shell_argfiles: bool = (false, parse_bool, [UNTRACKED], "allow argument files to be specified with POSIX \"shell-style\" argument quoting"), + show_suggestions_with_unavailable_source: bool = (false, parse_bool, [UNTRACKED], + "show span-only suggestions when the source code is unavailable"), simulate_remapped_rust_src_base: Option = (None, parse_opt_pathbuf, [TRACKED], "simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \ to rust's source base directory. only meant for testing purposes"), diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f068aaa584dce..66c5f7740a011 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1282,6 +1282,8 @@ impl Session { fn default_emitter(sopts: &config::Options, source_map: Arc) -> Box { let macro_backtrace = sopts.unstable_opts.macro_backtrace; let track_diagnostics = sopts.unstable_opts.track_diagnostics; + let show_suggestions_with_unavailable_source = + sopts.unstable_opts.show_suggestions_with_unavailable_source; let terminal_url = match sopts.unstable_opts.terminal_urls { TerminalUrl::Auto => { match (std::env::var("COLORTERM").as_deref(), std::env::var("TERM").as_deref()) { @@ -1309,6 +1311,9 @@ fn default_emitter(sopts: &config::Options, source_map: Arc) -> Box) -> Box TestCx<'test> { compiler.arg("-Zwrite-long-types-to-disk=no"); // FIXME: use this for other modes too, for perf? compiler.arg("-Cstrip=debuginfo"); + if self.config.suite != TestSuite::UiFullDeps { + compiler.arg("-Zshow-suggestions-with-unavailable-source"); + } if self.config.parallel_frontend_enabled() { // Currently, we only use multiple threads for the UI test suite, diff --git a/tests/ui/consts/const_in_pattern/suggest_equality_comparison_instead_of_pattern_matching.stderr b/tests/ui/consts/const_in_pattern/suggest_equality_comparison_instead_of_pattern_matching.stderr index 38440af675feb..8e642de858563 100644 --- a/tests/ui/consts/const_in_pattern/suggest_equality_comparison_instead_of_pattern_matching.stderr +++ b/tests/ui/consts/const_in_pattern/suggest_equality_comparison_instead_of_pattern_matching.stderr @@ -147,6 +147,8 @@ LL | if let V = vec![] {} = note: `Vec<()>` is not usable in patterns | = note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html for details +help: check for equality instead of pattern matching + --> $SRC_DIR/alloc/src/macros.rs:LL:COL error: constant of non-structural type `Vec<()>` in a pattern --> $DIR/suggest_equality_comparison_instead_of_pattern_matching.rs:28:13 diff --git a/tests/ui/pin-ergonomics/pinned-drop-check.stderr b/tests/ui/pin-ergonomics/pinned-drop-check.stderr index 6087d45dd2900..9046ac1f46a8e 100644 --- a/tests/ui/pin-ergonomics/pinned-drop-check.stderr +++ b/tests/ui/pin-ergonomics/pinned-drop-check.stderr @@ -293,36 +293,54 @@ error[E0040]: explicit use of destructor method | LL | Drop::pin_drop(todo!()); | ^^^^^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error[E0040]: explicit use of destructor method --> $DIR/pinned-drop-check.rs:156:13 | LL | Drop::pin_drop(todo!()); | ^^^^^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error[E0040]: explicit use of destructor method --> $DIR/pinned-drop-check.rs:168:13 | LL | Drop::drop(todo!()); | ^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error[E0040]: explicit use of destructor method --> $DIR/pinned-drop-check.rs:173:13 | LL | Drop::drop(todo!()); | ^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error[E0040]: explicit use of destructor method --> $DIR/pinned-drop-check.rs:183:13 | LL | Drop::drop(todo!()); | ^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error[E0040]: explicit use of destructor method --> $DIR/pinned-drop-check.rs:184:13 | LL | Drop::pin_drop(todo!()); | ^^^^^^^^^^^^^^ explicit destructor calls not allowed + | +help: consider using `drop` function + --> $SRC_DIR/core/src/macros/mod.rs:LL:COL error: aborting due to 25 previous errors diff --git a/tests/ui/structs/ice-line-bounds-issue-148684.stderr b/tests/ui/structs/ice-line-bounds-issue-148684.stderr index 700e59504853c..979cfd07da2ea 100644 --- a/tests/ui/structs/ice-line-bounds-issue-148684.stderr +++ b/tests/ui/structs/ice-line-bounds-issue-148684.stderr @@ -11,6 +11,8 @@ LL | A(2, vec![]) | ^^^^^^^^^^^^ | = note: a struct named `A` exists in another namespace +help: use struct literal syntax instead of calling + --> $SRC_DIR/alloc/src/macros.rs:LL:COL error: aborting due to 1 previous error