From 2dfe7de8b66c17b3f9025766597b8399394527b8 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:39:15 +0100 Subject: [PATCH] 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. --- crates/hir-ty/src/mir/eval.rs | 5 ++- crates/hir-ty/src/mir/eval/shim.rs | 65 ++++++++++++------------------ crates/ide/src/hover/tests.rs | 26 ++++++++++++ 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index fd98141af63e..7799c039bda1 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 628a1fe2d283..d4d669182f2e 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> { + // `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("".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(), diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 4451e31870f2..466e5570b337 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(