about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2025-06-24 11:20:06 +0200
committerGitHub <noreply@github.com>2025-06-24 11:20:06 +0200
commit5f2dae19c4c8553ffbfad493e4e213d06782fd06 (patch)
treedfdf1c66e9295adf2031f730549242dd644e6da2
parent24cd817cfa1e63242a6936c8faf64a47085ecf83 (diff)
parent7a4f09c2240ef2eb4d1e383f0d471b85ce8d0720 (diff)
downloadrust-5f2dae19c4c8553ffbfad493e4e213d06782fd06.tar.gz
rust-5f2dae19c4c8553ffbfad493e4e213d06782fd06.zip
Rollup merge of #140622 - petrochenkov:annusexp, r=jieyouxu
compiletest: Improve diagnostics for line annotation mismatches

When some line annotations are missing or misplaced, compiletest reports an error, but the error is not very convenient.
This PR attempts to improve the user experience.

- The "expected ... not found" messages are no longer duplicated.
- The `proc_res.status` and `proc_res.cmdline` message is no longer put in the middle of other messages describing the annotation mismatches, it's now put into the end.
- Compiletest now makes suggestions if there are fuzzy matches between expected and actually reported errors (e.g. the annotation is put on a wrong line).
- Missing diagnostic kinds are no longer produce an error eagerly, but instead treated as always mismatching kinds, so they can produce suggestions telling the right kind.

I'll post screenshots in the thread below, but the behavior shown on the screenshots can be reproduced locally using the new test `tests/ui/compiletest-self-test/line-annotation-mismatches.rs`.

This also fixes https://github.com/rust-lang/rust/issues/140940.

r? ``@jieyouxu``
-rw-r--r--src/tools/compiletest/src/errors.rs46
-rw-r--r--src/tools/compiletest/src/header.rs2
-rw-r--r--src/tools/compiletest/src/json.rs40
-rw-r--r--src/tools/compiletest/src/runtest.rs147
-rw-r--r--tests/incremental/issue-61323.rs2
-rw-r--r--tests/ui/argument-suggestions/issue-100478.rs4
-rw-r--r--tests/ui/async-await/incorrect-move-async-order-issue-79694.fixed2
-rw-r--r--tests/ui/async-await/incorrect-move-async-order-issue-79694.rs2
-rw-r--r--tests/ui/compiletest-self-test/line-annotation-mismatches.rs42
-rw-r--r--tests/ui/compiletest-self-test/line-annotation-mismatches.stderr61
-rw-r--r--tests/ui/feature-gates/feature-gate-cfi_encoding.rs2
-rw-r--r--tests/ui/feature-gates/feature-gate-unsized_tuple_coercion.rs2
-rw-r--r--tests/ui/impl-header-lifetime-elision/assoc-type.rs2
-rw-r--r--tests/ui/imports/issue-28134.rs2
-rw-r--r--tests/ui/inference/hint-closure-signature-119266.rs2
-rw-r--r--tests/ui/integral-indexing.rs16
-rw-r--r--tests/ui/issues/issue-92741.rs6
-rw-r--r--tests/ui/lifetimes/no_lending_iterators.rs6
-rw-r--r--tests/ui/mismatched_types/transforming-option-ref-issue-127545.rs8
-rw-r--r--tests/ui/sanitizer/cfi/invalid-attr-encoding.rs2
-rw-r--r--tests/ui/suggestions/issue-105645.rs2
-rw-r--r--tests/ui/suggestions/suggest-full-enum-variant-for-local-module.rs2
-rw-r--r--tests/ui/type/option-ref-advice.rs4
-rw-r--r--tests/ui/typeck/issue-100246.rs2
-rw-r--r--tests/ui/typeck/issue-89275.rs2
25 files changed, 297 insertions, 111 deletions
diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs
index b5a2b7feac9..9fa26305f6b 100644
--- a/src/tools/compiletest/src/errors.rs
+++ b/src/tools/compiletest/src/errors.rs
@@ -16,6 +16,8 @@ pub enum ErrorKind {
     Suggestion,
     Warning,
     Raw,
+    /// Used for better recovery and diagnostics in compiletest.
+    Unknown,
 }
 
 impl ErrorKind {
@@ -31,21 +33,25 @@ impl ErrorKind {
 
     /// Either the canonical uppercase string, or some additional versions for compatibility.
     /// FIXME: consider keeping only the canonical versions here.
-    pub fn from_user_str(s: &str) -> ErrorKind {
-        match s {
+    fn from_user_str(s: &str) -> Option<ErrorKind> {
+        Some(match s {
             "HELP" | "help" => ErrorKind::Help,
             "ERROR" | "error" => ErrorKind::Error,
-            // `MONO_ITEM` makes annotations in `codegen-units` tests syntactically correct,
-            // but those tests never use the error kind later on.
-            "NOTE" | "note" | "MONO_ITEM" => ErrorKind::Note,
+            "NOTE" | "note" => ErrorKind::Note,
             "SUGGESTION" => ErrorKind::Suggestion,
             "WARN" | "WARNING" | "warn" | "warning" => ErrorKind::Warning,
             "RAW" => ErrorKind::Raw,
-            _ => panic!(
+            _ => return None,
+        })
+    }
+
+    pub fn expect_from_user_str(s: &str) -> ErrorKind {
+        ErrorKind::from_user_str(s).unwrap_or_else(|| {
+            panic!(
                 "unexpected diagnostic kind `{s}`, expected \
-                 `ERROR`, `WARN`, `NOTE`, `HELP` or `SUGGESTION`"
-            ),
-        }
+                 `ERROR`, `WARN`, `NOTE`, `HELP`, `SUGGESTION` or `RAW`"
+            )
+        })
     }
 }
 
@@ -58,6 +64,7 @@ impl fmt::Display for ErrorKind {
             ErrorKind::Suggestion => write!(f, "SUGGESTION"),
             ErrorKind::Warning => write!(f, "WARN"),
             ErrorKind::Raw => write!(f, "RAW"),
+            ErrorKind::Unknown => write!(f, "UNKNOWN"),
         }
     }
 }
@@ -65,6 +72,7 @@ impl fmt::Display for ErrorKind {
 #[derive(Debug)]
 pub struct Error {
     pub line_num: Option<usize>,
+    pub column_num: Option<usize>,
     /// What kind of message we expect (e.g., warning, error, suggestion).
     pub kind: ErrorKind,
     pub msg: String,
@@ -74,17 +82,6 @@ pub struct Error {
     pub require_annotation: bool,
 }
 
-impl Error {
-    pub fn render_for_expected(&self) -> String {
-        use colored::Colorize;
-        format!("{: <10}line {: >3}: {}", self.kind, self.line_num_str(), self.msg.cyan())
-    }
-
-    pub fn line_num_str(&self) -> String {
-        self.line_num.map_or("?".to_string(), |line_num| line_num.to_string())
-    }
-}
-
 /// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE"
 /// The former is a "follow" that inherits its target from the preceding line;
 /// the latter is an "adjusts" that goes that many lines up.
@@ -168,8 +165,10 @@ fn parse_expected(
     let rest = line[tag.end()..].trim_start();
     let (kind_str, _) =
         rest.split_once(|c: char| c != '_' && !c.is_ascii_alphabetic()).unwrap_or((rest, ""));
-    let kind = ErrorKind::from_user_str(kind_str);
-    let untrimmed_msg = &rest[kind_str.len()..];
+    let (kind, untrimmed_msg) = match ErrorKind::from_user_str(kind_str) {
+        Some(kind) => (kind, &rest[kind_str.len()..]),
+        None => (ErrorKind::Unknown, rest),
+    };
     let msg = untrimmed_msg.strip_prefix(':').unwrap_or(untrimmed_msg).trim().to_owned();
 
     let line_num_adjust = &captures["adjust"];
@@ -182,6 +181,7 @@ fn parse_expected(
     } else {
         (false, Some(line_num - line_num_adjust.len()))
     };
+    let column_num = Some(tag.start() + 1);
 
     debug!(
         "line={:?} tag={:?} follow_prev={:?} kind={:?} msg={:?}",
@@ -191,7 +191,7 @@ fn parse_expected(
         kind,
         msg
     );
-    Some((follow_prev, Error { line_num, kind, msg, require_annotation: true }))
+    Some((follow_prev, Error { line_num, column_num, kind, msg, require_annotation: true }))
 }
 
 #[cfg(test)]
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 8bee9caacc9..2b203bb309c 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -593,7 +593,7 @@ impl TestProps {
                         config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
                     {
                         self.dont_require_annotations
-                            .insert(ErrorKind::from_user_str(err_kind.trim()));
+                            .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
                     }
                 },
             );
diff --git a/src/tools/compiletest/src/json.rs b/src/tools/compiletest/src/json.rs
index 6ed2b52c66d..a8e6416e56c 100644
--- a/src/tools/compiletest/src/json.rs
+++ b/src/tools/compiletest/src/json.rs
@@ -36,9 +36,7 @@ struct UnusedExternNotification {
 struct DiagnosticSpan {
     file_name: String,
     line_start: usize,
-    line_end: usize,
     column_start: usize,
-    column_end: usize,
     is_primary: bool,
     label: Option<String>,
     suggested_replacement: Option<String>,
@@ -148,6 +146,7 @@ pub fn parse_output(file_name: &str, output: &str) -> Vec<Error> {
             Ok(diagnostic) => push_actual_errors(&mut errors, &diagnostic, &[], file_name),
             Err(_) => errors.push(Error {
                 line_num: None,
+                column_num: None,
                 kind: ErrorKind::Raw,
                 msg: line.to_string(),
                 require_annotation: false,
@@ -193,25 +192,9 @@ fn push_actual_errors(
     // also ensure that `//~ ERROR E123` *always* works. The
     // assumption is that these multi-line error messages are on their
     // way out anyhow.
-    let with_code = |span: Option<&DiagnosticSpan>, text: &str| {
-        // FIXME(#33000) -- it'd be better to use a dedicated
-        // UI harness than to include the line/col number like
-        // this, but some current tests rely on it.
-        //
-        // Note: Do NOT include the filename. These can easily
-        // cause false matches where the expected message
-        // appears in the filename, and hence the message
-        // changes but the test still passes.
-        let span_str = match span {
-            Some(DiagnosticSpan { line_start, column_start, line_end, column_end, .. }) => {
-                format!("{line_start}:{column_start}: {line_end}:{column_end}")
-            }
-            None => format!("?:?: ?:?"),
-        };
-        match &diagnostic.code {
-            Some(code) => format!("{span_str}: {text} [{}]", code.code),
-            None => format!("{span_str}: {text}"),
-        }
+    let with_code = |text| match &diagnostic.code {
+        Some(code) => format!("{text} [{}]", code.code),
+        None => format!("{text}"),
     };
 
     // Convert multi-line messages into multiple errors.
@@ -225,8 +208,9 @@ fn push_actual_errors(
             || Regex::new(r"aborting due to \d+ previous errors?|\d+ warnings? emitted").unwrap();
         errors.push(Error {
             line_num: None,
+            column_num: None,
             kind,
-            msg: with_code(None, first_line),
+            msg: with_code(first_line),
             require_annotation: diagnostic.level != "failure-note"
                 && !RE.get_or_init(re_init).is_match(first_line),
         });
@@ -234,8 +218,9 @@ fn push_actual_errors(
         for span in primary_spans {
             errors.push(Error {
                 line_num: Some(span.line_start),
+                column_num: Some(span.column_start),
                 kind,
-                msg: with_code(Some(span), first_line),
+                msg: with_code(first_line),
                 require_annotation: true,
             });
         }
@@ -244,16 +229,18 @@ fn push_actual_errors(
         if primary_spans.is_empty() {
             errors.push(Error {
                 line_num: None,
+                column_num: None,
                 kind,
-                msg: with_code(None, next_line),
+                msg: with_code(next_line),
                 require_annotation: false,
             });
         } else {
             for span in primary_spans {
                 errors.push(Error {
                     line_num: Some(span.line_start),
+                    column_num: Some(span.column_start),
                     kind,
-                    msg: with_code(Some(span), next_line),
+                    msg: with_code(next_line),
                     require_annotation: false,
                 });
             }
@@ -266,6 +253,7 @@ fn push_actual_errors(
             for (index, line) in suggested_replacement.lines().enumerate() {
                 errors.push(Error {
                     line_num: Some(span.line_start + index),
+                    column_num: Some(span.column_start),
                     kind: ErrorKind::Suggestion,
                     msg: line.to_string(),
                     // Empty suggestions (suggestions to remove something) are common
@@ -288,6 +276,7 @@ fn push_actual_errors(
         if let Some(label) = &span.label {
             errors.push(Error {
                 line_num: Some(span.line_start),
+                column_num: Some(span.column_start),
                 kind: ErrorKind::Note,
                 msg: label.clone(),
                 // Empty labels (only underlining spans) are common and do not need annotations.
@@ -310,6 +299,7 @@ fn push_backtrace(
     if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
         errors.push(Error {
             line_num: Some(expansion.span.line_start),
+            column_num: Some(expansion.span.column_start),
             kind: ErrorKind::Note,
             msg: format!("in this expansion of {}", expansion.macro_decl_name),
             require_annotation: true,
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 42c851ea999..980e89889ab 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -11,7 +11,7 @@ use std::{env, iter, str};
 
 use build_helper::fs::remove_and_create_dir_all;
 use camino::{Utf8Path, Utf8PathBuf};
-use colored::Colorize;
+use colored::{Color, Colorize};
 use regex::{Captures, Regex};
 use tracing::*;
 
@@ -677,9 +677,6 @@ impl<'test> TestCx<'test> {
             return;
         }
 
-        // On Windows, translate all '\' path separators to '/'
-        let file_name = self.testpaths.file.to_string().replace(r"\", "/");
-
         // On Windows, keep all '\' path separators to match the paths reported in the JSON output
         // from the compiler
         let diagnostic_file_name = if self.props.remap_src_base {
@@ -704,6 +701,7 @@ impl<'test> TestCx<'test> {
             .map(|e| Error { msg: self.normalize_output(&e.msg, &[]), ..e });
 
         let mut unexpected = Vec::new();
+        let mut unimportant = Vec::new();
         let mut found = vec![false; expected_errors.len()];
         for actual_error in actual_errors {
             for pattern in &self.props.error_patterns {
@@ -738,14 +736,9 @@ impl<'test> TestCx<'test> {
                         && expected_kinds.contains(&actual_error.kind)
                         && !self.props.dont_require_annotations.contains(&actual_error.kind)
                     {
-                        self.error(&format!(
-                            "{}:{}: unexpected {}: '{}'",
-                            file_name,
-                            actual_error.line_num_str(),
-                            actual_error.kind,
-                            actual_error.msg
-                        ));
                         unexpected.push(actual_error);
+                    } else {
+                        unimportant.push(actual_error);
                     }
                 }
             }
@@ -755,39 +748,140 @@ impl<'test> TestCx<'test> {
         // anything not yet found is a problem
         for (index, expected_error) in expected_errors.iter().enumerate() {
             if !found[index] {
-                self.error(&format!(
-                    "{}:{}: expected {} not found: {}",
-                    file_name,
-                    expected_error.line_num_str(),
-                    expected_error.kind,
-                    expected_error.msg
-                ));
                 not_found.push(expected_error);
             }
         }
 
         if !unexpected.is_empty() || !not_found.is_empty() {
             self.error(&format!(
-                "{} unexpected errors found, {} expected errors not found",
+                "{} unexpected diagnostics reported, {} expected diagnostics not reported",
                 unexpected.len(),
                 not_found.len()
             ));
-            println!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
+
+            // Emit locations in a format that is short (relative paths) but "clickable" in editors.
+            // Also normalize path separators to `/`.
+            let file_name = self
+                .testpaths
+                .file
+                .strip_prefix(self.config.src_root.as_str())
+                .unwrap_or(&self.testpaths.file)
+                .to_string()
+                .replace(r"\", "/");
+            let line_str = |e: &Error| {
+                let line_num = e.line_num.map_or("?".to_string(), |line_num| line_num.to_string());
+                // `file:?:NUM` may be confusing to editors and unclickable.
+                let opt_col_num = match e.column_num {
+                    Some(col_num) if line_num != "?" => format!(":{col_num}"),
+                    _ => "".to_string(),
+                };
+                format!("{file_name}:{line_num}{opt_col_num}")
+            };
+            let print_error = |e| println!("{}: {}: {}", line_str(e), e.kind, e.msg.cyan());
+            let push_suggestion =
+                |suggestions: &mut Vec<_>, e: &Error, kind, line, msg, color, rank| {
+                    let mut ret = String::new();
+                    if kind {
+                        ret += &format!("{} {}", "with kind".color(color), e.kind);
+                    }
+                    if line {
+                        if !ret.is_empty() {
+                            ret.push(' ');
+                        }
+                        ret += &format!("{} {}", "on line".color(color), line_str(e));
+                    }
+                    if msg {
+                        if !ret.is_empty() {
+                            ret.push(' ');
+                        }
+                        ret += &format!("{} {}", "with message".color(color), e.msg.cyan());
+                    }
+                    suggestions.push((ret, rank));
+                };
+            let show_suggestions = |mut suggestions: Vec<_>, prefix: &str, color| {
+                // Only show suggestions with the highest rank.
+                suggestions.sort_by_key(|(_, rank)| *rank);
+                if let Some(&(_, top_rank)) = suggestions.first() {
+                    for (suggestion, rank) in suggestions {
+                        if rank == top_rank {
+                            println!("  {} {suggestion}", prefix.color(color));
+                        }
+                    }
+                }
+            };
+
+            // Fuzzy matching quality:
+            // - message and line / message and kind - great, suggested
+            // - only message - good, suggested
+            // - known line and kind - ok, suggested
+            // - only known line - meh, but suggested
+            // - others are not worth suggesting
             if !unexpected.is_empty() {
-                println!("{}", "--- unexpected errors (from JSON output) ---".green());
+                let header = "--- reported in JSON output but not expected in test file ---";
+                println!("{}", header.green());
                 for error in &unexpected {
-                    println!("{}", error.render_for_expected());
+                    print_error(error);
+                    let mut suggestions = Vec::new();
+                    for candidate in &not_found {
+                        let mut push_red_suggestion = |line, msg, rank| {
+                            push_suggestion(
+                                &mut suggestions,
+                                candidate,
+                                candidate.kind != error.kind,
+                                line,
+                                msg,
+                                Color::Red,
+                                rank,
+                            )
+                        };
+                        if error.msg.contains(&candidate.msg) {
+                            push_red_suggestion(candidate.line_num != error.line_num, false, 0);
+                        } else if candidate.line_num.is_some()
+                            && candidate.line_num == error.line_num
+                        {
+                            push_red_suggestion(false, true, 1);
+                        }
+                    }
+
+                    show_suggestions(suggestions, "expected", Color::Red);
                 }
                 println!("{}", "---".green());
             }
             if !not_found.is_empty() {
-                println!("{}", "--- not found errors (from test file) ---".red());
+                let header = "--- expected in test file but not reported in JSON output ---";
+                println!("{}", header.red());
                 for error in &not_found {
-                    println!("{}", error.render_for_expected());
+                    print_error(error);
+                    let mut suggestions = Vec::new();
+                    for candidate in unexpected.iter().chain(&unimportant) {
+                        let mut push_green_suggestion = |line, msg, rank| {
+                            push_suggestion(
+                                &mut suggestions,
+                                candidate,
+                                candidate.kind != error.kind,
+                                line,
+                                msg,
+                                Color::Green,
+                                rank,
+                            )
+                        };
+                        if candidate.msg.contains(&error.msg) {
+                            push_green_suggestion(candidate.line_num != error.line_num, false, 0);
+                        } else if candidate.line_num.is_some()
+                            && candidate.line_num == error.line_num
+                        {
+                            push_green_suggestion(false, true, 1);
+                        }
+                    }
+
+                    show_suggestions(suggestions, "reported", Color::Green);
                 }
-                println!("{}", "---\n".red());
+                println!("{}", "---".red());
             }
-            panic!("errors differ from expected");
+            panic!(
+                "errors differ from expected\nstatus: {}\ncommand: {}\n",
+                proc_res.status, proc_res.cmdline
+            );
         }
     }
 
@@ -2073,7 +2167,6 @@ impl<'test> TestCx<'test> {
             println!("{}", String::from_utf8_lossy(&output.stdout));
             eprintln!("{}", String::from_utf8_lossy(&output.stderr));
         } else {
-            use colored::Colorize;
             eprintln!("warning: no pager configured, falling back to unified diff");
             eprintln!(
                 "help: try configuring a git pager (e.g. `delta`) with `git config --global core.pager delta`"
diff --git a/tests/incremental/issue-61323.rs b/tests/incremental/issue-61323.rs
index b7423c81fc1..4845648d49c 100644
--- a/tests/incremental/issue-61323.rs
+++ b/tests/incremental/issue-61323.rs
@@ -1,7 +1,7 @@
 //@ revisions: rpass cfail
 
 enum A {
-    //[cfail]~^ ERROR 3:1: 3:7: recursive types `A` and `C` have infinite size [E0072]
+    //[cfail]~^ ERROR recursive types `A` and `C` have infinite size [E0072]
     B(C),
 }
 
diff --git a/tests/ui/argument-suggestions/issue-100478.rs b/tests/ui/argument-suggestions/issue-100478.rs
index b0a9703112e..219870f3b23 100644
--- a/tests/ui/argument-suggestions/issue-100478.rs
+++ b/tests/ui/argument-suggestions/issue-100478.rs
@@ -32,8 +32,8 @@ fn four_shuffle(_a: T1, _b: T2, _c: T3, _d: T4) {}
 
 fn main() {
     three_diff(T2::new(0)); //~ ERROR function takes
-    four_shuffle(T3::default(), T4::default(), T1::default(), T2::default()); //~ ERROR 35:5: 35:17: arguments to this function are incorrect [E0308]
-    four_shuffle(T3::default(), T2::default(), T1::default(), T3::default()); //~ ERROR 36:5: 36:17: arguments to this function are incorrect [E0308]
+    four_shuffle(T3::default(), T4::default(), T1::default(), T2::default()); //~ ERROR arguments to this function are incorrect [E0308]
+    four_shuffle(T3::default(), T2::default(), T1::default(), T3::default()); //~ ERROR arguments to this function are incorrect [E0308]
 
     let p1 = T1::new(0);
     let p2 = Arc::new(T2::new(0));
diff --git a/tests/ui/async-await/incorrect-move-async-order-issue-79694.fixed b/tests/ui/async-await/incorrect-move-async-order-issue-79694.fixed
index c74a32e442f..9e5e889506c 100644
--- a/tests/ui/async-await/incorrect-move-async-order-issue-79694.fixed
+++ b/tests/ui/async-await/incorrect-move-async-order-issue-79694.fixed
@@ -4,5 +4,5 @@
 // Regression test for issue 79694
 
 fn main() {
-    let _ = async move { }; //~ ERROR 7:13: 7:23: the order of `move` and `async` is incorrect
+    let _ = async move { }; //~ ERROR the order of `move` and `async` is incorrect
 }
diff --git a/tests/ui/async-await/incorrect-move-async-order-issue-79694.rs b/tests/ui/async-await/incorrect-move-async-order-issue-79694.rs
index 81ffbacc327..9c36a6c96da 100644
--- a/tests/ui/async-await/incorrect-move-async-order-issue-79694.rs
+++ b/tests/ui/async-await/incorrect-move-async-order-issue-79694.rs
@@ -4,5 +4,5 @@
 // Regression test for issue 79694
 
 fn main() {
-    let _ = move async { }; //~ ERROR 7:13: 7:23: the order of `move` and `async` is incorrect
+    let _ = move async { }; //~ ERROR the order of `move` and `async` is incorrect
 }
diff --git a/tests/ui/compiletest-self-test/line-annotation-mismatches.rs b/tests/ui/compiletest-self-test/line-annotation-mismatches.rs
new file mode 100644
index 00000000000..d2a14374ed4
--- /dev/null
+++ b/tests/ui/compiletest-self-test/line-annotation-mismatches.rs
@@ -0,0 +1,42 @@
+//@ should-fail
+
+// The warning is reported with unknown line
+//@ compile-flags: -D raw_pointer_derive
+//~? WARN kind and unknown line match the reported warning, but we do not suggest it
+
+// The error is expected but not reported at all.
+//~ ERROR this error does not exist
+
+// The error is reported but not expected at all.
+// "`main` function not found in crate" (the main function is intentionally not added)
+
+// An "unimportant" diagnostic is expected on a wrong line.
+//~ ERROR aborting due to
+
+// An "unimportant" diagnostic is expected with a wrong kind.
+//~? ERROR For more information about an error
+
+fn wrong_line_or_kind() {
+    // A diagnostic expected on a wrong line.
+    unresolved1;
+    //~ ERROR cannot find value `unresolved1` in this scope
+
+    // A diagnostic expected with a wrong kind.
+    unresolved2; //~ WARN cannot find value `unresolved2` in this scope
+
+    // A diagnostic expected with a missing kind (treated as a wrong kind).
+    unresolved3; //~ cannot find value `unresolved3` in this scope
+
+    // A diagnostic expected with a wrong line and kind.
+    unresolved4;
+    //~ WARN cannot find value `unresolved4` in this scope
+}
+
+fn wrong_message() {
+    // A diagnostic expected with a wrong message, but the line is known and right.
+    unresolvedA; //~ ERROR stub message 1
+
+    // A diagnostic expected with a wrong message, but the line is known and right,
+    // even if the kind doesn't match.
+    unresolvedB; //~ WARN stub message 2
+}
diff --git a/tests/ui/compiletest-self-test/line-annotation-mismatches.stderr b/tests/ui/compiletest-self-test/line-annotation-mismatches.stderr
new file mode 100644
index 00000000000..7ca3bfaf396
--- /dev/null
+++ b/tests/ui/compiletest-self-test/line-annotation-mismatches.stderr
@@ -0,0 +1,61 @@
+warning: lint `raw_pointer_derive` has been removed: using derive with raw pointers is ok
+   |
+   = note: requested on the command line with `-D raw_pointer_derive`
+   = note: `#[warn(renamed_and_removed_lints)]` on by default
+
+error[E0425]: cannot find value `unresolved1` in this scope
+  --> $DIR/line-annotation-mismatches.rs:21:5
+   |
+LL |     unresolved1;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `unresolved2` in this scope
+  --> $DIR/line-annotation-mismatches.rs:25:5
+   |
+LL |     unresolved2;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `unresolved3` in this scope
+  --> $DIR/line-annotation-mismatches.rs:28:5
+   |
+LL |     unresolved3;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `unresolved4` in this scope
+  --> $DIR/line-annotation-mismatches.rs:31:5
+   |
+LL |     unresolved4;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `unresolvedA` in this scope
+  --> $DIR/line-annotation-mismatches.rs:37:5
+   |
+LL |     unresolvedA;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `unresolvedB` in this scope
+  --> $DIR/line-annotation-mismatches.rs:41:5
+   |
+LL |     unresolvedB;
+   |     ^^^^^^^^^^^ not found in this scope
+
+warning: lint `raw_pointer_derive` has been removed: using derive with raw pointers is ok
+   |
+   = note: requested on the command line with `-D raw_pointer_derive`
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error[E0601]: `main` function not found in crate `line_annotation_mismatches`
+  --> $DIR/line-annotation-mismatches.rs:42:2
+   |
+LL | }
+   |  ^ consider adding a `main` function to `$DIR/line-annotation-mismatches.rs`
+
+warning: lint `raw_pointer_derive` has been removed: using derive with raw pointers is ok
+   |
+   = note: requested on the command line with `-D raw_pointer_derive`
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 7 previous errors; 3 warnings emitted
+
+Some errors have detailed explanations: E0425, E0601.
+For more information about an error, try `rustc --explain E0425`.
diff --git a/tests/ui/feature-gates/feature-gate-cfi_encoding.rs b/tests/ui/feature-gates/feature-gate-cfi_encoding.rs
index 3cef8156014..b6312dd7817 100644
--- a/tests/ui/feature-gates/feature-gate-cfi_encoding.rs
+++ b/tests/ui/feature-gates/feature-gate-cfi_encoding.rs
@@ -1,4 +1,4 @@
 #![crate_type = "lib"]
 
-#[cfi_encoding = "3Bar"] //~ERROR 3:1: 3:25: the `#[cfi_encoding]` attribute is an experimental feature [E0658]
+#[cfi_encoding = "3Bar"] //~ ERROR the `#[cfi_encoding]` attribute is an experimental feature [E0658]
 pub struct Foo(i32);
diff --git a/tests/ui/feature-gates/feature-gate-unsized_tuple_coercion.rs b/tests/ui/feature-gates/feature-gate-unsized_tuple_coercion.rs
index b5fbcc9ccf8..c1469863792 100644
--- a/tests/ui/feature-gates/feature-gate-unsized_tuple_coercion.rs
+++ b/tests/ui/feature-gates/feature-gate-unsized_tuple_coercion.rs
@@ -1,4 +1,4 @@
 fn main() {
     let _ : &(dyn Send,) = &((),);
-    //~^ ERROR 2:28: 2:34: mismatched types [E0308]
+    //~^ ERROR mismatched types [E0308]
 }
diff --git a/tests/ui/impl-header-lifetime-elision/assoc-type.rs b/tests/ui/impl-header-lifetime-elision/assoc-type.rs
index db3c416540f..14b2ea647f1 100644
--- a/tests/ui/impl-header-lifetime-elision/assoc-type.rs
+++ b/tests/ui/impl-header-lifetime-elision/assoc-type.rs
@@ -9,7 +9,7 @@ trait MyTrait {
 
 impl MyTrait for &i32 {
     type Output = &i32;
-    //~^ ERROR 11:19: 11:20: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
+    //~^ ERROR in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
 }
 
 impl MyTrait for &u32 {
diff --git a/tests/ui/imports/issue-28134.rs b/tests/ui/imports/issue-28134.rs
index 70d3a327c1a..aef2fe8facd 100644
--- a/tests/ui/imports/issue-28134.rs
+++ b/tests/ui/imports/issue-28134.rs
@@ -2,4 +2,4 @@
 
 #![allow(soft_unstable)]
 #![test]
-//~^ ERROR 4:1: 4:9: `test` attribute cannot be used at crate level
+//~^ ERROR `test` attribute cannot be used at crate level
diff --git a/tests/ui/inference/hint-closure-signature-119266.rs b/tests/ui/inference/hint-closure-signature-119266.rs
index 35be600fd6a..6e136c57cca 100644
--- a/tests/ui/inference/hint-closure-signature-119266.rs
+++ b/tests/ui/inference/hint-closure-signature-119266.rs
@@ -3,7 +3,7 @@ fn main() {
     //~^ NOTE: the found closure
 
     let x: fn(i32) = x;
-    //~^ ERROR: 5:22: 5:23: mismatched types [E0308]
+    //~^ ERROR: mismatched types [E0308]
     //~| NOTE: incorrect number of function parameters
     //~| NOTE: expected due to this
     //~| NOTE: expected fn pointer `fn(i32)`
diff --git a/tests/ui/integral-indexing.rs b/tests/ui/integral-indexing.rs
index f076dfcb0a4..e20553af8a2 100644
--- a/tests/ui/integral-indexing.rs
+++ b/tests/ui/integral-indexing.rs
@@ -3,14 +3,14 @@ pub fn main() {
     let s: String = "abcdef".to_string();
     v[3_usize];
     v[3];
-    v[3u8];  //~ERROR : the type `[isize]` cannot be indexed by `u8`
-    v[3i8];  //~ERROR : the type `[isize]` cannot be indexed by `i8`
-    v[3u32]; //~ERROR : the type `[isize]` cannot be indexed by `u32`
-    v[3i32]; //~ERROR : the type `[isize]` cannot be indexed by `i32`
+    v[3u8];  //~ ERROR the type `[isize]` cannot be indexed by `u8`
+    v[3i8];  //~ ERROR the type `[isize]` cannot be indexed by `i8`
+    v[3u32]; //~ ERROR the type `[isize]` cannot be indexed by `u32`
+    v[3i32]; //~ ERROR the type `[isize]` cannot be indexed by `i32`
     s.as_bytes()[3_usize];
     s.as_bytes()[3];
-    s.as_bytes()[3u8];  //~ERROR : the type `[u8]` cannot be indexed by `u8`
-    s.as_bytes()[3i8];  //~ERROR : the type `[u8]` cannot be indexed by `i8`
-    s.as_bytes()[3u32]; //~ERROR : the type `[u8]` cannot be indexed by `u32`
-    s.as_bytes()[3i32]; //~ERROR : the type `[u8]` cannot be indexed by `i32`
+    s.as_bytes()[3u8];  //~ ERROR the type `[u8]` cannot be indexed by `u8`
+    s.as_bytes()[3i8];  //~ ERROR the type `[u8]` cannot be indexed by `i8`
+    s.as_bytes()[3u32]; //~ ERROR the type `[u8]` cannot be indexed by `u32`
+    s.as_bytes()[3i32]; //~ ERROR the type `[u8]` cannot be indexed by `i32`
 }
diff --git a/tests/ui/issues/issue-92741.rs b/tests/ui/issues/issue-92741.rs
index f2e5fdafd9c..1c5d5810a57 100644
--- a/tests/ui/issues/issue-92741.rs
+++ b/tests/ui/issues/issue-92741.rs
@@ -1,17 +1,17 @@
 //@ run-rustfix
 fn main() {}
 fn _foo() -> bool {
-    &  //~ ERROR 4:5: 6:36: mismatched types [E0308]
+    &  //~ ERROR mismatched types [E0308]
     mut
     if true { true } else { false }
 }
 
 fn _bar() -> bool {
-    &  //~ ERROR 10:5: 11:40: mismatched types [E0308]
+    &  //~ ERROR mismatched types [E0308]
     mut if true { true } else { false }
 }
 
 fn _baz() -> bool {
-    & mut //~ ERROR 15:5: 16:36: mismatched types [E0308]
+    & mut //~ ERROR mismatched types [E0308]
     if true { true } else { false }
 }
diff --git a/tests/ui/lifetimes/no_lending_iterators.rs b/tests/ui/lifetimes/no_lending_iterators.rs
index b3e8ad08ba1..88b8cda0898 100644
--- a/tests/ui/lifetimes/no_lending_iterators.rs
+++ b/tests/ui/lifetimes/no_lending_iterators.rs
@@ -2,7 +2,7 @@ struct Data(String);
 
 impl Iterator for Data {
     type Item = &str;
-    //~^ ERROR 4:17: 4:18: associated type `Iterator::Item` is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
+    //~^ ERROR associated type `Iterator::Item` is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
 
     fn next(&mut self) -> Option<Self::Item> {
         Some(&self.0)
@@ -16,7 +16,7 @@ trait Bar {
 
 impl Bar for usize {
     type Item = &usize;
-    //~^ ERROR 18:17: 18:18: in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
+    //~^ ERROR in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
 
     fn poke(&mut self, item: Self::Item) {
         self += *item;
@@ -25,7 +25,7 @@ impl Bar for usize {
 
 impl Bar for isize {
     type Item<'a> = &'a isize;
-    //~^ ERROR 27:14: 27:18: lifetime parameters or bounds on associated type `Item` do not match the trait declaration [E0195]
+    //~^ ERROR lifetime parameters or bounds on associated type `Item` do not match the trait declaration [E0195]
 
     fn poke(&mut self, item: Self::Item) {
         self += *item;
diff --git a/tests/ui/mismatched_types/transforming-option-ref-issue-127545.rs b/tests/ui/mismatched_types/transforming-option-ref-issue-127545.rs
index f589e88f68e..0632b822c55 100644
--- a/tests/ui/mismatched_types/transforming-option-ref-issue-127545.rs
+++ b/tests/ui/mismatched_types/transforming-option-ref-issue-127545.rs
@@ -2,17 +2,17 @@
 #![crate_type = "lib"]
 
 pub fn foo(arg: Option<&Vec<i32>>) -> Option<&[i32]> {
-    arg //~ ERROR 5:5: 5:8: mismatched types [E0308]
+    arg //~ ERROR mismatched types [E0308]
 }
 
 pub fn bar(arg: Option<&Vec<i32>>) -> &[i32] {
-    arg.unwrap_or(&[]) //~ ERROR 9:19: 9:22: mismatched types [E0308]
+    arg.unwrap_or(&[]) //~ ERROR mismatched types [E0308]
 }
 
 pub fn barzz<'a>(arg: Option<&'a Vec<i32>>, v: &'a [i32]) -> &'a [i32] {
-    arg.unwrap_or(v) //~ ERROR 13:19: 13:20: mismatched types [E0308]
+    arg.unwrap_or(v) //~ ERROR mismatched types [E0308]
 }
 
 pub fn convert_result(arg: Result<&Vec<i32>, ()>) -> &[i32] {
-    arg.unwrap_or(&[]) //~ ERROR 17:19: 17:22: mismatched types [E0308]
+    arg.unwrap_or(&[]) //~ ERROR mismatched types [E0308]
 }
diff --git a/tests/ui/sanitizer/cfi/invalid-attr-encoding.rs b/tests/ui/sanitizer/cfi/invalid-attr-encoding.rs
index 7ef6bd2f0ac..23ffabad62f 100644
--- a/tests/ui/sanitizer/cfi/invalid-attr-encoding.rs
+++ b/tests/ui/sanitizer/cfi/invalid-attr-encoding.rs
@@ -7,5 +7,5 @@
 #![no_core]
 #![no_main]
 
-#[cfi_encoding] //~ERROR 10:1: 10:16: malformed `cfi_encoding` attribute input
+#[cfi_encoding] //~ ERROR malformed `cfi_encoding` attribute input
 pub struct Type1(i32);
diff --git a/tests/ui/suggestions/issue-105645.rs b/tests/ui/suggestions/issue-105645.rs
index 681ce1c6e37..f3ca8ccbb3c 100644
--- a/tests/ui/suggestions/issue-105645.rs
+++ b/tests/ui/suggestions/issue-105645.rs
@@ -2,7 +2,7 @@ fn main() {
     let mut buf = [0u8; 50];
     let mut bref = buf.as_slice();
     foo(&mut bref);
-    //~^ ERROR 4:9: 4:18: the trait bound `&[u8]: std::io::Write` is not satisfied [E0277]
+    //~^ ERROR the trait bound `&[u8]: std::io::Write` is not satisfied [E0277]
 }
 
 fn foo(_: &mut impl std::io::Write) {}
diff --git a/tests/ui/suggestions/suggest-full-enum-variant-for-local-module.rs b/tests/ui/suggestions/suggest-full-enum-variant-for-local-module.rs
index 1dfc0786668..807fba0ab7e 100644
--- a/tests/ui/suggestions/suggest-full-enum-variant-for-local-module.rs
+++ b/tests/ui/suggestions/suggest-full-enum-variant-for-local-module.rs
@@ -6,5 +6,5 @@ mod option {
 }
 
 fn main() {
-    let _: option::O<()> = (); //~ ERROR 9:28: 9:30: mismatched types [E0308]
+    let _: option::O<()> = (); //~ ERROR mismatched types [E0308]
 }
diff --git a/tests/ui/type/option-ref-advice.rs b/tests/ui/type/option-ref-advice.rs
index 2dcee5a2eb9..435b15d01e3 100644
--- a/tests/ui/type/option-ref-advice.rs
+++ b/tests/ui/type/option-ref-advice.rs
@@ -3,9 +3,9 @@
 fn takes_option(_arg: Option<&String>) {}
 
 fn main() {
-    takes_option(&None); //~ ERROR 6:18: 6:23: mismatched types [E0308]
+    takes_option(&None); //~ ERROR mismatched types [E0308]
 
     let x = String::from("x");
     let res = Some(x);
-    takes_option(&res); //~ ERROR 10:18: 10:22: mismatched types [E0308]
+    takes_option(&res); //~ ERROR mismatched types [E0308]
 }
diff --git a/tests/ui/typeck/issue-100246.rs b/tests/ui/typeck/issue-100246.rs
index 8f0b34bab0c..e05bb2a1362 100644
--- a/tests/ui/typeck/issue-100246.rs
+++ b/tests/ui/typeck/issue-100246.rs
@@ -25,6 +25,6 @@ fn downcast<'a, W: ?Sized>() -> std::io::Result<&'a W> {
 struct Other;
 
 fn main() -> std::io::Result<()> {
-    let other: Other = downcast()?;//~ERROR 28:24: 28:35: `?` operator has incompatible types
+    let other: Other = downcast()?; //~ ERROR `?` operator has incompatible types
     Ok(())
 }
diff --git a/tests/ui/typeck/issue-89275.rs b/tests/ui/typeck/issue-89275.rs
index b91c0017548..6e4211de185 100644
--- a/tests/ui/typeck/issue-89275.rs
+++ b/tests/ui/typeck/issue-89275.rs
@@ -25,5 +25,5 @@ fn downcast<'a, W: ?Sized>() -> &'a W {
 struct Other;
 
 fn main() {
-    let other: &mut Other = downcast();//~ERROR 28:29: 28:39: mismatched types [E0308]
+    let other: &mut Other = downcast();//~ ERROR mismatched types [E0308]
 }