Unnamed repository; edit this file 'description' to name the repository.
Merge #10933
10933: show values of constants in hover r=lnicola a=HKalbasi Fix #8497 cc #8655 Co-authored-by: hkalbasi <[email protected]>
bors[bot] 2021-12-23
parent cb0e270 · parent e6139cf · commit 7b7a1ed
-rw-r--r--crates/hir/src/lib.rs21
-rw-r--r--crates/hir_ty/src/consteval.rs248
-rw-r--r--crates/ide/src/hover/render.rs8
-rw-r--r--crates/ide/src/hover/tests.rs136
4 files changed, 406 insertions, 7 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0ced029b84..6306ae534d 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -31,7 +31,7 @@ pub mod db;
mod display;
-use std::{iter, ops::ControlFlow, sync::Arc};
+use std::{collections::HashMap, iter, ops::ControlFlow, sync::Arc};
use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId};
@@ -50,7 +50,7 @@ use hir_def::{
use hir_expand::{name::name, MacroCallKind, MacroDefKind};
use hir_ty::{
autoderef,
- consteval::ConstExt,
+ consteval::{eval_const, ComputedExpr, ConstEvalCtx, ConstEvalError, ConstExt},
could_unify,
diagnostics::BodyValidationDiagnostic,
method_resolution::{self, TyFingerprint},
@@ -1532,6 +1532,23 @@ impl Const {
let ty = ctx.lower_ty(&data.type_ref);
Type::new_with_resolver_inner(db, krate.id, &resolver, ty)
}
+
+ pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> {
+ let body = db.body(self.id.into());
+ let root = &body.exprs[body.body_expr];
+ let infer = db.infer_query(self.id.into());
+ let infer = infer.as_ref();
+ let result = eval_const(
+ root,
+ ConstEvalCtx {
+ exprs: &body.exprs,
+ pats: &body.pats,
+ local_data: HashMap::default(),
+ infer,
+ },
+ );
+ result
+ }
}
impl HasVisibility for Const {
diff --git a/crates/hir_ty/src/consteval.rs b/crates/hir_ty/src/consteval.rs
index a406ca22ca..0005a86b7f 100644
--- a/crates/hir_ty/src/consteval.rs
+++ b/crates/hir_ty/src/consteval.rs
@@ -1,14 +1,17 @@
//! Constant evaluation details
-use std::convert::TryInto;
+use std::{collections::HashMap, convert::TryInto, fmt::Display};
+use chalk_ir::{IntTy, Scalar};
use hir_def::{
builtin_type::BuiltinUint,
- expr::{Expr, Literal},
+ expr::{ArithOp, BinaryOp, Expr, Literal, Pat},
type_ref::ConstScalar,
};
+use hir_expand::name::Name;
+use la_arena::Arena;
-use crate::{Const, ConstData, ConstValue, Interner, TyKind};
+use crate::{Const, ConstData, ConstValue, InferenceResult, Interner, TyKind};
/// Extension trait for [`Const`]
pub trait ConstExt {
@@ -38,6 +41,245 @@ impl ConstExt for Const {
}
}
+#[derive(Clone)]
+pub struct ConstEvalCtx<'a> {
+ pub exprs: &'a Arena<Expr>,
+ pub pats: &'a Arena<Pat>,
+ pub local_data: HashMap<Name, ComputedExpr>,
+ pub infer: &'a InferenceResult,
+}
+
+#[derive(Debug, Clone)]
+pub enum ConstEvalError {
+ NotSupported(&'static str),
+ TypeError,
+ IncompleteExpr,
+ Panic(String),
+}
+
+#[derive(Clone)]
+pub enum ComputedExpr {
+ Literal(Literal),
+ Tuple(Box<[ComputedExpr]>),
+}
+
+impl Display for ComputedExpr {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ComputedExpr::Literal(l) => match l {
+ Literal::Int(x, _) => {
+ if *x >= 16 {
+ write!(f, "{} ({:#X})", x, x)
+ } else {
+ write!(f, "{}", x)
+ }
+ }
+ Literal::Uint(x, _) => {
+ if *x >= 16 {
+ write!(f, "{} ({:#X})", x, x)
+ } else {
+ write!(f, "{}", x)
+ }
+ }
+ Literal::Float(x, _) => write!(f, "{}", x),
+ Literal::Bool(x) => write!(f, "{}", x),
+ Literal::Char(x) => write!(f, "{:?}", x),
+ Literal::String(x) => write!(f, "{:?}", x),
+ Literal::ByteString(x) => write!(f, "{:?}", x),
+ },
+ ComputedExpr::Tuple(t) => {
+ write!(f, "(")?;
+ for x in &**t {
+ write!(f, "{}, ", x)?;
+ }
+ write!(f, ")")
+ }
+ }
+ }
+}
+
+fn scalar_max(scalar: &Scalar) -> i128 {
+ match scalar {
+ Scalar::Bool => 1,
+ Scalar::Char => u32::MAX as i128,
+ Scalar::Int(x) => match x {
+ IntTy::Isize => isize::MAX as i128,
+ IntTy::I8 => i8::MAX as i128,
+ IntTy::I16 => i16::MAX as i128,
+ IntTy::I32 => i32::MAX as i128,
+ IntTy::I64 => i64::MAX as i128,
+ IntTy::I128 => i128::MAX as i128,
+ },
+ Scalar::Uint(x) => match x {
+ chalk_ir::UintTy::Usize => usize::MAX as i128,
+ chalk_ir::UintTy::U8 => u8::MAX as i128,
+ chalk_ir::UintTy::U16 => u16::MAX as i128,
+ chalk_ir::UintTy::U32 => u32::MAX as i128,
+ chalk_ir::UintTy::U64 => u64::MAX as i128,
+ chalk_ir::UintTy::U128 => i128::MAX as i128, // ignore too big u128 for now
+ },
+ Scalar::Float(_) => 0,
+ }
+}
+
+fn is_valid(scalar: &Scalar, value: i128) -> bool {
+ if value < 0 {
+ !matches!(scalar, Scalar::Uint(_)) && -scalar_max(scalar) - 1 <= value
+ } else {
+ value <= scalar_max(scalar)
+ }
+}
+
+pub fn eval_const(expr: &Expr, mut ctx: ConstEvalCtx<'_>) -> Result<ComputedExpr, ConstEvalError> {
+ match expr {
+ Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
+ &Expr::UnaryOp { expr, op } => {
+ let ty = &ctx.infer[expr];
+ let ev = eval_const(&ctx.exprs[expr], ctx)?;
+ match op {
+ hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")),
+ hir_def::expr::UnaryOp::Not => {
+ let v = match ev {
+ ComputedExpr::Literal(Literal::Bool(b)) => {
+ return Ok(ComputedExpr::Literal(Literal::Bool(!b)))
+ }
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
+ ComputedExpr::Literal(Literal::Uint(v, _)) => v
+ .try_into()
+ .map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ let r = match ty.kind(Interner) {
+ TyKind::Scalar(Scalar::Uint(x)) => match x {
+ chalk_ir::UintTy::U8 => !(v as u8) as i128,
+ chalk_ir::UintTy::U16 => !(v as u16) as i128,
+ chalk_ir::UintTy::U32 => !(v as u32) as i128,
+ chalk_ir::UintTy::U64 => !(v as u64) as i128,
+ chalk_ir::UintTy::U128 => {
+ return Err(ConstEvalError::NotSupported("negation of u128"))
+ }
+ chalk_ir::UintTy::Usize => !(v as usize) as i128,
+ },
+ TyKind::Scalar(Scalar::Int(x)) => match x {
+ chalk_ir::IntTy::I8 => !(v as i8) as i128,
+ chalk_ir::IntTy::I16 => !(v as i16) as i128,
+ chalk_ir::IntTy::I32 => !(v as i32) as i128,
+ chalk_ir::IntTy::I64 => !(v as i64) as i128,
+ chalk_ir::IntTy::I128 => !v,
+ chalk_ir::IntTy::Isize => !(v as isize) as i128,
+ },
+ _ => return Err(ConstEvalError::NotSupported("unreachable?")),
+ };
+ Ok(ComputedExpr::Literal(Literal::Int(r, None)))
+ }
+ hir_def::expr::UnaryOp::Neg => {
+ let v = match ev {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
+ ComputedExpr::Literal(Literal::Uint(v, _)) => v
+ .try_into()
+ .map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ Ok(ComputedExpr::Literal(Literal::Int(
+ v.checked_neg().ok_or_else(|| {
+ ConstEvalError::Panic("overflow in negation".to_string())
+ })?,
+ None,
+ )))
+ }
+ }
+ }
+ &Expr::BinaryOp { lhs, rhs, op } => {
+ let ty = &ctx.infer[lhs];
+ let lhs = eval_const(&ctx.exprs[lhs], ctx.clone())?;
+ let rhs = eval_const(&ctx.exprs[rhs], ctx.clone())?;
+ let op = op.ok_or(ConstEvalError::IncompleteExpr)?;
+ let v1 = match lhs {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
+ ComputedExpr::Literal(Literal::Uint(v, _)) => {
+ v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
+ }
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ let v2 = match rhs {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
+ ComputedExpr::Literal(Literal::Uint(v, _)) => {
+ v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
+ }
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ match op {
+ BinaryOp::ArithOp(b) => {
+ let panic_arith = ConstEvalError::Panic(
+ "attempt to run invalid arithmetic operation".to_string(),
+ );
+ let r = match b {
+ ArithOp::Add => v1.checked_add(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Mul => v1.checked_mul(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Sub => v1.checked_sub(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Div => v1.checked_div(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Rem => v1.checked_rem(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Shl => v1
+ .checked_shl(v2.try_into().map_err(|_| panic_arith.clone())?)
+ .ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Shr => v1
+ .checked_shr(v2.try_into().map_err(|_| panic_arith.clone())?)
+ .ok_or_else(|| panic_arith.clone())?,
+ ArithOp::BitXor => v1 ^ v2,
+ ArithOp::BitOr => v1 | v2,
+ ArithOp::BitAnd => v1 & v2,
+ };
+ if let TyKind::Scalar(s) = ty.kind(Interner) {
+ if !is_valid(s, r) {
+ return Err(panic_arith);
+ }
+ }
+ Ok(ComputedExpr::Literal(Literal::Int(r, None)))
+ }
+ BinaryOp::LogicOp(_) => Err(ConstEvalError::TypeError),
+ _ => return Err(ConstEvalError::NotSupported("bin op on this operators")),
+ }
+ }
+ Expr::Block { statements, tail, .. } => {
+ for statement in &**statements {
+ match statement {
+ &hir_def::expr::Statement::Let { pat, initializer, .. } => {
+ let pat = &ctx.pats[pat];
+ let name = match pat {
+ Pat::Bind { name, subpat, .. } if subpat.is_none() => name.clone(),
+ _ => {
+ return Err(ConstEvalError::NotSupported("complex patterns in let"))
+ }
+ };
+ let value = match initializer {
+ Some(x) => eval_const(&ctx.exprs[x], ctx.clone())?,
+ None => continue,
+ };
+ ctx.local_data.insert(name, value);
+ }
+ &hir_def::expr::Statement::Expr { .. } => {
+ return Err(ConstEvalError::NotSupported("this kind of statement"))
+ }
+ }
+ }
+ let tail_expr = match tail {
+ &Some(x) => &ctx.exprs[x],
+ None => return Ok(ComputedExpr::Tuple(Box::new([]))),
+ };
+ eval_const(tail_expr, ctx)
+ }
+ Expr::Path(p) => {
+ let name = p.mod_path().as_ident().ok_or(ConstEvalError::NotSupported("big paths"))?;
+ let r = ctx
+ .local_data
+ .get(name)
+ .ok_or(ConstEvalError::NotSupported("Non local name resolution"))?;
+ Ok(r.clone())
+ }
+ _ => Err(ConstEvalError::NotSupported("This kind of expression")),
+ }
+}
+
// FIXME: support more than just evaluating literals
pub fn eval_usize(expr: &Expr) -> Option<u64> {
match expr {
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 2b53d4a27b..9ae122d8a4 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -360,7 +360,13 @@ pub(super) fn definition(
Definition::Function(it) => label_and_docs(db, it),
Definition::Adt(it) => label_and_docs(db, it),
Definition::Variant(it) => label_and_docs(db, it),
- Definition::Const(it) => label_value_and_docs(db, it, |it| it.value(db)),
+ Definition::Const(it) => label_value_and_docs(db, it, |it| {
+ let body = it.eval(db);
+ match body {
+ Ok(x) => Some(format!("{}", x)),
+ Err(_) => it.value(db).map(|x| format!("{}", x)),
+ }
+ }),
Definition::Static(it) => label_value_and_docs(db, it, |it| it.value(db)),
Definition::Trait(it) => label_and_docs(db, it),
Definition::TypeAlias(it) => label_and_docs(db, it),
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 842643cb97..82fc385040 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -552,7 +552,7 @@ fn hover_const_static() {
```
```rust
- const foo: u32 = 123
+ const foo: u32 = 123 (0x7B)
```
"#]],
);
@@ -3279,6 +3279,140 @@ impl<const LEN: usize> Foo<LEN$0> {}
}
#[test]
+fn hover_const_eval() {
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = !0 & !(!0 >> 1);
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 9223372036854775808 (0x8000000000000000)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = {
+ let a = 3 + 2;
+ let b = a * a;
+ b
+};
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 25 (0x19)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 1 << 10;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 1024 (0x400)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 2 - 3
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: i32 = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: i32 = -1
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 1 << 100;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 1 << 100
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
fn hover_const_pat() {
check(
r#"