diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
index a2d07fff506..e81790a7348 100644
--- a/compiler/rustc_lint/src/non_fmt_panic.rs
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
 
                 if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                     || Some(def_id) == cx.tcx.lang_items().panic_fn()
-                    || f_diagnostic_name == Some(sym::panic_str)
+                    || f_diagnostic_name == Some(sym::panic_str_2015)
                 {
                     if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                         if matches!(
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 2d3053ccee6..8abf42e2c13 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1348,7 +1348,7 @@ symbols! {
         panic_misaligned_pointer_dereference,
         panic_nounwind,
         panic_runtime,
-        panic_str,
+        panic_str_2015,
         panic_unwind,
         panicking,
         param_attrs,
diff --git a/library/core/src/option.rs b/library/core/src/option.rs
index 631e1654ce0..1e3ed0f7c49 100644
--- a/library/core/src/option.rs
+++ b/library/core/src/option.rs
@@ -554,7 +554,7 @@
 #![stable(feature = "rust1", since = "1.0.0")]
 
 use crate::iter::{self, FusedIterator, TrustedLen};
-use crate::panicking::{panic, panic_str};
+use crate::panicking::{panic, panic_display};
 use crate::pin::Pin;
 use crate::{
     cmp, convert, hint, mem,
@@ -1991,7 +1991,7 @@ const fn unwrap_failed() -> ! {
 #[track_caller]
 #[rustc_const_unstable(feature = "const_option", issue = "67441")]
 const fn expect_failed(msg: &str) -> ! {
-    panic_str(msg)
+    panic_display(&msg)
 }
 
 /////////////////////////////////////////////////////////////////////////////
diff --git a/library/core/src/panic.rs b/library/core/src/panic.rs
index b520efe93f9..8771f40f9b4 100644
--- a/library/core/src/panic.rs
+++ b/library/core/src/panic.rs
@@ -27,9 +27,9 @@ pub macro panic_2015 {
     ($msg:literal $(,)?) => (
         $crate::panicking::panic($msg)
     ),
-    // Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.
+    // Use `panic_str_2015` instead of `panic_display::<&str>` for non_fmt_panic lint.
     ($msg:expr $(,)?) => ({
-        $crate::panicking::panic_str($msg);
+        $crate::panicking::panic_str_2015($msg);
     }),
     // Special-case the single-argument case for const_panic.
     ("{}", $arg:expr $(,)?) => ({
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index cbb0a7d61db..3ee56e6c579 100644
--- a/library/core/src/panicking.rs
+++ b/library/core/src/panicking.rs
@@ -124,8 +124,8 @@ pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: boo
 // above.
 
 /// The underlying implementation of core's `panic!` macro when no formatting is used.
-// never inline unless panic_immediate_abort to avoid code
-// bloat at the call sites as much as possible
+// Never inline unless panic_immediate_abort to avoid code
+// bloat at the call sites as much as possible.
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[track_caller]
@@ -138,6 +138,11 @@ pub const fn panic(expr: &'static str) -> ! {
     // truncation and padding (even though none is used here). Using
     // Arguments::new_const may allow the compiler to omit Formatter::pad from the
     // output binary, saving up to a few kilobytes.
+    // However, this optimization only works for `'static` strings: `new_const` also makes this
+    // message return `Some` from `Arguments::as_str`, which means it can become part of the panic
+    // payload without any allocation or copying. Shorter-lived strings would become invalid as
+    // stack frames get popped during unwinding, and couldn't be directly referenced from the
+    // payload.
     panic_fmt(fmt::Arguments::new_const(&[expr]));
 }
 
@@ -223,14 +228,6 @@ pub fn panic_nounwind_nobacktrace(expr: &'static str) -> ! {
     panic_nounwind_fmt(fmt::Arguments::new_const(&[expr]), /* force_no_backtrace */ true);
 }
 
-#[inline]
-#[track_caller]
-#[rustc_diagnostic_item = "panic_str"]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
-pub const fn panic_str(expr: &str) -> ! {
-    panic_display(&expr);
-}
-
 #[track_caller]
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
@@ -246,6 +243,16 @@ pub fn unreachable_display<T: fmt::Display>(x: &T) -> ! {
     panic_fmt(format_args!("internal error: entered unreachable code: {}", *x));
 }
 
+/// This exists solely for the 2015 edition `panic!` macro to trigger
+/// a lint on `panic!(my_str_variable);`.
+#[inline]
+#[track_caller]
+#[rustc_diagnostic_item = "panic_str_2015"]
+#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+pub const fn panic_str_2015(expr: &str) -> ! {
+    panic_display(&expr);
+}
+
 #[inline]
 #[track_caller]
 #[rustc_do_not_const_check] // hooked by const-eval
diff --git a/tests/ui-fulldeps/stable-mir/check_transform.rs b/tests/ui-fulldeps/stable-mir/check_transform.rs
index e7d852a27df..6345ee24f78 100644
--- a/tests/ui-fulldeps/stable-mir/check_transform.rs
+++ b/tests/ui-fulldeps/stable-mir/check_transform.rs
@@ -137,9 +137,9 @@ fn generate_input(path: &str) -> std::io::Result<()> {
     write!(
         file,
         r#"
-        #![feature(panic_internals)]
+        fn panic_str(msg: &str) {{ panic!("{{}}", msg); }}
         pub fn dummy() {{
-            core::panicking::panic_str("oops");
+            panic_str("oops");
         }}
         "#
     )?;