Repository: rust-lang/rust-analyzer (rust)

Issue:
Hang when hovering over constant
<!--
Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
Forum for questions: https://users.rust-lang.org/c/ide/14

Before submitting, please make sure that you're not running into one of these known issues:

 1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107

Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
-->

**rust-analyzer version**:
- 0.3.1885-standalone (b6d1887bc 2024-03-17)
- 0.4.1889-standalone (5e276ae51 2024-03-20)
- 0.0.0 (8d74705b43 2024-03-21)

**rustc version**: rustc 1.79.0-nightly (a7e4de13c 2024-03-19)

**relevant settings**: N/A

**repository link (if public, optional)**: N/A

**code snippet to reproduce**: 
```rust
pub enum AA {
    BB,
}

pub fn hang() -> AA {
    const CONST: AA = AA::BB;
    CONST
}
```

Hovering over either `CONST` starts the hang and rust-analyzer becomes unresponsive, consuming about ~200MiB RAM per second in this case, though in other cases it can be much faster.

The server cannot be killed through hovering on the `rust-analyzer` button on the lower bar or with `rust-analyzer: Stop server`, and must be killed through the command line. It is either non-responsive or doesn't kill the process.

I can reproduce this consistently with recent rust-analyzer client and server, but only after updating `rustc` from `nightly-2024-03-19` (`rustc 1.79.0-nightly (3c85e5624 2024-03-18)`) to `nightly-2024-03-20` (`rustc 1.79.0-nightly (a7e4de13c 2024-03-19)`) or later with rustup (override works too).


Pull request:
Handle panicking like rustc CTFE does
Instead of using `core::fmt::format` to format panic messages, which may in turn panic too and cause recursive panics and other messy things, redirect `panic_fmt` to `const_panic_fmt` like CTFE, which in turn goes to `panic_display` and does the things normally. See the tests for the full call stack.

The tests don't work yet, I probably missed something in minicore.

fixes #16907 in my local testing, I also need to add a test for it

Patch:
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index fd98141af6..7799c039bd 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -2317,7 +2317,7 @@ impl Evaluator<'_> {
 
     fn exec_fn_with_args(
         &mut self,
-        def: FunctionId,
+        mut def: FunctionId,
         args: &[IntervalAndTy],
         generic_args: Substitution,
         locals: &Locals,
@@ -2335,6 +2335,9 @@ impl Evaluator<'_> {
         )? {
             return Ok(None);
         }
+        if let Some(redirect_def) = self.detect_and_redirect_special_function(def)? {
+            def = redirect_def;
+        }
         let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval));
         match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
             MirOrDynIndex::Dyn(self_ty_idx) => {
diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs
index 628a1fe2d2..d4d669182f 100644
--- a/crates/hir-ty/src/mir/eval/shim.rs
+++ b/crates/hir-ty/src/mir/eval/shim.rs
@@ -13,7 +13,7 @@ use crate::mir::eval::{
     name, pad16, static_lifetime, Address, AdtId, Arc, BuiltinType, Evaluator, FunctionId,
     HasModule, HirDisplay, Interned, InternedClosure, Interner, Interval, IntervalAndTy,
     IntervalOrOwned, ItemContainerId, LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan,
-    ModPath, Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
+    Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
 };
 
 mod simd;
@@ -158,6 +158,25 @@ impl Evaluator<'_> {
         Ok(false)
     }
 
+    pub(super) fn detect_and_redirect_special_function(
+        &mut self,
+        def: FunctionId,
+    ) -> Result<Option<FunctionId>> {
+        // `PanicFmt` is redirected to `ConstPanicFmt`
+        if let Some(LangItem::PanicFmt) = self.db.lang_attr(def.into()) {
+            let resolver =
+                self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast());
+
+            let Some(hir_def::lang_item::LangItemTarget::Function(const_panic_fmt)) =
+                self.db.lang_item(resolver.krate(), LangItem::ConstPanicFmt)
+            else {
+                not_supported!("const_panic_fmt lang item not found or not a function");
+            };
+            return Ok(Some(const_panic_fmt));
+        }
+        Ok(None)
+    }
+
     /// Clone has special impls for tuples and function pointers
     fn exec_clone(
         &mut self,
@@ -291,9 +310,14 @@ impl Evaluator<'_> {
         use LangItem::*;
         let candidate = self.db.lang_attr(def.into())?;
         // We want to execute these functions with special logic
-        if [PanicFmt, BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
+        // `PanicFmt` is not detected here as it's redirected later.
+        if [BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
             return Some(candidate);
         }
+        if self.db.attrs(def.into()).by_key("rustc_const_panic_str").exists() {
+            // `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE.
+            return Some(LangItem::BeginPanic);
+        }
         None
     }
 
@@ -309,43 +333,6 @@ impl Evaluator<'_> {
         let mut args = args.iter();
         match it {
             BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_owned())),
-            PanicFmt => {
-                let message = (|| {
-                    let resolver = self
-                        .db
-                        .crate_def_map(self.crate_id)
-                        .crate_root()
-                        .resolver(self.db.upcast());
-                    let Some(format_fn) = resolver.resolve_path_in_value_ns_fully(
-                        self.db.upcast(),
-                        &hir_def::path::Path::from_known_path_with_no_generic(
-                            ModPath::from_segments(
-                                hir_expand::mod_path::PathKind::Abs,
-                                [name![std], name![fmt], name![format]],
-                            ),
-                        ),
-                    ) else {
-                        not_supported!("std::fmt::format not found");
-                    };
-                    let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else {
-                        not_supported!("std::fmt::format is not a function")
-                    };
-                    let interval = self.interpret_mir(
-                        self.db
-                            .mir_body(format_fn.into())
-                            .map_err(|e| MirEvalError::MirLowerError(format_fn, e))?,
-                        args.map(|x| IntervalOrOwned::Owned(x.clone())),
-                    )?;
-                    let message_string = interval.get(self)?;
-                    let addr =
-                        Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?;
-                    let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]);
-                    Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?)
-                        .into_owned())
-                })()
-                .unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}"));
-                Err(MirEvalError::Panic(message))
-            }
             SliceLen => {
                 let arg = args.next().ok_or(MirEvalError::InternalError(
                     "argument of <[T]>::len() is not provided".into(),


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 4451e31870..466e5570b3 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -5105,6 +5105,32 @@ fn foo(e: E) {
     );
 }
 
+#[test]
+fn hover_const_value() {
+    check(
+        r#"
+pub enum AA {
+    BB,
+}
+const CONST: AA = AA::BB;
+pub fn the_function() -> AA {
+    CON$0ST
+}
+"#,
+        expect![[r#"
+            *CONST*
+
+            ```rust
+            test
+            ```
+
+            ```rust
+            const CONST: AA = BB
+            ```
+        "#]],
+    );
+}
+
 #[test]
 fn array_repeat_exp() {
     check(