Skip to content

Commit d6d4827

Browse files
bors[bot]HKalbasi
andauthored
Merge #10933
10933: show values of constants in hover r=flodiebold a=HKalbasi Fix #8497 cc #8655 Co-authored-by: hkalbasi <[email protected]>
2 parents 4ea1f58 + 5f5da7d commit d6d4827

File tree

4 files changed

+406
-7
lines changed

4 files changed

+406
-7
lines changed

crates/hir/src/lib.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod db;
3131

3232
mod display;
3333

34-
use std::{iter, ops::ControlFlow, sync::Arc};
34+
use std::{collections::HashMap, iter, ops::ControlFlow, sync::Arc};
3535

3636
use arrayvec::ArrayVec;
3737
use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId};
@@ -50,7 +50,7 @@ use hir_def::{
5050
use hir_expand::{name::name, MacroCallKind, MacroDefKind};
5151
use hir_ty::{
5252
autoderef,
53-
consteval::ConstExt,
53+
consteval::{eval_const, ComputedExpr, ConstEvalCtx, ConstEvalError, ConstExt},
5454
could_unify,
5555
diagnostics::BodyValidationDiagnostic,
5656
method_resolution::{self, TyFingerprint},
@@ -1532,6 +1532,23 @@ impl Const {
15321532
let ty = ctx.lower_ty(&data.type_ref);
15331533
Type::new_with_resolver_inner(db, krate.id, &resolver, ty)
15341534
}
1535+
1536+
pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> {
1537+
let body = db.body(self.id.into());
1538+
let root = &body.exprs[body.body_expr];
1539+
let infer = db.infer_query(self.id.into());
1540+
let infer = infer.as_ref();
1541+
let result = eval_const(
1542+
root,
1543+
ConstEvalCtx {
1544+
exprs: &body.exprs,
1545+
pats: &body.pats,
1546+
local_data: HashMap::default(),
1547+
infer,
1548+
},
1549+
);
1550+
result
1551+
}
15351552
}
15361553

15371554
impl HasVisibility for Const {

crates/hir_ty/src/consteval.rs

+245-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
//! Constant evaluation details
22
3-
use std::convert::TryInto;
3+
use std::{collections::HashMap, convert::TryInto, fmt::Display};
44

5+
use chalk_ir::{IntTy, Scalar};
56
use hir_def::{
67
builtin_type::BuiltinUint,
7-
expr::{Expr, Literal},
8+
expr::{ArithOp, BinaryOp, Expr, Literal, Pat},
89
type_ref::ConstScalar,
910
};
11+
use hir_expand::name::Name;
12+
use la_arena::Arena;
1013

11-
use crate::{Const, ConstData, ConstValue, Interner, TyKind};
14+
use crate::{Const, ConstData, ConstValue, InferenceResult, Interner, TyKind};
1215

1316
/// Extension trait for [`Const`]
1417
pub trait ConstExt {
@@ -38,6 +41,245 @@ impl ConstExt for Const {
3841
}
3942
}
4043

44+
#[derive(Clone)]
45+
pub struct ConstEvalCtx<'a> {
46+
pub exprs: &'a Arena<Expr>,
47+
pub pats: &'a Arena<Pat>,
48+
pub local_data: HashMap<Name, ComputedExpr>,
49+
pub infer: &'a InferenceResult,
50+
}
51+
52+
#[derive(Debug, Clone)]
53+
pub enum ConstEvalError {
54+
NotSupported(&'static str),
55+
TypeError,
56+
IncompleteExpr,
57+
Panic(String),
58+
}
59+
60+
#[derive(Clone)]
61+
pub enum ComputedExpr {
62+
Literal(Literal),
63+
Tuple(Box<[ComputedExpr]>),
64+
}
65+
66+
impl Display for ComputedExpr {
67+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68+
match self {
69+
ComputedExpr::Literal(l) => match l {
70+
Literal::Int(x, _) => {
71+
if *x >= 16 {
72+
write!(f, "{} ({:#X})", x, x)
73+
} else {
74+
write!(f, "{}", x)
75+
}
76+
}
77+
Literal::Uint(x, _) => {
78+
if *x >= 16 {
79+
write!(f, "{} ({:#X})", x, x)
80+
} else {
81+
write!(f, "{}", x)
82+
}
83+
}
84+
Literal::Float(x, _) => write!(f, "{}", x),
85+
Literal::Bool(x) => write!(f, "{}", x),
86+
Literal::Char(x) => write!(f, "{:?}", x),
87+
Literal::String(x) => write!(f, "{:?}", x),
88+
Literal::ByteString(x) => write!(f, "{:?}", x),
89+
},
90+
ComputedExpr::Tuple(t) => {
91+
write!(f, "(")?;
92+
for x in &**t {
93+
write!(f, "{}, ", x)?;
94+
}
95+
write!(f, ")")
96+
}
97+
}
98+
}
99+
}
100+
101+
fn scalar_max(scalar: &Scalar) -> i128 {
102+
match scalar {
103+
Scalar::Bool => 1,
104+
Scalar::Char => u32::MAX as i128,
105+
Scalar::Int(x) => match x {
106+
IntTy::Isize => isize::MAX as i128,
107+
IntTy::I8 => i8::MAX as i128,
108+
IntTy::I16 => i16::MAX as i128,
109+
IntTy::I32 => i32::MAX as i128,
110+
IntTy::I64 => i64::MAX as i128,
111+
IntTy::I128 => i128::MAX as i128,
112+
},
113+
Scalar::Uint(x) => match x {
114+
chalk_ir::UintTy::Usize => usize::MAX as i128,
115+
chalk_ir::UintTy::U8 => u8::MAX as i128,
116+
chalk_ir::UintTy::U16 => u16::MAX as i128,
117+
chalk_ir::UintTy::U32 => u32::MAX as i128,
118+
chalk_ir::UintTy::U64 => u64::MAX as i128,
119+
chalk_ir::UintTy::U128 => i128::MAX as i128, // ignore too big u128 for now
120+
},
121+
Scalar::Float(_) => 0,
122+
}
123+
}
124+
125+
fn is_valid(scalar: &Scalar, value: i128) -> bool {
126+
if value < 0 {
127+
!matches!(scalar, Scalar::Uint(_)) && -scalar_max(scalar) - 1 <= value
128+
} else {
129+
value <= scalar_max(scalar)
130+
}
131+
}
132+
133+
pub fn eval_const(expr: &Expr, mut ctx: ConstEvalCtx<'_>) -> Result<ComputedExpr, ConstEvalError> {
134+
match expr {
135+
Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
136+
&Expr::UnaryOp { expr, op } => {
137+
let ty = &ctx.infer[expr];
138+
let ev = eval_const(&ctx.exprs[expr], ctx)?;
139+
match op {
140+
hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")),
141+
hir_def::expr::UnaryOp::Not => {
142+
let v = match ev {
143+
ComputedExpr::Literal(Literal::Bool(b)) => {
144+
return Ok(ComputedExpr::Literal(Literal::Bool(!b)))
145+
}
146+
ComputedExpr::Literal(Literal::Int(v, _)) => v,
147+
ComputedExpr::Literal(Literal::Uint(v, _)) => v
148+
.try_into()
149+
.map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
150+
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
151+
};
152+
let r = match ty.kind(&Interner) {
153+
TyKind::Scalar(Scalar::Uint(x)) => match x {
154+
chalk_ir::UintTy::U8 => !(v as u8) as i128,
155+
chalk_ir::UintTy::U16 => !(v as u16) as i128,
156+
chalk_ir::UintTy::U32 => !(v as u32) as i128,
157+
chalk_ir::UintTy::U64 => !(v as u64) as i128,
158+
chalk_ir::UintTy::U128 => {
159+
return Err(ConstEvalError::NotSupported("negation of u128"))
160+
}
161+
chalk_ir::UintTy::Usize => !(v as usize) as i128,
162+
},
163+
TyKind::Scalar(Scalar::Int(x)) => match x {
164+
chalk_ir::IntTy::I8 => !(v as i8) as i128,
165+
chalk_ir::IntTy::I16 => !(v as i16) as i128,
166+
chalk_ir::IntTy::I32 => !(v as i32) as i128,
167+
chalk_ir::IntTy::I64 => !(v as i64) as i128,
168+
chalk_ir::IntTy::I128 => !v,
169+
chalk_ir::IntTy::Isize => !(v as isize) as i128,
170+
},
171+
_ => return Err(ConstEvalError::NotSupported("unreachable?")),
172+
};
173+
Ok(ComputedExpr::Literal(Literal::Int(r, None)))
174+
}
175+
hir_def::expr::UnaryOp::Neg => {
176+
let v = match ev {
177+
ComputedExpr::Literal(Literal::Int(v, _)) => v,
178+
ComputedExpr::Literal(Literal::Uint(v, _)) => v
179+
.try_into()
180+
.map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
181+
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
182+
};
183+
Ok(ComputedExpr::Literal(Literal::Int(
184+
v.checked_neg().ok_or_else(|| {
185+
ConstEvalError::Panic("overflow in negation".to_string())
186+
})?,
187+
None,
188+
)))
189+
}
190+
}
191+
}
192+
&Expr::BinaryOp { lhs, rhs, op } => {
193+
let ty = &ctx.infer[lhs];
194+
let lhs = eval_const(&ctx.exprs[lhs], ctx.clone())?;
195+
let rhs = eval_const(&ctx.exprs[rhs], ctx.clone())?;
196+
let op = op.ok_or(ConstEvalError::IncompleteExpr)?;
197+
let v1 = match lhs {
198+
ComputedExpr::Literal(Literal::Int(v, _)) => v,
199+
ComputedExpr::Literal(Literal::Uint(v, _)) => {
200+
v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
201+
}
202+
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
203+
};
204+
let v2 = match rhs {
205+
ComputedExpr::Literal(Literal::Int(v, _)) => v,
206+
ComputedExpr::Literal(Literal::Uint(v, _)) => {
207+
v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
208+
}
209+
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
210+
};
211+
match op {
212+
BinaryOp::ArithOp(b) => {
213+
let panic_arith = ConstEvalError::Panic(
214+
"attempt to run invalid arithmetic operation".to_string(),
215+
);
216+
let r = match b {
217+
ArithOp::Add => v1.checked_add(v2).ok_or_else(|| panic_arith.clone())?,
218+
ArithOp::Mul => v1.checked_mul(v2).ok_or_else(|| panic_arith.clone())?,
219+
ArithOp::Sub => v1.checked_sub(v2).ok_or_else(|| panic_arith.clone())?,
220+
ArithOp::Div => v1.checked_div(v2).ok_or_else(|| panic_arith.clone())?,
221+
ArithOp::Rem => v1.checked_rem(v2).ok_or_else(|| panic_arith.clone())?,
222+
ArithOp::Shl => v1
223+
.checked_shl(v2.try_into().map_err(|_| panic_arith.clone())?)
224+
.ok_or_else(|| panic_arith.clone())?,
225+
ArithOp::Shr => v1
226+
.checked_shr(v2.try_into().map_err(|_| panic_arith.clone())?)
227+
.ok_or_else(|| panic_arith.clone())?,
228+
ArithOp::BitXor => v1 ^ v2,
229+
ArithOp::BitOr => v1 | v2,
230+
ArithOp::BitAnd => v1 & v2,
231+
};
232+
if let TyKind::Scalar(s) = ty.kind(&Interner) {
233+
if !is_valid(s, r) {
234+
return Err(panic_arith);
235+
}
236+
}
237+
Ok(ComputedExpr::Literal(Literal::Int(r, None)))
238+
}
239+
BinaryOp::LogicOp(_) => Err(ConstEvalError::TypeError),
240+
_ => return Err(ConstEvalError::NotSupported("bin op on this operators")),
241+
}
242+
}
243+
Expr::Block { statements, tail, .. } => {
244+
for statement in &**statements {
245+
match statement {
246+
&hir_def::expr::Statement::Let { pat, initializer, .. } => {
247+
let pat = &ctx.pats[pat];
248+
let name = match pat {
249+
Pat::Bind { name, subpat, .. } if subpat.is_none() => name.clone(),
250+
_ => {
251+
return Err(ConstEvalError::NotSupported("complex patterns in let"))
252+
}
253+
};
254+
let value = match initializer {
255+
Some(x) => eval_const(&ctx.exprs[x], ctx.clone())?,
256+
None => continue,
257+
};
258+
ctx.local_data.insert(name, value);
259+
}
260+
&hir_def::expr::Statement::Expr { .. } => {
261+
return Err(ConstEvalError::NotSupported("this kind of statement"))
262+
}
263+
}
264+
}
265+
let tail_expr = match tail {
266+
&Some(x) => &ctx.exprs[x],
267+
None => return Ok(ComputedExpr::Tuple(Box::new([]))),
268+
};
269+
eval_const(tail_expr, ctx)
270+
}
271+
Expr::Path(p) => {
272+
let name = p.mod_path().as_ident().ok_or(ConstEvalError::NotSupported("big paths"))?;
273+
let r = ctx
274+
.local_data
275+
.get(name)
276+
.ok_or(ConstEvalError::NotSupported("Non local name resolution"))?;
277+
Ok(r.clone())
278+
}
279+
_ => Err(ConstEvalError::NotSupported("This kind of expression")),
280+
}
281+
}
282+
41283
// FIXME: support more than just evaluating literals
42284
pub fn eval_usize(expr: &Expr) -> Option<u64> {
43285
match expr {

crates/ide/src/hover/render.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,13 @@ pub(super) fn definition(
360360
Definition::Function(it) => label_and_docs(db, it),
361361
Definition::Adt(it) => label_and_docs(db, it),
362362
Definition::Variant(it) => label_and_docs(db, it),
363-
Definition::Const(it) => label_value_and_docs(db, it, |it| it.value(db)),
363+
Definition::Const(it) => label_value_and_docs(db, it, |it| {
364+
let body = it.eval(db);
365+
match body {
366+
Ok(x) => Some(format!("{}", x)),
367+
Err(_) => it.value(db).map(|x| format!("{}", x)),
368+
}
369+
}),
364370
Definition::Static(it) => label_value_and_docs(db, it, |it| it.value(db)),
365371
Definition::Trait(it) => label_and_docs(db, it),
366372
Definition::TypeAlias(it) => label_and_docs(db, it),

0 commit comments

Comments
 (0)