|
1 | 1 | //! Constant evaluation details
|
2 | 2 |
|
3 |
| -use std::convert::TryInto; |
| 3 | +use std::{collections::HashMap, convert::TryInto, fmt::Display}; |
4 | 4 |
|
| 5 | +use chalk_ir::{IntTy, Scalar}; |
5 | 6 | use hir_def::{
|
6 | 7 | builtin_type::BuiltinUint,
|
7 |
| - expr::{Expr, Literal}, |
| 8 | + expr::{ArithOp, BinaryOp, Expr, Literal, Pat}, |
8 | 9 | type_ref::ConstScalar,
|
9 | 10 | };
|
| 11 | +use hir_expand::name::Name; |
| 12 | +use la_arena::Arena; |
10 | 13 |
|
11 |
| -use crate::{Const, ConstData, ConstValue, Interner, TyKind}; |
| 14 | +use crate::{Const, ConstData, ConstValue, InferenceResult, Interner, TyKind}; |
12 | 15 |
|
13 | 16 | /// Extension trait for [`Const`]
|
14 | 17 | pub trait ConstExt {
|
@@ -38,6 +41,245 @@ impl ConstExt for Const {
|
38 | 41 | }
|
39 | 42 | }
|
40 | 43 |
|
| 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 | + |
41 | 283 | // FIXME: support more than just evaluating literals
|
42 | 284 | pub fn eval_usize(expr: &Expr) -> Option<u64> {
|
43 | 285 | match expr {
|
|
0 commit comments