diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 0ced029b84d..6306ae534da 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -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 { + 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 a406ca22ca7..0005a86b7f6 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 @@ fn is_unknown(&self) -> bool { } } +#[derive(Clone)] +pub struct ConstEvalCtx<'a> { + pub exprs: &'a Arena, + pub pats: &'a Arena, + pub local_data: HashMap, + 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 { + 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 { match expr { diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 2b53d4a27bf..9ae122d8a41 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 842643cb97c..82fc385040e 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) ``` "#]], ); @@ -3278,6 +3278,140 @@ impl Foo {} ); } +#[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(