diff --git a/Cargo.lock b/Cargo.lock
index 9d91dcde9b413..9e88b9089174a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4421,9 +4421,11 @@ dependencies = [
  "rustc_ast",
  "rustc_ast_pretty",
  "rustc_data_structures",
+ "rustc_errors",
  "rustc_hir",
  "rustc_hir_pretty",
  "rustc_lexer",
+ "rustc_macros",
  "rustc_middle",
  "rustc_session",
  "rustc_span",
diff --git a/compiler/rustc_error_messages/locales/en-US/save_analysis.ftl b/compiler/rustc_error_messages/locales/en-US/save_analysis.ftl
new file mode 100644
index 0000000000000..36c2ff4682301
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/save_analysis.ftl
@@ -0,0 +1 @@
+save_analysis_could_not_open = Could not open `{$file_name}`: `{$err}`
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 3e66d12bb37ec..2d001d445be02 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -43,6 +43,7 @@ fluent_messages! {
     passes => "../locales/en-US/passes.ftl",
     plugin_impl => "../locales/en-US/plugin_impl.ftl",
     privacy => "../locales/en-US/privacy.ftl",
+    save_analysis => "../locales/en-US/save_analysis.ftl",
     typeck => "../locales/en-US/typeck.ftl",
 }
 
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 356f9dfdb3b2e..506198df4d8ed 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -13,6 +13,7 @@ use rustc_span::{edition::Edition, Span, DUMMY_SP};
 use std::borrow::Cow;
 use std::fmt;
 use std::hash::{Hash, Hasher};
+use std::path::{Path, PathBuf};
 
 /// Error type for `Diagnostic`'s `suggestions` field, indicating that
 /// `.disable_suggestions()` was called on the `Diagnostic`.
@@ -83,6 +84,7 @@ into_diagnostic_arg_using_display!(
     u64,
     i128,
     u128,
+    std::io::Error,
     std::num::NonZeroU32,
     hir::Target,
     Edition,
@@ -124,6 +126,18 @@ impl IntoDiagnosticArg for String {
     }
 }
 
+impl<'a> IntoDiagnosticArg for &'a Path {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.display().to_string()))
+    }
+}
+
+impl IntoDiagnosticArg for PathBuf {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.display().to_string()))
+    }
+}
+
 impl IntoDiagnosticArg for usize {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Number(self)
diff --git a/compiler/rustc_save_analysis/Cargo.toml b/compiler/rustc_save_analysis/Cargo.toml
index 15a89d82fa696..181e27f334b40 100644
--- a/compiler/rustc_save_analysis/Cargo.toml
+++ b/compiler/rustc_save_analysis/Cargo.toml
@@ -9,9 +9,11 @@ rustc_middle = { path = "../rustc_middle" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_ast_pretty = { path = "../rustc_ast_pretty" }
 rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_errors = { path = "../rustc_errors" }
 rustc_hir = { path = "../rustc_hir" }
 rustc_hir_pretty = { path = "../rustc_hir_pretty" }
 rustc_lexer = { path = "../rustc_lexer" }
+rustc_macros = { path = "../rustc_macros" }
 serde_json = "1"
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
diff --git a/compiler/rustc_save_analysis/src/errors.rs b/compiler/rustc_save_analysis/src/errors.rs
new file mode 100644
index 0000000000000..f0ce41d02a6fb
--- /dev/null
+++ b/compiler/rustc_save_analysis/src/errors.rs
@@ -0,0 +1,10 @@
+use rustc_macros::SessionDiagnostic;
+
+use std::path::Path;
+
+#[derive(SessionDiagnostic)]
+#[diag(save_analysis::could_not_open)]
+pub(crate) struct CouldNotOpen<'a> {
+    pub file_name: &'a Path,
+    pub err: std::io::Error,
+}
diff --git a/compiler/rustc_save_analysis/src/lib.rs b/compiler/rustc_save_analysis/src/lib.rs
index a1a2040bbca14..619e083d89ad3 100644
--- a/compiler/rustc_save_analysis/src/lib.rs
+++ b/compiler/rustc_save_analysis/src/lib.rs
@@ -3,11 +3,15 @@
 #![feature(let_else)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
+#![feature(never_type)]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
 
 mod dump_visitor;
 mod dumper;
 #[macro_use]
 mod span_utils;
+mod errors;
 mod sig;
 
 use rustc_ast as ast;
@@ -928,7 +932,7 @@ impl<'a> DumpHandler<'a> {
         info!("Writing output to {}", file_name.display());
 
         let output_file = BufWriter::new(File::create(&file_name).unwrap_or_else(|e| {
-            sess.fatal(&format!("Could not open {}: {}", file_name.display(), e))
+            sess.emit_fatal(errors::CouldNotOpen { file_name: file_name.as_path(), err: e })
         }));
 
         (output_file, file_name)