use std::cmp::Ordering;
use rucc_ast::{BinaryOp, UnaryOp};
use rucc_base::Interner;
use rucc_base::float::{Float, Format, Status};
use rucc_diag::Diagnostic;
use rucc_target::TargetInfo;
use rucc_types::{IntegerInfo, TypeId, TypeKind, Types, float_format, integer_info, spell};
use crate::expr::{Conversion, ExprId, ExprKind};
use crate::tast::{Const, Tast};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NotConstant {
pub at: ExprId,
pub poisoned: bool,
}
#[derive(Debug)]
pub struct Eval<'a> {
tast: &'a Tast,
types: &'a Types,
target: &'a TargetInfo,
names: &'a Interner,
diagnostics: Vec<Diagnostic>,
}
impl<'a> Eval<'a> {
#[must_use]
pub fn new(
tast: &'a Tast,
types: &'a Types,
target: &'a TargetInfo,
names: &'a Interner,
) -> Eval<'a> {
Eval { tast, types, target, names, diagnostics: Vec::new() }
}
pub fn constant(&mut self, expr: ExprId) -> Result<Const, NotConstant> {
self.eval(expr)
}
pub fn integer(&mut self, expr: ExprId) -> Result<i128, NotConstant> {
let value = self.eval(expr)?;
let ty = self.tast[expr].ty;
match value {
Const::Int(value) if self.int_shape(ty).is_some() => Ok(value),
_ => Err(self.stop(expr)),
}
}
#[must_use]
pub fn finish(self) -> Vec<Diagnostic> {
self.diagnostics
}
fn eval(&mut self, expr: ExprId) -> Result<Const, NotConstant> {
match self.tast[expr].kind {
ExprKind::Error => Err(NotConstant { at: expr, poisoned: true }),
ExprKind::Const(value) => Ok(self.tast[value]),
ExprKind::Unary { op, operand } => self.unary(expr, op, operand),
ExprKind::Binary { op, lhs, rhs } => self.binary(expr, op, lhs, rhs),
ExprKind::Cond { cond, then, otherwise } => {
let cond = self.eval(cond)?;
let taken = if truth(cond) { then } else { otherwise };
self.eval(taken)
}
ExprKind::Cast(operand) => self.convert(expr, operand),
ExprKind::Convert { kind: Conversion::Arithmetic | Conversion::Bool, operand } => {
self.convert(expr, operand)
}
_ => Err(self.stop(expr)),
}
}
fn unary(&mut self, expr: ExprId, op: UnaryOp, operand: ExprId) -> Result<Const, NotConstant> {
let value = self.eval(operand)?;
match (op, value) {
(UnaryOp::Plus, value) => Ok(value),
(UnaryOp::Not, value) => Ok(Const::Int(i128::from(!truth(value)))),
(UnaryOp::Real, value) => Ok(value),
(UnaryOp::Imag, _) => self.zero(expr),
(UnaryOp::Minus, Const::Float(value)) => Ok(Const::Float(value.negated())),
(UnaryOp::Minus | UnaryOp::BitNot, Const::Int(value)) => {
let Some(info) = self.int_shape(self.tast[operand].ty) else {
return Err(self.stop(expr));
};
if matches!(op, UnaryOp::BitNot) {
return Ok(Const::Int(info.wrap(!value)));
}
let negated = info.wrap(value.wrapping_neg());
if info.signed && value == least(info) {
self.overflow(expr, negated);
}
Ok(Const::Int(negated))
}
_ => Err(self.stop(expr)),
}
}
fn binary(
&mut self,
expr: ExprId,
op: BinaryOp,
lhs: ExprId,
rhs: ExprId,
) -> Result<Const, NotConstant> {
match op {
BinaryOp::LogAnd | BinaryOp::LogOr => {
let wanted = matches!(op, BinaryOp::LogOr);
let left = self.eval(lhs)?;
if truth(left) == wanted {
return Ok(Const::Int(i128::from(wanted)));
}
let right = self.eval(rhs)?;
Ok(Const::Int(i128::from(truth(right))))
}
BinaryOp::Shl | BinaryOp::Shr => self.shift(expr, op, lhs, rhs),
_ => {
let left = self.eval(lhs)?;
let right = self.eval(rhs)?;
match (left, right) {
(Const::Int(left), Const::Int(right)) => {
let Some(info) = self.int_shape(self.tast[lhs].ty) else {
return Err(self.stop(expr));
};
self.int_binary(expr, op, left, right, info)
}
(Const::Float(left), Const::Float(right)) => {
self.float_binary(expr, op, left, right)
}
_ => Err(self.stop(expr)),
}
}
}
}
fn int_binary(
&mut self,
expr: ExprId,
op: BinaryOp,
left: i128,
right: i128,
info: IntegerInfo,
) -> Result<Const, NotConstant> {
if let Some(ordering) = compare_int(op, left, right, info) {
return Ok(Const::Int(i128::from(ordering)));
}
let value = match op {
BinaryOp::BitAnd => left & right,
BinaryOp::BitOr => left | right,
BinaryOp::BitXor => left ^ right,
BinaryOp::Div | BinaryOp::Rem if right == 0 => {
self.warn(expr, "division by zero", "E0521");
return Err(NotConstant { at: expr, poisoned: false });
}
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => {
return self.arithmetic(expr, op, left, right, info);
}
_ => return Err(self.stop(expr)),
};
Ok(Const::Int(info.wrap(value)))
}
fn arithmetic(
&mut self,
expr: ExprId,
op: BinaryOp,
left: i128,
right: i128,
info: IntegerInfo,
) -> Result<Const, NotConstant> {
if !info.signed {
let (left, right) = (left as u128, right as u128);
let value = match op {
BinaryOp::Add => left.wrapping_add(right),
BinaryOp::Sub => left.wrapping_sub(right),
BinaryOp::Mul => left.wrapping_mul(right),
BinaryOp::Div => left / right,
_ => left % right,
};
return Ok(Const::Int(info.wrap(value as i128)));
}
let (exact, wrapped) = match op {
BinaryOp::Add => (left.checked_add(right), left.wrapping_add(right)),
BinaryOp::Sub => (left.checked_sub(right), left.wrapping_sub(right)),
BinaryOp::Mul => (left.checked_mul(right), left.wrapping_mul(right)),
BinaryOp::Div => (left.checked_div(right), left.wrapping_div(right)),
_ => (left.checked_rem(right), left.wrapping_rem(right)),
};
let value = info.wrap(wrapped);
let extreme =
matches!(op, BinaryOp::Div | BinaryOp::Rem) && right == -1 && left == least(info);
if extreme || exact.is_none_or(|exact| !info.holds(exact)) {
self.overflow(expr, value);
}
Ok(Const::Int(value))
}
fn float_binary(
&mut self,
expr: ExprId,
op: BinaryOp,
left: Float,
right: Float,
) -> Result<Const, NotConstant> {
if let Some(ordering) = compare_float(op, left, right) {
return Ok(Const::Int(i128::from(ordering)));
}
let (value, _) = match op {
BinaryOp::Add => left.sum(right),
BinaryOp::Sub => left.difference(right),
BinaryOp::Mul => left.product(right),
BinaryOp::Div => left.quotient(right),
_ => return Err(self.stop(expr)),
};
Ok(Const::Float(value))
}
fn shift(
&mut self,
expr: ExprId,
op: BinaryOp,
lhs: ExprId,
rhs: ExprId,
) -> Result<Const, NotConstant> {
let left = self.eval(lhs)?;
let right = self.eval(rhs)?;
let (Const::Int(value), Const::Int(count)) = (left, right) else {
return Err(self.stop(expr));
};
let (Some(info), Some(counts)) =
(self.int_shape(self.tast[lhs].ty), self.int_shape(self.tast[rhs].ty))
else {
return Err(self.stop(expr));
};
let side = if matches!(op, BinaryOp::Shl) { "left" } else { "right" };
if counts.signed && count < 0 {
self.warn(expr, format!("{side} shift count is negative"), "E0522");
return Err(NotConstant { at: expr, poisoned: false });
}
let count = count as u128;
if count >= u128::from(info.width) {
self.warn(expr, format!("{side} shift count >= width of type"), "E0523");
let sign = matches!(op, BinaryOp::Shr) && info.signed && value < 0;
return Ok(Const::Int(if sign { -1 } else { 0 }));
}
let count = count as u32;
let value = match (op, info.signed) {
(BinaryOp::Shr, true) => value >> count,
(BinaryOp::Shr, false) => ((value as u128) >> count) as i128,
_ => value.wrapping_shl(count),
};
Ok(Const::Int(info.wrap(value)))
}
fn convert(&mut self, expr: ExprId, operand: ExprId) -> Result<Const, NotConstant> {
let value = self.eval(operand)?;
let (from, to) = (self.tast[operand].ty, self.tast[expr].ty);
match self.converted(value, from, to) {
Some(value) => Ok(value),
None => Err(self.stop(expr)),
}
}
fn converted(&self, value: Const, from: TypeId, to: TypeId) -> Option<Const> {
match bare(self.types, to) {
TypeKind::Bool => Some(Const::Int(i128::from(truth(value)))),
TypeKind::Int(_) | TypeKind::BitInt { .. } | TypeKind::Enum(_) => {
let info = self.int_shape(to)?;
match value {
Const::Int(value) => Some(Const::Int(info.wrap(value))),
Const::Float(value) => {
Some(Const::Int(value.to_integer(info.width, info.signed).0))
}
}
}
TypeKind::Float(kind) => {
let format = float_format(kind, self.target);
let (value, _) = match value {
Const::Float(value) => value.to_format(format),
Const::Int(value) => match self.int_shape(from) {
Some(info) if !info.signed => Float::from_unsigned(value as u128, format),
_ => Float::from_signed(value, format),
},
};
Some(Const::Float(value))
}
_ => None,
}
}
fn zero(&mut self, expr: ExprId) -> Result<Const, NotConstant> {
let ty = self.tast[expr].ty;
if self.int_shape(ty).is_some() {
return Ok(Const::Int(0));
}
match self.float_shape(ty) {
Some(format) => Ok(Const::Float(Float::zero(format, false))),
None => Err(self.stop(expr)),
}
}
fn int_shape(&self, ty: TypeId) -> Option<IntegerInfo> {
int_shape(self.types, ty, self.target)
}
fn float_shape(&self, ty: TypeId) -> Option<Format> {
match bare(self.types, ty) {
TypeKind::Float(kind) => Some(float_format(kind, self.target)),
_ => None,
}
}
fn stop(&self, expr: ExprId) -> NotConstant {
NotConstant { at: expr, poisoned: false }
}
fn overflow(&mut self, expr: ExprId, value: i128) {
let ty = spell(self.types, self.names, self.tast[expr].ty);
let message = format!("integer overflow in expression of type '{ty}' results in '{value}'");
self.warn(expr, message, "E0524");
}
fn warn(&mut self, expr: ExprId, message: impl Into<String>, code: &'static str) {
let span = self.tast.expr_span(expr);
self.diagnostics.push(Diagnostic::warning(message.into(), span).with_code(code));
}
}
pub(crate) fn int_shape(types: &Types, ty: TypeId, target: &TargetInfo) -> Option<IntegerInfo> {
let info = integer_info(types, ty, target)?;
(info.width > 0 && info.width <= 128).then_some(info)
}
fn truth(value: Const) -> bool {
match value {
Const::Int(value) => value != 0,
Const::Float(value) => !value.is_zero(),
}
}
fn compare_int(op: BinaryOp, left: i128, right: i128, info: IntegerInfo) -> Option<bool> {
let ordering = if info.signed {
left.cmp(&right)
} else {
(left as u128).cmp(&(right as u128))
};
holds(op, ordering)
}
fn compare_float(op: BinaryOp, left: Float, right: Float) -> Option<bool> {
match left.compare(right) {
Some(ordering) => holds(op, ordering),
None if holds(op, Ordering::Equal).is_some() => Some(matches!(op, BinaryOp::Ne)),
None => None,
}
}
fn holds(op: BinaryOp, ordering: Ordering) -> Option<bool> {
Some(match op {
BinaryOp::Lt => ordering.is_lt(),
BinaryOp::Gt => ordering.is_gt(),
BinaryOp::Le => ordering.is_le(),
BinaryOp::Ge => ordering.is_ge(),
BinaryOp::Eq => ordering.is_eq(),
BinaryOp::Ne => ordering.is_ne(),
_ => return None,
})
}
fn least(info: IntegerInfo) -> i128 {
info.wrap(1i128 << info.width.saturating_sub(1))
}
pub(crate) fn bare(types: &Types, ty: TypeId) -> TypeKind {
match types.kind(types.canonical(ty)) {
TypeKind::Atomic(inner) => types.kind(types.canonical(inner)),
other => other,
}
}
pub(crate) fn spell_int(value: i128, info: IntegerInfo) -> String {
if info.signed { format!("{value}") } else { format!("{}", value as u128) }
}
pub(crate) fn narrowed(value: Const, info: IntegerInfo) -> i128 {
match value {
Const::Int(value) => info.wrap(value),
Const::Float(value) => value.to_integer(info.width, info.signed).0,
}
}
pub(crate) fn spell_const(value: Const, info: Option<IntegerInfo>) -> String {
match value {
Const::Int(value) => match info {
Some(info) => spell_int(value, info),
None => format!("{value}"),
},
Const::Float(value) => value.to_hex(),
}
}
pub(crate) fn overflows(value: Const, info: IntegerInfo) -> bool {
match value {
Const::Int(value) => {
!IntegerInfo::new(true, info.width).holds(value)
&& !IntegerInfo::new(false, info.width).holds(value)
}
Const::Float(value) => value.to_integer(info.width, info.signed).1.has(Status::INVALID),
}
}
#[cfg(test)]
mod tests {
use rucc_ast as ast;
use rucc_base::float::Format;
use rucc_diag::Span;
use rucc_lex::{FloatConstant, FloatConstantType, IntConstant, IntConstantType, Remarks};
use rucc_session::Std;
use rucc_target::{TargetInfo, Triple};
use rucc_types::IntKind;
use super::*;
use crate::check::{Checker, Context};
struct Fixture {
ast: ast::Ast,
names: Interner,
target: TargetInfo,
}
impl Fixture {
fn new() -> Fixture {
let target =
TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"));
Fixture { ast: ast::Ast::new(), names: Interner::new(), target }
}
fn expr(&mut self, expr: ast::Expr) -> ast::ExprId {
self.ast.expr(expr, Span::DUMMY)
}
fn int(&mut self, value: u128, kind: IntKind) -> ast::ExprId {
let ty = IntConstantType::Standard(kind);
let id = self.ast.add_int(IntConstant { value, ty, remarks: Remarks::default() });
self.expr(ast::Expr::Int(id))
}
fn bit_int(&mut self, value: u128, signed: bool, width: u32) -> ast::ExprId {
let ty = IntConstantType::BitInt { signed, width };
let id = self.ast.add_int(IntConstant { value, ty, remarks: Remarks::default() });
self.expr(ast::Expr::Int(id))
}
fn double(&mut self, text: &str) -> ast::ExprId {
let (value, _) = Float::parse(text, Format::Double).expect("a float");
let constant = FloatConstant {
value,
ty: FloatConstantType::Double,
imaginary: false,
remarks: Remarks::default(),
};
let id = self.ast.add_float(constant);
self.expr(ast::Expr::Float(id))
}
fn binary(&mut self, op: BinaryOp, lhs: ast::ExprId, rhs: ast::ExprId) -> ast::ExprId {
self.expr(ast::Expr::Binary { op, lhs, rhs })
}
fn unary(&mut self, op: UnaryOp, operand: ast::ExprId) -> ast::ExprId {
self.expr(ast::Expr::Unary { op, operand })
}
fn checker(&self) -> Checker<'_> {
Checker::new(&self.ast, Context::new(&self.names, &self.target, Std::C23))
}
}
fn fold(checker: &mut Checker<'_>, expr: ast::ExprId) -> Result<i128, NotConstant> {
let id = checker.check_expr(expr);
checker.eval_integer(id)
}
fn messages(checker: &Checker<'_>) -> Vec<String> {
checker.errors.diagnostics().iter().map(|d| d.message.clone()).collect()
}
#[test]
fn arithmetic_folds_to_the_value_the_program_wrote() {
let mut f = Fixture::new();
let (one, two, three) =
(f.int(1, IntKind::Int), f.int(2, IntKind::Int), f.int(3, IntKind::Int));
let sum = f.binary(BinaryOp::Add, one, two);
let product = f.binary(BinaryOp::Mul, sum, three);
let mut c = f.checker();
assert_eq!(fold(&mut c, product), Ok(9));
assert!(messages(&c).is_empty());
}
#[test]
fn signed_overflow_is_warned_about_and_wrapped() {
let mut f = Fixture::new();
let (big, one) = (f.int(2_147_483_647, IntKind::Int), f.int(1, IntKind::Int));
let sum = f.binary(BinaryOp::Add, big, one);
let mut c = f.checker();
assert_eq!(fold(&mut c, sum), Ok(-2_147_483_648));
assert_eq!(
messages(&c),
["integer overflow in expression of type 'int' results in '-2147483648'"]
);
}
#[test]
fn unsigned_arithmetic_wraps_without_a_word_because_it_is_not_overflow() {
let mut f = Fixture::new();
let (big, one) = (f.int(4_294_967_295, IntKind::UInt), f.int(1, IntKind::UInt));
let sum = f.binary(BinaryOp::Add, big, one);
let mut c = f.checker();
assert_eq!(fold(&mut c, sum), Ok(0));
assert!(messages(&c).is_empty());
}
#[test]
fn a_bit_precise_type_overflows_in_its_own_width_and_not_in_an_int() {
let mut f = Fixture::new();
let (a, b) = (f.bit_int(100, true, 8), f.bit_int(100, true, 8));
let sum = f.binary(BinaryOp::Add, a, b);
let mut c = f.checker();
assert_eq!(fold(&mut c, sum), Ok(-56));
assert_eq!(messages(&c).len(), 1, "{:?}", messages(&c));
}
#[test]
fn division_by_zero_is_warned_about_and_has_no_value() {
let mut f = Fixture::new();
let (one, zero) = (f.int(1, IntKind::Int), f.int(0, IntKind::Int));
let quotient = f.binary(BinaryOp::Div, one, zero);
let mut c = f.checker();
let folded = fold(&mut c, quotient);
assert!(folded.is_err());
assert!(!folded.expect_err("no value").poisoned, "the caller still names the context");
assert_eq!(messages(&c), ["division by zero"]);
}
#[test]
fn the_least_value_over_minus_one_overflows_and_so_does_its_remainder() {
for op in [BinaryOp::Div, BinaryOp::Rem] {
let mut f = Fixture::new();
let (big, one) = (f.int(2_147_483_647, IntKind::Int), f.int(1, IntKind::Int));
let negated = f.unary(UnaryOp::Minus, big);
let least = f.binary(BinaryOp::Sub, negated, one);
let minus_one = f.unary(UnaryOp::Minus, one);
let divided = f.binary(op, least, minus_one);
let mut c = f.checker();
let expected = if matches!(op, BinaryOp::Div) { -2_147_483_648 } else { 0 };
assert_eq!(fold(&mut c, divided), Ok(expected));
assert_eq!(messages(&c).len(), 1, "{:?}", messages(&c));
}
}
#[test]
fn negating_the_least_value_overflows_onto_itself() {
let mut f = Fixture::new();
let (big, one) = (f.int(2_147_483_647, IntKind::Int), f.int(1, IntKind::Int));
let flipped = f.unary(UnaryOp::Minus, big);
let least = f.binary(BinaryOp::Sub, flipped, one);
let negated = f.unary(UnaryOp::Minus, least);
let mut c = f.checker();
assert_eq!(fold(&mut c, negated), Ok(-2_147_483_648));
assert_eq!(
messages(&c),
["integer overflow in expression of type 'int' results in '-2147483648'"]
);
}
#[test]
fn a_shift_past_the_width_is_warned_about_and_folded_the_way_gcc_folds_it() {
let mut f = Fixture::new();
let (one, thirty_two) = (f.int(1, IntKind::Int), f.int(32, IntKind::Int));
let shifted = f.binary(BinaryOp::Shl, one, thirty_two);
let mut c = f.checker();
assert_eq!(fold(&mut c, shifted), Ok(0));
assert_eq!(messages(&c), ["left shift count >= width of type"]);
}
#[test]
fn an_arithmetic_right_shift_past_the_width_keeps_the_sign() {
let mut f = Fixture::new();
let (one, forty) = (f.int(1, IntKind::Int), f.int(40, IntKind::Int));
let minus_one = f.unary(UnaryOp::Minus, one);
let shifted = f.binary(BinaryOp::Shr, minus_one, forty);
let mut c = f.checker();
assert_eq!(fold(&mut c, shifted), Ok(-1));
assert_eq!(messages(&c), ["right shift count >= width of type"]);
}
#[test]
fn a_negative_shift_count_is_warned_about_and_has_no_value() {
let mut f = Fixture::new();
let (one, two) = (f.int(1, IntKind::Int), f.int(2, IntKind::Int));
let count = f.unary(UnaryOp::Minus, two);
let shifted = f.binary(BinaryOp::Shl, one, count);
let mut c = f.checker();
assert!(fold(&mut c, shifted).is_err());
assert_eq!(messages(&c), ["left shift count is negative"]);
}
#[test]
fn a_shift_folds_in_the_width_of_its_left_operand_alone() {
let mut f = Fixture::new();
let (one, forty) = (f.int(1, IntKind::LongLong), f.int(40, IntKind::Int));
let shifted = f.binary(BinaryOp::Shl, one, forty);
let mut c = f.checker();
assert_eq!(fold(&mut c, shifted), Ok(1 << 40));
assert!(messages(&c).is_empty());
}
#[test]
fn an_unsigned_comparison_reads_the_top_bit_as_a_digit() {
let mut f = Fixture::new();
let one = f.int(1, IntKind::UInt);
let big = f.unary(UnaryOp::Minus, one);
let other = f.int(1, IntKind::UInt);
let greater = f.binary(BinaryOp::Gt, big, other);
let mut c = f.checker();
assert_eq!(fold(&mut c, greater), Ok(1));
assert!(messages(&c).is_empty());
}
#[test]
fn short_circuiting_does_not_fold_what_the_language_did_not_evaluate() {
let mut f = Fixture::new();
let zero = f.int(0, IntKind::Int);
let name = f.names.intern("x");
let x = f.expr(ast::Expr::Name(name));
let and = f.binary(BinaryOp::LogAnd, zero, x);
let mut c = f.checker();
let int = c.types.int(IntKind::Int);
c.declare_object(name, int, Span::DUMMY);
assert_eq!(fold(&mut c, and), Ok(0));
assert!(messages(&c).is_empty(), "{:?}", messages(&c));
}
#[test]
fn only_the_arm_the_condition_takes_is_folded() {
let mut f = Fixture::new();
let (one, two) = (f.int(1, IntKind::Int), f.int(2, IntKind::Int));
let name = f.names.intern("x");
let x = f.expr(ast::Expr::Name(name));
let conditional = f.expr(ast::Expr::Cond { cond: one, then: Some(two), otherwise: x });
let mut c = f.checker();
let int = c.types.int(IntKind::Int);
c.declare_object(name, int, Span::DUMMY);
assert_eq!(fold(&mut c, conditional), Ok(2));
assert!(messages(&c).is_empty(), "{:?}", messages(&c));
}
#[test]
fn reading_an_object_is_not_a_constant_however_const_it_is() {
let mut f = Fixture::new();
let name = f.names.intern("n");
let x = f.expr(ast::Expr::Name(name));
let mut c = f.checker();
let int = c.types.int(IntKind::Int);
let constant = c.types.qualified(int, rucc_types::Qualifiers::CONST);
c.declare_object(name, constant, Span::DUMMY);
assert!(fold(&mut c, x).is_err());
assert!(messages(&c).is_empty());
}
#[test]
fn a_comma_is_a_constant_nowhere() {
let mut f = Fixture::new();
let (one, two) = (f.int(1, IntKind::Int), f.int(2, IntKind::Int));
let comma = f.expr(ast::Expr::Comma { lhs: one, rhs: two });
let mut c = f.checker();
assert!(fold(&mut c, comma).is_err());
assert!(messages(&c).is_empty());
}
#[test]
fn nothing_is_said_about_an_expression_that_was_already_diagnosed() {
let mut f = Fixture::new();
let name = f.names.intern("undeclared");
let x = f.expr(ast::Expr::Name(name));
let one = f.int(1, IntKind::Int);
let sum = f.binary(BinaryOp::Add, x, one);
let mut c = f.checker();
let folded = fold(&mut c, sum);
assert!(folded.expect_err("no value").poisoned);
assert_eq!(messages(&c).len(), 1, "the undeclared name, and nothing about the addition");
}
#[test]
fn a_floating_constant_is_not_an_integer_constant_expression() {
let mut f = Fixture::new();
let three = f.double("3.0");
let mut c = f.checker();
let id = c.check_expr(three);
assert!(c.eval_integer(id).is_err());
let (three, _) = Float::parse("3.0", Format::Double).expect("a float");
assert_eq!(c.eval_constant(id), Ok(Const::Float(three)));
assert!(messages(&c).is_empty());
}
#[test]
fn floating_arithmetic_is_folded_in_the_target_format() {
let mut f = Fixture::new();
let (one, three) = (f.double("1.0"), f.double("3.0"));
let third = f.binary(BinaryOp::Div, one, three);
let mut c = f.checker();
let id = c.check_expr(third);
let Ok(Const::Float(value)) = c.eval_constant(id) else { panic!("a folded float") };
assert_eq!(value.to_bits(), 0x3fd5_5555_5555_5555, "the correctly rounded double third");
assert!(messages(&c).is_empty());
}
#[test]
fn a_comparison_against_a_nan_is_false_except_for_the_inequality() {
for (op, expected) in [(BinaryOp::Eq, 0), (BinaryOp::Ne, 1), (BinaryOp::Lt, 0)] {
let mut f = Fixture::new();
let (a, b) = (f.double("0.0"), f.double("0.0"));
let nan = f.binary(BinaryOp::Div, a, b);
let (c1, c2) = (f.double("0.0"), f.double("0.0"));
let other = f.binary(BinaryOp::Div, c1, c2);
let compared = f.binary(op, nan, other);
let mut c = f.checker();
assert_eq!(fold(&mut c, compared), Ok(expected));
assert!(messages(&c).is_empty());
}
}
#[test]
fn a_conversion_between_arithmetic_types_folds_through_the_node_the_checking_wrote() {
let mut f = Fixture::new();
let (half, one) = (f.double("0.5"), f.int(1, IntKind::Int));
let sum = f.binary(BinaryOp::Add, half, one);
let mut c = f.checker();
let id = c.check_expr(sum);
let Ok(Const::Float(value)) = c.eval_constant(id) else { panic!("a folded float") };
assert_eq!(value.to_bits(), 0x3ff8_0000_0000_0000, "one and a half, in a double");
assert!(messages(&c).is_empty());
}
}