rust-analyzer/crates/hir-ty/src/consteval.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

479 lines
18 KiB
Rust
Raw Normal View History

//! Constant evaluation details
2022-03-21 08:43:36 +00:00
use std::{
collections::HashMap,
convert::TryInto,
fmt::{Display, Write},
};
2022-03-09 18:50:24 +00:00
use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar};
use hir_def::{
expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId},
2022-03-09 18:50:24 +00:00
path::ModPath,
2022-03-20 13:45:28 +00:00
resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
type_ref::ConstScalar,
2022-03-20 13:45:28 +00:00
ConstId, DefWithBodyId,
};
use la_arena::{Arena, Idx};
2022-03-09 18:50:24 +00:00
use stdx::never;
2022-03-09 18:50:24 +00:00
use crate::{
2022-03-20 13:45:28 +00:00
db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx,
utils::Generics, Const, ConstData, ConstValue, GenericArg, InferenceResult, Interner, Ty,
2022-04-07 01:00:33 +00:00
TyBuilder, TyKind,
2022-03-09 18:50:24 +00:00
};
/// Extension trait for [`Const`]
pub trait ConstExt {
/// Is a [`Const`] unknown?
fn is_unknown(&self) -> bool;
}
impl ConstExt for Const {
fn is_unknown(&self) -> bool {
2021-12-19 16:58:39 +00:00
match self.data(Interner).value {
// interned Unknown
chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
interned: ConstScalar::Unknown,
}) => true,
// interned concrete anything else
chalk_ir::ConstValue::Concrete(..) => false,
_ => {
2021-08-15 12:46:13 +00:00
tracing::error!(
"is_unknown was called on a non-concrete constant value! {:?}",
self
);
true
}
}
}
}
2021-12-04 22:21:36 +00:00
pub struct ConstEvalCtx<'a> {
2022-03-20 13:45:28 +00:00
pub db: &'a dyn HirDatabase,
pub owner: DefWithBodyId,
2021-12-04 22:21:36 +00:00
pub exprs: &'a Arena<Expr>,
pub pats: &'a Arena<Pat>,
pub local_data: HashMap<PatId, ComputedExpr>,
2022-03-20 13:45:28 +00:00
infer: &'a InferenceResult,
2021-12-04 22:21:36 +00:00
}
2022-03-20 13:45:28 +00:00
impl ConstEvalCtx<'_> {
fn expr_ty(&mut self, expr: ExprId) -> Ty {
self.infer[expr].clone()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
2021-12-04 22:21:36 +00:00
pub enum ConstEvalError {
NotSupported(&'static str),
2022-03-20 13:45:28 +00:00
SemanticError(&'static str),
Loop,
2021-12-04 22:21:36 +00:00
IncompleteExpr,
Panic(String),
}
2022-03-20 13:45:28 +00:00
#[derive(Debug, Clone, PartialEq, Eq)]
2021-12-04 22:21:36 +00:00
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, _) => {
2022-03-24 08:36:27 +00:00
if *x >= 10 {
2021-12-04 22:21:36 +00:00
write!(f, "{} ({:#X})", x, x)
} else {
2022-03-21 08:43:36 +00:00
x.fmt(f)
2021-12-04 22:21:36 +00:00
}
}
Literal::Uint(x, _) => {
2022-03-24 08:36:27 +00:00
if *x >= 10 {
2021-12-04 22:21:36 +00:00
write!(f, "{} ({:#X})", x, x)
} else {
2022-03-21 08:43:36 +00:00
x.fmt(f)
2021-12-04 22:21:36 +00:00
}
}
2022-03-21 08:43:36 +00:00
Literal::Float(x, _) => x.fmt(f),
Literal::Bool(x) => x.fmt(f),
Literal::Char(x) => std::fmt::Debug::fmt(x, f),
Literal::String(x) => std::fmt::Debug::fmt(x, f),
Literal::ByteString(x) => std::fmt::Debug::fmt(x, f),
2021-12-04 22:21:36 +00:00
},
ComputedExpr::Tuple(t) => {
2022-03-21 08:43:36 +00:00
f.write_char('(')?;
2021-12-04 22:21:36 +00:00
for x in &**t {
2022-03-21 08:43:36 +00:00
x.fmt(f)?;
f.write_str(", ")?;
2021-12-04 22:21:36 +00:00
}
2022-03-21 08:43:36 +00:00
f.write_char(')')
2021-12-04 22:21:36 +00:00
}
}
}
}
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)
}
}
2022-03-20 13:45:28 +00:00
pub fn eval_const(
expr_id: ExprId,
ctx: &mut ConstEvalCtx<'_>,
) -> Result<ComputedExpr, ConstEvalError> {
let expr = &ctx.exprs[expr_id];
2021-12-04 22:21:36 +00:00
match expr {
2022-03-24 08:36:27 +00:00
Expr::Missing => Err(ConstEvalError::IncompleteExpr),
2021-12-04 22:21:36 +00:00
Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
&Expr::UnaryOp { expr, op } => {
2022-03-20 13:45:28 +00:00
let ty = &ctx.expr_ty(expr);
let ev = eval_const(expr, ctx)?;
2021-12-04 22:21:36 +00:00
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 } => {
2022-03-20 13:45:28 +00:00
let ty = &ctx.expr_ty(lhs);
let lhs = eval_const(lhs, ctx)?;
let rhs = eval_const(rhs, ctx)?;
2021-12-04 22:21:36 +00:00
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)))
}
2022-03-20 13:45:28 +00:00
BinaryOp::LogicOp(_) => Err(ConstEvalError::SemanticError("logic op on numbers")),
2022-03-12 13:56:26 +00:00
_ => Err(ConstEvalError::NotSupported("bin op on this operators")),
2021-12-04 22:21:36 +00:00
}
}
Expr::Block { statements, tail, .. } => {
let mut prev_values = HashMap::<PatId, Option<ComputedExpr>>::default();
2021-12-04 22:21:36 +00:00
for statement in &**statements {
match *statement {
hir_def::expr::Statement::Let { pat: pat_id, initializer, .. } => {
let pat = &ctx.pats[pat_id];
match pat {
Pat::Bind { subpat, .. } if subpat.is_none() => (),
2021-12-04 22:21:36 +00:00
_ => {
return Err(ConstEvalError::NotSupported("complex patterns in let"))
}
};
let value = match initializer {
2022-03-20 13:45:28 +00:00
Some(x) => eval_const(x, ctx)?,
2021-12-04 22:21:36 +00:00
None => continue,
};
if !prev_values.contains_key(&pat_id) {
let prev = ctx.local_data.insert(pat_id, value);
prev_values.insert(pat_id, prev);
} else {
ctx.local_data.insert(pat_id, value);
}
2021-12-04 22:21:36 +00:00
}
hir_def::expr::Statement::Expr { .. } => {
2021-12-04 22:21:36 +00:00
return Err(ConstEvalError::NotSupported("this kind of statement"))
}
}
}
let r = match tail {
2022-03-20 13:45:28 +00:00
&Some(x) => eval_const(x, ctx),
None => Ok(ComputedExpr::Tuple(Box::new([]))),
2021-12-04 22:21:36 +00:00
};
// clean up local data, so caller will receive the exact map that passed to us
for (name, val) in prev_values {
match val {
Some(x) => ctx.local_data.insert(name, x),
None => ctx.local_data.remove(&name),
};
}
r
2021-12-04 22:21:36 +00:00
}
Expr::Path(p) => {
2022-03-20 13:45:28 +00:00
let resolver = resolver_for_expr(ctx.db.upcast(), ctx.owner, expr_id);
let pr = resolver
.resolve_path_in_value_ns(ctx.db.upcast(), p.mod_path())
.ok_or(ConstEvalError::SemanticError("unresolved path"))?;
let pr = match pr {
ResolveValueResult::ValueNs(v) => v,
ResolveValueResult::Partial(..) => {
return match ctx
.infer
.assoc_resolutions_for_expr(expr_id)
.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))?
{
hir_def::AssocItemId::FunctionId(_) => {
Err(ConstEvalError::NotSupported("assoc function"))
}
hir_def::AssocItemId::ConstId(c) => ctx.db.const_eval(c),
hir_def::AssocItemId::TypeAliasId(_) => {
Err(ConstEvalError::NotSupported("assoc type alias"))
}
}
}
};
match pr {
ValueNs::LocalBinding(pat_id) => {
2022-03-20 13:45:28 +00:00
let r = ctx
.local_data
.get(&pat_id)
2022-03-20 13:45:28 +00:00
.ok_or(ConstEvalError::NotSupported("Unexpected missing local"))?;
Ok(r.clone())
}
ValueNs::ConstId(id) => ctx.db.const_eval(id),
2022-03-24 08:36:27 +00:00
ValueNs::GenericParam(_) => {
Err(ConstEvalError::NotSupported("const generic without substitution"))
}
2022-03-20 13:45:28 +00:00
_ => Err(ConstEvalError::NotSupported("path that are not const or local")),
}
2021-12-04 22:21:36 +00:00
}
_ => Err(ConstEvalError::NotSupported("This kind of expression")),
}
}
pub fn eval_usize(expr: Idx<Expr>, mut ctx: ConstEvalCtx<'_>) -> Option<u64> {
2022-03-12 12:04:13 +00:00
if let Ok(ce) = eval_const(expr, &mut ctx) {
match ce {
ComputedExpr::Literal(Literal::Int(x, _)) => return x.try_into().ok(),
ComputedExpr::Literal(Literal::Uint(x, _)) => return x.try_into().ok(),
_ => {}
}
}
None
}
2022-03-09 18:50:24 +00:00
pub(crate) fn path_to_const(
db: &dyn HirDatabase,
resolver: &Resolver,
path: &ModPath,
mode: ParamLoweringMode,
args_lazy: impl FnOnce() -> Generics,
debruijn: DebruijnIndex,
) -> Option<Const> {
match resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
Some(ValueNs::GenericParam(p)) => {
let ty = db.const_param_ty(p);
let args = args_lazy();
let value = match mode {
ParamLoweringMode::Placeholder => {
ConstValue::Placeholder(to_placeholder_idx(db, p.into()))
}
ParamLoweringMode::Variable => match args.param_idx(p.into()) {
Some(x) => ConstValue::BoundVar(BoundVar::new(debruijn, x)),
None => {
never!(
"Generic list doesn't contain this param: {:?}, {}, {:?}",
args,
path,
p
);
return None;
}
},
};
Some(ConstData { ty, value }.intern(Interner))
}
_ => None,
}
}
pub fn unknown_const(ty: Ty) -> Const {
ConstData {
ty,
value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: ConstScalar::Unknown }),
}
.intern(Interner)
}
pub fn unknown_const_as_generic(ty: Ty) -> GenericArg {
GenericArgData::Const(unknown_const(ty)).intern(Interner)
}
2022-04-07 01:00:33 +00:00
/// Interns a constant scalar with the given type
pub fn intern_scalar_const(value: ConstScalar, ty: Ty) -> Const {
ConstData { ty, value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: value }) }
.intern(Interner)
}
/// Interns a possibly-unknown target usize
pub fn usize_const(value: Option<u64>) -> Const {
2022-04-07 01:00:33 +00:00
intern_scalar_const(
value.map(ConstScalar::Usize).unwrap_or(ConstScalar::Unknown),
TyBuilder::usize(),
)
}
2022-03-09 18:50:24 +00:00
2022-03-20 13:45:28 +00:00
pub(crate) fn const_eval_recover(
_: &dyn HirDatabase,
_: &[String],
_: &ConstId,
) -> Result<ComputedExpr, ConstEvalError> {
Err(ConstEvalError::Loop)
}
pub(crate) fn const_eval_query(
db: &dyn HirDatabase,
const_id: ConstId,
) -> Result<ComputedExpr, ConstEvalError> {
let def = const_id.into();
let body = db.body(def);
2022-03-24 08:36:27 +00:00
let infer = &db.infer(def);
2022-03-20 13:45:28 +00:00
let result = eval_const(
body.body_expr,
&mut ConstEvalCtx {
db,
owner: const_id.into(),
exprs: &body.exprs,
pats: &body.pats,
local_data: HashMap::default(),
2022-03-24 08:36:27 +00:00
infer,
2022-03-20 13:45:28 +00:00
},
);
result
}
pub(crate) fn eval_to_const<'a>(
2022-03-09 18:50:24 +00:00
expr: Idx<Expr>,
mode: ParamLoweringMode,
2022-03-20 13:45:28 +00:00
ctx: &mut InferenceContext<'a>,
2022-03-09 18:50:24 +00:00
args: impl FnOnce() -> Generics,
debruijn: DebruijnIndex,
) -> Const {
if let Expr::Path(p) = &ctx.body.exprs[expr] {
let db = ctx.db;
let resolver = &ctx.resolver;
if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) {
return c;
}
}
let body = ctx.body.clone();
let ctx = ConstEvalCtx {
2022-03-20 13:45:28 +00:00
db: ctx.db,
owner: ctx.owner,
2022-03-09 18:50:24 +00:00
exprs: &body.exprs,
pats: &body.pats,
local_data: HashMap::default(),
2022-03-20 13:45:28 +00:00
infer: &ctx.result,
2022-03-09 18:50:24 +00:00
};
usize_const(eval_usize(expr, ctx))
}
2022-03-24 08:36:27 +00:00
#[cfg(test)]
mod tests;