Merge #10933
10933: show values of constants in hover r=lnicola a=HKalbasi Fix #8497 cc #8655 Co-authored-by: hkalbasi <hamidrezakalbasi@protonmail.com>
This commit is contained in:
commit
7b7a1ed062
@ -31,7 +31,7 @@
|
||||
|
||||
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_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 @@ pub fn ty(self, db: &dyn HirDatabase) -> Type {
|
||||
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 {
|
||||
|
@ -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 @@ fn is_unknown(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
@ -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),
|
||||
|
@ -552,7 +552,7 @@ fn hover_const_static() {
|
||||
```
|
||||
|
||||
```rust
|
||||
const foo: u32 = 123
|
||||
const foo: u32 = 123 (0x7B)
|
||||
```
|
||||
"#]],
|
||||
);
|
||||
@ -3278,6 +3278,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(
|
||||
|
Loading…
Reference in New Issue
Block a user