Repository: rust-lang/rust-analyzer
Pull request #18469 (2024-11-03 12:33:34)

feat: Show `static` values on hover


Patch:
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index e73c2ee6f6..eb55ba1d53 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -837,16 +837,18 @@ impl InTypeConstId {
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum GeneralConstId {
     ConstId(ConstId),
+    StaticId(StaticId),
     ConstBlockId(ConstBlockId),
     InTypeConstId(InTypeConstId),
 }
 
-impl_from!(ConstId, ConstBlockId, InTypeConstId for GeneralConstId);
+impl_from!(ConstId, StaticId, ConstBlockId, InTypeConstId for GeneralConstId);
 
 impl GeneralConstId {
     pub fn generic_def(self, db: &dyn DefDatabase) -> Option<GenericDefId> {
         match self {
             GeneralConstId::ConstId(it) => Some(it.into()),
+            GeneralConstId::StaticId(_) => None,
             GeneralConstId::ConstBlockId(it) => it.lookup(db).parent.as_generic_def_id(db),
             GeneralConstId::InTypeConstId(it) => it.lookup(db).owner.as_generic_def_id(db),
         }
@@ -854,6 +856,9 @@ impl GeneralConstId {
 
     pub fn name(self, db: &dyn DefDatabase) -> String {
         match self {
+            GeneralConstId::StaticId(it) => {
+                db.static_data(it).name.display(db.upcast(), Edition::CURRENT).to_string()
+            }
             GeneralConstId::ConstId(const_id) => db
                 .const_data(const_id)
                 .name
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs
index 091cfcd465..a56056b077 100644
--- a/crates/hir-ty/src/consteval.rs
+++ b/crates/hir-ty/src/consteval.rs
@@ -8,7 +8,7 @@ use hir_def::{
     path::Path,
     resolver::{Resolver, ValueNs},
     type_ref::LiteralConstRef,
-    ConstBlockLoc, EnumVariantId, GeneralConstId, StaticId,
+    ConstBlockLoc, EnumVariantId, GeneralConstId, HasModule as _, StaticId,
 };
 use hir_expand::Lookup;
 use stdx::never;
@@ -236,6 +236,10 @@ pub(crate) fn const_eval_query(
         GeneralConstId::ConstId(c) => {
             db.monomorphized_mir_body(c.into(), subst, db.trait_environment(c.into()))?
         }
+        GeneralConstId::StaticId(s) => {
+            let krate = s.module(db.upcast()).krate();
+            db.monomorphized_mir_body(s.into(), subst, TraitEnvironment::empty(krate))?
+        }
         GeneralConstId::ConstBlockId(c) => {
             let ConstBlockLoc { parent, root } = db.lookup_intern_anonymous_const(c);
             let body = db.body(parent);
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index e73b9dc27d..47026995c0 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -12,8 +12,8 @@ use hir_def::{
     lang_item::LangItem,
     layout::{TagEncoding, Variants},
     resolver::{HasResolver, TypeNs, ValueNs},
-    AdtId, ConstId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup,
-    StaticId, VariantId,
+    AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, StaticId,
+    VariantId,
 };
 use hir_expand::{mod_path::path, name::Name, HirFileIdExt, InFile};
 use intern::sym;
@@ -40,8 +40,8 @@ use crate::{
     static_lifetime,
     traits::FnTrait,
     utils::{detect_variant_from_bytes, ClosureSubst},
-    CallableDefId, ClosureId, ComplexMemoryMap, Const, ConstScalar, FnDefId, Interner, MemoryMap,
-    Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind,
+    CallableDefId, ClosureId, ComplexMemoryMap, Const, ConstData, ConstScalar, FnDefId, Interner,
+    MemoryMap, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind,
 };
 
 use super::{
@@ -1899,8 +1899,8 @@ impl Evaluator<'_> {
 
     #[allow(clippy::double_parens)]
     fn allocate_const_in_heap(&mut self, locals: &Locals, konst: &Const) -> Result<Interval> {
-        let ty = &konst.data(Interner).ty;
-        let chalk_ir::ConstValue::Concrete(c) = &konst.data(Interner).value else {
+        let ConstData { ty, value: chalk_ir::ConstValue::Concrete(c) } = &konst.data(Interner)
+        else {
             not_supported!("evaluating non concrete constant");
         };
         let result_owner;
@@ -2908,14 +2908,14 @@ impl Evaluator<'_> {
 
 pub fn render_const_using_debug_impl(
     db: &dyn HirDatabase,
-    owner: ConstId,
+    owner: DefWithBodyId,
     c: &Const,
 ) -> Result<String> {
-    let mut evaluator = Evaluator::new(db, owner.into(), false, None)?;
+    let mut evaluator = Evaluator::new(db, owner, false, None)?;
     let locals = &Locals {
         ptr: ArenaMap::new(),
         body: db
-            .mir_body(owner.into())
+            .mir_body(owner)
             .map_err(|_| MirEvalError::NotSupported("unreachable".to_owned()))?,
         drop_flags: DropFlags::default(),
     };
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 56bf1d2742..a4045dc5ad 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2600,7 +2600,7 @@ impl Const {
                 }
             }
         }
-        if let Ok(s) = mir::render_const_using_debug_impl(db, self.id, &c) {
+        if let Ok(s) = mir::render_const_using_debug_impl(db, self.id.into(), &c) {
             Ok(s)
         } else {
             Ok(format!("{}", c.display(db, edition)))
@@ -2639,6 +2639,45 @@ impl Static {
     pub fn ty(self, db: &dyn HirDatabase) -> Type {
         Type::from_value_def(db, self.id)
     }
+
+    /// Evaluate the constant and return the result as a string, with more detailed information.
+    ///
+    /// This function is intended for user-facing display.
+    pub fn render_eval(
+        self,
+        db: &dyn HirDatabase,
+        edition: Edition,
+    ) -> Result<String, ConstEvalError> {
+        let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?;
+        let data = &c.data(Interner);
+        if let TyKind::Scalar(s) = data.ty.kind(Interner) {
+            if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) {
+                if let hir_ty::ConstValue::Concrete(c) = &data.value {
+                    if let hir_ty::ConstScalar::Bytes(b, _) = &c.interned {
+                        let value = u128::from_le_bytes(mir::pad16(b, false));
+                        let value_signed =
+                            i128::from_le_bytes(mir::pad16(b, matches!(s, Scalar::Int(_))));
+                        let mut result = if let Scalar::Int(_) = s {
+                            value_signed.to_string()
+                        } else {
+                            value.to_string()
+                        };
+                        if value >= 10 {
+                            format_to!(result, " ({value:#X})");
+                            return Ok(result);
+                        } else {
+                            return Ok(result);
+                        }
+                    }
+                }
+            }
+        }
+        if let Ok(s) = mir::render_const_using_debug_impl(db, self.id.into(), &c) {
+            Ok(s)
+        } else {
+            Ok(format!("{}", c.display(db, edition)))
+        }
+    }
 }
 
 impl HasVisibility for Static {
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 51a7728345..d9ddc2d015 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -486,13 +486,19 @@ pub(super) fn definition(
             }
         }
         Definition::Static(it) => {
-            let source = it.source(db)?;
-            let mut body = source.value.body()?.syntax().clone();
-            if let Some(macro_file) = source.file_id.macro_file() {
-                let span_map = db.expansion_span_map(macro_file);
-                body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
+            let body = it.render_eval(db, edition);
+            match body {
+                Ok(it) => Some(it),
+                Err(_) => {
+                    let source = it.source(db)?;
+                    let mut body = source.value.body()?.syntax().clone();
+                    if let Some(macro_file) = source.file_id.macro_file() {
+                        let span_map = db.expansion_span_map(macro_file);
+                        body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
+                    }
+                    Some(body.to_string())
+                }
             }
-            Some(body.to_string())
         }
         _ => None,
     };
diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs
index 4045e25fdf..c965257a5c 100644
--- a/crates/proc-macro-api/src/process.rs
+++ b/crates/proc-macro-api/src/process.rs
@@ -56,8 +56,8 @@ impl ProcMacroProcessSrv {
         match srv.version_check() {
             Ok(v) if v > CURRENT_API_VERSION => Err(io::Error::new(
                 io::ErrorKind::Other,
-                format!( "The version of the proc-macro server ({v}) in your Rust toolchain is newer than the version supported by your rust-analyzer ({CURRENT_API_VERSION}).
-            This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain."
+                format!(
+                    "proc-macro server's api version ({v}) is newer than rust-analyzer's ({CURRENT_API_VERSION})"
                 ),
             )),
             Ok(v) => {
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 51c81a0d1a..155fb6f7c8 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -172,7 +172,6 @@ impl flags::AnalysisStats {
         let mut num_decls = 0;
         let mut bodies = Vec::new();
         let mut adts = Vec::new();
-        let mut consts = Vec::new();
         let mut file_ids = Vec::new();
         while let Some(module) = visit_queue.pop() {
             if visited_modules.insert(module) {
@@ -193,7 +192,6 @@ impl flags::AnalysisStats {
                         }
                         ModuleDef::Const(c) => {
                             bodies.push(DefWithBody::from(c));
-                            consts.push(c)
                         }
                         ModuleDef::Static(s) => bodies.push(DefWithBody::from(s)),
                         _ => (),
@@ -207,7 +205,6 @@ impl flags::AnalysisStats {
                             AssocItem::Function(f) => bodies.push(DefWithBody::from(f)),
                             AssocItem::Const(c) => {
                                 bodies.push(DefWithBody::from(c));
-                                consts.push(c);
                             }
                             _ => (),
                         }
@@ -220,7 +217,10 @@ impl flags::AnalysisStats {
             visited_modules.len(),
             bodies.len(),
             adts.len(),
-            consts.len(),
+            bodies
+                .iter()
+                .filter(|it| matches!(it, DefWithBody::Const(_) | DefWithBody::Static(_)))
+                .count(),
         );
         let crate_def_map_time = crate_def_map_sw.elapsed();
         eprintln!("{:<20} {}", "Item Collection:", crate_def_map_time);
@@ -247,7 +247,7 @@ impl flags::AnalysisStats {
         }
 
         if !self.skip_const_eval {
-            self.run_const_eval(db, &consts, verbosity);
+            self.run_const_eval(db, &bodies, verbosity);
         }
 
         if self.run_all_ide_things {
@@ -320,18 +320,23 @@ impl flags::AnalysisStats {
         report_metric("data layout time", data_layout_time.time.as_millis() as u64, "ms");
     }
 
-    fn run_const_eval(&self, db: &RootDatabase, consts: &[hir::Const], verbosity: Verbosity) {
+    fn run_const_eval(&self, db: &RootDatabase, bodies: &[DefWithBody], verbosity: Verbosity) {
         let mut sw = self.stop_watch();
         let mut all = 0;
         let mut fail = 0;
-        for &c in consts {
+        for &b in bodies {
+            let res = match b {
+                DefWithBody::Const(c) => c.render_eval(db, Edition::LATEST),
+                DefWithBody::Static(s) => s.render_eval(db, Edition::LATEST),
+                _ => continue,
+            };
             all += 1;
-            let Err(error) = c.render_eval(db, Edition::LATEST) else {
+            let Err(error) = res else {
                 continue;
             };
             if verbosity.is_spammy() {
                 let full_name =
-                    full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
+                    full_name_of_item(db, b.module(db), b.name(db).unwrap_or(Name::missing()));
                 println!("Const eval for {full_name} failed due {error:?}");
             }
             fail += 1;


Test patch:
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 1fad3d6bd6..0ffbf988c3 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -1544,7 +1544,7 @@ const foo$0: u32 = {
             ```
 
             ```rust
-            static foo: u32 = 456
+            static foo: u32 = 456 (0x1C8)
             ```
         "#]],
     );