use std::collections::{HashMap, HashSet};
use super::cpp_ast::*;
use super::cpp_token::CppStandard;
#[derive(Debug, Clone, PartialEq)]
pub enum ConstValue {
Int(i128),
UInt(u128),
Float(f64),
Bool(bool),
NullPtr,
Address(String),
StringLiteral(String),
Array(Vec<ConstValue>),
Struct(Vec<(String, ConstValue)>),
NotEvaluable,
}
impl ConstValue {
pub fn as_i64(&self) -> Option<i64> {
match self {
ConstValue::Int(v) => {
if *v >= i64::MIN as i128 && *v <= i64::MAX as i128 {
Some(*v as i64)
} else {
None
}
}
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
ConstValue::UInt(v) => {
if *v <= u64::MAX as u128 {
Some(*v as u64)
} else {
None
}
}
ConstValue::Int(v) => {
if *v >= 0 && *v <= u64::MAX as i128 {
Some(*v as u64)
} else {
None
}
}
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
ConstValue::Bool(b) => Some(*b),
ConstValue::Int(v) => Some(*v != 0),
ConstValue::UInt(v) => Some(*v != 0),
_ => None,
}
}
pub fn is_evaluable(&self) -> bool {
!matches!(self, ConstValue::NotEvaluable)
}
pub fn type_name(&self) -> &'static str {
match self {
ConstValue::Int(_) => "int",
ConstValue::UInt(_) => "unsigned int",
ConstValue::Float(_) => "float",
ConstValue::Bool(_) => "bool",
ConstValue::NullPtr => "nullptr_t",
ConstValue::Address(_) => "address",
ConstValue::StringLiteral(_) => "string",
ConstValue::Array(_) => "array",
ConstValue::Struct(_) => "struct",
ConstValue::NotEvaluable => "<not evaluable>",
}
}
}
impl std::fmt::Display for ConstValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstValue::Int(v) => write!(f, "{}", v),
ConstValue::UInt(v) => write!(f, "{}", v),
ConstValue::Float(v) => write!(f, "{}", v),
ConstValue::Bool(true) => write!(f, "true"),
ConstValue::Bool(false) => write!(f, "false"),
ConstValue::NullPtr => write!(f, "nullptr"),
ConstValue::Address(a) => write!(f, "&{}", a),
ConstValue::StringLiteral(s) => write!(f, "\"{}\"", s),
ConstValue::Array(elems) => {
write!(f, "{{")?;
for (i, e) in elems.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", e)?;
}
write!(f, "}}")
}
ConstValue::Struct(fields) => {
write!(f, "{{")?;
for (i, (name, val)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", name, val)?;
}
write!(f, "}}")
}
ConstValue::NotEvaluable => write!(f, "<not evaluable>"),
}
}
}
#[derive(Debug, Clone)]
pub enum ConstEvalError {
NotConstant(String),
DivisionByZero,
Overflow,
InvalidShift,
OutOfBounds,
RecursionLimitExceeded,
VirtualCall,
ReinterpretCast,
UndefinedBehavior(String),
OdrUseViolation(String),
Unsupported(String),
}
pub struct ConstExprEvaluator {
pub standard: CppStandard,
pub variables: HashMap<String, ConstValue>,
pub functions: HashMap<String, CXXDecl>,
pub call_depth: usize,
pub max_call_depth: usize,
pub in_consteval: bool,
pub diagnostics: Vec<String>,
}
impl ConstExprEvaluator {
pub fn new(standard: CppStandard) -> Self {
Self {
standard,
variables: HashMap::new(),
functions: HashMap::new(),
call_depth: 0,
max_call_depth: 512,
in_consteval: false,
diagnostics: Vec::new(),
}
}
pub fn evaluate(&mut self, expr: &CXXExpr) -> Result<ConstValue, ConstEvalError> {
match expr {
CXXExpr::IntegerLiteral(v) => Ok(ConstValue::Int(*v as i128)),
CXXExpr::FloatLiteral(v) => Ok(ConstValue::Float(*v)),
CXXExpr::BoolLiteral(b) => Ok(ConstValue::Bool(*b)),
CXXExpr::NullPtrLiteral => Ok(ConstValue::NullPtr),
CXXExpr::CharLiteral(c) => Ok(ConstValue::Int(*c as i128)),
CXXExpr::StringLiteral(s) => Ok(ConstValue::StringLiteral(s.clone())),
CXXExpr::ThisExpr => Err(ConstEvalError::NotConstant(
"'this' is not a constant expression".into(),
)),
CXXExpr::CXXNewExpr { .. } => Err(ConstEvalError::NotConstant(
"'new' is not allowed in constant expressions".into(),
)),
CXXExpr::CXXDeleteExpr { .. } => Err(ConstEvalError::NotConstant(
"'delete' is not allowed in constant expressions".into(),
)),
CXXExpr::CXXThrowExpr(_) => Err(ConstEvalError::NotConstant(
"'throw' is not a constant expression".into(),
)),
CXXExpr::ReinterpretCast { .. } => Err(ConstEvalError::ReinterpretCast),
CXXExpr::CXXConstructExpr { .. } => {
if self.standard >= CppStandard::Cxx20 || self.standard >= CppStandard::GnuXx20 {
Err(ConstEvalError::Unsupported(
"constexpr construction evaluation".into(),
))
} else {
Err(ConstEvalError::NotConstant(
"construction is not a constant expression before C++20".into(),
))
}
}
CXXExpr::LambdaExpr { .. } => {
if self.standard >= CppStandard::Cxx17 || self.standard >= CppStandard::GnuXx17 {
Err(ConstEvalError::Unsupported("lambda evaluation".into()))
} else {
Err(ConstEvalError::NotConstant(
"lambda expression is not a constant expression before C++17".into(),
))
}
}
CXXExpr::CXXFunctionalCastExpr { expr, .. } => self.evaluate(expr),
CXXExpr::CXXCStyleCastExpr { expr, .. } => self.evaluate(expr),
_ => Err(ConstEvalError::Unsupported(format!(
"evaluation of {}",
expr
))),
}
}
pub fn evaluate_integer_constant(&mut self, expr: &CXXExpr) -> Result<i128, ConstEvalError> {
let val = self.evaluate(expr)?;
match val {
ConstValue::Int(v) => Ok(v),
ConstValue::UInt(v) => Ok(v as i128),
ConstValue::Bool(b) => Ok(b as i128),
_ => Err(ConstEvalError::NotConstant(format!(
"expected integer constant, got {}",
val.type_name()
))),
}
}
pub fn evaluate_float_constant(&mut self, expr: &CXXExpr) -> Result<f64, ConstEvalError> {
let val = self.evaluate(expr)?;
match val {
ConstValue::Float(v) => Ok(v),
ConstValue::Int(v) => Ok(v as f64),
_ => Err(ConstEvalError::NotConstant(format!(
"expected floating-point constant, got {}",
val.type_name()
))),
}
}
pub fn evaluate_address_constant(&mut self, expr: &CXXExpr) -> Result<String, ConstEvalError> {
let val = self.evaluate(expr)?;
match val {
ConstValue::Address(a) => Ok(a),
ConstValue::NullPtr => Ok("nullptr".into()),
ConstValue::StringLiteral(s) => Ok(s),
_ => Err(ConstEvalError::NotConstant(format!(
"expected address constant, got {}",
val.type_name()
))),
}
}
pub fn is_core_constant_expression(&self, expr: &CXXExpr) -> bool {
match expr {
CXXExpr::IntegerLiteral(_)
| CXXExpr::FloatLiteral(_)
| CXXExpr::BoolLiteral(_)
| CXXExpr::NullPtrLiteral
| CXXExpr::CharLiteral(_)
| CXXExpr::StringLiteral(_) => true,
CXXExpr::ThisExpr => false,
CXXExpr::CXXNewExpr { .. } => false,
CXXExpr::CXXDeleteExpr { .. } => false,
CXXExpr::CXXThrowExpr(_) => false,
CXXExpr::ReinterpretCast { .. } => false,
CXXExpr::TypeidExpr { .. } => {
true
}
CXXExpr::LambdaExpr { is_constexpr, .. } => *is_constexpr,
CXXExpr::CXXFunctionalCastExpr { expr, .. } => self.is_core_constant_expression(expr),
CXXExpr::CXXCStyleCastExpr { expr, .. } => self.is_core_constant_expression(expr),
_ => false,
}
}
pub fn check_undefined_behavior(&self, _expr: &CXXExpr) -> Option<String> {
None
}
pub fn is_literal_type(&self, _type_name: &str) -> bool {
matches!(
_type_name,
"void"
| "bool"
| "char"
| "signed char"
| "unsigned char"
| "short"
| "unsigned short"
| "int"
| "unsigned int"
| "long"
| "unsigned long"
| "long long"
| "unsigned long long"
| "float"
| "double"
| "long double"
| "nullptr_t"
) || _type_name.ends_with('*')
|| _type_name.ends_with('&')
}
pub fn is_literal_class(&self, _class_name: &str) -> bool {
self.standard >= CppStandard::Cxx11 || self.standard >= CppStandard::GnuXx11
}
pub fn register_variable(&mut self, name: &str, value: ConstValue) {
self.variables.insert(name.to_string(), value);
}
pub fn register_function(&mut self, name: &str, decl: CXXDecl) {
self.functions.insert(name.to_string(), decl);
}
pub fn lookup_variable(&self, name: &str) -> Option<&ConstValue> {
self.variables.get(name)
}
pub fn validate_constexpr_variable(
&mut self,
name: &str,
init_expr: Option<&CXXExpr>,
is_thread_local: bool,
) -> Result<(), String> {
if is_thread_local {
return Err(format!(
"'{}': constexpr variable cannot have thread_local storage duration",
name
));
}
match init_expr {
Some(expr) => match self.evaluate(expr) {
Ok(val) => {
self.register_variable(name, val);
Ok(())
}
Err(e) => Err(format!(
"'{}': constexpr variable must be initialized by a constant expression ({:?})",
name, e
)),
},
None => Err(format!(
"'{}': constexpr variable must have an initializer",
name
)),
}
}
pub fn validate_consteval_function(&self, has_body: bool) -> Result<(), String> {
if !has_body {
return Err("consteval function must have a body".into());
}
Ok(())
}
pub fn evaluate_consteval_call(
&mut self,
func_name: &str,
args: &[CXXExpr],
) -> Result<ConstValue, ConstEvalError> {
if self.call_depth >= self.max_call_depth {
return Err(ConstEvalError::RecursionLimitExceeded);
}
self.in_consteval = true;
self.call_depth += 1;
let mut arg_values = Vec::new();
for arg in args {
arg_values.push(self.evaluate(arg)?);
}
let _func_decl = self.functions.get(func_name).ok_or_else(|| {
ConstEvalError::NotConstant(format!("consteval function '{}' not found", func_name))
})?;
let result = Err(ConstEvalError::Unsupported(format!(
"evaluation of consteval function '{}' body",
func_name
)));
self.call_depth -= 1;
self.in_consteval = false;
result
}
pub fn validate_constinit(
&mut self,
name: &str,
init_expr: Option<&CXXExpr>,
is_static: bool,
is_thread_local: bool,
) -> Result<(), String> {
if !is_static && !is_thread_local {
return Err(format!(
"'{}': constinit can only be applied to variables with static or thread storage duration",
name
));
}
match init_expr {
Some(expr) => match self.evaluate(expr) {
Ok(_) => Ok(()),
Err(e) => Err(format!(
"'{}': constinit variable must have constant initialization ({:?})",
name, e
)),
},
None => Err(format!(
"'{}': constinit variable must have an initializer",
name
)),
}
}
pub fn supports_constant_destruction(&self) -> bool {
self.standard >= CppStandard::Cxx20 || self.standard >= CppStandard::GnuXx20
}
pub fn validate_constexpr_destructor(
&self,
class_name: &str,
is_constexpr: bool,
) -> Result<(), String> {
if !is_constexpr {
return Ok(());
}
if !self.supports_constant_destruction() {
return Err(format!(
"'{}': constexpr destructor requires C++20 or later",
class_name
));
}
Ok(())
}
pub fn evaluate_constexpr_if(&mut self, condition: &CXXExpr) -> Result<bool, ConstEvalError> {
let val = self.evaluate(condition)?;
match val.as_bool() {
Some(b) => Ok(b),
None => Err(ConstEvalError::NotConstant(
"condition of 'if constexpr' must be a constant expression".into(),
)),
}
}
pub fn eval_unary_plus(&self, val: &ConstValue) -> Result<ConstValue, ConstEvalError> {
match val {
ConstValue::Int(v) => Ok(ConstValue::Int(*v)),
ConstValue::UInt(v) => Ok(ConstValue::UInt(*v)),
ConstValue::Float(v) => Ok(ConstValue::Float(*v)),
_ => Err(ConstEvalError::Unsupported(
"unary plus on non-arithmetic type".into(),
)),
}
}
pub fn eval_unary_minus(&self, val: &ConstValue) -> Result<ConstValue, ConstEvalError> {
match val {
ConstValue::Int(v) => Ok(ConstValue::Int(-v)),
ConstValue::Float(v) => Ok(ConstValue::Float(-v)),
_ => Err(ConstEvalError::Unsupported(
"unary minus on non-arithmetic type".into(),
)),
}
}
pub fn eval_add(
&self,
lhs: &ConstValue,
rhs: &ConstValue,
) -> Result<ConstValue, ConstEvalError> {
match (lhs, rhs) {
(ConstValue::Int(a), ConstValue::Int(b)) => Ok(ConstValue::Int(a.wrapping_add(*b))),
(ConstValue::UInt(a), ConstValue::UInt(b)) => Ok(ConstValue::UInt(a.wrapping_add(*b))),
(ConstValue::Float(a), ConstValue::Float(b)) => Ok(ConstValue::Float(a + b)),
(ConstValue::Int(a), ConstValue::Float(b)) => Ok(ConstValue::Float(*a as f64 + b)),
(ConstValue::Float(a), ConstValue::Int(b)) => Ok(ConstValue::Float(a + *b as f64)),
_ => Err(ConstEvalError::Unsupported(
"addition on incompatible types".into(),
)),
}
}
pub fn eval_sub(
&self,
lhs: &ConstValue,
rhs: &ConstValue,
) -> Result<ConstValue, ConstEvalError> {
match (lhs, rhs) {
(ConstValue::Int(a), ConstValue::Int(b)) => Ok(ConstValue::Int(a.wrapping_sub(*b))),
(ConstValue::UInt(a), ConstValue::UInt(b)) => Ok(ConstValue::UInt(a.wrapping_sub(*b))),
(ConstValue::Float(a), ConstValue::Float(b)) => Ok(ConstValue::Float(a - b)),
_ => Err(ConstEvalError::Unsupported(
"subtraction on incompatible types".into(),
)),
}
}
pub fn eval_mul(
&self,
lhs: &ConstValue,
rhs: &ConstValue,
) -> Result<ConstValue, ConstEvalError> {
match (lhs, rhs) {
(ConstValue::Int(a), ConstValue::Int(b)) => Ok(ConstValue::Int(a.wrapping_mul(*b))),
(ConstValue::UInt(a), ConstValue::UInt(b)) => Ok(ConstValue::UInt(a.wrapping_mul(*b))),
(ConstValue::Float(a), ConstValue::Float(b)) => Ok(ConstValue::Float(a * b)),
_ => Err(ConstEvalError::Unsupported(
"multiplication on incompatible types".into(),
)),
}
}
pub fn eval_div(
&self,
lhs: &ConstValue,
rhs: &ConstValue,
) -> Result<ConstValue, ConstEvalError> {
match rhs {
ConstValue::Int(0) | ConstValue::UInt(0) => return Err(ConstEvalError::DivisionByZero),
ConstValue::Float(f) if *f == 0.0 => return Err(ConstEvalError::DivisionByZero),
_ => {}
}
match (lhs, rhs) {
(ConstValue::Int(a), ConstValue::Int(b)) => Ok(ConstValue::Int(a / b)),
(ConstValue::UInt(a), ConstValue::UInt(b)) => Ok(ConstValue::UInt(a / b)),
(ConstValue::Float(a), ConstValue::Float(b)) => Ok(ConstValue::Float(a / b)),
_ => Err(ConstEvalError::Unsupported(
"division on incompatible types".into(),
)),
}
}
pub fn eval_compare(
&self,
lhs: &ConstValue,
rhs: &ConstValue,
op: &str,
) -> Result<ConstValue, ConstEvalError> {
let cmp = match (lhs, rhs) {
(ConstValue::Int(a), ConstValue::Int(b)) => match op {
"==" => a == b,
"!=" => a != b,
"<" => a < b,
">" => a > b,
"<=" => a <= b,
">=" => a >= b,
_ => return Err(ConstEvalError::Unsupported("unknown comparison".into())),
},
(ConstValue::UInt(a), ConstValue::UInt(b)) => match op {
"==" => a == b,
"!=" => a != b,
"<" => a < b,
">" => a > b,
"<=" => a <= b,
">=" => a >= b,
_ => return Err(ConstEvalError::Unsupported("unknown comparison".into())),
},
(ConstValue::Float(a), ConstValue::Float(b)) => match op {
"==" => a == b,
"!=" => a != b,
"<" => a < b,
">" => a > b,
"<=" => a <= b,
">=" => a >= b,
_ => return Err(ConstEvalError::Unsupported("unknown comparison".into())),
},
_ => {
return Err(ConstEvalError::Unsupported(
"comparison on incompatible types".into(),
))
}
};
Ok(ConstValue::Bool(cmp))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evaluate_integer_literal() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::IntegerLiteral(42));
assert_eq!(result, Ok(ConstValue::Int(42)));
}
#[test]
fn test_evaluate_bool_literal() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::BoolLiteral(true));
assert_eq!(result, Ok(ConstValue::Bool(true)));
}
#[test]
fn test_evaluate_nullptr() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::NullPtrLiteral);
assert_eq!(result, Ok(ConstValue::NullPtr));
}
#[test]
fn test_evaluate_this_not_constant() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::ThisExpr);
assert!(result.is_err());
}
#[test]
fn test_evaluate_new_not_constant() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::CXXNewExpr {
allocated_ty: QualType::int(),
placement_args: vec![],
constructor_args: vec![],
is_array: false,
array_size: None,
});
assert!(result.is_err());
}
#[test]
fn test_evaluate_delete_not_constant() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::CXXDeleteExpr {
operand: Box::new(CXXExpr::NullPtrLiteral),
is_array: false,
});
assert!(result.is_err());
}
#[test]
fn test_evaluate_reinterpret_cast_not_constant() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::ReinterpretCast {
target_ty: QualType::int(),
expr: Box::new(CXXExpr::IntegerLiteral(0)),
});
assert!(result.is_err());
}
#[test]
fn test_integer_constant_expression() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate_integer_constant(&CXXExpr::IntegerLiteral(100));
assert_eq!(result, Ok(100));
}
#[test]
fn test_float_constant_expression() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate_float_constant(&CXXExpr::FloatLiteral(3.14));
assert_eq!(result, Ok(3.14));
}
#[test]
fn test_address_constant_nullptr() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate_address_constant(&CXXExpr::NullPtrLiteral);
assert!(result.is_ok());
}
#[test]
fn test_is_core_constant_expression() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
assert!(eval.is_core_constant_expression(&CXXExpr::IntegerLiteral(1)));
assert!(eval.is_core_constant_expression(&CXXExpr::BoolLiteral(false)));
assert!(!eval.is_core_constant_expression(&CXXExpr::ThisExpr));
assert!(!eval.is_core_constant_expression(&CXXExpr::CXXNewExpr {
allocated_ty: QualType::int(),
placement_args: vec![],
constructor_args: vec![],
is_array: false,
array_size: None,
}));
}
#[test]
fn test_is_literal_type_scalar() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
assert!(eval.is_literal_type("int"));
assert!(eval.is_literal_type("double"));
assert!(eval.is_literal_type("bool"));
assert!(eval.is_literal_type("char"));
assert!(eval.is_literal_type("int*"));
}
#[test]
fn test_is_literal_class() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
assert!(eval.is_literal_class("MyLiteralClass"));
}
#[test]
fn test_validate_constexpr_variable() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result =
eval.validate_constexpr_variable("x", Some(&CXXExpr::IntegerLiteral(5)), false);
assert!(result.is_ok());
assert_eq!(eval.lookup_variable("x"), Some(&ConstValue::Int(5)));
}
#[test]
fn test_validate_constexpr_variable_no_init() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.validate_constexpr_variable("y", None, false);
assert!(result.is_err());
}
#[test]
fn test_validate_constexpr_variable_non_const() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.validate_constexpr_variable(
"z",
Some(&CXXExpr::CXXNewExpr {
allocated_ty: QualType::int(),
placement_args: vec![],
constructor_args: vec![],
is_array: false,
array_size: None,
}),
false,
);
assert!(result.is_err());
}
#[test]
fn test_validate_constexpr_variable_thread_local() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.validate_constexpr_variable("t", Some(&CXXExpr::IntegerLiteral(0)), true);
assert!(result.is_err());
}
#[test]
fn test_validate_consteval_function() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx20);
assert!(eval.validate_consteval_function(true).is_ok());
assert!(eval.validate_consteval_function(false).is_err());
}
#[test]
fn test_validate_constinit() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx20);
let result = eval.validate_constinit("v", Some(&CXXExpr::IntegerLiteral(42)), true, false);
assert!(result.is_ok());
}
#[test]
fn test_validate_constinit_non_static() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx20);
let result = eval.validate_constinit("v", Some(&CXXExpr::IntegerLiteral(42)), false, false);
assert!(result.is_err());
}
#[test]
fn test_validate_constinit_non_const() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx20);
let result = eval.validate_constinit("v", Some(&CXXExpr::ThisExpr), true, false);
assert!(result.is_err());
}
#[test]
fn test_supports_constant_destruction() {
let eval17 = ConstExprEvaluator::new(CppStandard::Cxx17);
assert!(!eval17.supports_constant_destruction());
let eval20 = ConstExprEvaluator::new(CppStandard::Cxx20);
assert!(eval20.supports_constant_destruction());
}
#[test]
fn test_validate_constexpr_destructor_cxx20() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx20);
assert!(eval.validate_constexpr_destructor("Foo", true).is_ok());
}
#[test]
fn test_validate_constexpr_destructor_cxx17() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
assert!(eval.validate_constexpr_destructor("Foo", true).is_err());
}
#[test]
fn test_eval_arithmetic_add() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_add(&ConstValue::Int(3), &ConstValue::Int(4));
assert_eq!(result, Ok(ConstValue::Int(7)));
}
#[test]
fn test_eval_arithmetic_div() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_div(&ConstValue::Int(10), &ConstValue::Int(2));
assert_eq!(result, Ok(ConstValue::Int(5)));
}
#[test]
fn test_eval_arithmetic_div_by_zero() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_div(&ConstValue::Int(10), &ConstValue::Int(0));
assert_eq!(result, Err(ConstEvalError::DivisionByZero));
}
#[test]
fn test_eval_compare_eq() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_compare(&ConstValue::Int(5), &ConstValue::Int(5), "==");
assert_eq!(result, Ok(ConstValue::Bool(true)));
}
#[test]
fn test_eval_compare_lt() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_compare(&ConstValue::Int(3), &ConstValue::Int(7), "<");
assert_eq!(result, Ok(ConstValue::Bool(true)));
}
#[test]
fn test_eval_compare_ne() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_compare(&ConstValue::Int(1), &ConstValue::Int(2), "!=");
assert_eq!(result, Ok(ConstValue::Bool(true)));
}
#[test]
fn test_const_value_display() {
assert_eq!(format!("{}", ConstValue::Int(42)), "42");
assert_eq!(format!("{}", ConstValue::Bool(true)), "true");
assert_eq!(format!("{}", ConstValue::NullPtr), "nullptr");
assert_eq!(
format!("{}", ConstValue::StringLiteral("hello".into())),
"\"hello\""
);
}
#[test]
fn test_const_value_as_bool() {
assert_eq!(ConstValue::Bool(true).as_bool(), Some(true));
assert_eq!(ConstValue::Int(1).as_bool(), Some(true));
assert_eq!(ConstValue::Int(0).as_bool(), Some(false));
assert_eq!(ConstValue::Float(1.0).as_bool(), None);
}
#[test]
fn test_const_value_as_i64() {
assert_eq!(ConstValue::Int(42).as_i64(), Some(42));
assert_eq!(ConstValue::Int(-1).as_i64(), Some(-1));
}
#[test]
fn test_evaluate_constexpr_if_true() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate_constexpr_if(&CXXExpr::BoolLiteral(true));
assert_eq!(result, Ok(true));
}
#[test]
fn test_evaluate_constexpr_if_integer() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate_constexpr_if(&CXXExpr::IntegerLiteral(1));
assert!(result.is_err()); }
#[test]
fn test_evaluate_char_literal() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::CharLiteral('A'));
assert_eq!(result, Ok(ConstValue::Int(65)));
}
#[test]
fn test_evaluate_float_literal() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.evaluate(&CXXExpr::FloatLiteral(2.718));
assert_eq!(result, Ok(ConstValue::Float(2.718)));
}
#[test]
fn test_recursion_limit() {
let mut eval = ConstExprEvaluator::new(CppStandard::Cxx17);
eval.call_depth = eval.max_call_depth;
let result = eval.evaluate_consteval_call("any", &[]);
assert_eq!(result, Err(ConstEvalError::RecursionLimitExceeded));
}
#[test]
fn test_eval_unary_plus_minus() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
assert_eq!(
eval.eval_unary_plus(&ConstValue::Int(5)),
Ok(ConstValue::Int(5))
);
assert_eq!(
eval.eval_unary_minus(&ConstValue::Int(5)),
Ok(ConstValue::Int(-5))
);
}
#[test]
fn test_eval_mul() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_mul(&ConstValue::Int(6), &ConstValue::Int(7));
assert_eq!(result, Ok(ConstValue::Int(42)));
}
#[test]
fn test_eval_add_float_int() {
let eval = ConstExprEvaluator::new(CppStandard::Cxx17);
let result = eval.eval_add(&ConstValue::Int(2), &ConstValue::Float(3.5));
assert_eq!(result, Ok(ConstValue::Float(5.5)));
}
}