use crate::clang::ast::*;
use crate::clang::CLangStandard;
#[allow(unused_imports)]
use crate::types::TypeKind as LlvmTypeKind;
use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IntegerRank {
Bool = 0,
Char = 1,
Short = 2,
Int = 3,
Long = 4,
LongLong = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConversionKind {
LvalueToRvalue,
ArrayToPointer,
FunctionToPointer,
IntegerPromotion,
UsualArithmetic,
FloatingPromotion,
IntegralToFloating,
FloatingToIntegral,
PointerInteger,
PointerConversion,
BooleanConversion,
Identity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct CVQualifiers {
pub is_const: bool,
pub is_volatile: bool,
pub is_restrict: bool,
}
impl CVQualifiers {
pub fn from_qual_type(qt: &QualType) -> Self {
Self {
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
}
}
pub fn is_superset_of(&self, other: &CVQualifiers) -> bool {
(!other.is_const || self.is_const)
&& (!other.is_volatile || self.is_volatile)
&& (!other.is_restrict || self.is_restrict)
}
}
pub struct TypeSystem {
typedefs: HashMap<String, QualType>,
structs: HashMap<String, StructDecl>,
enums: HashMap<String, EnumDecl>,
standard: CLangStandard,
}
impl TypeSystem {
pub fn new(standard: CLangStandard) -> Self {
Self {
typedefs: HashMap::new(),
structs: HashMap::new(),
enums: HashMap::new(),
standard,
}
}
pub fn register_typedef(&mut self, name: &str, underlying: QualType) {
self.typedefs.insert(name.to_string(), underlying);
}
pub fn register_struct(&mut self, sd: &StructDecl) {
if let Some(ref name) = sd.name {
self.structs.insert(name.clone(), sd.clone());
}
}
pub fn register_enum(&mut self, ed: &EnumDecl) {
if let Some(ref name) = ed.name {
self.enums.insert(name.clone(), ed.clone());
}
}
pub fn lookup_typedef(&self, name: &str) -> Option<&QualType> {
self.typedefs.get(name)
}
pub fn lookup_struct(&self, name: &str) -> Option<&StructDecl> {
self.structs.get(name)
}
pub fn lookup_enum(&self, name: &str) -> Option<&EnumDecl> {
self.enums.get(name)
}
pub fn is_void(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Void)
}
pub fn is_integer(&self, qt: &QualType) -> bool {
matches!(
*qt.base,
TypeNode::Char
| TypeNode::SChar
| TypeNode::UChar
| TypeNode::Short
| TypeNode::UShort
| TypeNode::Int
| TypeNode::UInt
| TypeNode::Long
| TypeNode::ULong
| TypeNode::LongLong
| TypeNode::ULongLong
| TypeNode::Bool
| TypeNode::Enum { .. }
)
}
pub fn is_unsigned(&self, qt: &QualType) -> bool {
matches!(
*qt.base,
TypeNode::UChar
| TypeNode::UShort
| TypeNode::UInt
| TypeNode::ULong
| TypeNode::ULongLong
)
}
pub fn is_signed_integer(&self, qt: &QualType) -> bool {
self.is_integer(qt) && !self.is_unsigned(qt)
}
pub fn is_floating(&self, qt: &QualType) -> bool {
matches!(
*qt.base,
TypeNode::Float | TypeNode::Double | TypeNode::LongDouble
)
}
pub fn is_arithmetic(&self, qt: &QualType) -> bool {
self.is_integer(qt) || self.is_floating(qt)
}
pub fn is_scalar(&self, qt: &QualType) -> bool {
self.is_arithmetic(qt) || self.is_pointer(qt)
}
pub fn is_pointer(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Pointer(_))
}
pub fn is_array(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Array { .. })
}
pub fn is_function(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Function { .. })
}
pub fn is_struct(&self, qt: &QualType) -> bool {
matches!(
*qt.base,
TypeNode::Struct {
is_union: false,
..
}
)
}
pub fn is_union(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Struct { is_union: true, .. })
}
pub fn is_aggregate(&self, qt: &QualType) -> bool {
self.is_array(qt) || self.is_struct(qt) || self.is_union(qt)
}
pub fn is_bool(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Bool)
}
pub fn integer_rank(&self, qt: &QualType) -> Option<IntegerRank> {
match *qt.base {
TypeNode::Bool => Some(IntegerRank::Bool),
TypeNode::Char | TypeNode::SChar | TypeNode::UChar => Some(IntegerRank::Char),
TypeNode::Short | TypeNode::UShort => Some(IntegerRank::Short),
TypeNode::Int | TypeNode::UInt => Some(IntegerRank::Int),
TypeNode::Long | TypeNode::ULong => Some(IntegerRank::Long),
TypeNode::LongLong | TypeNode::ULongLong => Some(IntegerRank::LongLong),
TypeNode::Enum { .. } => Some(IntegerRank::Int), _ => None,
}
}
pub fn integer_promotion(&self, qt: &QualType) -> QualType {
if !self.is_integer(qt) {
return qt.clone();
}
let rank = match self.integer_rank(qt) {
Some(r) => r,
None => return qt.clone(),
};
if rank < IntegerRank::Int {
QualType::int()
} else {
qt.clone()
}
}
pub fn usual_arithmetic_conversions(&self, lhs: &QualType, rhs: &QualType) -> QualType {
if matches!(*lhs.base, TypeNode::LongDouble) || matches!(*rhs.base, TypeNode::LongDouble) {
return QualType::ldouble();
}
if matches!(*lhs.base, TypeNode::Double) || matches!(*rhs.base, TypeNode::Double) {
return QualType::double();
}
if matches!(*lhs.base, TypeNode::Float) || matches!(*rhs.base, TypeNode::Float) {
return QualType::float_();
}
let l = self.integer_promotion(lhs);
let r = self.integer_promotion(rhs);
if l == r {
return l;
}
let l_rank = self.integer_rank(&l);
let r_rank = self.integer_rank(&r);
let l_unsigned = self.is_unsigned(&l);
let r_unsigned = self.is_unsigned(&r);
match (l_rank, r_rank) {
(Some(lr), Some(rr)) => {
if lr > rr {
l
} else if rr > lr {
r
} else {
if l_unsigned || r_unsigned {
if l_unsigned {
l
} else {
r
}
} else {
l
}
}
}
_ => {
l
}
}
}
pub fn type_size(&self, qt: &QualType) -> usize {
qt.base.size_bytes()
}
pub fn are_compatible(&self, a: &QualType, b: &QualType) -> bool {
self.are_compatible_inner(&a.base, &b.base)
}
fn are_compatible_inner(&self, a: &TypeNode, b: &TypeNode) -> bool {
use TypeNode::*;
match (a, b) {
(Void, Void) => true,
(Char, Char)
| (SChar, SChar)
| (UChar, UChar)
| (Short, Short)
| (UShort, UShort)
| (Int, Int)
| (UInt, UInt)
| (Long, Long)
| (ULong, ULong)
| (LongLong, LongLong)
| (ULongLong, ULongLong) => true,
(Float, Float) | (Double, Double) | (LongDouble, LongDouble) => true,
(Bool, Bool) => true,
(Char, _) | (SChar, _) | (UChar, _) => false,
(Pointer(a_inner), Pointer(b_inner)) => {
self.are_compatible_inner(&a_inner.base, &b_inner.base)
}
(
Array {
elem: a_elem,
size: a_size,
},
Array {
elem: b_elem,
size: b_size,
},
) => {
if !self.are_compatible_inner(&a_elem.base, &b_elem.base) {
return false;
}
match (a_size, b_size) {
(None, _) | (_, None) => true,
(Some(a), Some(b)) => a == b,
}
}
(
Function {
ret: a_ret,
params: a_params,
..
},
Function {
ret: b_ret,
params: b_params,
..
},
) => {
if !self.are_compatible_inner(&a_ret.base, &b_ret.base) {
return false;
}
if a_params.len() != b_params.len() {
return false;
}
a_params
.iter()
.zip(b_params.iter())
.all(|(ap, bp)| self.are_compatible(&ap, &bp))
}
(
Struct {
name: a_name,
is_union: a_union,
..
},
Struct {
name: b_name,
is_union: b_union,
..
},
) => a_name == b_name && a_union == b_union,
(Enum { name: a_name, .. }, Enum { name: b_name, .. }) => match (a_name, b_name) {
(Some(a), Some(b)) => a == b,
_ => true, },
(Enum { .. }, _) | (_, Enum { .. }) => {
matches!(a, Int | UInt) || matches!(b, Int | UInt)
}
(Typedef { underlying, .. }, other) => {
self.are_compatible_inner(&underlying.base, other)
}
(other, Typedef { underlying, .. }) => {
self.are_compatible_inner(other, &underlying.base)
}
_ => false,
}
}
pub fn composite_type(&self, a: &QualType, b: &QualType) -> Option<QualType> {
if !self.are_compatible(a, b) {
return None;
}
Some(self.composite_type_inner(&a.base, &b.base))
}
fn composite_type_inner(&self, a: &TypeNode, b: &TypeNode) -> QualType {
use TypeNode::*;
match (a, b) {
(
Array {
elem: a_elem,
size: a_size,
},
Array {
elem: b_elem,
size: b_size,
},
) => {
let composite_elem = if **a_elem != **b_elem {
self.composite_type_inner(&a_elem.base, &b_elem.base)
} else {
(**a_elem).clone()
};
let composite_size = match (a_size, b_size) {
(Some(sa), Some(sb)) => Some(*sa.max(sb)),
(Some(s), None) | (None, Some(s)) => Some(*s),
(None, None) => None,
};
QualType::new(Array {
elem: Box::new(composite_elem),
size: composite_size,
})
}
(
Function {
ret: a_ret,
params: a_p,
is_vararg: a_v,
},
Function {
ret: b_ret,
params: b_p,
is_vararg: b_v,
},
) => {
let composite_ret = if **a_ret != **b_ret {
self.composite_type_inner(&a_ret.base, &b_ret.base)
} else {
(**a_ret).clone()
};
let max_params = a_p.len().max(b_p.len());
let mut composite_params: Vec<QualType> = Vec::with_capacity(max_params);
for i in 0..max_params {
match (a_p.get(i), b_p.get(i)) {
(Some(pa), Some(pb)) => {
if pa != pb {
composite_params
.push(self.composite_type_inner(&pa.base, &pb.base));
} else {
composite_params.push(pa.clone());
}
}
(Some(p), None) | (None, Some(p)) => composite_params.push(p.clone()),
(None, None) => unreachable!(),
}
}
QualType::new(Function {
ret: Box::new(composite_ret),
params: composite_params,
is_vararg: *a_v || *b_v,
})
}
_ => {
QualType::new(a.clone())
}
}
}
pub fn canonical_type(&self, qt: &QualType) -> QualType {
match *qt.base {
TypeNode::Typedef { ref underlying, .. } => self.canonical_type(underlying),
TypeNode::Pointer(ref inner) => {
let canonical_inner = self.canonical_type(inner);
QualType {
base: Box::new(TypeNode::Pointer(Box::new(canonical_inner))),
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
}
}
_ => qt.clone(),
}
}
pub fn base_element_type(&self, qt: &QualType) -> QualType {
match *qt.base {
TypeNode::Pointer(ref inner) => self.base_element_type(inner),
TypeNode::Array { ref elem, .. } => self.base_element_type(elem),
_ => qt.clone(),
}
}
pub fn type_to_string(&self, qt: &QualType) -> String {
format!("{}", qt)
}
pub fn describe_type(&self, qt: &QualType) -> String {
let mut desc = String::new();
if qt.is_const {
desc.push_str("const ");
}
if qt.is_volatile {
desc.push_str("volatile ");
}
if qt.is_restrict {
desc.push_str("restrict ");
}
let class = self.type_class(qt);
desc.push_str(match class {
TypeClass::Void => "void",
TypeClass::Bool => "_Bool",
TypeClass::Char => "char",
TypeClass::SChar => "signed char",
TypeClass::UChar => "unsigned char",
TypeClass::Short => "short int",
TypeClass::UShort => "unsigned short int",
TypeClass::Int => "int",
TypeClass::UInt => "unsigned int",
TypeClass::Long => "long int",
TypeClass::ULong => "unsigned long int",
TypeClass::LongLong => "long long int",
TypeClass::ULongLong => "unsigned long long int",
TypeClass::Float => "float",
TypeClass::Double => "double",
TypeClass::LongDouble => "long double",
TypeClass::Pointer => "pointer",
TypeClass::Array => "array",
TypeClass::Function => "function",
TypeClass::Struct => "struct",
TypeClass::Union => "union",
TypeClass::Enum => "enum",
TypeClass::Typedef => "typedef",
TypeClass::Complex => "_Complex",
TypeClass::Auto => "auto",
TypeClass::Record => "record",
});
desc
}
pub fn needs_lvalue_conversion(qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Array { .. } | TypeNode::Function { .. })
}
pub fn is_valid_parameter_type(&self, qt: &QualType) -> bool {
if self.is_void(qt) {
return true; }
true
}
pub fn is_complete_type(&self, qt: &QualType) -> bool {
match &*qt.base {
TypeNode::Void => false,
TypeNode::Array { size: None, .. } => false,
TypeNode::Struct { fields, .. } if fields.is_empty() => false, TypeNode::Enum { name: None, .. } => true, _ => true,
}
}
pub fn common_type(&self, types: &[QualType]) -> Option<QualType> {
if types.is_empty() {
return None;
}
let mut common = types[0].clone();
for ty in &types[1..] {
common = self.usual_arithmetic_conversions(&common, ty);
}
Some(common)
}
pub fn is_object_type(&self, qt: &QualType) -> bool {
!self.is_void(qt) && !self.is_function(qt)
}
pub fn is_character_type(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Char | TypeNode::SChar | TypeNode::UChar)
}
pub fn to_signed(&self, qt: &QualType) -> QualType {
match *qt.base {
TypeNode::UChar => QualType::schar(),
TypeNode::UShort => QualType::short(),
TypeNode::UInt => QualType::int(),
TypeNode::ULong => QualType::long(),
TypeNode::ULongLong => QualType::longlong(),
_ => qt.clone(),
}
}
pub fn to_unsigned(&self, qt: &QualType) -> QualType {
match *qt.base {
TypeNode::Char => QualType::uchar(),
TypeNode::SChar => QualType::uchar(),
TypeNode::Short => QualType::ushort(),
TypeNode::Int => QualType::uint(),
TypeNode::Long => QualType::ulong(),
TypeNode::LongLong => QualType::ulonglong(),
_ => qt.clone(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeClass {
Void,
Bool,
Char,
SChar,
UChar,
Short,
UShort,
Int,
UInt,
Long,
ULong,
LongLong,
ULongLong,
Float,
Double,
LongDouble,
Pointer,
Array,
Function,
Struct,
Union,
Enum,
Typedef,
Complex,
Auto,
Record,
}
impl TypeClass {
pub fn from_type_node(tn: &TypeNode) -> Self {
match tn {
TypeNode::Void => TypeClass::Void,
TypeNode::Char => TypeClass::Char,
TypeNode::SChar => TypeClass::SChar,
TypeNode::UChar => TypeClass::UChar,
TypeNode::Short => TypeClass::Short,
TypeNode::UShort => TypeClass::UShort,
TypeNode::Int => TypeClass::Int,
TypeNode::UInt => TypeClass::UInt,
TypeNode::Long => TypeClass::Long,
TypeNode::ULong => TypeClass::ULong,
TypeNode::LongLong => TypeClass::LongLong,
TypeNode::ULongLong => TypeClass::ULongLong,
TypeNode::Float => TypeClass::Float,
TypeNode::Double => TypeClass::Double,
TypeNode::LongDouble => TypeClass::LongDouble,
TypeNode::Bool => TypeClass::Bool,
TypeNode::Complex => TypeClass::Complex,
TypeNode::Auto => TypeClass::Auto,
TypeNode::Record(_) => TypeClass::Record,
TypeNode::Pointer(_) => TypeClass::Pointer,
TypeNode::Array { .. } => TypeClass::Array,
TypeNode::Function { .. } => TypeClass::Function,
TypeNode::Struct { is_union: true, .. } => TypeClass::Union,
TypeNode::Struct { .. } => TypeClass::Struct,
TypeNode::Enum { .. } => TypeClass::Enum,
TypeNode::Typedef { .. } => TypeClass::Typedef,
}
}
pub fn is_integer(self) -> bool {
matches!(
self,
TypeClass::Bool
| TypeClass::Char
| TypeClass::SChar
| TypeClass::UChar
| TypeClass::Short
| TypeClass::UShort
| TypeClass::Int
| TypeClass::UInt
| TypeClass::Long
| TypeClass::ULong
| TypeClass::LongLong
| TypeClass::ULongLong
| TypeClass::Enum
)
}
pub fn is_floating(self) -> bool {
matches!(
self,
TypeClass::Float | TypeClass::Double | TypeClass::LongDouble
)
}
pub fn is_arithmetic(self) -> bool {
self.is_integer() || self.is_floating()
}
pub fn is_unsigned(self) -> bool {
matches!(
self,
TypeClass::UChar
| TypeClass::UShort
| TypeClass::UInt
| TypeClass::ULong
| TypeClass::ULongLong
)
}
pub fn size_bytes(self) -> usize {
match self {
TypeClass::Char | TypeClass::SChar | TypeClass::UChar | TypeClass::Bool => 1,
TypeClass::Short | TypeClass::UShort => 2,
TypeClass::Int | TypeClass::UInt | TypeClass::Float | TypeClass::Enum => 4,
TypeClass::Long
| TypeClass::ULong
| TypeClass::LongLong
| TypeClass::ULongLong
| TypeClass::Double
| TypeClass::Pointer => 8,
TypeClass::LongDouble => 16,
_ => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompatibilityResult {
pub compatible: bool,
pub reason: Option<String>,
pub conversion_needed: Option<ConversionKind>,
}
impl CompatibilityResult {
pub fn yes() -> Self {
Self {
compatible: true,
reason: None,
conversion_needed: Some(ConversionKind::Identity),
}
}
pub fn no(reason: &str) -> Self {
Self {
compatible: false,
reason: Some(reason.to_string()),
conversion_needed: None,
}
}
pub fn with_conversion(kind: ConversionKind) -> Self {
Self {
compatible: true,
reason: None,
conversion_needed: Some(kind),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExprTypeResult {
pub ty: QualType,
pub is_lvalue: bool,
pub is_bitfield: bool,
pub implicit_conversions: Vec<ConversionKind>,
}
impl ExprTypeResult {
pub fn new(ty: QualType, is_lvalue: bool) -> Self {
Self {
ty,
is_lvalue,
is_bitfield: false,
implicit_conversions: Vec::new(),
}
}
pub fn rvalue(ty: QualType) -> Self {
Self {
ty,
is_lvalue: false,
is_bitfield: false,
implicit_conversions: vec![ConversionKind::LvalueToRvalue],
}
}
}
impl TypeSystem {
pub fn type_class(&self, qt: &QualType) -> TypeClass {
TypeClass::from_type_node(&qt.base)
}
pub fn canonical_type_class(&self, qt: &QualType) -> TypeClass {
let canonical = self.canonical_type(qt);
TypeClass::from_type_node(&canonical.base)
}
pub fn strip_qualifiers(qt: &QualType) -> QualType {
let mut result = qt.clone();
result.is_const = false;
result.is_volatile = false;
result.is_restrict = false;
result
}
pub fn check_qualifier_compatibility(&self, from: &CVQualifiers, to: &CVQualifiers) -> bool {
to.is_superset_of(from)
}
pub fn add_inner_const(qt: &QualType) -> QualType {
match *qt.base {
TypeNode::Pointer(ref inner) => {
let mut new_inner = (**inner).clone();
new_inner.is_const = true;
QualType {
base: Box::new(TypeNode::Pointer(Box::new(new_inner))),
..qt.clone()
}
}
_ => {
let mut result = qt.clone();
result.is_const = true;
result
}
}
}
pub fn is_assignable(&self, from: &QualType, to: &QualType) -> bool {
let from_cv = CVQualifiers::from_qual_type(from);
let to_cv = CVQualifiers::from_qual_type(to);
if !to_cv.is_superset_of(&from_cv) {
return false;
}
let from_stripped = Self::strip_qualifiers(from);
let to_stripped = Self::strip_qualifiers(to);
if self.are_compatible(&from_stripped, &to_stripped) {
return true;
}
if self.is_pointer(&from_stripped) && self.is_pointer(&to_stripped) {
let from_elem = self.base_element_type(&from_stripped);
let to_elem = self.base_element_type(&to_stripped);
if self.is_void(&from_elem) || self.is_void(&to_elem) {
return true;
}
}
if (self.is_integer(&from_stripped) && self.is_pointer(&to_stripped))
|| (self.is_pointer(&from_stripped) && self.is_integer(&to_stripped))
{
return true;
}
false
}
pub fn type_alignment(&self, qt: &QualType) -> usize {
match *qt.base {
TypeNode::Char | TypeNode::SChar | TypeNode::UChar | TypeNode::Bool => 1,
TypeNode::Short | TypeNode::UShort => 2,
TypeNode::Int | TypeNode::UInt | TypeNode::Float | TypeNode::Enum { .. } => 4,
TypeNode::Long
| TypeNode::ULong
| TypeNode::LongLong
| TypeNode::ULongLong
| TypeNode::Double
| TypeNode::Pointer(_) => 8,
TypeNode::LongDouble => 16,
TypeNode::Struct { .. } => {
8
}
TypeNode::Array { ref elem, .. } => self.type_alignment(elem),
_ => 1,
}
}
pub fn deep_clone_type(qt: &QualType) -> QualType {
match *qt.base {
TypeNode::Pointer(ref inner) => QualType {
base: Box::new(TypeNode::Pointer(Box::new(Self::deep_clone_type(inner)))),
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
},
TypeNode::Array { ref elem, size } => QualType {
base: Box::new(TypeNode::Array {
elem: Box::new(Self::deep_clone_type(elem)),
size,
}),
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
},
TypeNode::Function {
ref ret,
ref params,
is_vararg,
} => QualType {
base: Box::new(TypeNode::Function {
ret: Box::new(Self::deep_clone_type(ret)),
params: params.iter().map(|p| Self::deep_clone_type(p)).collect(),
is_vararg,
}),
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
},
_ => qt.clone(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ScopeKind {
#[default]
File,
FunctionPrototype,
Block,
Function,
Namespace,
Class,
TemplateParam,
}
#[derive(Debug, Clone)]
pub enum ScopeEntry {
Variable(VarDecl),
Function(FunctionDecl),
Typedef(TypedefDecl),
StructTag(StructDecl),
EnumTag(EnumDecl),
EnumConstant { name: String, value: Option<i64> },
Label { name: String, defined: bool },
}
impl ScopeEntry {
pub fn name(&self) -> &str {
match self {
ScopeEntry::Variable(v) => &v.name,
ScopeEntry::Function(f) => &f.name,
ScopeEntry::Typedef(t) => &t.name,
ScopeEntry::StructTag(s) => s.name.as_deref().unwrap_or("<anon>"),
ScopeEntry::EnumTag(e) => e.name.as_deref().unwrap_or("<anon>"),
ScopeEntry::EnumConstant { name, .. } => name,
ScopeEntry::Label { name, .. } => name,
}
}
pub fn as_variable(&self) -> Option<&VarDecl> {
match self {
ScopeEntry::Variable(v) => Some(v),
_ => None,
}
}
pub fn as_function(&self) -> Option<&FunctionDecl> {
match self {
ScopeEntry::Function(f) => Some(f),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Scope {
pub kind: ScopeKind,
entries: HashMap<String, Vec<ScopeEntry>>,
labels: HashMap<String, bool>,
pub has_returned: bool,
pub is_closed: bool,
}
impl Scope {
pub fn new(kind: ScopeKind) -> Self {
Self {
kind,
entries: HashMap::new(),
labels: HashMap::new(),
has_returned: false,
is_closed: false,
}
}
pub fn insert(&mut self, entry: ScopeEntry) {
let name = entry.name().to_string();
self.entries
.entry(name)
.or_insert_with(Vec::new)
.push(entry);
}
pub fn lookup_local(&self, name: &str) -> Vec<&ScopeEntry> {
self.entries
.get(name)
.map(|v| v.iter().collect())
.unwrap_or_default()
}
pub fn define_label(&mut self, name: &str) {
self.labels.insert(name.to_string(), true);
}
pub fn has_label(&self, name: &str) -> bool {
self.labels.get(name).copied().unwrap_or(false)
}
pub fn mark_returned(&mut self) {
self.has_returned = true;
}
}
pub struct ScopeManager {
scopes: Vec<Scope>,
file_scope_entries: HashMap<String, Vec<ScopeEntry>>,
namespaces: HashMap<String, HashMap<String, ScopeEntry>>,
current_function_return: Option<QualType>,
current_function_name: Option<String>,
loop_depth: u32,
switch_depth: u32,
}
impl ScopeManager {
pub fn new() -> Self {
Self {
scopes: Vec::new(),
file_scope_entries: HashMap::new(),
namespaces: HashMap::new(),
current_function_return: None,
current_function_name: None,
loop_depth: 0,
switch_depth: 0,
}
}
pub fn enter_scope(&mut self, kind: ScopeKind) {
self.scopes.push(Scope::new(kind));
}
pub fn leave_scope(&mut self) -> Option<Scope> {
self.scopes.pop()
}
pub fn current_scope(&self) -> Option<&Scope> {
self.scopes.last()
}
pub fn current_scope_mut(&mut self) -> Option<&mut Scope> {
self.scopes.last_mut()
}
pub fn file_scope(&self) -> Option<&Scope> {
self.scopes.first()
}
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
pub fn declare(&mut self, entry: ScopeEntry) {
let name = entry.name().to_string();
if let Some(scope) = self.scopes.last_mut() {
scope.insert(entry.clone());
}
if self.scopes.len() == 1 {
self.file_scope_entries
.entry(name)
.or_insert_with(Vec::new)
.push(entry);
}
}
pub fn declare_variable(&mut self, var: VarDecl) {
self.declare(ScopeEntry::Variable(var));
}
pub fn declare_function(&mut self, func: FunctionDecl) {
self.declare(ScopeEntry::Function(func));
}
pub fn declare_typedef(&mut self, td: TypedefDecl) {
self.declare(ScopeEntry::Typedef(td));
}
pub fn declare_struct_tag(&mut self, sd: StructDecl) {
self.declare(ScopeEntry::StructTag(sd));
}
pub fn declare_enum_tag(&mut self, ed: EnumDecl) {
self.declare(ScopeEntry::EnumTag(ed));
}
pub fn declare_enum_constant(&mut self, name: &str, value: Option<i64>) {
self.declare(ScopeEntry::EnumConstant {
name: name.to_string(),
value,
});
}
pub fn define_label(&mut self, name: &str) {
if let Some(scope) = self.scopes.last_mut() {
scope.define_label(name);
}
self.declare(ScopeEntry::Label {
name: name.to_string(),
defined: true,
});
}
pub fn lookup(&self, name: &str) -> Vec<&ScopeEntry> {
for scope in self.scopes.iter().rev() {
let results = scope.lookup_local(name);
if !results.is_empty() {
return results;
}
}
Vec::new()
}
pub fn lookup_file_scope(&self, name: &str) -> Vec<&ScopeEntry> {
self.file_scope_entries
.get(name)
.map(|v| v.iter().collect())
.unwrap_or_default()
}
pub fn qualified_lookup(&self, namespace: &str, name: &str) -> Option<&ScopeEntry> {
self.namespaces.get(namespace).and_then(|ns| ns.get(name))
}
pub fn lookup_variable(&self, name: &str) -> Option<&VarDecl> {
for entry in self.lookup(name) {
if let Some(v) = entry.as_variable() {
return Some(v);
}
}
None
}
pub fn lookup_function(&self, name: &str) -> Option<&FunctionDecl> {
for entry in self.lookup(name) {
if let Some(f) = entry.as_function() {
return Some(f);
}
}
None
}
pub fn lookup_typedef(&self, name: &str) -> Option<QualType> {
for entry in self.lookup(name) {
if let ScopeEntry::Typedef(td) = entry {
return Some(td.underlying.clone());
}
}
None
}
pub fn adl_lookup(&self, _name: &str, _argument_types: &[QualType]) -> Vec<&ScopeEntry> {
Vec::new()
}
pub fn enter_function(&mut self, name: &str, ret_ty: QualType) {
self.current_function_name = Some(name.to_string());
self.current_function_return = Some(ret_ty);
}
pub fn leave_function(&mut self) {
self.current_function_name = None;
self.current_function_return = None;
}
pub fn current_return_type(&self) -> Option<&QualType> {
self.current_function_return.as_ref()
}
pub fn current_function_name(&self) -> Option<&str> {
self.current_function_name.as_deref()
}
pub fn enter_loop(&mut self) {
self.loop_depth += 1;
}
pub fn leave_loop(&mut self) {
if self.loop_depth > 0 {
self.loop_depth -= 1;
}
}
pub fn in_loop(&self) -> bool {
self.loop_depth > 0
}
pub fn enter_switch(&mut self) {
self.switch_depth += 1;
}
pub fn leave_switch(&mut self) {
if self.switch_depth > 0 {
self.switch_depth -= 1;
}
}
pub fn in_switch(&self) -> bool {
self.switch_depth > 0
}
pub fn find_label(&self, name: &str) -> bool {
for scope in self.scopes.iter().rev() {
if scope.has_label(name) {
return true;
}
if scope.kind == ScopeKind::Function {
break;
}
}
false
}
pub fn collect_undefined_gotos(&self) -> Vec<String> {
Vec::new()
}
pub fn register_namespace(&mut self, name: &str) {
self.namespaces
.entry(name.to_string())
.or_insert_with(HashMap::new);
}
pub fn declare_in_namespace(&mut self, namespace: &str, entry: ScopeEntry) {
let ns = self
.namespaces
.entry(namespace.to_string())
.or_insert_with(HashMap::new);
ns.insert(entry.name().to_string(), entry);
}
pub fn lookup_in_namespace(&self, namespace: &str, name: &str) -> Option<&ScopeEntry> {
self.namespaces.get(namespace).and_then(|ns| ns.get(name))
}
pub fn dump_scopes(&self) -> String {
let mut out = String::from("=== Scope Dump ===\n");
for (i, scope) in self.scopes.iter().enumerate() {
out.push_str(&format!(
" [{}] {:?} (labels: {}, has_returned: {})\n",
i,
scope.kind,
scope.labels.len(),
scope.has_returned
));
}
out.push_str(&format!(
" loop_depth: {}, switch_depth: {}\n",
self.loop_depth, self.switch_depth
));
out.push_str(&format!(
" namespaces: {:?}\n",
self.namespaces.keys().collect::<Vec<_>>()
));
out
}
pub fn nearest_scope_of_kind(&self, kind: ScopeKind) -> Option<&Scope> {
self.scopes.iter().rev().find(|s| s.kind == kind)
}
pub fn in_scope_kind(&self, kind: ScopeKind) -> bool {
self.scopes.iter().any(|s| s.kind == kind)
}
pub fn in_function(&self) -> bool {
self.in_scope_kind(ScopeKind::Function)
}
pub fn at_file_scope(&self) -> bool {
!self.in_function()
}
}
pub struct DeclChecker {
errors: Vec<String>,
warnings: Vec<String>,
type_system: TypeSystem,
standard: CLangStandard,
tentative_defs: HashMap<String, (QualType, bool)>,
known_decls: HashSet<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StorageClass {
None,
Auto,
Register,
Static,
Extern,
Typedef,
ThreadLocal,
}
impl DeclChecker {
pub fn new(standard: CLangStandard) -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
type_system: TypeSystem::new(standard),
standard,
tentative_defs: HashMap::new(),
known_decls: HashSet::new(),
}
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn take_errors(&mut self) -> Vec<String> {
std::mem::take(&mut self.errors)
}
pub fn take_warnings(&mut self) -> Vec<String> {
std::mem::take(&mut self.warnings)
}
pub fn type_system(&self) -> &TypeSystem {
&self.type_system
}
pub fn type_system_mut(&mut self) -> &mut TypeSystem {
&mut self.type_system
}
pub fn check_var_decl(&mut self, var: &VarDecl) -> Result<(), Vec<String>> {
if self.type_system.is_void(&var.ty) {
self.errors.push(format!(
"variable '{}' has incomplete type 'void'",
var.name
));
}
self.check_storage_class(var);
if self.known_decls.contains(&var.name) && var.is_global {
self.warnings
.push(format!("redeclaration of '{}' at file scope", var.name));
}
self.known_decls.insert(var.name.clone());
if var.is_global && var.init.is_none() && !var.is_extern {
self.tentative_defs
.insert(var.name.clone(), (var.ty.clone(), false));
}
if var.is_global && var.init.is_some() {
self.tentative_defs
.entry(var.name.clone())
.and_modify(|(_, has_init)| *has_init = true)
.or_insert((var.ty.clone(), true));
}
if let Some(ref init) = var.init {
}
Ok(())
}
fn check_storage_class(&mut self, var: &VarDecl) {
if var.is_extern && var.is_static {
self.errors.push(format!(
"variable '{}' declared both 'extern' and 'static'",
var.name
));
}
if var.is_extern && var.init.is_some() && var.is_global {
if matches!(self.standard, CLangStandard::C11 | CLangStandard::C17) {
self.warnings.push(format!(
"'extern' variable '{}' has an initializer",
var.name
));
}
}
}
pub fn check_function_decl(&mut self, func: &FunctionDecl) -> Result<(), Vec<String>> {
if self.type_system.is_array(&func.ret_ty) {
self.errors.push(format!(
"function '{}' cannot return array type '{}'",
func.name, func.ret_ty
));
}
if self.type_system.is_function(&func.ret_ty) {
self.warnings.push(format!(
"function '{}' returns function type (decays to pointer)",
func.name
));
}
for param in &func.params {
if self.type_system.is_void(¶m.ty) {
if func.params.len() != 1 {
self.errors.push(format!(
"'void' must be the only parameter in function '{}'",
func.name
));
}
}
if self.type_system.is_array(¶m.ty) {
self.warnings.push(format!(
"parameter '{}' of function '{}' has array type (decays to pointer)",
param.name, func.name
));
}
if self.type_system.is_function(¶m.ty) {
self.warnings.push(format!(
"parameter '{}' of function '{}' has function type (decays to pointer)",
param.name, func.name
));
}
}
if func.is_noreturn && !self.type_system.is_void(&func.ret_ty) {
self.warnings.push(format!(
"_Noreturn function '{}' should return void",
func.name
));
}
if self.known_decls.contains(&func.name) {
self.warnings
.push(format!("redeclaration of function '{}'", func.name));
}
self.known_decls.insert(func.name.clone());
Ok(())
}
pub fn check_call_args(
&mut self,
func_name: &str,
params: &[VarDecl],
args: &[QualType],
is_vararg: bool,
) -> Result<(), Vec<String>> {
if is_vararg {
if args.len() < params.len() {
self.errors.push(format!(
"too few arguments to function '{}': expected at least {}, got {}",
func_name,
params.len(),
args.len()
));
return Err(self.errors.clone());
}
} else if args.len() != params.len() {
self.errors.push(format!(
"function '{}' expects {} arguments, got {}",
func_name,
params.len(),
args.len()
));
return Err(self.errors.clone());
}
for (i, (param, arg_ty)) in params.iter().zip(args.iter()).enumerate() {
let param_ty = self.type_system.canonical_type(¶m.ty);
if !self.type_system.are_compatible(¶m_ty, arg_ty) {
self.warnings.push(format!(
"incompatible type for argument {} in call to '{}': expected '{}', got '{}'",
i + 1,
func_name,
param.ty,
arg_ty
));
}
}
Ok(())
}
pub fn compute_linkage(&self, var: &VarDecl) -> Linkage {
if var.is_static {
Linkage::Internal
} else if var.is_extern || var.is_global {
Linkage::External
} else {
Linkage::None
}
}
pub fn finalize_tentative_defs(&self) -> Vec<(String, QualType)> {
self.tentative_defs
.iter()
.filter(|(_, (_, has_init))| !has_init)
.map(|(name, (ty, _))| (name.clone(), ty.clone()))
.collect()
}
pub fn has_tentative_def(&self, name: &str) -> bool {
self.tentative_defs.contains_key(name)
}
pub fn check_struct_decl(&mut self, sd: &StructDecl) -> Result<(), Vec<String>> {
let mut field_names = HashSet::new();
for field in &sd.fields {
if !field_names.insert(&field.name) {
self.errors.push(format!(
"duplicate member '{}' in {}",
field.name,
if sd.is_union { "union" } else { "struct" }
));
}
if self.type_system.is_void(&field.ty) {
self.errors.push(format!(
"field '{}' declared with incomplete type 'void'",
field.name
));
}
}
if !field_names.is_empty() && sd.name.is_some() {
self.type_system.register_struct(sd);
}
Ok(())
}
pub fn check_enum_decl(&mut self, ed: &EnumDecl) -> Result<(), Vec<String>> {
let mut variant_names = HashSet::new();
for variant in &ed.variants {
if !variant_names.insert(&variant.name) {
self.errors
.push(format!("duplicate enumerator '{}' in enum", variant.name));
}
}
if ed.name.is_some() {
self.type_system.register_enum(ed);
}
Ok(())
}
pub fn check_typedef(&mut self, td: &TypedefDecl) -> Result<(), Vec<String>> {
self.type_system
.register_typedef(&td.name, td.underlying.clone());
Ok(())
}
pub fn infer_storage_class(var: &VarDecl) -> StorageClass {
if var.is_static {
StorageClass::Static
} else if var.is_extern {
StorageClass::Extern
} else if var.is_global {
StorageClass::None
} else {
StorageClass::Auto
}
}
pub fn check_storage_class_context(
&mut self,
sc: StorageClass,
at_file_scope: bool,
is_function_param: bool,
) -> Result<(), Vec<String>> {
match sc {
StorageClass::Auto | StorageClass::Register => {
if at_file_scope {
self.errors.push(format!(
"'{:?}' storage class not allowed at file scope",
sc
));
return Err(self.errors.clone());
}
}
StorageClass::Static => {
if is_function_param {
self.errors
.push("'static' not allowed on function parameters".to_string());
return Err(self.errors.clone());
}
}
StorageClass::Extern => {
if is_function_param {
self.errors
.push("'extern' not allowed on function parameters".to_string());
return Err(self.errors.clone());
}
}
_ => {}
}
Ok(())
}
pub fn check_parameter(
&mut self,
param: &VarDecl,
param_index: usize,
) -> Result<(), Vec<String>> {
let sc = Self::infer_storage_class(param);
let _ = self.check_storage_class_context(sc, false, true);
if self.type_system.is_void(¶m.ty) {
if param_index != 0 {
self.errors
.push("'void' must be the only parameter if used".to_string());
return Err(self.errors.clone());
}
}
if self.type_system.is_array(¶m.ty) {
self.warnings.push(format!(
"parameter '{}' has array type — decays to pointer",
param.name
));
}
Ok(())
}
pub fn check_redeclaration(
&mut self,
existing: &VarDecl,
new: &VarDecl,
) -> Result<(), Vec<String>> {
if !self.type_system.are_compatible(&existing.ty, &new.ty) {
self.errors.push(format!(
"redeclaration of '{}' with incompatible type: '{}' vs '{}'",
existing.name, existing.ty, new.ty
));
return Err(self.errors.clone());
}
let old_linkage = self.compute_linkage(existing);
let new_linkage = self.compute_linkage(new);
if old_linkage != new_linkage
&& old_linkage != Linkage::None
&& new_linkage != Linkage::None
{
self.errors.push(format!(
"redeclaration of '{}' with different linkage",
existing.name
));
return Err(self.errors.clone());
}
Ok(())
}
pub fn tentative_def_count(&self) -> usize {
self.tentative_defs.len()
}
pub fn is_tentative(&self, name: &str) -> bool {
self.tentative_defs
.get(name)
.map(|(_, has_init)| !has_init)
.unwrap_or(false)
}
}
pub struct ExprChecker {
type_system: TypeSystem,
scope_manager: ScopeManager,
errors: Vec<String>,
warnings: Vec<String>,
standard: CLangStandard,
pending_gotos: Vec<String>,
}
impl ExprChecker {
pub fn new(standard: CLangStandard) -> Self {
Self {
type_system: TypeSystem::new(standard),
scope_manager: ScopeManager::new(),
errors: Vec::new(),
warnings: Vec::new(),
standard,
pending_gotos: Vec::new(),
}
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn take_errors(&mut self) -> Vec<String> {
std::mem::take(&mut self.errors)
}
pub fn take_warnings(&mut self) -> Vec<String> {
std::mem::take(&mut self.warnings)
}
pub fn type_system(&self) -> &TypeSystem {
&self.type_system
}
pub fn type_system_mut(&mut self) -> &mut TypeSystem {
&mut self.type_system
}
pub fn scope_manager(&self) -> &ScopeManager {
&self.scope_manager
}
pub fn scope_manager_mut(&mut self) -> &mut ScopeManager {
&mut self.scope_manager
}
pub fn expr_type(&mut self, expr: &Expr) -> Option<QualType> {
match expr {
Expr::IntLiteral(_) => Some(QualType::int()),
Expr::UIntLiteral(_, is_ll) => {
if *is_ll {
Some(QualType::ulonglong())
} else {
Some(QualType::uint())
}
}
Expr::FloatLiteral(_) => Some(QualType::double()), Expr::DoubleLiteral(_) => Some(QualType::double()),
Expr::CharLiteral(_) => Some(QualType::int()), Expr::StringLiteral(_) => Some(QualType::const_char_ptr()),
Expr::Ident(name) => self.lookup_type(name),
Expr::Unary(op, inner) => self.unary_expr_type(*op, inner),
Expr::SizeOf(_) => Some(QualType::ulong()), Expr::SizeOfType(_) => Some(QualType::ulong()),
Expr::AlignOf(_) => Some(QualType::ulong()),
Expr::AlignOfType(_) => Some(QualType::ulong()),
Expr::Cast(ty, _) => Some(ty.clone()),
Expr::Binary(op, lhs, rhs) => self.binary_expr_type(*op, lhs, rhs),
Expr::Assign(op, lhs, rhs) => self.assign_expr_type(*op, lhs, rhs),
Expr::Conditional(cond, then, els) => self.conditional_type(cond, then, els),
Expr::Call { callee, args } => self.call_type(callee, args),
Expr::Subscript { base, index } => self.subscript_type(base, index),
Expr::Member {
base,
field,
is_arrow,
} => self.member_type(base, field, *is_arrow),
Expr::PostInc(inner)
| Expr::PostDec(inner)
| Expr::PreInc(inner)
| Expr::PreDec(inner) => {
let ty = self.expr_type(inner)?;
if !self.type_system.is_scalar(&ty) {
self.errors.push(format!(
"operand of increment/decrement must be a scalar type, got '{}'",
ty
));
return None;
}
Some(ty)
}
Expr::CompoundLiteral(ty, _) => Some(ty.clone()),
Expr::AggregateLiteral(vals) => {
for v in vals {
ExprChecker::expr_type(v);
}
None
}
}
}
pub fn is_lvalue(&self, expr: &Expr) -> bool {
match expr {
Expr::Ident(_) => true,
Expr::Subscript { .. } => true,
Expr::Member { .. } => true,
Expr::Unary(UnaryOp::Deref, _) => true,
Expr::StringLiteral(_) => true,
Expr::CompoundLiteral(_, _) => true,
_ => false,
}
}
pub fn is_rvalue(&self, expr: &Expr) -> bool {
!self.is_lvalue(expr)
}
pub fn lvalue_to_rvalue(&self, qt: &QualType) -> QualType {
let mut result = qt.clone();
result.is_const = false;
result.is_volatile = false;
result.is_restrict = false;
result
}
pub fn array_to_pointer(&self, qt: &QualType) -> QualType {
if let TypeNode::Array { ref elem, .. } = *qt.base {
QualType {
base: Box::new(TypeNode::Pointer(elem.clone())),
is_const: qt.is_const,
is_volatile: qt.is_volatile,
is_restrict: qt.is_restrict,
}
} else {
qt.clone()
}
}
pub fn function_to_pointer(&self, qt: &QualType) -> QualType {
if matches!(*qt.base, TypeNode::Function { .. }) {
QualType::pointer_to(qt.clone())
} else {
qt.clone()
}
}
pub fn adjust_expr_type(&self, expr: &Expr, qt: &QualType) -> QualType {
let mut result = qt.clone();
if self.is_lvalue(expr) {
result = self.lvalue_to_rvalue(&result);
}
result = self.array_to_pointer(&result);
result = self.function_to_pointer(&result);
result
}
pub fn check_implicit_conversion(
&mut self,
from: &QualType,
to: &QualType,
) -> Result<ConversionKind, String> {
if self.type_system.are_compatible(from, to) {
return Ok(ConversionKind::Identity);
}
if self.type_system.is_integer(from) && self.type_system.is_integer(to) {
let promoted = self.type_system.integer_promotion(from);
if self.type_system.are_compatible(&promoted, to) {
return Ok(ConversionKind::IntegerPromotion);
}
}
if matches!(*from.base, TypeNode::Float) && matches!(*to.base, TypeNode::Double) {
return Ok(ConversionKind::FloatingPromotion);
}
if self.type_system.is_integer(from) && self.type_system.is_floating(to) {
return Ok(ConversionKind::IntegralToFloating);
}
if self.type_system.is_floating(from) && self.type_system.is_integer(to) {
return Ok(ConversionKind::FloatingToIntegral);
}
if self.type_system.is_pointer(from) && self.type_system.is_pointer(to) {
let from_inner = self.type_system.base_element_type(from);
let to_inner = self.type_system.base_element_type(to);
if self.type_system.is_void(&from_inner) || self.type_system.is_void(&to_inner) {
return Ok(ConversionKind::PointerConversion);
}
if self.type_system.are_compatible(&from_inner, &to_inner) {
return Ok(ConversionKind::PointerConversion);
}
}
if self.type_system.is_integer(from) && self.type_system.is_pointer(to) {
self.warnings.push(format!(
"implicit conversion from integer to pointer '{}'",
to
));
return Ok(ConversionKind::PointerInteger);
}
if self.type_system.is_pointer(from) && self.type_system.is_integer(to) {
self.warnings.push(format!(
"implicit conversion from pointer to integer '{}'",
to
));
return Ok(ConversionKind::PointerInteger);
}
if self.type_system.is_bool(to) && self.type_system.is_scalar(from) {
return Ok(ConversionKind::BooleanConversion);
}
Err(format!("cannot implicitly convert '{}' to '{}'", from, to))
}
pub fn check_explicit_cast(&mut self, target: &QualType, source: &QualType) -> bool {
if self.type_system.is_scalar(target) && self.type_system.is_scalar(source) {
return true;
}
if self.type_system.is_pointer(target) && self.type_system.is_pointer(source) {
return true;
}
if (self.type_system.is_integer(target) && self.type_system.is_pointer(source))
|| (self.type_system.is_pointer(target) && self.type_system.is_integer(source))
{
return true;
}
if self.type_system.is_void(target) {
return true;
}
self.errors
.push(format!("invalid cast from '{}' to '{}'", source, target));
false
}
fn lookup_type(&self, name: &str) -> Option<QualType> {
for entry in self.scope_manager.lookup(name) {
match entry {
ScopeEntry::Variable(v) => return Some(v.ty.clone()),
ScopeEntry::Function(f) => {
return Some(QualType::new(TypeNode::Function {
ret: Box::new(f.ret_ty.clone()),
params: f.params.iter().map(|p| p.ty.clone()).collect(),
is_vararg: f.is_vararg,
}))
}
ScopeEntry::EnumConstant { .. } => return Some(QualType::int()),
_ => {}
}
}
self.type_system.lookup_typedef(name).cloned()
}
fn unary_expr_type(&mut self, op: UnaryOp, inner: &Expr) -> Option<QualType> {
let inner_ty = self.expr_type(inner)?;
match op {
UnaryOp::Plus | UnaryOp::Minus => {
if !self.type_system.is_arithmetic(&inner_ty) {
self.errors.push(format!(
"unary '{:?}' requires arithmetic type, got '{}'",
op, inner_ty
));
return None;
}
Some(self.type_system.integer_promotion(&inner_ty))
}
UnaryOp::Not => {
if !self.type_system.is_scalar(&inner_ty) {
self.errors.push(format!(
"unary '!' requires scalar type, got '{}'",
inner_ty
));
return None;
}
Some(QualType::int()) }
UnaryOp::BitNot => {
if !self.type_system.is_integer(&inner_ty) {
self.errors.push(format!(
"unary '~' requires integer type, got '{}'",
inner_ty
));
return None;
}
Some(self.type_system.integer_promotion(&inner_ty))
}
UnaryOp::AddrOf => {
if !self.is_lvalue(inner) {
self.errors
.push("cannot take address of rvalue".to_string());
return None;
}
Some(QualType::pointer_to(inner_ty))
}
UnaryOp::Deref => {
if let TypeNode::Pointer(ref pointee) = *inner_ty.base {
let mut result = (**pointee).clone();
result.is_const |= inner_ty.is_const;
result.is_volatile |= inner_ty.is_volatile;
Some(result)
} else {
self.errors
.push(format!("dereference of non-pointer type '{}'", inner_ty));
None
}
}
}
}
fn binary_expr_type(&mut self, op: BinaryOp, lhs: &Expr, rhs: &Expr) -> Option<QualType> {
let lhs_ty = self.expr_type(lhs)?;
let rhs_ty = self.expr_type(rhs)?;
match op {
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => {
if self.type_system.is_arithmetic(&lhs_ty)
&& self.type_system.is_arithmetic(&rhs_ty)
{
Some(
self.type_system
.usual_arithmetic_conversions(&lhs_ty, &rhs_ty),
)
} else if self.type_system.is_pointer(&lhs_ty)
&& self.type_system.is_integer(&rhs_ty)
&& (op == BinaryOp::Add || op == BinaryOp::Sub)
{
Some(lhs_ty.clone())
} else {
self.errors.push(format!(
"invalid operands to binary '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
));
None
}
}
BinaryOp::Mod => {
if self.type_system.is_integer(&lhs_ty) && self.type_system.is_integer(&rhs_ty) {
Some(
self.type_system
.usual_arithmetic_conversions(&lhs_ty, &rhs_ty),
)
} else {
self.errors.push(format!(
"invalid operands to '%': '{}' and '{}'",
lhs_ty, rhs_ty
));
None
}
}
BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => {
if self.type_system.is_integer(&lhs_ty) && self.type_system.is_integer(&rhs_ty) {
Some(
self.type_system
.usual_arithmetic_conversions(&lhs_ty, &rhs_ty),
)
} else {
self.errors.push(format!(
"invalid operands to bitwise '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
));
None
}
}
BinaryOp::Shl | BinaryOp::Shr => {
if self.type_system.is_integer(&lhs_ty) && self.type_system.is_integer(&rhs_ty) {
Some(self.type_system.integer_promotion(&lhs_ty))
} else {
self.errors.push(format!(
"invalid operands to shift: '{}' and '{}'",
lhs_ty, rhs_ty
));
None
}
}
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Gt
| BinaryOp::Le
| BinaryOp::Ge => {
if self.type_system.is_arithmetic(&lhs_ty)
&& self.type_system.is_arithmetic(&rhs_ty)
{
} else if self.type_system.is_pointer(&lhs_ty)
&& self.type_system.is_pointer(&rhs_ty)
{
} else if (self.type_system.is_pointer(&lhs_ty)
&& self.type_system.is_integer(&rhs_ty))
|| (self.type_system.is_integer(&lhs_ty)
&& self.type_system.is_pointer(&rhs_ty))
{
self.warnings
.push(format!("comparison between pointer and integer"));
} else {
self.errors.push(format!(
"invalid operands to comparison '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
));
return None;
}
Some(QualType::int()) }
BinaryOp::LogicAnd | BinaryOp::LogicOr => {
if self.type_system.is_scalar(&lhs_ty) && self.type_system.is_scalar(&rhs_ty) {
Some(QualType::int())
} else {
self.errors.push(format!(
"invalid operands to logical '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
));
None
}
}
BinaryOp::Comma => Some(rhs_ty), _ => {
self.errors
.push(format!("'{:?}' is not a binary operator", op));
None
}
}
}
fn assign_expr_type(&mut self, op: BinaryOp, lhs: &Expr, rhs: &Expr) -> Option<QualType> {
if !self.is_lvalue(lhs) {
self.errors
.push("assignment requires lvalue as left operand".to_string());
return None;
}
let lhs_ty = self.expr_type(lhs)?;
let rhs_ty = self.expr_type(rhs)?;
let base_op = op.without_assign();
match base_op {
Some(inner_op) => {
let _ = self.binary_expr_type(inner_op, lhs, rhs)?;
Some(lhs_ty.clone())
}
None => {
match self.check_implicit_conversion(&rhs_ty, &lhs_ty) {
Ok(_) => Some(lhs_ty.clone()),
Err(msg) => {
self.warnings.push(msg);
Some(lhs_ty.clone())
}
}
}
}
}
fn conditional_type(&mut self, cond: &Expr, then: &Expr, els: &Expr) -> Option<QualType> {
let cond_ty = self.expr_type(cond)?;
if !self.type_system.is_scalar(&cond_ty) {
self.errors.push(format!(
"condition must have scalar type, got '{}'",
cond_ty
));
return None;
}
let then_ty = self.expr_type(then)?;
let els_ty = self.expr_type(els)?;
if self.type_system.are_compatible(&then_ty, &els_ty) {
Some(then_ty)
} else if self.type_system.is_arithmetic(&then_ty)
&& self.type_system.is_arithmetic(&els_ty)
{
Some(
self.type_system
.usual_arithmetic_conversions(&then_ty, &els_ty),
)
} else {
self.type_system.composite_type(&then_ty, &els_ty)
}
}
fn call_type(&mut self, callee: &Expr, args: &[Expr]) -> Option<QualType> {
let callee_ty = self.expr_type(callee)?;
let fn_ty = self.function_to_pointer(&callee_ty);
match *fn_ty.base {
TypeNode::Function {
ref ret,
ref params,
is_vararg,
} => {
let arg_types: Vec<QualType> =
args.iter().filter_map(|a| self.expr_type(a)).collect();
if args.len() != arg_types.len() {
self.errors.push("type error in call arguments".to_string());
return None;
}
let param_vars: Vec<VarDecl> = params
.iter()
.map(|ty| VarDecl::new("<param>", ty.clone()))
.collect();
let callee_name = match callee {
Expr::Ident(name) => name.clone(),
_ => "<function>".to_string(),
};
let mut decl_checker = DeclChecker::new(self.standard);
let _ =
decl_checker.check_call_args(&callee_name, ¶m_vars, &arg_types, is_vararg);
self.errors.append(&mut decl_checker.take_errors());
self.warnings.append(&mut decl_checker.take_warnings());
Some((**ret).clone())
}
_ => {
self.errors.push(format!(
"called object is not a function or function pointer: '{}'",
callee_ty
));
None
}
}
}
fn subscript_type(&mut self, base: &Expr, index: &Expr) -> Option<QualType> {
let base_ty = self.expr_type(base)?;
let _index_ty = self.expr_type(index)?;
match *base_ty.base {
TypeNode::Pointer(ref pointee) => Some((**pointee).clone()),
TypeNode::Array { ref elem, .. } => Some((**elem).clone()),
_ => {
self.errors.push(format!(
"subscript of non-array, non-pointer type '{}'",
base_ty
));
None
}
}
}
fn member_type(&mut self, base: &Expr, field: &str, is_arrow: bool) -> Option<QualType> {
let base_ty = self.expr_type(base)?;
let target_ty = if is_arrow {
match *base_ty.base {
TypeNode::Pointer(ref pointee) => (**pointee).clone(),
_ => {
self.errors.push(format!(
"member access '->' requires pointer type, got '{}'",
base_ty
));
return None;
}
}
} else {
base_ty.clone()
};
match *target_ty.base {
TypeNode::Struct { ref fields, .. } => {
for f in fields {
if f.name == field {
return Some(f.ty.clone());
}
}
self.errors.push(format!("no member '{}' in struct", field));
None
}
_ => {
self.errors.push(format!(
"member access requires struct or union type, got '{}'",
target_ty
));
None
}
}
}
pub fn expr_type_detailed(&mut self, expr: &Expr) -> Option<ExprTypeResult> {
let ty = self.expr_type(expr)?;
let is_lv = self.is_lvalue(expr);
Some(ExprTypeResult::new(ty, is_lv))
}
pub fn check_condition(&mut self, expr: &Expr) -> Result<QualType, String> {
let cond_ty = self.expr_type(expr).ok_or("type error in condition")?;
if !self.type_system.is_scalar(&cond_ty) {
return Err(format!(
"condition must have scalar type, got '{}'",
cond_ty
));
}
Ok(cond_ty)
}
pub fn is_null_pointer_constant(&mut self, expr: &Expr) -> bool {
match expr {
Expr::IntLiteral(0) => true,
Expr::UIntLiteral(0, _) => true,
Expr::Cast(ty, inner) => {
self.type_system.is_pointer(ty) && self.is_null_pointer_constant(inner)
}
_ => false,
}
}
pub fn perform_implicit_conversions(
&mut self,
expr_ty: &QualType,
target_ty: &QualType,
) -> Result<QualType, String> {
let mut result = expr_ty.clone();
if self.type_system.is_array(&result) {
result = self.array_to_pointer(&result);
}
if self.type_system.is_function(&result) {
result = self.function_to_pointer(&result);
}
result = self.lvalue_to_rvalue(&result);
if self.type_system.is_integer(&result) && self.type_system.is_integer(target_ty) {
result = self.type_system.integer_promotion(&result);
}
if self.type_system.is_arithmetic(&result) && self.type_system.is_arithmetic(target_ty) {
result = self
.type_system
.usual_arithmetic_conversions(&result, target_ty);
}
Ok(result)
}
pub fn check_binary_op_validity(
&mut self,
op: BinaryOp,
lhs_ty: &QualType,
rhs_ty: &QualType,
) -> Result<QualType, String> {
match op {
BinaryOp::Add => {
if self.type_system.is_pointer(lhs_ty) && self.type_system.is_integer(rhs_ty) {
return Ok(lhs_ty.clone());
}
if self.type_system.is_integer(lhs_ty) && self.type_system.is_pointer(rhs_ty) {
return Ok(rhs_ty.clone());
}
if self.type_system.is_arithmetic(lhs_ty) && self.type_system.is_arithmetic(rhs_ty)
{
return Ok(self
.type_system
.usual_arithmetic_conversions(lhs_ty, rhs_ty));
}
Err(format!(
"invalid operands to '+': '{}' and '{}'",
lhs_ty, rhs_ty
))
}
BinaryOp::Sub => {
if self.type_system.is_pointer(lhs_ty) && self.type_system.is_pointer(rhs_ty) {
return Ok(QualType::long());
}
if self.type_system.is_pointer(lhs_ty) && self.type_system.is_integer(rhs_ty) {
return Ok(lhs_ty.clone());
}
if self.type_system.is_arithmetic(lhs_ty) && self.type_system.is_arithmetic(rhs_ty)
{
return Ok(self
.type_system
.usual_arithmetic_conversions(lhs_ty, rhs_ty));
}
Err(format!(
"invalid operands to '-': '{}' and '{}'",
lhs_ty, rhs_ty
))
}
BinaryOp::Mul | BinaryOp::Div => {
if self.type_system.is_arithmetic(lhs_ty) && self.type_system.is_arithmetic(rhs_ty)
{
return Ok(self
.type_system
.usual_arithmetic_conversions(lhs_ty, rhs_ty));
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
BinaryOp::Mod => {
if self.type_system.is_integer(lhs_ty) && self.type_system.is_integer(rhs_ty) {
return Ok(self
.type_system
.usual_arithmetic_conversions(lhs_ty, rhs_ty));
}
Err(format!(
"invalid operands to '%': '{}' and '{}'",
lhs_ty, rhs_ty
))
}
BinaryOp::Shl | BinaryOp::Shr => {
if self.type_system.is_integer(lhs_ty) && self.type_system.is_integer(rhs_ty) {
return Ok(self.type_system.integer_promotion(lhs_ty));
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => {
if self.type_system.is_integer(lhs_ty) && self.type_system.is_integer(rhs_ty) {
return Ok(self
.type_system
.usual_arithmetic_conversions(lhs_ty, rhs_ty));
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
BinaryOp::Eq | BinaryOp::Ne => {
if (self.type_system.is_arithmetic(lhs_ty)
&& self.type_system.is_arithmetic(rhs_ty))
|| (self.type_system.is_pointer(lhs_ty) && self.type_system.is_pointer(rhs_ty))
|| (self.type_system.is_pointer(lhs_ty) && self.type_system.is_integer(rhs_ty))
|| (self.type_system.is_pointer(rhs_ty) && self.type_system.is_integer(lhs_ty))
{
return Ok(QualType::int());
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
if (self.type_system.is_arithmetic(lhs_ty)
&& self.type_system.is_arithmetic(rhs_ty))
|| (self.type_system.is_pointer(lhs_ty) && self.type_system.is_pointer(rhs_ty))
{
return Ok(QualType::int());
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
BinaryOp::LogicAnd | BinaryOp::LogicOr => {
if self.type_system.is_scalar(lhs_ty) && self.type_system.is_scalar(rhs_ty) {
return Ok(QualType::int());
}
Err(format!(
"invalid operands to '{:?}': '{}' and '{}'",
op, lhs_ty, rhs_ty
))
}
_ => Err(format!("operator '{:?}' not yet implemented", op)),
}
}
pub fn sizeof_expr_type(&self, expr: &Expr) -> Option<usize> {
match expr {
Expr::Ident(_) => Some(8), Expr::IntLiteral(_) => Some(4),
Expr::UIntLiteral(_, _) => Some(4),
Expr::FloatLiteral(_) | Expr::DoubleLiteral(_) => Some(8),
Expr::CharLiteral(_) => Some(4),
Expr::StringLiteral(s) => Some(s.len() + 1), _ => None,
}
}
}
pub struct StmtChecker {
errors: Vec<String>,
warnings: Vec<String>,
current_return_type: Option<QualType>,
loop_depth: u32,
switch_depth: u32,
case_values: Vec<i64>,
has_default: bool,
defined_labels: HashSet<String>,
pending_gotos: Vec<String>,
path_returned: bool,
}
impl StmtChecker {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
current_return_type: None,
loop_depth: 0,
switch_depth: 0,
case_values: Vec::new(),
has_default: false,
defined_labels: HashSet::new(),
pending_gotos: Vec::new(),
path_returned: false,
}
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn take_errors(&mut self) -> Vec<String> {
std::mem::take(&mut self.errors)
}
pub fn take_warnings(&mut self) -> Vec<String> {
std::mem::take(&mut self.warnings)
}
pub fn set_return_type(&mut self, ret_ty: QualType) {
self.current_return_type = Some(ret_ty);
}
pub fn clear_return_type(&mut self) {
self.current_return_type = None;
}
pub fn is_jump(&self, stmt: &Stmt) -> bool {
matches!(
stmt,
Stmt::Return(_) | Stmt::Break | Stmt::Continue | Stmt::Goto { .. }
)
}
pub fn is_compound(&self, stmt: &Stmt) -> bool {
matches!(stmt, Stmt::Compound(_))
}
pub fn check_return(&mut self, expr: &Option<Box<Expr>>) -> Result<(), Vec<String>> {
let ret_ty = match self.current_return_type.as_ref() {
Some(ty) => ty.clone(),
None => {
self.errors
.push("return statement outside of function".to_string());
return Err(self.errors.clone());
}
};
match expr {
Some(e) => {
if self.is_void_type(&ret_ty) {
self.warnings
.push(format!("return with value in void function"));
}
}
None => {
if !self.is_void_type(&ret_ty) {
self.warnings.push(format!(
"return without value in function returning non-void"
));
}
}
}
self.path_returned = true;
Ok(())
}
fn is_void_type(&self, qt: &QualType) -> bool {
matches!(*qt.base, TypeNode::Void)
}
pub fn check_break(&mut self) -> Result<(), Vec<String>> {
if self.loop_depth == 0 && self.switch_depth == 0 {
self.errors
.push("break statement not within loop or switch".to_string());
return Err(self.errors.clone());
}
Ok(())
}
pub fn check_continue(&mut self) -> Result<(), Vec<String>> {
if self.loop_depth == 0 {
self.errors
.push("continue statement not within a loop".to_string());
return Err(self.errors.clone());
}
Ok(())
}
pub fn enter_switch(&mut self) {
self.switch_depth += 1;
self.case_values.clear();
self.has_default = false;
}
pub fn leave_switch(&mut self) {
if self.switch_depth > 0 {
self.switch_depth -= 1;
}
}
pub fn check_case(&mut self, value: &Expr) -> Result<i64, Vec<String>> {
if self.switch_depth == 0 {
self.errors
.push("case label not within switch statement".to_string());
return Err(self.errors.clone());
}
let case_val = match self.eval_constant_int(value) {
Some(v) => v,
None => {
self.errors
.push("case label does not reduce to an integer constant".to_string());
return Err(self.errors.clone());
}
};
if self.case_values.contains(&case_val) {
self.errors
.push(format!("duplicate case value '{}' in switch", case_val));
}
self.case_values.push(case_val);
Ok(case_val)
}
pub fn check_default(&mut self) -> Result<(), Vec<String>> {
if self.switch_depth == 0 {
self.errors
.push("default label not within switch statement".to_string());
return Err(self.errors.clone());
}
if self.has_default {
self.errors
.push("multiple default labels in one switch".to_string());
return Err(self.errors.clone());
}
self.has_default = true;
Ok(())
}
fn eval_constant_int(&self, expr: &Expr) -> Option<i64> {
match expr {
Expr::IntLiteral(v) => Some(*v),
Expr::UIntLiteral(v, _) => Some(*v as i64),
Expr::CharLiteral(c) => Some(*c as i64),
Expr::Unary(UnaryOp::Minus, inner) => self.eval_constant_int(inner).map(|v| -v),
Expr::Unary(UnaryOp::Plus, inner) => self.eval_constant_int(inner),
Expr::Ident(name) if name.chars().all(|c| c.is_uppercase() || c == '_') => {
None }
_ => None,
}
}
pub fn check_goto(&mut self, label: &str) -> Result<(), Vec<String>> {
if !self.defined_labels.contains(label) {
self.pending_gotos.push(label.to_string());
}
Ok(())
}
pub fn define_label(&mut self, name: &str) {
self.defined_labels.insert(name.to_string());
}
pub fn resolve_gotos(&mut self) {
let undefined: Vec<String> = self
.pending_gotos
.iter()
.filter(|l| !self.defined_labels.contains(*l))
.cloned()
.collect();
for label in &undefined {
self.errors
.push(format!("use of undeclared label '{}'", label));
}
self.pending_gotos
.retain(|l| self.defined_labels.contains(l));
}
pub fn has_unresolved_gotos(&self) -> bool {
self.pending_gotos
.iter()
.any(|l| !self.defined_labels.contains(l))
}
pub fn check_fallthrough(&mut self, from_case: Option<i64>, to_case: Option<i64>) {
if from_case.is_some() && to_case.is_some() {
}
}
pub fn is_unreachable(&self) -> bool {
self.path_returned
}
pub fn reset_path_returned(&mut self) {
self.path_returned = false;
}
}
pub struct ConstExprEvaluator {
enum_values: HashMap<String, i64>,
var_addresses: HashMap<String, usize>,
errors: Vec<String>,
standard: CLangStandard,
}
impl ConstExprEvaluator {
pub fn new(standard: CLangStandard) -> Self {
Self {
enum_values: HashMap::new(),
var_addresses: HashMap::new(),
errors: Vec::new(),
standard,
}
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn take_errors(&mut self) -> Vec<String> {
std::mem::take(&mut self.errors)
}
pub fn register_enum_value(&mut self, name: &str, value: i64) {
self.enum_values.insert(name.to_string(), value);
}
pub fn register_var_address(&mut self, name: &str, addr: usize) {
self.var_addresses.insert(name.to_string(), addr);
}
pub fn evaluate_integer_constant(&mut self, expr: &Expr) -> Option<i64> {
match expr {
Expr::IntLiteral(v) => Some(*v),
Expr::UIntLiteral(v, _) => {
if *v > i64::MAX as u64 {
self.errors
.push(format!("unsigned literal {} exceeds i64 range", v));
None
} else {
Some(*v as i64)
}
}
Expr::CharLiteral(c) => Some(*c as i64),
Expr::Ident(name) => self.enum_values.get(name).copied(),
Expr::Unary(op, inner) => {
let val = self.evaluate_integer_constant(inner)?;
match op {
UnaryOp::Plus => Some(val),
UnaryOp::Minus => Some(-val),
UnaryOp::BitNot => Some(!val),
UnaryOp::Not => Some(if val == 0 { 1 } else { 0 }),
_ => {
self.errors.push(format!(
"unary '{:?}' not allowed in integer constant expression",
op
));
None
}
}
}
Expr::Binary(op, lhs, rhs) => {
let l = self.evaluate_integer_constant(lhs)?;
let r = self.evaluate_integer_constant(rhs)?;
self.eval_binary_op(*op, l, r)
}
Expr::SizeOf(inner) => {
let ts = TypeSystem::new(self.standard);
self.sizeof_expr(inner)
}
Expr::SizeOfType(qt) => Some(qt.base.size_bytes() as i64),
Expr::AlignOfType(qt) => {
let size = qt.base.size_bytes();
Some(if size == 0 { 1 } else { size } as i64)
}
Expr::AlignOf(inner) => self.alignof_expr(inner),
Expr::Cast(ty, inner) => {
let val = self.evaluate_integer_constant(inner)?;
Some(val)
}
Expr::Conditional(cond, then, els) => {
let c = self.evaluate_integer_constant(cond)?;
if c != 0 {
self.evaluate_integer_constant(then)
} else {
self.evaluate_integer_constant(els)
}
}
_ => {
self.errors
.push(format!("expression is not an integer constant expression"));
None
}
}
}
pub fn is_integer_constant_expression(&mut self, expr: &Expr) -> bool {
self.evaluate_integer_constant(expr).is_some()
}
fn eval_binary_op(&mut self, op: BinaryOp, lhs: i64, rhs: i64) -> Option<i64> {
match op {
BinaryOp::Add => lhs.checked_add(rhs),
BinaryOp::Sub => lhs.checked_sub(rhs),
BinaryOp::Mul => lhs.checked_mul(rhs),
BinaryOp::Div => {
if rhs == 0 {
self.errors
.push("division by zero in constant expression".to_string());
None
} else {
lhs.checked_div(rhs)
}
}
BinaryOp::Mod => {
if rhs == 0 {
self.errors
.push("modulo by zero in constant expression".to_string());
None
} else {
lhs.checked_rem(rhs)
}
}
BinaryOp::And => Some(lhs & rhs),
BinaryOp::Or => Some(lhs | rhs),
BinaryOp::Xor => Some(lhs ^ rhs),
BinaryOp::Shl => {
if rhs < 0 || rhs >= 64 {
self.errors.push("shift count out of range".to_string());
None
} else {
Some(lhs << (rhs as u32))
}
}
BinaryOp::Shr => {
if rhs < 0 || rhs >= 64 {
self.errors.push("shift count out of range".to_string());
None
} else {
Some(lhs >> (rhs as u32))
}
}
BinaryOp::Eq => Some(if lhs == rhs { 1 } else { 0 }),
BinaryOp::Ne => Some(if lhs != rhs { 1 } else { 0 }),
BinaryOp::Lt => Some(if lhs < rhs { 1 } else { 0 }),
BinaryOp::Gt => Some(if lhs > rhs { 1 } else { 0 }),
BinaryOp::Le => Some(if lhs <= rhs { 1 } else { 0 }),
BinaryOp::Ge => Some(if lhs >= rhs { 1 } else { 0 }),
BinaryOp::LogicAnd => Some(if lhs != 0 && rhs != 0 { 1 } else { 0 }),
BinaryOp::LogicOr => Some(if lhs != 0 || rhs != 0 { 1 } else { 0 }),
_ => {
self.errors.push(format!(
"'{:?}' not allowed in integer constant expression",
op
));
None
}
}
}
pub fn evaluate_address_constant(&mut self, expr: &Expr) -> Option<usize> {
match expr {
Expr::Unary(UnaryOp::AddrOf, inner) => match **inner {
Expr::Ident(ref name) => self.var_addresses.get(name).copied(),
Expr::Subscript {
ref base,
ref index,
} => {
let base_addr = self.evaluate_address_constant(base)?;
let offset = self.evaluate_integer_constant(index)?;
let elem_size = 4; Some(base_addr + offset as usize * elem_size)
}
_ => {
self.errors
.push("address-of expression is not an address constant".to_string());
None
}
},
Expr::Ident(name) => {
self.var_addresses.get(name).copied()
}
Expr::IntLiteral(v) if *v == 0 => Some(0), Expr::Cast(_, inner) => self.evaluate_address_constant(inner),
_ => {
self.errors
.push("expression is not an address constant".to_string());
None
}
}
}
pub fn evaluate_arithmetic_constant(&mut self, expr: &Expr) -> Option<f64> {
match expr {
Expr::IntLiteral(v) => Some(*v as f64),
Expr::UIntLiteral(v, _) => Some(*v as f64),
Expr::FloatLiteral(v) => Some(*v),
Expr::DoubleLiteral(v) => Some(*v),
Expr::Unary(UnaryOp::Minus, inner) => Some(-self.evaluate_arithmetic_constant(inner)?),
Expr::Unary(UnaryOp::Plus, inner) => self.evaluate_arithmetic_constant(inner),
Expr::Binary(op, lhs, rhs) => {
let l = self.evaluate_arithmetic_constant(lhs)?;
let r = self.evaluate_arithmetic_constant(rhs)?;
match op {
BinaryOp::Add => Some(l + r),
BinaryOp::Sub => Some(l - r),
BinaryOp::Mul => Some(l * r),
BinaryOp::Div => Some(l / r),
_ => {
self.errors.push(format!(
"'{:?}' in arithmetic constant expression not yet implemented",
op
));
None
}
}
}
_ => {
self.errors
.push("expression is not an arithmetic constant".to_string());
None
}
}
}
fn sizeof_expr(&mut self, expr: &Expr) -> Option<i64> {
match expr {
Expr::Ident(name) => {
if let Some(_) = self.enum_values.get(name) {
Some(4) } else {
Some(8) }
}
Expr::IntLiteral(_) => Some(4), Expr::UIntLiteral(_, is_ll) => {
if *is_ll {
Some(8)
} else {
Some(4)
}
} Expr::FloatLiteral(_) | Expr::DoubleLiteral(_) => Some(8), Expr::CharLiteral(_) => Some(4), _ => {
self.errors
.push("cannot compute sizeof for expression in constant context".to_string());
None
}
}
}
fn alignof_expr(&mut self, expr: &Expr) -> Option<i64> {
self.sizeof_expr(expr)
}
pub fn evaluate_boolean_constant(&mut self, expr: &Expr) -> Option<bool> {
self.evaluate_integer_constant(expr).map(|v| v != 0)
}
pub fn evaluate(&mut self, expr: &Expr) -> Option<i64> {
self.evaluate_integer_constant(expr)
}
pub fn evaluate_float_constant(&mut self, expr: &Expr) -> Option<f64> {
self.evaluate_arithmetic_constant(expr)
}
pub fn is_valid_array_size(&mut self, expr: &Expr) -> bool {
match self.evaluate_integer_constant(expr) {
Some(v) => v > 0,
None => false,
}
}
pub fn is_valid_bitfield_width(&mut self, expr: &Expr) -> bool {
match self.evaluate_integer_constant(expr) {
Some(v) => v > 0 && v <= 64,
None => false,
}
}
pub fn evaluate_enum_value(&mut self, expr: &Expr) -> Option<i64> {
self.evaluate_integer_constant(expr)
}
pub fn evaluate_debug(&mut self, expr: &Expr) -> String {
match self.evaluate_integer_constant(expr) {
Some(v) => format!("{} (integer constant)", v),
None => match self.evaluate_arithmetic_constant(expr) {
Some(v) => format!("{:.6} (floating-point constant)", v),
None => "not a constant expression".to_string(),
},
}
}
pub fn fold_binary_float(&mut self, op: BinaryOp, lhs: f64, rhs: f64) -> Option<f64> {
match op {
BinaryOp::Add => Some(lhs + rhs),
BinaryOp::Sub => Some(lhs - rhs),
BinaryOp::Mul => Some(lhs * rhs),
BinaryOp::Div => {
if rhs == 0.0 {
self.errors
.push("floating-point division by zero in constant expression".to_string());
None
} else {
Some(lhs / rhs)
}
}
BinaryOp::Eq => Some(if (lhs - rhs).abs() < f64::EPSILON {
1.0
} else {
0.0
}),
BinaryOp::Ne => Some(if (lhs - rhs).abs() >= f64::EPSILON {
1.0
} else {
0.0
}),
BinaryOp::Lt => Some(if lhs < rhs { 1.0 } else { 0.0 }),
BinaryOp::Gt => Some(if lhs > rhs { 1.0 } else { 0.0 }),
BinaryOp::Le => Some(if lhs <= rhs { 1.0 } else { 0.0 }),
BinaryOp::Ge => Some(if lhs >= rhs { 1.0 } else { 0.0 }),
_ => {
self.errors.push(format!(
"'{:?}' not supported for floating-point constant folding",
op
));
None
}
}
}
pub fn enum_count(&self) -> usize {
self.enum_values.len()
}
pub fn var_address_count(&self) -> usize {
self.var_addresses.len()
}
}
#[derive(Debug, Clone)]
pub struct TypePropsTable {
table: HashMap<TypeClass, (usize, usize, bool, bool, bool)>,
}
impl TypePropsTable {
pub fn new() -> Self {
let mut table = HashMap::new();
table.insert(TypeClass::Char, (1, 1, false, true, false));
table.insert(TypeClass::SChar, (1, 1, true, true, false));
table.insert(TypeClass::UChar, (1, 1, false, true, false));
table.insert(TypeClass::Short, (2, 2, true, true, false));
table.insert(TypeClass::UShort, (2, 2, false, true, false));
table.insert(TypeClass::Int, (4, 4, true, true, false));
table.insert(TypeClass::UInt, (4, 4, false, true, false));
table.insert(TypeClass::Long, (8, 8, true, true, false));
table.insert(TypeClass::ULong, (8, 8, false, true, false));
table.insert(TypeClass::LongLong, (8, 8, true, true, false));
table.insert(TypeClass::ULongLong, (8, 8, false, true, false));
table.insert(TypeClass::Float, (4, 4, true, false, true));
table.insert(TypeClass::Double, (8, 8, true, false, true));
table.insert(TypeClass::LongDouble, (16, 16, true, false, true));
table.insert(TypeClass::Bool, (1, 1, false, true, false));
table.insert(TypeClass::Pointer, (8, 8, false, false, false));
table.insert(TypeClass::Enum, (4, 4, false, true, false));
Self { table }
}
pub fn get_props(&self, class: TypeClass) -> Option<&(usize, usize, bool, bool, bool)> {
self.table.get(&class)
}
pub fn size_of(&self, class: TypeClass) -> usize {
self.table.get(&class).map(|p| p.0).unwrap_or(0)
}
pub fn align_of(&self, class: TypeClass) -> usize {
self.table.get(&class).map(|p| p.1).unwrap_or(1)
}
pub fn is_signed(&self, class: TypeClass) -> bool {
self.table.get(&class).map(|p| p.2).unwrap_or(false)
}
pub fn integer_rank_ordinal(&self, class: TypeClass) -> Option<u32> {
match class {
TypeClass::Bool => Some(0),
TypeClass::Char | TypeClass::SChar | TypeClass::UChar => Some(1),
TypeClass::Short | TypeClass::UShort => Some(2),
TypeClass::Int | TypeClass::UInt | TypeClass::Enum => Some(3),
TypeClass::Long | TypeClass::ULong => Some(4),
TypeClass::LongLong | TypeClass::ULongLong => Some(5),
_ => None,
}
}
pub fn usual_conversion_result(&self, a: TypeClass, b: TypeClass) -> Option<TypeClass> {
if a.is_floating() || b.is_floating() {
if a == TypeClass::LongDouble || b == TypeClass::LongDouble {
return Some(TypeClass::LongDouble);
}
if a == TypeClass::Double || b == TypeClass::Double {
return Some(TypeClass::Double);
}
return Some(TypeClass::Float);
}
let a_rank = self.integer_rank_ordinal(a)?;
let b_rank = self.integer_rank_ordinal(b)?;
let a_unsigned = a.is_unsigned();
let b_unsigned = b.is_unsigned();
if a_rank > b_rank {
Some(a)
} else if b_rank > a_rank {
Some(b)
} else if a_unsigned || b_unsigned {
if a_unsigned {
Some(a)
} else {
Some(b)
}
} else {
Some(a)
}
}
pub fn all_classes(&self) -> Vec<TypeClass> {
self.table.keys().cloned().collect()
}
}
impl Default for TypePropsTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct SemaContext {
pub standard: CLangStandard,
pub type_props: TypePropsTable,
pub in_loop: u32,
pub in_switch: u32,
pub current_return_type: Option<QualType>,
}
impl SemaContext {
pub fn new(standard: CLangStandard) -> Self {
Self {
standard,
type_props: TypePropsTable::new(),
in_loop: 0,
in_switch: 0,
current_return_type: None,
}
}
pub fn enter_loop(&mut self) {
self.in_loop += 1;
}
pub fn leave_loop(&mut self) {
if self.in_loop > 0 {
self.in_loop -= 1;
}
}
pub fn enter_switch(&mut self) {
self.in_switch += 1;
}
pub fn leave_switch(&mut self) {
if self.in_switch > 0 {
self.in_switch -= 1;
}
}
pub fn can_break(&self) -> bool {
self.in_loop > 0 || self.in_switch > 0
}
pub fn can_continue(&self) -> bool {
self.in_loop > 0
}
}
#[derive(Debug, Clone)]
pub struct SemaOptions {
pub warnings_as_errors: bool,
pub pedantic: bool,
pub error_limit: usize,
pub extensions: HashSet<String>,
pub disable_warnings: HashSet<String>,
pub enable_warnings: Option<HashSet<String>>,
}
impl Default for SemaOptions {
fn default() -> Self {
Self {
warnings_as_errors: false,
pedantic: false,
error_limit: 20,
extensions: HashSet::new(),
disable_warnings: HashSet::new(),
enable_warnings: None,
}
}
}
impl SemaOptions {
pub fn new() -> Self {
Self::default()
}
pub fn pedantic() -> Self {
Self {
pedantic: true,
..Self::default()
}
}
pub fn strict() -> Self {
Self {
warnings_as_errors: true,
pedantic: true,
..Self::default()
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstantValue {
Int(i64),
UInt(u64),
Float(f64),
NullPointer,
StringLiteral(usize),
Address(usize),
NonConstant(String),
}
impl ConstantValue {
pub fn as_int(&self) -> Option<i64> {
match self {
ConstantValue::Int(v) => Some(*v),
ConstantValue::UInt(v) => Some(*v as i64),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
ConstantValue::Float(v) => Some(*v),
ConstantValue::Int(v) => Some(*v as f64),
_ => None,
}
}
pub fn is_constant(&self) -> bool {
!matches!(self, ConstantValue::NonConstant(_))
}
pub fn is_zero(&self) -> bool {
match self {
ConstantValue::Int(0) | ConstantValue::UInt(0) => true,
ConstantValue::Float(v) if *v == 0.0 => true,
ConstantValue::NullPointer => true,
_ => false,
}
}
}
impl fmt::Display for ConstantValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConstantValue::Int(v) => write!(f, "{}", v),
ConstantValue::UInt(v) => write!(f, "{}u", v),
ConstantValue::Float(v) => write!(f, "{:.6}", v),
ConstantValue::NullPointer => write!(f, "nullptr"),
ConstantValue::StringLiteral(addr) => write!(f, "string@0x{:x}", addr),
ConstantValue::Address(addr) => write!(f, "&0x{:x}", addr),
ConstantValue::NonConstant(reason) => write!(f, "<non-constant: {}>", reason),
}
}
}
pub fn evaluate_to_constant_value(
expr: &Expr,
evaluator: &mut ConstExprEvaluator,
) -> ConstantValue {
match expr {
Expr::IntLiteral(v) => ConstantValue::Int(*v),
Expr::UIntLiteral(v, _) => ConstantValue::UInt(*v),
Expr::FloatLiteral(v) => ConstantValue::Float(*v),
Expr::DoubleLiteral(v) => ConstantValue::Float(*v),
Expr::CharLiteral(c) => ConstantValue::Int(*c as i64),
Expr::Ident(name) => {
if let Some(val) = evaluator.evaluate_integer_constant(expr) {
ConstantValue::Int(val)
} else {
ConstantValue::NonConstant(format!("unknown identifier '{}'", name))
}
}
Expr::Unary(op, inner) => {
let inner_val = evaluate_to_constant_value(inner, evaluator);
match (op, &inner_val) {
(UnaryOp::Minus, ConstantValue::Int(v)) => ConstantValue::Int(-v),
(UnaryOp::Minus, ConstantValue::Float(v)) => ConstantValue::Float(-v),
(UnaryOp::Plus, _) => inner_val,
(UnaryOp::BitNot, ConstantValue::Int(v)) => ConstantValue::Int(!v),
(UnaryOp::Not, ConstantValue::Int(v)) => {
ConstantValue::Int(if *v == 0 { 1 } else { 0 })
}
(UnaryOp::AddrOf, _) => {
ConstantValue::NonConstant("address-of in constant expression".to_string())
}
_ => ConstantValue::NonConstant(format!("unary {:?} not constant", op)),
}
}
Expr::Binary(op, lhs, rhs) => {
let l = evaluate_to_constant_value(lhs, evaluator);
let r = evaluate_to_constant_value(rhs, evaluator);
match (&l, &r) {
(ConstantValue::Int(lv), ConstantValue::Int(rv)) => {
match evaluator.eval_binary_op_infallible(*op, *lv, *rv) {
Ok(v) => ConstantValue::Int(v),
Err(e) => ConstantValue::NonConstant(e),
}
}
_ => ConstantValue::NonConstant(
"binary operation on non-integer constants".to_string(),
),
}
}
Expr::Conditional(cond, then, els) => {
let c = evaluate_to_constant_value(cond, evaluator);
if c.is_zero() {
evaluate_to_constant_value(els, evaluator)
} else {
evaluate_to_constant_value(then, evaluator)
}
}
Expr::Cast(_, inner) => evaluate_to_constant_value(inner, evaluator),
Expr::SizeOfType(qt) => ConstantValue::Int(qt.base.size_bytes() as i64),
_ => ConstantValue::NonConstant("not a constant expression".to_string()),
}
}
impl ConstExprEvaluator {
pub fn eval_binary_op_infallible(
&mut self,
op: BinaryOp,
lhs: i64,
rhs: i64,
) -> Result<i64, String> {
match op {
BinaryOp::Add => lhs.checked_add(rhs).ok_or("addition overflow".to_string()),
BinaryOp::Sub => lhs
.checked_sub(rhs)
.ok_or("subtraction overflow".to_string()),
BinaryOp::Mul => lhs
.checked_mul(rhs)
.ok_or("multiplication overflow".to_string()),
BinaryOp::Div => {
if rhs == 0 {
Err("division by zero".to_string())
} else {
lhs.checked_div(rhs).ok_or("division overflow".to_string())
}
}
BinaryOp::Mod => {
if rhs == 0 {
Err("modulo by zero".to_string())
} else {
lhs.checked_rem(rhs).ok_or("modulo overflow".to_string())
}
}
BinaryOp::And => Ok(lhs & rhs),
BinaryOp::Or => Ok(lhs | rhs),
BinaryOp::Xor => Ok(lhs ^ rhs),
BinaryOp::Shl => {
if rhs < 0 || rhs >= 64 {
Err("shift count out of range".to_string())
} else {
Ok(lhs.checked_shl(rhs as u32).unwrap_or(0))
}
}
BinaryOp::Shr => {
if rhs < 0 || rhs >= 64 {
Err("shift count out of range".to_string())
} else {
Ok(lhs.checked_shr(rhs as u32).unwrap_or(0))
}
}
BinaryOp::Eq => Ok(if lhs == rhs { 1 } else { 0 }),
BinaryOp::Ne => Ok(if lhs != rhs { 1 } else { 0 }),
BinaryOp::Lt => Ok(if lhs < rhs { 1 } else { 0 }),
BinaryOp::Gt => Ok(if lhs > rhs { 1 } else { 0 }),
BinaryOp::Le => Ok(if lhs <= rhs { 1 } else { 0 }),
BinaryOp::Ge => Ok(if lhs >= rhs { 1 } else { 0 }),
BinaryOp::LogicAnd => Ok(if lhs != 0 && rhs != 0 { 1 } else { 0 }),
BinaryOp::LogicOr => Ok(if lhs != 0 || rhs != 0 { 1 } else { 0 }),
_ => Err(format!(
"operator '{:?}' not supported in constant expressions",
op
)),
}
}
}
pub fn expr_structural_eq(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(Expr::IntLiteral(av), Expr::IntLiteral(bv)) => av == bv,
(Expr::UIntLiteral(av, _), Expr::UIntLiteral(bv, _)) => av == bv,
(Expr::FloatLiteral(av), Expr::FloatLiteral(bv)) => (av - bv).abs() < f64::EPSILON,
(Expr::DoubleLiteral(av), Expr::DoubleLiteral(bv)) => (av - bv).abs() < f64::EPSILON,
(Expr::CharLiteral(av), Expr::CharLiteral(bv)) => av == bv,
(Expr::StringLiteral(av), Expr::StringLiteral(bv)) => av == bv,
(Expr::Ident(av), Expr::Ident(bv)) => av == bv,
(Expr::Unary(aop, ae), Expr::Unary(bop, be)) => aop == bop && expr_structural_eq(ae, be),
(Expr::Binary(aop, al, ar), Expr::Binary(bop, bl, br)) => {
aop == bop && expr_structural_eq(al, bl) && expr_structural_eq(ar, br)
}
(Expr::Assign(aop, al, ar), Expr::Assign(bop, bl, br)) => {
aop == bop && expr_structural_eq(al, bl) && expr_structural_eq(ar, br)
}
(Expr::Conditional(ac, at, ae), Expr::Conditional(bc, bt, be)) => {
expr_structural_eq(ac, bc) && expr_structural_eq(at, bt) && expr_structural_eq(ae, be)
}
(
Expr::Call {
callee: ac,
args: aa,
},
Expr::Call {
callee: bc,
args: ba,
},
) => {
expr_structural_eq(ac, bc)
&& aa.len() == ba.len()
&& aa
.iter()
.zip(ba.iter())
.all(|(a, b)| expr_structural_eq(a, b))
}
_ => false,
}
}
pub fn expr_nesting_depth(expr: &Expr) -> usize {
match expr {
Expr::IntLiteral(_)
| Expr::UIntLiteral(..)
| Expr::FloatLiteral(_)
| Expr::DoubleLiteral(_)
| Expr::CharLiteral(_)
| Expr::StringLiteral(_)
| Expr::Ident(_) => 0,
Expr::Unary(_, inner)
| Expr::SizeOf(inner)
| Expr::AlignOf(inner)
| Expr::Cast(_, inner)
| Expr::PostInc(inner)
| Expr::PostDec(inner)
| Expr::PreInc(inner)
| Expr::PreDec(inner) => 1 + expr_nesting_depth(inner),
Expr::Binary(_, lhs, rhs) | Expr::Assign(_, lhs, rhs) => {
1 + expr_nesting_depth(lhs).max(expr_nesting_depth(rhs))
}
Expr::Conditional(cond, then, els) => {
1 + expr_nesting_depth(cond)
.max(expr_nesting_depth(then))
.max(expr_nesting_depth(els))
}
Expr::Call { callee, args } => {
1 + expr_nesting_depth(callee)
+ args.iter().map(|a| expr_nesting_depth(a)).sum::<usize>()
}
Expr::Subscript { base, index } => {
1 + expr_nesting_depth(base).max(expr_nesting_depth(index))
}
Expr::Member { base, .. } => 1 + expr_nesting_depth(base),
Expr::SizeOfType(_) | Expr::AlignOfType(_) => 0,
Expr::CompoundLiteral(_, _) => 1,
Expr::AggregateLiteral(vals) => {
1 + vals
.iter()
.map(|v| expr_nesting_depth(v))
.max()
.unwrap_or(0)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum KnownAttribute {
Deprecated,
Unused,
Noreturn,
NoInline,
AlwaysInline,
Pure,
Const,
Malloc,
AllocSize,
Format,
NonNull,
Fallthrough,
MaybeUnused,
NoDiscard,
Packed,
Aligned,
Section,
Weak,
Visibility,
NoReturnCpp,
CarriesDependency,
NoUniqueAddress,
Likely,
Unlikely,
}
impl KnownAttribute {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"deprecated" | "__deprecated__" => Some(KnownAttribute::Deprecated),
"unused" | "__unused__" => Some(KnownAttribute::Unused),
"noreturn" | "__noreturn__" | "_Noreturn" => Some(KnownAttribute::Noreturn),
"noinline" | "__noinline__" => Some(KnownAttribute::NoInline),
"always_inline" | "__always_inline__" | "__forceinline" => {
Some(KnownAttribute::AlwaysInline)
}
"pure" | "__pure__" => Some(KnownAttribute::Pure),
"const" | "__const__" => Some(KnownAttribute::Const),
"malloc" | "__malloc__" => Some(KnownAttribute::Malloc),
"alloc_size" | "__alloc_size__" => Some(KnownAttribute::AllocSize),
"format" | "__format__" => Some(KnownAttribute::Format),
"nonnull" | "__nonnull__" | "_Nonnull" => Some(KnownAttribute::NonNull),
"fallthrough" | "__fallthrough__" => Some(KnownAttribute::Fallthrough),
"maybe_unused" => Some(KnownAttribute::MaybeUnused),
"nodiscard" | "warn_unused_result" => Some(KnownAttribute::NoDiscard),
"packed" | "__packed__" => Some(KnownAttribute::Packed),
"aligned" | "__aligned__" => Some(KnownAttribute::Aligned),
"section" | "__section__" => Some(KnownAttribute::Section),
"weak" | "__weak__" => Some(KnownAttribute::Weak),
"visibility" | "__visibility__" => Some(KnownAttribute::Visibility),
"noreturn" => Some(KnownAttribute::NoReturnCpp),
"carries_dependency" => Some(KnownAttribute::CarriesDependency),
"no_unique_address" => Some(KnownAttribute::NoUniqueAddress),
"likely" => Some(KnownAttribute::Likely),
"unlikely" => Some(KnownAttribute::Unlikely),
_ => None,
}
}
pub fn valid_on(&self) -> &'static [AttrTarget] {
use AttrTarget::*;
match self {
KnownAttribute::Deprecated => &[Function, Variable, Type, Enum],
KnownAttribute::Unused => &[Function, Variable, Type, Label],
KnownAttribute::Noreturn => &[Function],
KnownAttribute::NoInline => &[Function],
KnownAttribute::AlwaysInline => &[Function],
KnownAttribute::Pure => &[Function],
KnownAttribute::Const => &[Function],
KnownAttribute::Malloc => &[Function],
KnownAttribute::AllocSize => &[Function],
KnownAttribute::Format => &[Function],
KnownAttribute::NonNull => &[Function, Parameter],
KnownAttribute::Fallthrough => &[Statement],
KnownAttribute::MaybeUnused => &[Function, Variable, Type, Parameter, Label],
KnownAttribute::NoDiscard => &[Function, Type],
KnownAttribute::Packed => &[Struct, Union],
KnownAttribute::Aligned => &[Variable, Struct, Union, Type],
KnownAttribute::Section => &[Function, Variable],
KnownAttribute::Weak => &[Function, Variable],
KnownAttribute::Visibility => &[Function, Variable],
KnownAttribute::NoReturnCpp => &[Function],
KnownAttribute::CarriesDependency => &[Function, Parameter],
KnownAttribute::NoUniqueAddress => &[Variable],
KnownAttribute::Likely => &[Statement],
KnownAttribute::Unlikely => &[Statement],
}
}
pub fn description(&self) -> &'static str {
match self {
KnownAttribute::Deprecated => "marks a declaration as deprecated",
KnownAttribute::Unused => "suppresses unused-variable warnings",
KnownAttribute::Noreturn => "indicates the function does not return",
KnownAttribute::NoInline => "prevents inlining",
KnownAttribute::AlwaysInline => "forces inlining when possible",
KnownAttribute::Pure => "function has no side effects beyond its return value",
KnownAttribute::Const => "function has no side effects and does not read global memory",
KnownAttribute::Malloc => "function returns newly-allocated memory",
KnownAttribute::AllocSize => "indicates which parameter provides allocation size",
KnownAttribute::Format => "enables printf/scanf format string checking",
KnownAttribute::NonNull => "parameter must not be null",
KnownAttribute::Fallthrough => "suppresses fallthrough warnings",
KnownAttribute::MaybeUnused => "suppresses unused-entity warnings",
KnownAttribute::NoDiscard => "warns if return value is discarded",
KnownAttribute::Packed => "removes padding between struct fields",
KnownAttribute::Aligned => "sets minimum alignment for a variable or type",
KnownAttribute::Section => "places symbol in a specific section",
KnownAttribute::Weak => "allows the symbol to be overridden",
KnownAttribute::Visibility => "controls ELF symbol visibility",
KnownAttribute::NoReturnCpp => "C++ [[noreturn]] attribute",
KnownAttribute::CarriesDependency => "indicates dependency ordering",
KnownAttribute::NoUniqueAddress => "allows empty base optimization",
KnownAttribute::Likely => "branch is likely to be taken",
KnownAttribute::Unlikely => "branch is unlikely to be taken",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrTarget {
Function,
Variable,
Type,
Parameter,
Statement,
Label,
Struct,
Union,
Enum,
}
pub struct AttrChecker {
registry: HashMap<String, KnownAttribute>,
errors: Vec<String>,
warnings: Vec<String>,
}
impl AttrChecker {
pub fn new() -> Self {
let mut registry = HashMap::new();
let common_attrs = [
"deprecated",
"unused",
"noreturn",
"noinline",
"always_inline",
"pure",
"const",
"malloc",
"format",
"nonnull",
"fallthrough",
"maybe_unused",
"nodiscard",
"packed",
"aligned",
"section",
"weak",
"visibility",
];
for name in &common_attrs {
if let Some(attr) = KnownAttribute::from_name(name) {
registry.insert(name.to_string(), attr);
}
}
Self {
registry,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn take_errors(&mut self) -> Vec<String> {
std::mem::take(&mut self.errors)
}
pub fn take_warnings(&mut self) -> Vec<String> {
std::mem::take(&mut self.warnings)
}
pub fn lookup(&self, name: &str) -> Option<&KnownAttribute> {
self.registry.get(name)
}
pub fn is_known(&self, name: &str) -> bool {
self.registry.contains_key(name) || KnownAttribute::from_name(name).is_some()
}
pub fn check_applicability(
&mut self,
attr_name: &str,
target: AttrTarget,
) -> Result<(), Vec<String>> {
let attr = match KnownAttribute::from_name(attr_name) {
Some(a) => a,
None => {
self.warnings
.push(format!("unknown attribute '{}' ignored", attr_name));
return Err(self.errors.clone());
}
};
if !attr.valid_on().contains(&target) {
let target_str = match target {
AttrTarget::Function => "function",
AttrTarget::Variable => "variable",
AttrTarget::Type => "type",
AttrTarget::Parameter => "parameter",
AttrTarget::Statement => "statement",
AttrTarget::Label => "label",
AttrTarget::Struct => "struct",
AttrTarget::Union => "union",
AttrTarget::Enum => "enum",
};
self.warnings.push(format!(
"attribute '{}' cannot be applied to {}",
attr_name, target_str
));
}
Ok(())
}
pub fn check_attributes(
&mut self,
attr_names: &[String],
target: AttrTarget,
) -> Result<(), Vec<String>> {
let mut seen = HashSet::new();
for name in attr_names {
if !seen.insert(name.as_str()) {
self.warnings
.push(format!("duplicate attribute '{}'", name));
}
let _ = self.check_applicability(name, target);
}
let has_noinline = attr_names.iter().any(|n| n == "noinline");
let has_always_inline = attr_names.iter().any(|n| n == "always_inline");
if has_noinline && has_always_inline {
self.errors
.push("'noinline' and 'always_inline' are conflicting attributes".to_string());
return Err(self.errors.clone());
}
let has_pure = attr_names.iter().any(|n| n == "pure");
let has_const = attr_names.iter().any(|n| n == "const");
if has_pure && has_const {
self.warnings
.push("'pure' attribute is redundant with 'const'".to_string());
}
Ok(())
}
pub fn register_custom(&mut self, name: &str) {
self.registry.entry(name.to_string()).or_insert_with(|| {
KnownAttribute::Deprecated
});
}
pub fn describe(&self, name: &str) -> Option<&'static str> {
KnownAttribute::from_name(name).map(|a| a.description())
}
pub fn known_attributes(&self) -> Vec<&str> {
self.registry.keys().map(|s| s.as_str()).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DiagSeverity {
Ignored = 0,
Note = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}
impl fmt::Display for DiagSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiagSeverity::Ignored => write!(f, "ignored"),
DiagSeverity::Note => write!(f, "note"),
DiagSeverity::Warning => write!(f, "warning"),
DiagSeverity::Error => write!(f, "error"),
DiagSeverity::Fatal => write!(f, "fatal error"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
impl SourceLocation {
pub fn new(file: &str, line: u32, column: u32) -> Self {
Self {
file: file.to_string(),
line,
column,
}
}
pub fn dummy() -> Self {
Self {
file: "<unknown>".to_string(),
line: 0,
column: 0,
}
}
}
impl fmt::Display for SourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceRange {
pub start: SourceLocation,
pub end: SourceLocation,
}
impl SourceRange {
pub fn new(start: SourceLocation, end: SourceLocation) -> Self {
Self { start, end }
}
pub fn point(loc: SourceLocation) -> Self {
Self {
start: loc.clone(),
end: loc,
}
}
}
impl fmt::Display for SourceRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.start == self.end {
write!(f, "{}", self.start)
} else {
write!(f, "{}..{}", self.start, self.end)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixItHint {
pub range: SourceRange,
pub replacement: String,
pub description: String,
}
impl FixItHint {
pub fn new(range: SourceRange, replacement: &str, description: &str) -> Self {
Self {
range,
replacement: replacement.to_string(),
description: description.to_string(),
}
}
pub fn insertion(loc: SourceLocation, text: &str, description: &str) -> Self {
Self {
range: SourceRange::point(loc),
replacement: text.to_string(),
description: description.to_string(),
}
}
pub fn removal(range: SourceRange, description: &str) -> Self {
Self {
range,
replacement: String::new(),
description: description.to_string(),
}
}
}
impl fmt::Display for FixItHint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"fix-it: \"{}\": replace {} with \"{}\"",
self.description, self.range, self.replacement
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DiagCategory {
Semantic,
Parse,
Lex,
Preprocessor,
CodeGen,
Optimization,
Driver,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub severity: DiagSeverity,
pub message: String,
pub location: SourceLocation,
pub ranges: Vec<SourceRange>,
pub fixits: Vec<FixItHint>,
pub notes: Vec<String>,
pub diag_id: Option<String>,
}
impl Diagnostic {
pub fn new(severity: DiagSeverity, message: &str) -> Self {
Self {
severity,
message: message.to_string(),
location: SourceLocation::dummy(),
ranges: Vec::new(),
fixits: Vec::new(),
notes: Vec::new(),
diag_id: None,
}
}
pub fn at(mut self, loc: SourceLocation) -> Self {
self.location = loc;
self
}
pub fn with_range(mut self, range: SourceRange) -> Self {
self.ranges.push(range);
self
}
pub fn with_fixit(mut self, fixit: FixItHint) -> Self {
self.fixits.push(fixit);
self
}
pub fn with_note(mut self, note: &str) -> Self {
self.notes.push(note.to_string());
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.diag_id = Some(id.to_string());
self
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}: {}", self.location, self.severity, self.message)?;
for note in &self.notes {
write!(f, "\n note: {}", note)?;
}
for fixit in &self.fixits {
write!(f, "\n {}", fixit)?;
}
Ok(())
}
}
pub struct DiagnosticEmitter {
diagnostics: Vec<Diagnostic>,
warnings_as_errors: bool,
suppressed: HashSet<String>,
enabled_only: Option<HashSet<String>>,
error_limit: usize,
error_count: usize,
warning_count: usize,
source_files: HashMap<String, Vec<String>>,
}
impl DiagnosticEmitter {
pub fn new() -> Self {
Self {
diagnostics: Vec::new(),
warnings_as_errors: false,
suppressed: HashSet::new(),
enabled_only: None,
error_limit: 20,
error_count: 0,
warning_count: 0,
source_files: HashMap::new(),
}
}
pub fn emit(&mut self, diag: Diagnostic) {
if let Some(ref id) = diag.diag_id {
if self.suppressed.contains(id) {
return;
}
if let Some(ref enabled) = self.enabled_only {
if !enabled.contains(id) {
return;
}
}
}
let effective_severity =
if diag.severity == DiagSeverity::Warning && self.warnings_as_errors {
DiagSeverity::Error
} else {
diag.severity
};
match effective_severity {
DiagSeverity::Error | DiagSeverity::Fatal => {
self.error_count += 1;
}
DiagSeverity::Warning => {
self.warning_count += 1;
}
_ => {}
}
self.diagnostics.push(diag);
}
pub fn error(&mut self, message: &str) {
self.emit(Diagnostic::new(DiagSeverity::Error, message).with_id("err_generic"));
}
pub fn error_at(&mut self, loc: SourceLocation, message: &str) {
self.emit(
Diagnostic::new(DiagSeverity::Error, message)
.at(loc)
.with_id("err_generic"),
);
}
pub fn warning(&mut self, message: &str) {
self.emit(Diagnostic::new(DiagSeverity::Warning, message).with_id("warn_generic"));
}
pub fn warning_at(&mut self, loc: SourceLocation, message: &str) {
self.emit(
Diagnostic::new(DiagSeverity::Warning, message)
.at(loc)
.with_id("warn_generic"),
);
}
pub fn note(&mut self, message: &str) {
self.emit(Diagnostic::new(DiagSeverity::Note, message));
}
pub fn fatal(&mut self, message: &str) -> Diagnostic {
let diag = Diagnostic::new(DiagSeverity::Fatal, message);
self.emit(diag.clone());
diag
}
pub fn set_warnings_as_errors(&mut self, enable: bool) {
self.warnings_as_errors = enable;
}
pub fn suppress(&mut self, diag_id: &str) {
self.suppressed.insert(diag_id.to_string());
}
pub fn enable_only(&mut self, ids: Vec<String>) {
self.enabled_only = Some(ids.into_iter().collect());
}
pub fn set_error_limit(&mut self, limit: usize) {
self.error_limit = limit;
}
pub fn add_source_file(&mut self, filename: &str, content: &str) {
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
self.source_files.insert(filename.to_string(), lines);
}
pub fn get_source_line(&self, file: &str, line: u32) -> Option<&str> {
self.source_files
.get(file)
.and_then(|lines| lines.get(line.saturating_sub(1) as usize))
.map(|s| s.as_str())
}
pub fn format_snippet(&self, loc: &SourceLocation, context_lines: u32) -> String {
let mut result = String::new();
if let Some(lines) = self.source_files.get(&loc.file) {
let line_idx = loc.line.saturating_sub(1) as usize;
let start = line_idx.saturating_sub(context_lines as usize);
let end = (line_idx + context_lines as usize + 1).min(lines.len());
for i in start..end {
let line_num = i + 1;
let marker = if line_num as u32 == loc.line {
">"
} else {
" "
};
result.push_str(&format!("{} {:4} | {}\n", marker, line_num, lines[i]));
if line_num as u32 == loc.line {
let caret = " ".repeat(4 + 5 + loc.column.saturating_sub(1) as usize);
result.push_str(&format!("{}^\n", caret));
}
}
}
result
}
pub fn format_all(&self) -> String {
let mut output = String::new();
for diag in &self.diagnostics {
output.push_str(&format!("{}\n", diag));
if diag.location.line > 0 {
output.push_str(&self.format_snippet(&diag.location, 1));
}
}
output
}
pub fn print_all(&self) {
eprint!("{}", self.format_all());
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn error_count(&self) -> usize {
self.error_count
}
pub fn warning_count(&self) -> usize {
self.warning_count
}
pub fn error_limit_reached(&self) -> bool {
self.error_count >= self.error_limit
}
pub fn has_no_errors(&self) -> bool {
self.error_count == 0
}
pub fn clear(&mut self) {
self.diagnostics.clear();
self.error_count = 0;
self.warning_count = 0;
}
pub fn fixit(&mut self, loc: SourceLocation, replacement: &str, description: &str) {
let fixit = FixItHint::insertion(loc, replacement, description);
let diag = Diagnostic::new(
DiagSeverity::Note,
&format!("suggested fix: {}", description),
)
.with_fixit(fixit);
self.emit(diag);
}
pub fn suggest_const(&mut self, loc: SourceLocation) {
self.fixit(loc, "const ", "add 'const' qualifier");
}
pub fn suggest_parentheses(&mut self, range: SourceRange) {
let fixit = FixItHint::new(
SourceRange::new(range.start.clone(), range.start.clone()),
"(",
"add '('",
);
let diag = Diagnostic::new(DiagSeverity::Note, "suggest parentheses around expression")
.with_fixit(fixit);
self.emit(diag);
}
pub fn suggest_removal(&mut self, range: SourceRange, description: &str) {
let fixit = FixItHint::removal(range, description);
let diag = Diagnostic::new(DiagSeverity::Note, description).with_fixit(fixit);
self.emit(diag);
}
pub fn warn_unused_variable(&mut self, loc: SourceLocation, name: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!("unused variable '{}'", name),
)
.at(loc)
.with_id("warn_unused_variable");
self.emit(diag);
}
pub fn warn_unused_parameter(&mut self, loc: SourceLocation, name: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!("unused parameter '{}'", name),
)
.at(loc)
.with_id("warn_unused_parameter");
self.emit(diag);
}
pub fn warn_unused_function(&mut self, loc: SourceLocation, name: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!("unused function '{}'", name),
)
.at(loc)
.with_id("warn_unused_function");
self.emit(diag);
}
pub fn warn_missing_return(&mut self, loc: SourceLocation, func_name: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!("control reaches end of non-void function '{}'", func_name),
)
.at(loc)
.with_id("warn_return_type");
self.emit(diag);
}
pub fn warn_division_by_zero(&mut self, loc: SourceLocation) {
let diag = Diagnostic::new(DiagSeverity::Warning, "division by zero is undefined")
.at(loc)
.with_id("warn_division_by_zero");
self.emit(diag);
}
pub fn warn_format_string(&mut self, loc: SourceLocation, expected: &str, got: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!(
"format specifies type '{}' but the argument has type '{}'",
expected, got
),
)
.at(loc)
.with_id("warn_format");
self.emit(diag);
}
pub fn note_previous_definition(&mut self, loc: SourceLocation, name: &str) {
let diag = Diagnostic::new(
DiagSeverity::Note,
&format!("previous definition of '{}' is here", name),
)
.at(loc);
self.emit(diag);
}
pub fn note_implicit_conversion(&mut self, loc: SourceLocation, from: &str, to: &str) {
let diag = Diagnostic::new(
DiagSeverity::Note,
&format!("implicit conversion from '{}' to '{}'", from, to),
)
.at(loc);
self.emit(diag);
}
pub fn warn_int_to_pointer_cast(&mut self, loc: SourceLocation) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
"cast to pointer from integer of different size",
)
.at(loc)
.with_id("warn_int_to_pointer_cast");
self.emit(diag);
}
pub fn warn_pointer_to_int_cast(&mut self, loc: SourceLocation) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
"cast to integer from pointer of different size",
)
.at(loc)
.with_id("warn_pointer_to_int_cast");
self.emit(diag);
}
pub fn warn_incompatible_pointer_types(
&mut self,
loc: SourceLocation,
expected: &str,
got: &str,
) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!(
"incompatible pointer types assigning to '{}' from '{}'",
expected, got
),
)
.at(loc)
.with_id("warn_incompatible_pointer_types");
self.emit(diag);
}
pub fn warn_deprecated(&mut self, loc: SourceLocation, name: &str, message: &str) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
&format!("'{}' is deprecated: {}", name, message),
)
.at(loc)
.with_id("warn_deprecated");
self.emit(diag);
}
pub fn warn_fallthrough(&mut self, loc: SourceLocation) {
let diag = Diagnostic::new(
DiagSeverity::Warning,
"unannotated fallthrough between switch cases",
)
.at(loc.clone())
.with_id("warn_implicit_fallthrough")
.with_note("insert '[[fallthrough]];' to silence this warning")
.with_fixit(FixItHint::insertion(
loc.clone(),
"[[fallthrough]]; ",
"add fallthrough annotation",
));
self.emit(diag);
}
pub fn custom_diag(
&mut self,
severity: DiagSeverity,
id: &str,
loc: SourceLocation,
message: &str,
) {
let diag = Diagnostic::new(severity, message).at(loc).with_id(id);
self.emit(diag);
}
pub fn categorize(&self, diag_id: &str) -> DiagCategory {
if diag_id.starts_with("err_") {
DiagCategory::Semantic
} else if diag_id.starts_with("warn_") {
DiagCategory::Semantic
} else if diag_id.starts_with("pp_") {
DiagCategory::Preprocessor
} else if diag_id.starts_with("drv_") {
DiagCategory::Driver
} else {
DiagCategory::Unknown
}
}
pub fn format_gcc_style(&self, diag: &Diagnostic) -> String {
let severity_str = match diag.severity {
DiagSeverity::Error => "error",
DiagSeverity::Warning => "warning",
DiagSeverity::Note => "note",
DiagSeverity::Fatal => "fatal error",
DiagSeverity::Ignored => "ignored",
};
let id_str = diag
.diag_id
.as_ref()
.map(|id| format!(" [{}]", id))
.unwrap_or_default();
format!(
"{}: {}:{} {}",
diag.location, severity_str, id_str, diag.message
)
}
pub fn format_gcc_all(&self) -> String {
let mut out = String::new();
for diag in &self.diagnostics {
out.push_str(&self.format_gcc_style(diag));
out.push('\n');
}
out
}
pub fn summary(&self) -> String {
format!(
"{} error(s), {} warning(s)",
self.error_count, self.warning_count
)
}
pub fn should_fail(&self) -> bool {
self.error_count > 0
}
pub fn max_severity(&self) -> DiagSeverity {
self.diagnostics
.iter()
.map(|d| d.severity)
.max()
.unwrap_or(DiagSeverity::Ignored)
}
pub fn count_by_severity(&self, severity: DiagSeverity) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity == severity)
.count()
}
pub fn note_previous_declaration(&mut self, loc: SourceLocation, name: &str) {
let note = Diagnostic::new(
DiagSeverity::Note,
&format!("previous declaration of '{}' was here", name),
)
.at(loc);
self.emit(note);
}
pub fn note_conflicting_type(&mut self, loc: SourceLocation, expected: &str, got: &str) {
let note = Diagnostic::new(
DiagSeverity::Note,
&format!("expected '{}' but got '{}'", expected, got),
)
.at(loc);
self.emit(note);
}
}
pub struct DeepSema {
pub standard: CLangStandard,
pub type_system: TypeSystem,
pub scope_manager: ScopeManager,
pub decl_checker: DeclChecker,
pub expr_checker: ExprChecker,
pub stmt_checker: StmtChecker,
pub const_eval: ConstExprEvaluator,
pub attr_checker: AttrChecker,
pub diagnostics: DiagnosticEmitter,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl DeepSema {
pub fn new(standard: CLangStandard) -> Self {
Self {
standard,
type_system: TypeSystem::new(standard),
scope_manager: ScopeManager::new(),
decl_checker: DeclChecker::new(standard),
expr_checker: ExprChecker::new(standard),
stmt_checker: StmtChecker::new(),
const_eval: ConstExprEvaluator::new(standard),
attr_checker: AttrChecker::new(),
diagnostics: DiagnosticEmitter::new(),
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn analyze(&mut self, tu: &TranslationUnit) -> Result<(), Vec<String>> {
self.scope_manager.enter_scope(ScopeKind::File);
self.collect_declarations(tu);
for decl in &tu.decls {
self.check_decl(decl);
}
let _tentative = self.decl_checker.finalize_tentative_defs();
self.stmt_checker.resolve_gotos();
self.scope_manager.leave_scope();
self.collect_errors();
if self.errors.is_empty() {
Ok(())
} else {
Err(self.errors.clone())
}
}
pub fn register_typedef(&mut self, name: &str, underlying: QualType) {
self.type_system.register_typedef(name, underlying.clone());
self.scope_manager
.declare_typedef(TypedefDecl::new(name, underlying));
}
pub fn register_struct(&mut self, sd: &StructDecl) {
self.type_system.register_struct(sd);
self.scope_manager.declare_struct_tag(sd.clone());
}
pub fn register_enum(&mut self, ed: &EnumDecl) {
self.type_system.register_enum(ed);
self.scope_manager.declare_enum_tag(ed.clone());
let mut val: i64 = 0;
for variant in &ed.variants {
if let Some(v) = variant.value {
val = v;
}
self.const_eval.register_enum_value(&variant.name, val);
self.scope_manager
.declare_enum_constant(&variant.name, Some(val));
val += 1;
}
}
pub fn register_function(&mut self, func: &FunctionDecl) {
self.scope_manager.declare_function(func.clone());
}
pub fn register_variable(&mut self, var: &VarDecl) {
self.scope_manager.declare_variable(var.clone());
}
fn collect_declarations(&mut self, tu: &TranslationUnit) {
for decl in &tu.decls {
match decl {
Decl::Struct(sd) => self.register_struct(sd),
Decl::Enum(ed) => self.register_enum(ed),
Decl::Typedef(td) => {
self.register_typedef(&td.name, td.underlying.clone());
}
Decl::Function(fd) => {
self.register_function(fd);
}
Decl::Variable(vd) => {
self.register_variable(vd);
}
Decl::EnumVariant(ev) => {
self.const_eval
.register_enum_value(&ev.name, ev.value.unwrap_or(0));
self.scope_manager.declare_enum_constant(&ev.name, ev.value);
}
}
}
}
fn check_decl(&mut self, decl: &Decl) {
match decl {
Decl::Function(fd) => self.check_function(fd),
Decl::Variable(vd) => {
let _ = self.decl_checker.check_var_decl(vd);
}
Decl::Typedef(td) => {
let _ = self.decl_checker.check_typedef(td);
}
Decl::Struct(sd) => {
let _ = self.decl_checker.check_struct_decl(sd);
}
Decl::Enum(ed) => {
let _ = self.decl_checker.check_enum_decl(ed);
}
Decl::EnumVariant(_) => {}
}
}
fn check_function(&mut self, func: &FunctionDecl) {
let _ = self.decl_checker.check_function_decl(func);
self.scope_manager
.enter_function(&func.name, func.ret_ty.clone());
self.stmt_checker.set_return_type(func.ret_ty.clone());
self.scope_manager.enter_scope(ScopeKind::Function);
for param in &func.params {
self.scope_manager.declare_variable(param.clone());
}
if let Some(ref body) = func.body {
self.check_compound_stmt(body);
}
if !func.ret_ty.is_void()
&& !self
.scope_manager
.current_scope()
.map(|s| s.has_returned)
.unwrap_or(false)
{
if func.body.is_some() {
self.warnings.push(format!(
"control reaches end of non-void function '{}'",
func.name
));
}
}
self.scope_manager.leave_scope();
self.stmt_checker.clear_return_type();
self.scope_manager.leave_function();
}
fn check_compound_stmt(&mut self, compound: &CompoundStmt) {
self.scope_manager.enter_scope(ScopeKind::Block);
self.stmt_checker.reset_path_returned();
for stmt in &compound.stmts {
self.check_stmt(stmt);
}
self.scope_manager.leave_scope();
}
fn check_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Compound(cs) => self.check_compound_stmt(cs),
Stmt::Return(expr) => self.check_return_stmt(expr),
Stmt::If { cond, then, els } => self.check_if_stmt(cond, then, els.as_deref()),
Stmt::While { cond, body } => self.check_while_stmt(cond, body),
Stmt::DoWhile { body, cond } => self.check_do_while_stmt(body, cond),
Stmt::For {
init,
cond,
incr,
body,
} => self.check_for_stmt(init.as_deref(), cond.as_deref(), incr.as_deref(), body),
Stmt::Switch { expr, body } => self.check_switch_stmt(expr, body),
Stmt::Case {
value,
stmt: case_body,
} => self.check_case_stmt(value, case_body),
Stmt::Default { stmt: default_body } => self.check_default_stmt(default_body),
Stmt::Break => {
let _ = self.stmt_checker.check_break();
}
Stmt::Continue => {
let _ = self.stmt_checker.check_continue();
}
Stmt::Goto { label } => {
let _ = self.stmt_checker.check_goto(label);
}
Stmt::Label {
name,
stmt: labeled_body,
} => {
self.stmt_checker.define_label(name);
self.scope_manager.define_label(name);
self.check_stmt(labeled_body);
}
Stmt::Expr(expr) => {
let _ = self.expr_checker.expr_type(expr);
}
Stmt::Decl(var) => {
let _ = self.decl_checker.check_var_decl(var);
self.scope_manager.declare_variable((**var).clone());
}
Stmt::Null => {}
}
if self.stmt_checker.is_unreachable() && !matches!(stmt, Stmt::Compound(_)) {
self.warnings.push(format!("unreachable code detected"));
}
}
fn check_return_stmt(&mut self, expr: &Option<Box<Expr>>) {
let _ = self.stmt_checker.check_return(expr);
if let Some(ref e) = expr {
if let Some(expr_ty) = self.expr_checker.expr_type(e) {
if let Some(ret_ty) = self.scope_manager.current_return_type() {
if !self.type_system.are_compatible(&expr_ty, ret_ty) {
self.warnings.push(format!(
"return type mismatch: expected '{}', got '{}'",
ret_ty, expr_ty
));
}
}
}
}
if let Some(scope) = self.scope_manager.current_scope_mut() {
scope.mark_returned();
}
}
fn check_if_stmt(&mut self, cond: &Expr, then: &Stmt, els: Option<&Stmt>) {
if let Some(cond_ty) = self.expr_checker.expr_type(cond) {
if !self.type_system.is_scalar(&cond_ty) {
self.errors.push(format!(
"if condition must have scalar type, got '{}'",
cond_ty
));
}
}
self.check_stmt(then);
if let Some(else_stmt) = els {
self.check_stmt(else_stmt);
}
}
fn check_while_stmt(&mut self, cond: &Expr, body: &Stmt) {
if let Some(cond_ty) = self.expr_checker.expr_type(cond) {
if !self.type_system.is_scalar(&cond_ty) {
self.errors.push(format!(
"while condition must have scalar type, got '{}'",
cond_ty
));
}
}
self.scope_manager.enter_loop();
self.check_stmt(body);
self.scope_manager.leave_loop();
}
fn check_do_while_stmt(&mut self, body: &Stmt, cond: &Expr) {
self.scope_manager.enter_loop();
self.check_stmt(body);
self.scope_manager.leave_loop();
if let Some(cond_ty) = self.expr_checker.expr_type(cond) {
if !self.type_system.is_scalar(&cond_ty) {
self.errors.push(format!(
"do-while condition must have scalar type, got '{}'",
cond_ty
));
}
}
}
fn check_for_stmt(
&mut self,
init: Option<&Stmt>,
cond: Option<&Expr>,
incr: Option<&Expr>,
body: &Stmt,
) {
self.scope_manager.enter_scope(ScopeKind::Block);
self.scope_manager.enter_loop();
if let Some(init_stmt) = init {
self.check_stmt(init_stmt);
}
if let Some(cond_expr) = cond {
if let Some(cond_ty) = self.expr_checker.expr_type(cond_expr) {
if !self.type_system.is_scalar(&cond_ty) {
self.errors.push(format!(
"for condition must have scalar type, got '{}'",
cond_ty
));
}
}
}
if let Some(incr_expr) = incr {
let _ = self.expr_checker.expr_type(incr_expr);
}
self.check_stmt(body);
self.scope_manager.leave_loop();
self.scope_manager.leave_scope();
}
fn check_switch_stmt(&mut self, expr: &Expr, body: &Stmt) {
if let Some(expr_ty) = self.expr_checker.expr_type(expr) {
if !self
.type_system
.is_integer(&self.type_system.integer_promotion(&expr_ty))
{
self.errors.push(format!(
"switch expression must have integer type, got '{}'",
expr_ty
));
}
}
self.scope_manager.enter_switch();
self.stmt_checker.enter_switch();
self.check_stmt(body);
self.stmt_checker.leave_switch();
self.scope_manager.leave_switch();
}
fn check_case_stmt(&mut self, value: &Expr, case_body: &Stmt) {
let _ = self.stmt_checker.check_case(value);
if self.const_eval.is_integer_constant_expression(value) {
} else {
self.errors
.push("case label does not reduce to an integer constant".to_string());
}
self.check_stmt(case_body);
}
fn check_default_stmt(&mut self, default_body: &Stmt) {
let _ = self.stmt_checker.check_default();
self.check_stmt(default_body);
}
fn collect_errors(&mut self) {
self.errors.append(&mut self.decl_checker.take_errors());
self.warnings.append(&mut self.decl_checker.take_warnings());
self.errors.append(&mut self.expr_checker.take_errors());
self.warnings.append(&mut self.expr_checker.take_warnings());
self.errors.append(&mut self.stmt_checker.take_errors());
self.warnings.append(&mut self.stmt_checker.take_warnings());
self.errors.append(&mut self.const_eval.take_errors());
self.errors.append(&mut self.attr_checker.take_errors());
self.warnings.append(&mut self.attr_checker.take_warnings());
}
pub fn type_of_expr(&mut self, expr: &Expr) -> Option<QualType> {
self.expr_checker.expr_type(expr)
}
pub fn check_type_compatibility(&mut self, expected: &QualType, actual: &QualType) -> bool {
if !self.type_system.are_compatible(expected, actual) {
self.errors.push(format!(
"type mismatch: expected '{}', got '{}'",
expected, actual
));
false
} else {
true
}
}
pub fn dump_symbol_table(&self) -> String {
let mut out = String::from("=== Symbol Table ===\n");
for (i, scope) in self.scope_manager.scopes.iter().enumerate() {
out.push_str(&format!("Scope {} ({:?}):\n", i, scope.kind));
out.push_str(" (entries not directly accessible)\n");
}
out
}
}
pub fn make_int_var(name: &str) -> VarDecl {
VarDecl::new(name, QualType::int())
}
pub fn make_ptr_var(name: &str, pointee: QualType) -> VarDecl {
VarDecl::new(name, QualType::pointer_to(pointee))
}
pub fn make_simple_function(name: &str, body: CompoundStmt) -> FunctionDecl {
FunctionDecl {
name: name.to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}
}
pub fn make_void_function(name: &str, body: CompoundStmt) -> FunctionDecl {
FunctionDecl {
name: name.to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}
}
pub fn make_tu(functions: Vec<FunctionDecl>) -> TranslationUnit {
TranslationUnit {
decls: functions.into_iter().map(Decl::Function).collect(),
includes: Vec::new(),
defines: Vec::new(),
}
}
pub fn analyze_function(sema: &mut DeepSema, func: &FunctionDecl) -> Result<(), Vec<String>> {
let tu = TranslationUnit {
decls: vec![Decl::Function(func.clone())],
includes: Vec::new(),
defines: Vec::new(),
};
sema.analyze(&tu)
}
pub fn contains_message(errors: &[String], substring: &str) -> bool {
errors.iter().any(|e| e.contains(substring))
}
pub fn run_sema(tu: &TranslationUnit, standard: CLangStandard) -> (Vec<String>, Vec<String>) {
let mut sema = DeepSema::new(standard);
let result = sema.analyze(tu);
match result {
Ok(()) => (Vec::new(), sema.warnings),
Err(errors) => (errors, sema.warnings),
}
}
pub fn make_hello_world_tu() -> TranslationUnit {
let body = CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(Expr::IntLiteral(0))))],
};
let main_func = FunctionDecl {
name: "main".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
TranslationUnit {
decls: vec![Decl::Function(main_func)],
includes: vec!["<stdio.h>".to_string()],
defines: Vec::new(),
}
}
pub fn make_factorial_tu() -> TranslationUnit {
let n_param = VarDecl::new("n", QualType::int());
let cond = Expr::Binary(
BinaryOp::Le,
Box::new(Expr::Ident("n".to_string())),
Box::new(Expr::IntLiteral(1)),
);
let base_case = Stmt::Return(Some(Box::new(Expr::IntLiteral(1))));
let recurse = Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Ident("n".to_string())),
Box::new(Expr::Call {
callee: Box::new(Expr::Ident("factorial".to_string())),
args: vec![Expr::Binary(
BinaryOp::Sub,
Box::new(Expr::Ident("n".to_string())),
Box::new(Expr::IntLiteral(1)),
)],
}),
);
let recursive_case = Stmt::Return(Some(Box::new(recurse)));
let if_stmt = Stmt::If {
cond: Box::new(cond),
then: Box::new(base_case),
els: Some(Box::new(recursive_case)),
};
let body = CompoundStmt {
stmts: vec![if_stmt],
};
let func = FunctionDecl {
name: "factorial".to_string(),
ret_ty: QualType::int(),
params: vec![n_param],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
}
}
pub fn make_struct_tu() -> TranslationUnit {
let point_struct = StructDecl {
name: Some("Point".to_string()),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
};
let body = CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(Expr::IntLiteral(0))))],
};
let func = FunctionDecl {
name: "make_point".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
TranslationUnit {
decls: vec![Decl::Struct(point_struct), Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
}
}
pub fn make_enum_tu() -> TranslationUnit {
let color_enum = EnumDecl {
name: Some("Color".to_string()),
variants: vec![
EnumVariant::new("RED", Some(0)),
EnumVariant::new("GREEN", Some(1)),
EnumVariant::new("BLUE", Some(2)),
],
underlying: QualType::int(),
};
let body = CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(Expr::Ident("RED".to_string()))))],
};
let func = FunctionDecl {
name: "get_color".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
TranslationUnit {
decls: vec![Decl::Enum(color_enum), Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
}
}
pub fn validate_tu(tu: &TranslationUnit) -> Result<(), String> {
let mut sema = DeepSema::new(CLangStandard::C17);
match sema.analyze(tu) {
Ok(()) => Ok(()),
Err(errors) => Err(errors.join("; ")),
}
}
pub fn check_stmt_in_void_func(stmt: &Stmt) -> Result<(), Vec<String>> {
let body = CompoundStmt {
stmts: vec![stmt.clone()],
};
let func = make_void_function("test", body);
let mut sema = DeepSema::new(CLangStandard::C17);
let tu = make_tu(vec![func]);
sema.analyze(&tu)
}
pub fn check_expr_in_func(expr: &Expr) -> Result<(), Vec<String>> {
let body = CompoundStmt {
stmts: vec![Stmt::Return(Some(Box::new(expr.clone())))],
};
let func = make_simple_function("test", body);
let mut sema = DeepSema::new(CLangStandard::C17);
let tu = make_tu(vec![func]);
sema.analyze(&tu)
}
pub struct IntegerTypeInfo {
pub name: &'static str,
pub size_bytes: usize,
pub alignment: usize,
pub is_signed: bool,
pub min_value: i64,
pub max_value: u64,
}
pub fn integer_type_infos() -> Vec<IntegerTypeInfo> {
vec![
IntegerTypeInfo {
name: "char",
size_bytes: 1,
alignment: 1,
is_signed: false,
min_value: -128,
max_value: 127,
},
IntegerTypeInfo {
name: "signed char",
size_bytes: 1,
alignment: 1,
is_signed: true,
min_value: -128,
max_value: 127,
},
IntegerTypeInfo {
name: "unsigned char",
size_bytes: 1,
alignment: 1,
is_signed: false,
min_value: 0,
max_value: 255,
},
IntegerTypeInfo {
name: "short",
size_bytes: 2,
alignment: 2,
is_signed: true,
min_value: -32768,
max_value: 32767,
},
IntegerTypeInfo {
name: "unsigned short",
size_bytes: 2,
alignment: 2,
is_signed: false,
min_value: 0,
max_value: 65535,
},
IntegerTypeInfo {
name: "int",
size_bytes: 4,
alignment: 4,
is_signed: true,
min_value: -2_147_483_648,
max_value: 2_147_483_647,
},
IntegerTypeInfo {
name: "unsigned int",
size_bytes: 4,
alignment: 4,
is_signed: false,
min_value: 0,
max_value: 4_294_967_295u64,
},
IntegerTypeInfo {
name: "long",
size_bytes: 8,
alignment: 8,
is_signed: true,
min_value: i64::MIN,
max_value: i64::MAX as u64,
},
IntegerTypeInfo {
name: "unsigned long",
size_bytes: 8,
alignment: 8,
is_signed: false,
min_value: 0,
max_value: u64::MAX,
},
IntegerTypeInfo {
name: "long long",
size_bytes: 8,
alignment: 8,
is_signed: true,
min_value: i64::MIN,
max_value: i64::MAX as u64,
},
IntegerTypeInfo {
name: "unsigned long long",
size_bytes: 8,
alignment: 8,
is_signed: false,
min_value: 0,
max_value: u64::MAX,
},
]
}
pub struct FloatTypeInfo {
pub name: &'static str,
pub size_bytes: usize,
pub alignment: usize,
}
pub fn float_type_infos() -> Vec<FloatTypeInfo> {
vec![
FloatTypeInfo {
name: "float",
size_bytes: 4,
alignment: 4,
},
FloatTypeInfo {
name: "double",
size_bytes: 8,
alignment: 8,
},
FloatTypeInfo {
name: "long double",
size_bytes: 16,
alignment: 16,
},
]
}
pub fn operator_precedence(op: BinaryOp) -> u32 {
match op {
BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => 130,
BinaryOp::Add | BinaryOp::Sub => 120,
BinaryOp::Shl | BinaryOp::Shr => 110,
BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => 100,
BinaryOp::Eq | BinaryOp::Ne => 90,
BinaryOp::And => 80,
BinaryOp::Xor => 70,
BinaryOp::Or => 60,
BinaryOp::LogicAnd => 50,
BinaryOp::LogicOr => 40,
BinaryOp::Assign
| BinaryOp::AddAssign
| BinaryOp::SubAssign
| BinaryOp::MulAssign
| BinaryOp::DivAssign
| BinaryOp::ModAssign
| BinaryOp::AndAssign
| BinaryOp::OrAssign
| BinaryOp::XorAssign
| BinaryOp::ShlAssign
| BinaryOp::ShrAssign => 20,
BinaryOp::Comma => 10,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Associativity {
LeftToRight,
RightToLeft,
}
pub fn operator_associativity(op: BinaryOp) -> Associativity {
match op {
BinaryOp::Assign
| BinaryOp::AddAssign
| BinaryOp::SubAssign
| BinaryOp::MulAssign
| BinaryOp::DivAssign
| BinaryOp::ModAssign
| BinaryOp::AndAssign
| BinaryOp::OrAssign
| BinaryOp::XorAssign
| BinaryOp::ShlAssign
| BinaryOp::ShrAssign => Associativity::RightToLeft,
_ => Associativity::LeftToRight,
}
}
pub fn common_real_type(types: &[QualType]) -> Option<QualType> {
if types.is_empty() {
return None;
}
let ts = TypeSystem::new(CLangStandard::C17);
let mut common = types[0].clone();
for ty in &types[1..] {
common = ts.usual_arithmetic_conversions(&common, ty);
}
Some(common)
}
pub fn check_constant_expression(expr: &Expr, evaluator: &mut ConstExprEvaluator) -> ConstantValue {
evaluate_to_constant_value(expr, evaluator)
}
pub fn debug_type(qt: &QualType) -> String {
let ts = TypeSystem::new(CLangStandard::C17);
format!(
"{} (class={:?}, size={}, align={}, complete={})",
qt,
ts.type_class(qt),
ts.type_size(qt),
ts.type_alignment(qt),
ts.is_complete_type(qt)
)
}
pub fn make_quadratic(a: &str, b: &str, c: &str, x: &str) -> Expr {
Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Ident(a.to_string())),
Box::new(Expr::Ident(x.to_string())),
)),
Box::new(Expr::Ident(x.to_string())),
)),
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Ident(b.to_string())),
Box::new(Expr::Ident(x.to_string())),
)),
)),
Box::new(Expr::Ident(c.to_string())),
)
}
pub fn make_abs_expr(x: &str) -> Expr {
Expr::Conditional(
Box::new(Expr::Binary(
BinaryOp::Ge,
Box::new(Expr::Ident(x.to_string())),
Box::new(Expr::IntLiteral(0)),
)),
Box::new(Expr::Ident(x.to_string())),
Box::new(Expr::Unary(
UnaryOp::Minus,
Box::new(Expr::Ident(x.to_string())),
)),
)
}
pub fn make_max_expr(a: &str, b: &str) -> Expr {
Expr::Conditional(
Box::new(Expr::Binary(
BinaryOp::Gt,
Box::new(Expr::Ident(a.to_string())),
Box::new(Expr::Ident(b.to_string())),
)),
Box::new(Expr::Ident(a.to_string())),
Box::new(Expr::Ident(b.to_string())),
)
}
pub fn make_min_expr(a: &str, b: &str) -> Expr {
Expr::Conditional(
Box::new(Expr::Binary(
BinaryOp::Lt,
Box::new(Expr::Ident(a.to_string())),
Box::new(Expr::Ident(b.to_string())),
)),
Box::new(Expr::Ident(a.to_string())),
Box::new(Expr::Ident(b.to_string())),
)
}
pub fn make_while(cond: Expr, body: Stmt) -> Stmt {
Stmt::While {
cond: Box::new(cond),
body: Box::new(body),
}
}
pub fn make_for(init: Option<Stmt>, cond: Option<Expr>, incr: Option<Expr>, body: Stmt) -> Stmt {
Stmt::For {
init: init.map(Box::new),
cond: cond.map(Box::new),
incr: incr.map(Box::new),
body: Box::new(body),
}
}
pub fn make_switch(expr: Expr, body: Stmt) -> Stmt {
Stmt::Switch {
expr: Box::new(expr),
body: Box::new(body),
}
}
pub fn make_case(value: i64, stmt: Stmt) -> Stmt {
Stmt::Case {
value: Box::new(Expr::IntLiteral(value)),
stmt: Box::new(stmt),
}
}
pub fn make_default(stmt: Stmt) -> Stmt {
Stmt::Default {
stmt: Box::new(stmt),
}
}
pub fn make_goto(label: &str) -> Stmt {
Stmt::Goto {
label: label.to_string(),
}
}
pub fn make_label(name: &str, stmt: Stmt) -> Stmt {
Stmt::Label {
name: name.to_string(),
stmt: Box::new(stmt),
}
}
pub fn make_sizeof(expr: Expr) -> Expr {
Expr::SizeOf(Box::new(expr))
}
pub fn make_alignof(expr: Expr) -> Expr {
Expr::AlignOf(Box::new(expr))
}
pub fn make_cast(target: QualType, expr: Expr) -> Expr {
Expr::Cast(target, Box::new(expr))
}
pub fn make_call(name: &str, args: Vec<Expr>) -> Expr {
Expr::Call {
callee: Box::new(Expr::Ident(name.to_string())),
args,
}
}
pub fn make_subscript(base: &str, index: i64) -> Expr {
Expr::Subscript {
base: Box::new(Expr::Ident(base.to_string())),
index: Box::new(Expr::IntLiteral(index)),
}
}
pub fn make_member(base: &str, field: &str, is_arrow: bool) -> Expr {
Expr::Member {
base: Box::new(Expr::Ident(base.to_string())),
field: field.to_string(),
is_arrow,
}
}
pub fn make_postinc(var: &str) -> Expr {
Expr::PostInc(Box::new(Expr::Ident(var.to_string())))
}
pub fn make_postdec(var: &str) -> Expr {
Expr::PostDec(Box::new(Expr::Ident(var.to_string())))
}
pub fn make_preinc(var: &str) -> Expr {
Expr::PreInc(Box::new(Expr::Ident(var.to_string())))
}
pub fn make_predec(var: &str) -> Expr {
Expr::PreDec(Box::new(Expr::Ident(var.to_string())))
}
pub fn make_compound_literal(ty: QualType, init: Expr) -> Expr {
Expr::CompoundLiteral(ty, Box::new(init))
}
pub fn make_str_literal(s: &str) -> Expr {
Expr::StringLiteral(s.to_string())
}
pub fn make_char_literal(c: char) -> Expr {
Expr::CharLiteral(c)
}
pub fn make_float_literal(v: f64) -> Expr {
Expr::FloatLiteral(v)
}
pub fn make_double_literal(v: f64) -> Expr {
Expr::DoubleLiteral(v)
}
pub fn make_uint_literal(v: u64) -> Expr {
Expr::UIntLiteral(v, false)
}
pub fn make_sizeof_type(ty: QualType) -> Expr {
Expr::SizeOfType(ty)
}
pub fn make_alignof_type(ty: QualType) -> Expr {
Expr::AlignOfType(ty)
}
pub fn make_addrof(var: &str) -> Expr {
Expr::Unary(UnaryOp::AddrOf, Box::new(Expr::Ident(var.to_string())))
}
pub fn make_deref(var: &str) -> Expr {
Expr::Unary(UnaryOp::Deref, Box::new(Expr::Ident(var.to_string())))
}
pub fn make_bitnot(expr: Expr) -> Expr {
Expr::Unary(UnaryOp::BitNot, Box::new(expr))
}
pub fn make_lnot(expr: Expr) -> Expr {
Expr::Unary(UnaryOp::Not, Box::new(expr))
}
pub fn make_add(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Add, Box::new(lhs), Box::new(rhs))
}
pub fn make_sub(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Sub, Box::new(lhs), Box::new(rhs))
}
pub fn make_mul(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Mul, Box::new(lhs), Box::new(rhs))
}
pub fn make_div(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Div, Box::new(lhs), Box::new(rhs))
}
pub fn make_eq(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Eq, Box::new(lhs), Box::new(rhs))
}
pub fn make_ne(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Ne, Box::new(lhs), Box::new(rhs))
}
pub fn make_lt(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Lt, Box::new(lhs), Box::new(rhs))
}
pub fn make_gt(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::Gt, Box::new(lhs), Box::new(rhs))
}
pub fn make_land(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::LogicAnd, Box::new(lhs), Box::new(rhs))
}
pub fn make_lor(lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(BinaryOp::LogicOr, Box::new(lhs), Box::new(rhs))
}
pub fn make_assign(lhs: Expr, rhs: Expr) -> Expr {
Expr::Assign(BinaryOp::Assign, Box::new(lhs), Box::new(rhs))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sema() -> DeepSema {
DeepSema::new(CLangStandard::C17)
}
fn make_int_literal(v: i64) -> Expr {
Expr::IntLiteral(v)
}
fn make_ident(name: &str) -> Expr {
Expr::Ident(name.to_string())
}
fn make_binary(op: BinaryOp, lhs: Expr, rhs: Expr) -> Expr {
Expr::Binary(op, Box::new(lhs), Box::new(rhs))
}
fn make_assign(op: BinaryOp, lhs: Expr, rhs: Expr) -> Expr {
Expr::Assign(op, Box::new(lhs), Box::new(rhs))
}
fn make_return(expr: Option<Expr>) -> Stmt {
Stmt::Return(expr.map(Box::new))
}
fn make_if(cond: Expr, then: Stmt, els: Option<Stmt>) -> Stmt {
Stmt::If {
cond: Box::new(cond),
then: Box::new(then),
els: els.map(Box::new),
}
}
fn make_compound(stmts: Vec<Stmt>) -> Stmt {
Stmt::Compound(CompoundStmt { stmts })
}
#[test]
fn test_type_system_basic_integer() {
let ts = TypeSystem::new(CLangStandard::C17);
let int_ty = QualType::int();
assert!(ts.is_integer(&int_ty));
assert!(ts.is_signed_integer(&int_ty));
assert!(!ts.is_unsigned(&int_ty));
}
#[test]
fn test_type_system_unsigned() {
let ts = TypeSystem::new(CLangStandard::C17);
let uint_ty = QualType::uint();
assert!(ts.is_integer(&uint_ty));
assert!(ts.is_unsigned(&uint_ty));
assert!(!ts.is_signed_integer(&uint_ty));
}
#[test]
fn test_type_system_floating() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.is_floating(&QualType::float_()));
assert!(ts.is_floating(&QualType::double()));
assert!(!ts.is_integer(&QualType::float_()));
}
#[test]
fn test_type_system_pointer() {
let ts = TypeSystem::new(CLangStandard::C17);
let ptr_ty = QualType::pointer_to(QualType::int());
assert!(ts.is_pointer(&ptr_ty));
assert!(ts.is_scalar(&ptr_ty));
assert!(!ts.is_arithmetic(&ptr_ty));
}
#[test]
fn test_integer_rank() {
let ts = TypeSystem::new(CLangStandard::C17);
assert_eq!(ts.integer_rank(&QualType::char()), Some(IntegerRank::Char));
assert_eq!(
ts.integer_rank(&QualType::short()),
Some(IntegerRank::Short)
);
assert_eq!(ts.integer_rank(&QualType::int()), Some(IntegerRank::Int));
assert_eq!(ts.integer_rank(&QualType::long()), Some(IntegerRank::Long));
assert!(ts.integer_rank(&QualType::float_()).is_none());
}
#[test]
fn test_integer_promotion() {
let ts = TypeSystem::new(CLangStandard::C17);
let promoted = ts.integer_promotion(&QualType::char());
assert!(matches!(*promoted.base, TypeNode::Int));
let promoted = ts.integer_promotion(&QualType::int());
assert!(matches!(*promoted.base, TypeNode::Int));
}
#[test]
fn test_usual_arithmetic_conversions() {
let ts = TypeSystem::new(CLangStandard::C17);
let result = ts.usual_arithmetic_conversions(&QualType::int(), &QualType::float_());
assert!(matches!(*result.base, TypeNode::Float));
let result = ts.usual_arithmetic_conversions(&QualType::int(), &QualType::double());
assert!(matches!(*result.base, TypeNode::Double));
let result = ts.usual_arithmetic_conversions(&QualType::long(), &QualType::int());
assert!(matches!(*result.base, TypeNode::Long));
}
#[test]
fn test_type_compatibility_same_types() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.are_compatible(&QualType::int(), &QualType::int()));
assert!(ts.are_compatible(&QualType::double(), &QualType::double()));
}
#[test]
fn test_type_compatibility_different_types() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(!ts.are_compatible(&QualType::int(), &QualType::float_()));
assert!(!ts.are_compatible(&QualType::char(), &QualType::short()));
}
#[test]
fn test_type_compatibility_pointers() {
let ts = TypeSystem::new(CLangStandard::C17);
let p1 = QualType::pointer_to(QualType::int());
let p2 = QualType::pointer_to(QualType::int());
let p3 = QualType::pointer_to(QualType::float_());
assert!(ts.are_compatible(&p1, &p2));
assert!(!ts.are_compatible(&p1, &p3));
}
#[test]
fn test_void_type() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.is_void(&QualType::void()));
assert!(!ts.is_integer(&QualType::void()));
assert!(!ts.is_scalar(&QualType::void()));
}
#[test]
fn test_canonical_type_strips_typedef() {
let ts = TypeSystem::new(CLangStandard::C17);
let td = QualType::new(TypeNode::Typedef {
name: "myint".to_string(),
underlying: Box::new(QualType::int()),
});
let canonical = ts.canonical_type(&td);
assert!(matches!(*canonical.base, TypeNode::Int));
}
#[test]
fn test_base_element_type() {
let ts = TypeSystem::new(CLangStandard::C17);
let ptr_to_ptr = QualType::pointer_to(QualType::pointer_to(QualType::int()));
let base = ts.base_element_type(&ptr_to_ptr);
assert!(matches!(*base.base, TypeNode::Int));
}
#[test]
fn test_bool_type() {
let ts = TypeSystem::new(CLangStandard::C17);
let b = QualType::bool_();
assert!(ts.is_bool(&b));
assert!(ts.is_integer(&b));
}
#[test]
fn test_scope_enter_leave() {
let mut sm = ScopeManager::new();
assert_eq!(sm.scope_depth(), 0);
sm.enter_scope(ScopeKind::File);
assert_eq!(sm.scope_depth(), 1);
sm.enter_scope(ScopeKind::Block);
assert_eq!(sm.scope_depth(), 2);
sm.leave_scope();
assert_eq!(sm.scope_depth(), 1);
sm.leave_scope();
assert_eq!(sm.scope_depth(), 0);
}
#[test]
fn test_variable_lookup() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
sm.declare_variable(VarDecl::new("x", QualType::int()));
let found = sm.lookup_variable("x");
assert!(found.is_some());
assert_eq!(found.unwrap().name, "x");
assert!(sm.lookup_variable("y").is_none());
}
#[test]
fn test_nested_scope_shadowing() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
sm.declare_variable(VarDecl::new("x", QualType::int()));
sm.enter_scope(ScopeKind::Block);
sm.declare_variable(VarDecl::new("x", QualType::float_()));
let found = sm.lookup_variable("x");
assert!(found.is_some());
assert!(matches!(*found.unwrap().ty.base, TypeNode::Float));
}
#[test]
fn test_loop_switch_tracking() {
let mut sm = ScopeManager::new();
assert!(!sm.in_loop());
assert!(!sm.in_switch());
sm.enter_loop();
assert!(sm.in_loop());
sm.enter_switch();
assert!(sm.in_loop());
assert!(sm.in_switch());
sm.leave_switch();
assert!(!sm.in_switch());
sm.leave_loop();
assert!(!sm.in_loop());
}
#[test]
fn test_function_context() {
let mut sm = ScopeManager::new();
sm.enter_function("main", QualType::int());
assert_eq!(sm.current_function_name(), Some("main"));
assert!(sm.current_return_type().is_some());
sm.leave_function();
assert!(sm.current_function_name().is_none());
assert!(sm.current_return_type().is_none());
}
#[test]
fn test_var_decl_void_error() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let var = VarDecl::new("v", QualType::void());
let result = dc.check_var_decl(&var);
assert!(result.is_ok());
assert!(!dc.errors().is_empty());
}
#[test]
fn test_var_decl_extern_static_conflict() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let mut var = VarDecl::new("x", QualType::int());
var.is_extern = true;
var.is_static = true;
let _ = dc.check_var_decl(&var);
assert!(dc
.errors()
.iter()
.any(|e| e.contains("extern") && e.contains("static")));
}
#[test]
fn test_function_return_array_error() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let mut func = FunctionDecl::new(
"f",
QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
}),
);
let _ = dc.check_function_decl(&func);
assert!(dc
.errors()
.iter()
.any(|e| e.contains("cannot return array")));
}
#[test]
fn test_check_call_args_count_mismatch() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let params = vec![
VarDecl::new("a", QualType::int()),
VarDecl::new("b", QualType::int()),
];
let args = vec![QualType::int()]; let result = dc.check_call_args("f", ¶ms, &args, false);
assert!(result.is_err());
}
#[test]
fn test_expr_type_int_literal() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let ty = ec.expr_type(&Expr::IntLiteral(42));
assert!(ty.is_some());
assert!(matches!(*ty.unwrap().base, TypeNode::Int));
}
#[test]
fn test_expr_type_add_int() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let expr = Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
);
let ty = ec.expr_type(&expr);
assert!(ty.is_some());
assert!(matches!(*ty.unwrap().base, TypeNode::Int));
}
#[test]
fn test_is_lvalue_ident() {
let ec = ExprChecker::new(CLangStandard::C17);
assert!(ec.is_lvalue(&Expr::Ident("x".to_string())));
}
#[test]
fn test_is_rvalue_literal() {
let ec = ExprChecker::new(CLangStandard::C17);
assert!(ec.is_rvalue(&Expr::IntLiteral(5)));
}
#[test]
fn test_array_to_pointer_decay() {
let ec = ExprChecker::new(CLangStandard::C17);
let arr = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let ptr = ec.array_to_pointer(&arr);
assert!(matches!(*ptr.base, TypeNode::Pointer(_)));
}
#[test]
fn test_break_outside_loop() {
let mut sc = StmtChecker::new();
let result = sc.check_break();
assert!(result.is_err());
}
#[test]
fn test_continue_outside_loop() {
let mut sc = StmtChecker::new();
let result = sc.check_continue();
assert!(result.is_err());
}
#[test]
fn test_case_outside_switch() {
let mut sc = StmtChecker::new();
let result = sc.check_case(&Expr::IntLiteral(1));
assert!(result.is_err());
}
#[test]
fn test_default_outside_switch() {
let mut sc = StmtChecker::new();
let result = sc.check_default();
assert!(result.is_err());
}
#[test]
fn test_duplicate_case() {
let mut sc = StmtChecker::new();
sc.enter_switch();
let _ = sc.check_case(&Expr::IntLiteral(1));
let _ = sc.check_case(&Expr::IntLiteral(1)); assert!(sc.errors().iter().any(|e| e.contains("duplicate case")));
}
#[test]
fn test_duplicate_default() {
let mut sc = StmtChecker::new();
sc.enter_switch();
let _ = sc.check_default();
let result = sc.check_default();
assert!(result.is_err());
}
#[test]
fn test_goto_resolution() {
let mut sc = StmtChecker::new();
let _ = sc.check_goto("L1");
sc.define_label("L1");
sc.resolve_gotos();
assert!(sc.errors().is_empty());
}
#[test]
fn test_unresolved_goto() {
let mut sc = StmtChecker::new();
let _ = sc.check_goto("UNDEFINED");
sc.resolve_gotos();
assert!(!sc.errors().is_empty());
assert!(sc.errors().iter().any(|e| e.contains("undeclared label")));
}
#[test]
fn test_const_eval_int_literal() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::IntLiteral(42));
assert_eq!(val, Some(42));
}
#[test]
fn test_const_eval_binary_add() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(10)),
Box::new(Expr::IntLiteral(32)),
);
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, Some(42));
}
#[test]
fn test_const_eval_division_by_zero() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Binary(
BinaryOp::Div,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(0)),
);
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, None);
assert!(ce.errors().iter().any(|e| e.contains("division by zero")));
}
#[test]
fn test_const_eval_unary_minus() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Unary(UnaryOp::Minus, Box::new(Expr::IntLiteral(10)));
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, Some(-10));
}
#[test]
fn test_const_eval_enum_value() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
ce.register_enum_value("RED", 0);
ce.register_enum_value("GREEN", 1);
let val = ce.evaluate_integer_constant(&Expr::Ident("RED".to_string()));
assert_eq!(val, Some(0));
}
#[test]
fn test_const_eval_sizeof() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::SizeOfType(QualType::int()));
assert_eq!(val, Some(4));
}
#[test]
fn test_const_eval_conditional() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Conditional(
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(100)),
Box::new(Expr::IntLiteral(200)),
);
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, Some(100));
}
#[test]
fn test_attr_known_attribute() {
let ac = AttrChecker::new();
assert!(ac.is_known("deprecated"));
assert!(ac.is_known("noreturn"));
assert!(!ac.is_known("made_up_attr"));
}
#[test]
fn test_attr_conflict_noinline_always_inline() {
let mut ac = AttrChecker::new();
let attrs = vec!["noinline".to_string(), "always_inline".to_string()];
let result = ac.check_attributes(&attrs, AttrTarget::Function);
assert!(result.is_err());
}
#[test]
fn test_attr_deprecated_on_function() {
let mut ac = AttrChecker::new();
let result = ac.check_applicability("deprecated", AttrTarget::Function);
assert!(result.is_ok());
}
#[test]
fn test_attr_noreturn_on_non_function() {
let mut ac = AttrChecker::new();
let result = ac.check_applicability("noreturn", AttrTarget::Variable);
assert!(result.is_ok());
assert!(ac
.warnings()
.iter()
.any(|w| w.contains("cannot be applied")));
}
#[test]
fn test_diag_emitter_error_count() {
let mut de = DiagnosticEmitter::new();
de.error("test error");
de.warning("test warning");
assert_eq!(de.error_count(), 1);
assert_eq!(de.warning_count(), 1);
}
#[test]
fn test_diag_emitter_suppression() {
let mut de = DiagnosticEmitter::new();
de.suppress("err_generic");
de.error("should be suppressed");
assert_eq!(de.error_count(), 0);
}
#[test]
fn test_diag_emitter_warnings_as_errors() {
let mut de = DiagnosticEmitter::new();
de.set_warnings_as_errors(true);
de.warning("this is now an error");
assert_eq!(de.error_count(), 1);
assert_eq!(de.warning_count(), 0);
}
#[test]
fn test_diag_source_location_format() {
let loc = SourceLocation::new("test.c", 10, 5);
let formatted = format!("{}", loc);
assert!(formatted.contains("test.c"));
assert!(formatted.contains("10"));
assert!(formatted.contains("5"));
}
#[test]
fn test_diag_fixit_hint() {
let loc = SourceLocation::new("test.c", 1, 1);
let fixit = FixItHint::insertion(loc.clone(), "const ", "add const");
let formatted = format!("{}", fixit);
assert!(formatted.contains("add const"));
assert!(formatted.contains("test.c"));
}
#[test]
fn test_diag_snippet_format() {
let mut de = DiagnosticEmitter::new();
de.add_source_file("test.c", "line 1\nline 2\nline 3\n");
let snippet = de.format_snippet(&SourceLocation::new("test.c", 2, 3), 0);
assert!(snippet.contains("line 2"));
}
#[test]
fn test_deep_sema_empty_tu() {
let mut sema = make_sema();
let tu = TranslationUnit {
decls: Vec::new(),
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_deep_sema_simple_variable() {
let mut sema = make_sema();
let tu = TranslationUnit {
decls: vec![Decl::Variable(VarDecl::new("x", QualType::int()).global())],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_deep_sema_simple_function() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "main".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![make_return(Some(make_int_literal(0)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_deep_sema_void_func_no_return() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "do_stuff".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt { stmts: Vec::new() }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_deep_sema_non_void_no_return_warning() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "compute".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(CompoundStmt { stmts: Vec::new() }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
assert!(!sema.warnings.is_empty());
}
#[test]
fn test_deep_sema_register_typedef() {
let mut sema = make_sema();
sema.register_typedef("size_t", QualType::ulong());
let looked_up = sema.type_system.lookup_typedef("size_t");
assert!(looked_up.is_some());
}
#[test]
fn test_deep_sema_register_enum() {
let mut sema = make_sema();
let ed = EnumDecl {
name: Some("Color".to_string()),
variants: vec![
EnumVariant::new("RED", Some(0)),
EnumVariant::new("GREEN", Some(1)),
EnumVariant::new("BLUE", Some(2)),
],
underlying: QualType::int(),
};
sema.register_enum(&ed);
assert_eq!(
sema.const_eval
.evaluate_integer_constant(&Expr::Ident("RED".to_string())),
Some(0)
);
}
#[test]
fn test_deep_sema_dump_symbol_table() {
let mut sema = make_sema();
sema.scope_manager.enter_scope(ScopeKind::File);
sema.scope_manager
.declare_variable(VarDecl::new("x", QualType::int()));
let dump = sema.dump_symbol_table();
assert!(dump.contains("Scope 0"));
assert!(dump.contains("File"));
}
#[test]
fn test_deep_sema_if_condition_non_scalar() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "test".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![make_if(
Expr::Ident("undefined_var".to_string()),
Stmt::Null,
None,
)],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let _ = sema.analyze(&tu);
}
#[test]
fn test_deep_sema_for_loop_with_variables() {
let mut sema = make_sema();
let mut body = CompoundStmt::new();
body.push(Stmt::Null);
let for_stmt = Stmt::For {
init: Some(Box::new(Stmt::Decl(Box::new(VarDecl::new(
"i",
QualType::int(),
))))),
cond: Some(Box::new(Expr::Binary(
BinaryOp::Lt,
Box::new(Expr::Ident("i".to_string())),
Box::new(Expr::IntLiteral(10)),
))),
incr: Some(Box::new(Expr::PreInc(Box::new(Expr::Ident(
"i".to_string(),
))))),
body: Box::new(Stmt::Compound(body)),
};
let func = FunctionDecl {
name: "loop_test".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![for_stmt],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_type_class_from_node() {
assert_eq!(TypeClass::from_type_node(&TypeNode::Int), TypeClass::Int);
assert_eq!(
TypeClass::from_type_node(&TypeNode::Double),
TypeClass::Double
);
assert!(TypeClass::Int.is_integer());
assert!(!TypeClass::Float.is_integer());
}
#[test]
fn test_type_class_too() {
let ts = TypeSystem::new(CLangStandard::C17);
assert_eq!(ts.type_class(&QualType::int()), TypeClass::Int);
assert_eq!(ts.type_class(&QualType::void()), TypeClass::Void);
}
#[test]
fn test_compatibility_result() {
let yes = CompatibilityResult::yes();
assert!(yes.compatible);
assert_eq!(yes.conversion_needed, Some(ConversionKind::Identity));
let no = CompatibilityResult::no("different types");
assert!(!no.compatible);
assert!(no.reason.is_some());
}
#[test]
fn test_type_strip_qualifiers() {
let const_int = QualType::const_(TypeNode::Int);
let stripped = TypeSystem::strip_qualifiers(&const_int);
assert!(!stripped.is_const);
assert!(matches!(*stripped.base, TypeNode::Int));
}
#[test]
fn test_add_inner_const() {
let ptr = QualType::pointer_to(QualType::int());
let const_ptr = TypeSystem::add_inner_const(&ptr);
if let TypeNode::Pointer(ref inner) = *const_ptr.base {
assert!(inner.is_const);
} else {
panic!("expected pointer");
}
}
#[test]
fn test_assignable_same_types() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.is_assignable(&QualType::int(), &QualType::int()));
}
#[test]
fn test_assignable_void_ptr() {
let ts = TypeSystem::new(CLangStandard::C17);
let void_ptr = QualType::pointer_to(QualType::void());
let int_ptr = QualType::pointer_to(QualType::int());
assert!(ts.is_assignable(&int_ptr, &void_ptr));
assert!(ts.is_assignable(&void_ptr, &int_ptr));
}
#[test]
fn test_type_alignment() {
let ts = TypeSystem::new(CLangStandard::C17);
assert_eq!(ts.type_alignment(&QualType::char()), 1);
assert_eq!(ts.type_alignment(&QualType::int()), 4);
assert_eq!(ts.type_alignment(&QualType::long()), 8);
}
#[test]
fn test_deep_clone_type() {
let original = QualType::pointer_to(QualType::int());
let cloned = TypeSystem::deep_clone_type(&original);
assert_eq!(original, cloned);
}
#[test]
fn test_composite_type_simple() {
let ts = TypeSystem::new(CLangStandard::C17);
let result = ts.composite_type(&QualType::int(), &QualType::int());
assert!(result.is_some());
assert!(matches!(*result.unwrap().base, TypeNode::Int));
}
#[test]
fn test_in_function() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
assert!(!sm.in_function());
sm.enter_scope(ScopeKind::Function);
assert!(sm.in_function());
sm.enter_scope(ScopeKind::Block);
assert!(sm.in_function()); }
#[test]
fn test_at_file_scope() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
assert!(sm.at_file_scope());
sm.enter_scope(ScopeKind::Function);
assert!(!sm.at_file_scope());
}
#[test]
fn test_nearest_scope_of_kind() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
sm.enter_scope(ScopeKind::Block);
sm.enter_scope(ScopeKind::Block);
let found = sm.nearest_scope_of_kind(ScopeKind::File);
assert!(found.is_some());
}
#[test]
fn test_namespace_registration() {
let mut sm = ScopeManager::new();
sm.register_namespace("std");
sm.register_namespace("my");
}
#[test]
fn test_scope_dump() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::File);
sm.enter_scope(ScopeKind::Function);
sm.enter_loop();
let dump = sm.dump_scopes();
assert!(dump.contains("Scope 0"));
assert!(dump.contains("Scope 1"));
assert!(dump.contains("loop_depth: 1"));
}
#[test]
fn test_function_prototype_scope() {
let mut sm = ScopeManager::new();
sm.enter_scope(ScopeKind::FunctionPrototype);
sm.declare_variable(VarDecl::new("x", QualType::int()));
let found = sm.lookup_variable("x");
assert!(found.is_some());
sm.leave_scope();
assert!(sm.lookup_variable("x").is_empty());
}
#[test]
fn test_storage_class_from_var() {
let var = VarDecl::new("x", QualType::int());
assert_eq!(DeclChecker::infer_storage_class(&var), StorageClass::Auto);
let var = VarDecl {
is_static: true,
..VarDecl::new("y", QualType::int())
};
assert_eq!(DeclChecker::infer_storage_class(&var), StorageClass::Static);
}
#[test]
fn test_storage_class_context_auto_at_file_scope() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let result = dc.check_storage_class_context(
StorageClass::Auto,
true, false, );
assert!(result.is_err());
}
#[test]
fn test_storage_class_static_on_param() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let result = dc.check_storage_class_context(
StorageClass::Static,
false,
true, );
assert!(result.is_err());
}
#[test]
fn test_redeclaration_compatible() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let v1 = VarDecl::new("x", QualType::int());
let v2 = VarDecl::new("x", QualType::int());
let result = dc.check_redeclaration(&v1, &v2);
assert!(result.is_ok());
}
#[test]
fn test_redeclaration_incompatible_type() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let v1 = VarDecl::new("x", QualType::int());
let v2 = VarDecl::new("x", QualType::float_());
let result = dc.check_redeclaration(&v1, &v2);
assert!(result.is_err());
}
#[test]
fn test_tentative_def_count() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let mut var = VarDecl::new("x", QualType::int());
var.is_global = true;
let _ = dc.check_var_decl(&var);
assert_eq!(dc.tentative_def_count(), 1);
assert!(dc.is_tentative("x"));
assert!(!dc.is_tentative("y"));
}
#[test]
fn test_parameter_void_sole() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let param = VarDecl::new("", QualType::void());
let result = dc.check_parameter(¶m, 0);
assert!(result.is_ok());
}
#[test]
fn test_parameter_void_not_sole() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let param = VarDecl::new("v", QualType::void());
let result = dc.check_parameter(¶m, 1); assert!(result.is_err());
}
#[test]
fn test_null_pointer_constant() {
let mut ec = ExprChecker::new(CLangStandard::C17);
assert!(ec.is_null_pointer_constant(&Expr::IntLiteral(0)));
assert!(!ec.is_null_pointer_constant(&Expr::IntLiteral(42)));
}
#[test]
fn test_check_condition_scalar() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let result = ec.check_condition(&Expr::IntLiteral(1));
assert!(result.is_ok());
}
#[test]
fn test_check_condition_non_scalar() {
let mut ec = ExprChecker::new(CLangStandard::C17);
}
#[test]
fn test_binary_op_validity_add_int() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let result = ec.check_binary_op_validity(BinaryOp::Add, &QualType::int(), &QualType::int());
assert!(result.is_ok());
}
#[test]
fn test_binary_op_validity_add_pointer_int() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let ptr_ty = QualType::pointer_to(QualType::int());
let result = ec.check_binary_op_validity(BinaryOp::Add, &ptr_ty, &QualType::int());
assert!(result.is_ok());
}
#[test]
fn test_binary_op_validity_sub_pointer_pointer() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let ptr_ty = QualType::pointer_to(QualType::int());
let result = ec.check_binary_op_validity(BinaryOp::Sub, &ptr_ty, &ptr_ty);
assert!(result.is_ok());
assert!(matches!(*result.unwrap().base, TypeNode::Long));
}
#[test]
fn test_binary_op_validity_mod_on_float() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let result =
ec.check_binary_op_validity(BinaryOp::Mod, &QualType::float_(), &QualType::float_());
assert!(result.is_err());
}
#[test]
fn test_perform_implicit_conversions_array() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let arr = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let result = ec.perform_implicit_conversions(&arr, &QualType::pointer_to(QualType::int()));
assert!(result.is_ok());
assert!(matches!(*result.unwrap().base, TypeNode::Pointer(_)));
}
#[test]
fn test_expr_type_detailed() {
let mut ec = ExprChecker::new(CLangStandard::C17);
let result = ec.expr_type_detailed(&Expr::IntLiteral(42));
assert!(result.is_some());
assert!(!result.unwrap().is_lvalue);
}
#[test]
fn test_sizeof_expr_type() {
let ec = ExprChecker::new(CLangStandard::C17);
assert_eq!(ec.sizeof_expr_type(&Expr::IntLiteral(1)), Some(4));
assert_eq!(ec.sizeof_expr_type(&Expr::DoubleLiteral(1.0)), Some(8));
assert_eq!(
ec.sizeof_expr_type(&Expr::StringLiteral("hello".to_string())),
Some(6)
);
}
#[test]
fn test_is_jump_return() {
let sc = StmtChecker::new();
assert!(sc.is_jump(&make_return(Some(make_int_literal(0)))));
assert!(sc.is_jump(&Stmt::Break));
assert!(sc.is_jump(&Stmt::Continue));
assert!(!sc.is_jump(&Stmt::Null));
}
#[test]
fn test_is_compound() {
let sc = StmtChecker::new();
assert!(sc.is_compound(&make_compound(Vec::new())));
assert!(!sc.is_compound(&Stmt::Null));
}
#[test]
fn test_check_return_void_function_with_value() {
let mut sc = StmtChecker::new();
sc.set_return_type(QualType::void());
let result = sc.check_return(&Some(Box::new(Expr::IntLiteral(0))));
assert!(result.is_ok());
assert!(!sc.take_warnings().is_empty());
}
#[test]
fn test_check_return_nonvoid_without_value() {
let mut sc = StmtChecker::new();
sc.set_return_type(QualType::int());
let result = sc.check_return(&None);
assert!(result.is_ok());
assert!(!sc.take_warnings().is_empty());
}
#[test]
fn test_check_return_outside_function() {
let mut sc = StmtChecker::new();
let result = sc.check_return(&None);
assert!(result.is_err());
}
#[test]
fn test_is_unreachable() {
let mut sc = StmtChecker::new();
sc.set_return_type(QualType::void());
assert!(!sc.is_unreachable());
let _ = sc.check_return(&None);
assert!(sc.is_unreachable());
sc.reset_path_returned();
assert!(!sc.is_unreachable());
}
#[test]
fn test_break_inside_loop() {
let mut sc = StmtChecker::new();
sc.loop_depth = 1;
assert!(sc.check_break().is_ok());
}
#[test]
fn test_continue_inside_loop() {
let mut sc = StmtChecker::new();
sc.loop_depth = 1;
assert!(sc.check_continue().is_ok());
}
#[test]
fn test_const_eval_boolean_constant() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
assert_eq!(
ce.evaluate_boolean_constant(&Expr::IntLiteral(1)),
Some(true)
);
assert_eq!(
ce.evaluate_boolean_constant(&Expr::IntLiteral(0)),
Some(false)
);
}
#[test]
fn test_const_eval_float_constant() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_float_constant(&Expr::FloatLiteral(3.14));
assert!((val.unwrap() - 3.14).abs() < 0.001);
}
#[test]
fn test_const_eval_valid_array_size() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
assert!(ce.is_valid_array_size(&Expr::IntLiteral(10)));
assert!(!ce.is_valid_array_size(&Expr::IntLiteral(0)));
assert!(!ce.is_valid_array_size(&Expr::IntLiteral(-1)));
}
#[test]
fn test_const_eval_bitfield_width() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
assert!(ce.is_valid_bitfield_width(&Expr::IntLiteral(1)));
assert!(ce.is_valid_bitfield_width(&Expr::IntLiteral(32)));
assert!(!ce.is_valid_bitfield_width(&Expr::IntLiteral(0)));
assert!(!ce.is_valid_bitfield_width(&Expr::IntLiteral(65)));
}
#[test]
fn test_const_eval_debug_format() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let debug = ce.evaluate_debug(&Expr::IntLiteral(100));
assert!(debug.contains("100"));
assert!(debug.contains("integer constant"));
}
#[test]
fn test_const_eval_fold_binary_float() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.fold_binary_float(BinaryOp::Add, 1.5, 2.5);
assert_eq!(val, Some(4.0));
let val = ce.fold_binary_float(BinaryOp::Div, 1.0, 0.0);
assert_eq!(val, None);
assert!(ce.errors().iter().any(|e| e.contains("division by zero")));
}
#[test]
fn test_const_eval_enum_count() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
ce.register_enum_value("A", 0);
ce.register_enum_value("B", 1);
ce.register_enum_value("C", 2);
assert_eq!(ce.enum_count(), 3);
}
#[test]
fn test_const_eval_var_address() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
ce.register_var_address("global_var", 0x1000);
assert_eq!(ce.var_address_count(), 1);
let val = ce.evaluate_address_constant(&Expr::Unary(
UnaryOp::AddrOf,
Box::new(Expr::Ident("global_var".to_string())),
));
assert_eq!(val, Some(0x1000));
}
#[test]
fn test_const_eval_complex_expression() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Binary(
BinaryOp::Sub,
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(10)),
Box::new(Expr::IntLiteral(20)),
)),
Box::new(Expr::IntLiteral(3)),
)),
Box::new(Expr::IntLiteral(5)),
);
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, Some(85));
}
#[test]
fn test_diag_gcc_style_format() {
let de = DiagnosticEmitter::new();
let diag = Diagnostic::new(DiagSeverity::Error, "something went wrong")
.at(SourceLocation::new("main.c", 10, 1))
.with_id("err_type_mismatch");
let formatted = de.format_gcc_style(&diag);
assert!(formatted.contains("main.c"));
assert!(formatted.contains("error"));
assert!(formatted.contains("err_type_mismatch"));
}
#[test]
fn test_diag_summary() {
let mut de = DiagnosticEmitter::new();
de.error("e1");
de.error("e2");
de.warning("w1");
let summary = de.summary();
assert!(summary.contains("2 error"));
assert!(summary.contains("1 warning"));
}
#[test]
fn test_diag_should_fail() {
let mut de = DiagnosticEmitter::new();
assert!(!de.should_fail());
de.error("fail");
assert!(de.should_fail());
}
#[test]
fn test_diag_max_severity() {
let mut de = DiagnosticEmitter::new();
de.warning("warn");
assert_eq!(de.max_severity(), DiagSeverity::Warning);
de.error("err");
assert_eq!(de.max_severity(), DiagSeverity::Error);
de.fatal("fatal");
assert_eq!(de.max_severity(), DiagSeverity::Fatal);
}
#[test]
fn test_diag_count_by_severity() {
let mut de = DiagnosticEmitter::new();
de.error("e1");
de.error("e2");
de.warning("w1");
assert_eq!(de.count_by_severity(DiagSeverity::Error), 2);
assert_eq!(de.count_by_severity(DiagSeverity::Warning), 1);
assert_eq!(de.count_by_severity(DiagSeverity::Note), 0);
}
#[test]
fn test_diag_categorize() {
let de = DiagnosticEmitter::new();
assert_eq!(de.categorize("err_foo"), DiagCategory::Semantic);
assert_eq!(de.categorize("warn_bar"), DiagCategory::Semantic);
assert_eq!(de.categorize("pp_define"), DiagCategory::Preprocessor);
assert_eq!(de.categorize("drv_opt"), DiagCategory::Driver);
assert_eq!(de.categorize("unknown"), DiagCategory::Unknown);
}
#[test]
fn test_attr_packed_on_struct() {
let mut ac = AttrChecker::new();
let result = ac.check_applicability("packed", AttrTarget::Struct);
assert!(result.is_ok());
}
#[test]
fn test_attr_packed_on_function() {
let mut ac = AttrChecker::new();
let result = ac.check_applicability("packed", AttrTarget::Function);
assert!(result.is_ok()); assert!(ac
.warnings()
.iter()
.any(|w| w.contains("cannot be applied")));
}
#[test]
fn test_attr_unknown_attribute() {
let mut ac = AttrChecker::new();
let result = ac.check_applicability("garbage_xyz", AttrTarget::Function);
assert!(result.is_err()); }
#[test]
fn test_attr_duplicate_warning() {
let mut ac = AttrChecker::new();
let attrs = vec!["deprecated".to_string(), "deprecated".to_string()];
let result = ac.check_attributes(&attrs, AttrTarget::Function);
assert!(result.is_ok());
assert!(ac.warnings().iter().any(|w| w.contains("duplicate")));
}
#[test]
fn test_attr_pure_const_combination() {
let mut ac = AttrChecker::new();
let attrs = vec!["pure".to_string(), "const".to_string()];
let result = ac.check_attributes(&attrs, AttrTarget::Function);
assert!(result.is_ok());
assert!(ac.warnings().iter().any(|w| w.contains("redundant")));
}
#[test]
fn test_attr_lookup() {
let ac = AttrChecker::new();
assert!(ac.lookup("deprecated").is_some());
assert!(ac.lookup("noreturn").is_some());
assert!(ac.lookup("nonexistent").is_none());
}
#[test]
fn test_attr_description() {
let ac = AttrChecker::new();
let desc = ac.describe("noreturn");
assert!(desc.is_some());
assert!(desc.unwrap().contains("does not return"));
}
#[test]
fn test_attr_known_attributes_list() {
let ac = AttrChecker::new();
let list = ac.known_attributes();
assert!(!list.is_empty());
assert!(list.contains(&"deprecated"));
}
#[test]
fn test_integration_function_with_params() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "add".to_string(),
ret_ty: QualType::int(),
params: vec![
VarDecl::new("a", QualType::int()),
VarDecl::new("b", QualType::int()),
],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Ident("a".to_string())),
Box::new(Expr::Ident("b".to_string())),
)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_switch_with_cases() {
let mut sema = make_sema();
let switch_stmt = Stmt::Switch {
expr: Box::new(Expr::Ident("val".to_string())),
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![
Stmt::Case {
value: Box::new(Expr::IntLiteral(1)),
stmt: Box::new(make_return(Some(make_int_literal(10)))),
},
Stmt::Case {
value: Box::new(Expr::IntLiteral(2)),
stmt: Box::new(make_return(Some(make_int_literal(20)))),
},
Stmt::Default {
stmt: Box::new(make_return(Some(make_int_literal(0)))),
},
],
})),
};
let func = FunctionDecl {
name: "dispatch".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("val", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![switch_stmt],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_struct_typedef() {
let mut sema = make_sema();
let sd = StructDecl {
name: Some("Point".to_string()),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
};
let td = TypedefDecl::new(
"Point",
QualType::new(TypeNode::Struct {
name: Some("Point".to_string()),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
}),
);
let tu = TranslationUnit {
decls: vec![Decl::Struct(sd), Decl::Typedef(td)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_if_else_chain() {
let mut sema = make_sema();
let if_chain = make_if(
make_binary(BinaryOp::Gt, make_ident("x"), make_int_literal(0)),
make_return(Some(make_int_literal(1))),
Some(make_if(
make_binary(BinaryOp::Lt, make_ident("x"), make_int_literal(0)),
make_return(Some(Expr::Unary(
UnaryOp::Minus,
Box::new(make_int_literal(1)),
))),
Some(make_return(Some(make_int_literal(0)))),
)),
);
let func = FunctionDecl {
name: "sign".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("x", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![if_chain],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_while_loop() {
let mut sema = make_sema();
let while_stmt = Stmt::While {
cond: Box::new(make_binary(
BinaryOp::Lt,
make_ident("i"),
make_int_literal(100),
)),
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(Expr::PreInc(Box::new(make_ident(
"i",
)))))],
})),
};
let func = FunctionDecl {
name: "count_up".to_string(),
ret_ty: QualType::void(),
params: vec![VarDecl::new("i", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![while_stmt],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_do_while() {
let mut sema = make_sema();
let do_while = Stmt::DoWhile {
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(Expr::PostInc(Box::new(make_ident(
"n",
)))))],
})),
cond: Box::new(make_binary(
BinaryOp::Lt,
make_ident("n"),
make_int_literal(10),
)),
};
let func = FunctionDecl {
name: "do_count".to_string(),
ret_ty: QualType::void(),
params: vec![VarDecl::new("n", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![do_while],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_string_literal() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "hello".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![make_return(Some(Expr::StringLiteral(
"hello world".to_string(),
)))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_cast_expression() {
let mut sema = make_sema();
let cast = Expr::Cast(QualType::int(), Box::new(Expr::FloatLiteral(3.14)));
let func = FunctionDecl {
name: "cast_test".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![make_return(Some(cast))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_ternary_operator() {
let mut sema = make_sema();
let ternary = Expr::Conditional(
Box::new(make_binary(
BinaryOp::Gt,
make_ident("x"),
make_int_literal(0),
)),
Box::new(make_ident("x")),
Box::new(make_int_literal(0)),
);
let func = FunctionDecl {
name: "max0".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("x", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(ternary))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_sizeof_alignof() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "type_info".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![
Stmt::Expr(Box::new(Expr::SizeOfType(QualType::int()))),
Stmt::Expr(Box::new(Expr::AlignOfType(QualType::double()))),
],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_pointer_deref() {
let mut sema = make_sema();
let deref = Expr::Unary(UnaryOp::Deref, Box::new(make_ident("ptr")));
let func = FunctionDecl {
name: "read_ptr".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("ptr", QualType::pointer_to(QualType::int()))],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(deref))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_address_of() {
let mut sema = make_sema();
let addr = Expr::Unary(UnaryOp::AddrOf, Box::new(make_ident("var")));
let func = FunctionDecl {
name: "get_ptr".to_string(),
ret_ty: QualType::pointer_to(QualType::int()),
params: vec![VarDecl::new("var", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(addr))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_bitwise_operations() {
let mut sema = make_sema();
let bitwise_and = Expr::Binary(
BinaryOp::And,
Box::new(make_int_literal(0xFF)),
Box::new(make_ident("mask")),
);
let bitwise_or = Expr::Binary(
BinaryOp::Or,
Box::new(make_ident("flags")),
Box::new(make_int_literal(0x01)),
);
let shift = Expr::Binary(
BinaryOp::Shl,
Box::new(make_int_literal(1)),
Box::new(make_ident("bit")),
);
let func = FunctionDecl {
name: "bit_ops".to_string(),
ret_ty: QualType::int(),
params: vec![
VarDecl::new("mask", QualType::int()),
VarDecl::new("flags", QualType::int()),
VarDecl::new("bit", QualType::int()),
],
body: Some(CompoundStmt {
stmts: vec![
Stmt::Expr(Box::new(bitwise_and)),
Stmt::Expr(Box::new(bitwise_or)),
make_return(Some(shift)),
],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_comparisons() {
let mut sema = make_sema();
let eq = Expr::Binary(
BinaryOp::Eq,
Box::new(make_ident("a")),
Box::new(make_ident("b")),
);
let func = FunctionDecl {
name: "cmp".to_string(),
ret_ty: QualType::bool_(),
params: vec![
VarDecl::new("a", QualType::int()),
VarDecl::new("b", QualType::int()),
],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(eq))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_compound_assignments() {
let mut sema = make_sema();
let add_assign = Expr::Assign(
BinaryOp::AddAssign,
Box::new(make_ident("total")),
Box::new(make_int_literal(5)),
);
let func = FunctionDecl {
name: "accumulate".to_string(),
ret_ty: QualType::void(),
params: vec![VarDecl::new("total", QualType::int())],
body: Some(CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(add_assign))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_global_and_local() {
let mut sema = make_sema();
let global = VarDecl::new("counter", QualType::int()).global();
let func = FunctionDecl {
name: "inc".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![Stmt::Expr(Box::new(Expr::PreInc(Box::new(make_ident(
"counter",
)))))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Variable(global), Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_multiple_functions() {
let mut sema = make_sema();
let f1 = FunctionDecl {
name: "init".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt { stmts: Vec::new() }),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let f2 = FunctionDecl {
name: "run".to_string(),
ret_ty: QualType::int(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![
Stmt::Expr(Box::new(Expr::Call {
callee: Box::new(Expr::Ident("init".to_string())),
args: Vec::new(),
})),
make_return(Some(make_int_literal(0))),
],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(f1), Decl::Function(f2)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_label_and_goto() {
let mut sema = make_sema();
let func = FunctionDecl {
name: "with_goto".to_string(),
ret_ty: QualType::void(),
params: Vec::new(),
body: Some(CompoundStmt {
stmts: vec![
Stmt::Goto {
label: "cleanup".to_string(),
},
Stmt::Expr(Box::new(make_int_literal(0))),
Stmt::Label {
name: "cleanup".to_string(),
stmt: Box::new(Stmt::Null),
},
],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_extern_variable() {
let mut sema = make_sema();
let mut var = VarDecl::new("external_var", QualType::int());
var.is_extern = true;
var.is_global = true;
var.linkage = Linkage::External;
let tu = TranslationUnit {
decls: vec![Decl::Variable(var)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_static_function() {
let mut sema = make_sema();
let mut func = FunctionDecl::new("internal_helper", QualType::void());
func.linkage = Linkage::Internal;
func.body = Some(CompoundStmt { stmts: Vec::new() });
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integration_array_subscript() {
let mut sema = make_sema();
let subscript = Expr::Subscript {
base: Box::new(make_ident("arr")),
index: Box::new(make_int_literal(0)),
};
let func = FunctionDecl {
name: "first_elem".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("arr", QualType::pointer_to(QualType::int()))],
body: Some(CompoundStmt {
stmts: vec![make_return(Some(subscript))],
}),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
let tu = TranslationUnit {
decls: vec![Decl::Function(func)],
includes: Vec::new(),
defines: Vec::new(),
};
let result = sema.analyze(&tu);
assert!(result.is_ok());
}
#[test]
fn test_deep_sema_type_of_expr() {
let mut sema = make_sema();
let ty = sema.type_of_expr(&Expr::IntLiteral(10));
assert!(ty.is_some());
assert!(matches!(*ty.unwrap().base, TypeNode::Int));
}
#[test]
fn test_deep_sema_check_type_compatibility() {
let mut sema = make_sema();
assert!(sema.check_type_compatibility(&QualType::int(), &QualType::int()));
assert!(!sema.check_type_compatibility(&QualType::int(), &QualType::float_()));
}
#[test]
fn test_deep_sema_dump_symbol_table_empty() {
let mut sema = make_sema();
sema.scope_manager.enter_scope(ScopeKind::File);
let dump = sema.dump_symbol_table();
assert!(dump.contains("Scope 0"));
}
#[test]
fn test_void_variable_error() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let var = VarDecl::new("v", QualType::void());
let _ = dc.check_var_decl(&var);
assert!(dc.errors().iter().any(|e| e.contains("void")));
}
#[test]
fn test_function_returning_function() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let fn_ty = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: vec![QualType::int()],
is_vararg: false,
});
let func = FunctionDecl::new("f", fn_ty);
let _ = dc.check_function_decl(&func);
assert!(dc.warnings().iter().any(|w| w.contains("function type")));
}
#[test]
fn test_noreturn_non_void_warning() {
let mut dc = DeclChecker::new(CLangStandard::C17);
let mut func = FunctionDecl::new("die", QualType::int());
func.is_noreturn = true;
let _ = dc.check_function_decl(&func);
assert!(dc
.warnings()
.iter()
.any(|w| w.contains("_Noreturn") || w.contains("void")));
}
#[test]
fn test_const_eval_precedence() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let expr = Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::IntLiteral(2)),
Box::new(Expr::IntLiteral(3)),
)),
);
let val = ce.evaluate_integer_constant(&expr);
assert_eq!(val, Some(7));
}
#[test]
fn test_const_eval_comparison_chain() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::Eq,
Box::new(Expr::IntLiteral(5)),
Box::new(Expr::IntLiteral(5)),
));
assert_eq!(val, Some(1));
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::Eq,
Box::new(Expr::IntLiteral(5)),
Box::new(Expr::IntLiteral(6)),
));
assert_eq!(val, Some(0));
}
#[test]
fn test_const_eval_logical_operators() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::LogicAnd,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
));
assert_eq!(val, Some(1));
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::LogicOr,
Box::new(Expr::IntLiteral(0)),
Box::new(Expr::IntLiteral(1)),
));
assert_eq!(val, Some(1));
}
#[test]
fn test_const_eval_shift() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::Shl,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(4)),
));
assert_eq!(val, Some(16));
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::Shr,
Box::new(Expr::IntLiteral(16)),
Box::new(Expr::IntLiteral(2)),
));
assert_eq!(val, Some(4));
}
#[test]
fn test_const_eval_shift_out_of_range() {
let mut ce = ConstExprEvaluator::new(CLangStandard::C17);
let val = ce.evaluate_integer_constant(&Expr::Binary(
BinaryOp::Shl,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(64)),
));
assert_eq!(val, None);
assert!(ce.errors().iter().any(|e| e.contains("shift")));
}
#[test]
fn test_type_system_to_signed_unsigned() {
let ts = TypeSystem::new(CLangStandard::C17);
let signed = ts.to_signed(&QualType::uint());
assert!(matches!(*signed.base, TypeNode::Int));
let unsigned = ts.to_unsigned(&QualType::int());
assert!(matches!(*unsigned.base, TypeNode::UInt));
}
#[test]
fn test_type_system_is_complete() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(!ts.is_complete_type(&QualType::void()));
assert!(ts.is_complete_type(&QualType::int()));
assert!(ts.is_complete_type(&QualType::float_()));
}
#[test]
fn test_type_system_common_type() {
let ts = TypeSystem::new(CLangStandard::C17);
let common = ts.common_type(&[QualType::int(), QualType::int(), QualType::int()]);
assert!(common.is_some());
assert!(matches!(*common.unwrap().base, TypeNode::Int));
}
#[test]
fn test_diag_clear() {
let mut de = DiagnosticEmitter::new();
de.error("e1");
de.error("e2");
assert_eq!(de.error_count(), 2);
de.clear();
assert_eq!(de.error_count(), 0);
assert!(de.diagnostics().is_empty());
}
#[test]
fn test_make_int_var() {
let var = super::make_int_var("x");
assert_eq!(var.name, "x");
assert!(matches!(*var.ty.base, TypeNode::Int));
}
#[test]
fn test_make_ptr_var() {
let var = super::make_ptr_var("p", QualType::int());
assert_eq!(var.name, "p");
assert!(matches!(*var.ty.base, TypeNode::Pointer(_)));
}
#[test]
fn test_make_simple_function() {
let body = CompoundStmt::new();
let func = super::make_simple_function("f", body);
assert_eq!(func.name, "f");
assert!(matches!(*func.ret_ty.base, TypeNode::Int));
assert!(func.body.is_some());
}
#[test]
fn test_hello_world_tu() {
let tu = super::make_hello_world_tu();
assert_eq!(tu.decls.len(), 1);
let result = super::validate_tu(&tu);
assert!(result.is_ok());
}
#[test]
fn test_factorial_tu() {
let tu = super::make_factorial_tu();
assert_eq!(tu.decls.len(), 1);
let result = super::validate_tu(&tu);
assert!(result.is_ok());
}
#[test]
fn test_struct_tu() {
let tu = super::make_struct_tu();
assert_eq!(tu.decls.len(), 2);
let result = super::validate_tu(&tu);
assert!(result.is_ok());
}
#[test]
fn test_enum_tu() {
let tu = super::make_enum_tu();
assert_eq!(tu.decls.len(), 2);
let result = super::validate_tu(&tu);
assert!(result.is_ok());
}
#[test]
fn test_integer_type_infos() {
let infos = super::integer_type_infos();
assert_eq!(infos.len(), 11);
assert!(infos.iter().any(|i| i.name == "int"));
assert!(infos.iter().any(|i| i.name == "unsigned long"));
}
#[test]
fn test_float_type_infos() {
let infos = super::float_type_infos();
assert_eq!(infos.len(), 3);
assert!(infos.iter().any(|i| i.name == "float"));
assert!(infos.iter().any(|i| i.name == "double"));
}
#[test]
fn test_operator_precedence() {
assert!(
super::operator_precedence(BinaryOp::Mul) > super::operator_precedence(BinaryOp::Add)
);
assert!(
super::operator_precedence(BinaryOp::Add) > super::operator_precedence(BinaryOp::Eq)
);
assert!(
super::operator_precedence(BinaryOp::Eq)
> super::operator_precedence(BinaryOp::LogicAnd)
);
assert!(
super::operator_precedence(BinaryOp::LogicAnd)
> super::operator_precedence(BinaryOp::Assign)
);
}
#[test]
fn test_operator_associativity() {
assert_eq!(
super::operator_associativity(BinaryOp::Add),
super::Associativity::LeftToRight
);
assert_eq!(
super::operator_associativity(BinaryOp::Assign),
super::Associativity::RightToLeft
);
}
#[test]
fn test_common_real_type() {
let common = super::common_real_type(&[QualType::int(), QualType::int()]);
assert!(common.is_some());
assert!(matches!(*common.unwrap().base, TypeNode::Int));
}
#[test]
fn test_debug_type() {
let debug = super::debug_type(&QualType::int());
assert!(debug.contains("int"));
assert!(debug.contains("size=4"));
}
#[test]
fn test_make_quadratic() {
let quad = super::make_quadratic("a", "b", "c", "x");
assert!(
std::mem::discriminant(&quad)
== std::mem::discriminant(&Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(0)),
Box::new(Expr::IntLiteral(0))
))
);
}
#[test]
fn test_make_abs_expr() {
let abs = super::make_abs_expr("x");
assert!(matches!(abs, Expr::Conditional(..)));
}
#[test]
fn test_make_max_expr() {
let max = super::make_max_expr("a", "b");
assert!(matches!(max, Expr::Conditional(..)));
}
#[test]
fn test_make_while_loop() {
let cond = Expr::IntLiteral(1);
let body = Stmt::Null;
let while_stmt = super::make_while(cond, body);
assert!(matches!(while_stmt, Stmt::While { .. }));
}
#[test]
fn test_make_for_loop() {
let body = Stmt::Null;
let for_stmt = super::make_for(None, None, None, body);
assert!(matches!(for_stmt, Stmt::For { .. }));
}
#[test]
fn test_make_switch() {
let expr = Expr::IntLiteral(0);
let body = Stmt::Null;
let switch = super::make_switch(expr, body);
assert!(matches!(switch, Stmt::Switch { .. }));
}
#[test]
fn test_make_case_and_default() {
let case1 = super::make_case(1, Stmt::Null);
let def = super::make_default(Stmt::Null);
assert!(matches!(case1, Stmt::Case { .. }));
assert!(matches!(def, Stmt::Default { .. }));
}
#[test]
fn test_make_goto_and_label() {
let goto = super::make_goto("L");
let label = super::make_label("L", Stmt::Null);
assert!(matches!(goto, Stmt::Goto { .. }));
assert!(matches!(label, Stmt::Label { .. }));
}
#[test]
fn test_make_sizeof_alignof() {
let sz = super::make_sizeof(Expr::IntLiteral(1));
let al = super::make_alignof(Expr::IntLiteral(1));
assert!(matches!(sz, Expr::SizeOf(_)));
assert!(matches!(al, Expr::AlignOf(_)));
}
#[test]
fn test_make_cast() {
let cast = super::make_cast(QualType::float_(), Expr::IntLiteral(42));
assert!(matches!(cast, Expr::Cast(..)));
}
#[test]
fn test_make_call_expr() {
let call = super::make_call("printf", vec![Expr::StringLiteral("hello".to_string())]);
assert!(matches!(call, Expr::Call { .. }));
}
#[test]
fn test_make_subscript() {
let sub = super::make_subscript("arr", 0);
assert!(matches!(sub, Expr::Subscript { .. }));
}
#[test]
fn test_make_member_access() {
let dot = super::make_member("obj", "field", false);
let arrow = super::make_member("ptr", "field", true);
assert!(matches!(
dot,
Expr::Member {
is_arrow: false,
..
}
));
assert!(matches!(arrow, Expr::Member { is_arrow: true, .. }));
}
#[test]
fn test_make_inc_dec_exprs() {
let postinc = super::make_postinc("i");
let postdec = super::make_postdec("i");
let preinc = super::make_preinc("i");
let predec = super::make_predec("i");
assert!(matches!(postinc, Expr::PostInc(_)));
assert!(matches!(postdec, Expr::PostDec(_)));
assert!(matches!(preinc, Expr::PreInc(_)));
assert!(matches!(predec, Expr::PreDec(_)));
}
#[test]
fn test_make_literals() {
let c = super::make_char_literal('A');
let f = super::make_float_literal(3.14);
let d = super::make_double_literal(2.718);
let u = super::make_uint_literal(42);
let s = super::make_str_literal("test");
assert!(matches!(c, Expr::CharLiteral('A')));
assert!(matches!(f, Expr::FloatLiteral(_)));
assert!(matches!(d, Expr::DoubleLiteral(_)));
assert!(matches!(u, Expr::UIntLiteral(42, _)));
assert!(matches!(s, Expr::StringLiteral(_)));
}
#[test]
fn test_make_unary_ops() {
let addr = super::make_addrof("var");
let deref = super::make_deref("ptr");
let bnot = super::make_bitnot(Expr::IntLiteral(0));
let lnot = super::make_lnot(Expr::IntLiteral(1));
assert!(matches!(addr, Expr::Unary(UnaryOp::AddrOf, _)));
assert!(matches!(deref, Expr::Unary(UnaryOp::Deref, _)));
assert!(matches!(bnot, Expr::Unary(UnaryOp::BitNot, _)));
assert!(matches!(lnot, Expr::Unary(UnaryOp::Not, _)));
}
#[test]
fn test_make_binary_ops() {
let one = Expr::IntLiteral(1);
let two = Expr::IntLiteral(2);
assert!(matches!(
super::make_add(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Add, ..)
));
assert!(matches!(
super::make_sub(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Sub, ..)
));
assert!(matches!(
super::make_mul(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Mul, ..)
));
assert!(matches!(
super::make_div(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Div, ..)
));
assert!(matches!(
super::make_eq(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Eq, ..)
));
assert!(matches!(
super::make_ne(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Ne, ..)
));
assert!(matches!(
super::make_lt(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Lt, ..)
));
assert!(matches!(
super::make_gt(one.clone(), two.clone()),
Expr::Binary(BinaryOp::Gt, ..)
));
assert!(matches!(
super::make_land(one.clone(), two.clone()),
Expr::Binary(BinaryOp::LogicAnd, ..)
));
assert!(matches!(
super::make_lor(one.clone(), two.clone()),
Expr::Binary(BinaryOp::LogicOr, ..)
));
}
#[test]
fn test_make_assign_expr() {
let assign = super::make_assign(Expr::Ident("x".to_string()), Expr::IntLiteral(5));
assert!(matches!(assign, Expr::Assign(BinaryOp::Assign, ..)));
}
#[test]
fn test_run_sema_on_empty_tu() {
let tu = TranslationUnit {
decls: vec![],
includes: vec![],
defines: vec![],
};
let (errors, _warnings) = super::run_sema(&tu, CLangStandard::C17);
assert!(errors.is_empty());
}
#[test]
fn test_contains_message() {
let errors = vec![
"error: type mismatch".to_string(),
"warning: unused".to_string(),
];
assert!(super::contains_message(&errors, "type mismatch"));
assert!(!super::contains_message(&errors, "segfault"));
}
#[test]
fn test_c11_integer_promotion_rule() {
let ts = TypeSystem::new(CLangStandard::C11);
let promoted = ts.integer_promotion(&QualType::short());
assert!(matches!(*promoted.base, TypeNode::Int));
}
#[test]
fn test_c11_usual_arithmetic_float_dominates() {
let ts = TypeSystem::new(CLangStandard::C11);
let result = ts.usual_arithmetic_conversions(&QualType::int(), &QualType::float_());
assert!(matches!(*result.base, TypeNode::Float));
}
#[test]
fn test_c11_void_pointer_conversions() {
let ts = TypeSystem::new(CLangStandard::C11);
let void_ptr = QualType::pointer_to(QualType::void());
let int_ptr = QualType::pointer_to(QualType::int());
assert!(ts.is_assignable(&int_ptr, &void_ptr));
assert!(ts.is_assignable(&void_ptr, &int_ptr));
}
#[test]
fn test_c11_array_to_pointer_decay() {
let ec = ExprChecker::new(CLangStandard::C11);
let arr = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let ptr = ec.array_to_pointer(&arr);
assert!(matches!(*ptr.base, TypeNode::Pointer(_)));
}
#[test]
fn test_c11_function_to_pointer_decay() {
let ec = ExprChecker::new(CLangStandard::C11);
let fn_ty = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: Vec::new(),
is_vararg: false,
});
let ptr = ec.function_to_pointer(&fn_ty);
assert!(matches!(*ptr.base, TypeNode::Pointer(_)));
}
#[test]
fn test_c11_lvalue_to_rvalue() {
let ec = ExprChecker::new(CLangStandard::C11);
let const_int = QualType::const_(TypeNode::Int);
let rvalue = ec.lvalue_to_rvalue(&const_int);
assert!(!rvalue.is_const);
assert!(matches!(*rvalue.base, TypeNode::Int));
}
#[test]
fn test_c11_null_pointer_constant() {
let mut ec = ExprChecker::new(CLangStandard::C11);
assert!(ec.is_null_pointer_constant(&Expr::IntLiteral(0)));
assert!(!ec.is_null_pointer_constant(&Expr::IntLiteral(1)));
}
#[test]
fn test_c11_compatible_types_composite() {
let ts = TypeSystem::new(CLangStandard::C11);
let a = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let b = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: None,
});
let composite = ts.composite_type(&a, &b);
assert!(composite.is_some());
if let TypeNode::Array { size, .. } = &*composite.unwrap().base {
assert_eq!(*size, Some(10));
} else {
panic!("Expected array type");
}
}
#[test]
fn test_diagnostic_severity_ordering() {
assert!(DiagSeverity::Error > DiagSeverity::Warning);
assert!(DiagSeverity::Fatal > DiagSeverity::Error);
assert!(DiagSeverity::Ignored < DiagSeverity::Note);
}
#[test]
fn test_cv_qualifier_superset() {
let base = CVQualifiers::default();
let const_ = CVQualifiers { is_const: true };
let volatile = CVQualifiers { is_volatile: true };
let both = CVQualifiers {
is_const: true,
is_volatile: true,
};
assert!(both.is_superset_of(&base));
assert!(both.is_superset_of(&const_));
assert!(!const_.is_superset_of(&both));
}
#[test]
fn test_conversion_kind_variants() {
let kinds = [
ConversionKind::LvalueToRvalue,
ConversionKind::ArrayToPointer,
ConversionKind::FunctionToPointer,
ConversionKind::IntegerPromotion,
ConversionKind::UsualArithmetic,
ConversionKind::FloatingPromotion,
ConversionKind::IntegralToFloating,
ConversionKind::FloatingToIntegral,
ConversionKind::PointerInteger,
ConversionKind::PointerConversion,
ConversionKind::BooleanConversion,
ConversionKind::Identity,
];
assert_eq!(kinds.len(), 12);
}
#[test]
fn test_constant_value_is_zero() {
assert!(ConstantValue::Int(0).is_zero());
assert!(ConstantValue::UInt(0).is_zero());
assert!(ConstantValue::Float(0.0).is_zero());
assert!(ConstantValue::NullPointer.is_zero());
assert!(!ConstantValue::Int(1).is_zero());
assert!(!ConstantValue::Float(1.0).is_zero());
}
#[test]
fn test_constant_value_as_int() {
assert_eq!(ConstantValue::Int(42).as_int(), Some(42));
assert_eq!(ConstantValue::UInt(42).as_int(), Some(42));
assert_eq!(ConstantValue::Float(3.14).as_int(), None);
assert_eq!(
ConstantValue::NonConstant("nope".to_string()).as_int(),
None
);
}
#[test]
fn test_expr_nesting_depth() {
assert_eq!(expr_nesting_depth(&Expr::IntLiteral(1)), 0);
assert_eq!(
expr_nesting_depth(&Expr::Unary(UnaryOp::Minus, Box::new(Expr::IntLiteral(1)))),
1
);
assert_eq!(
expr_nesting_depth(&Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
)),
1
);
}
#[test]
fn test_expr_structural_eq() {
let a = Expr::IntLiteral(5);
let b = Expr::IntLiteral(5);
let c = Expr::IntLiteral(6);
assert!(expr_structural_eq(&a, &b));
assert!(!expr_structural_eq(&a, &c));
}
#[test]
fn test_type_system_describe_type() {
let ts = TypeSystem::new(CLangStandard::C17);
let desc = ts.describe_type(&QualType::int());
assert_eq!(desc, "int");
let desc = ts.describe_type(&QualType::long());
assert_eq!(desc, "long int");
let desc = ts.describe_type(&QualType::void());
assert_eq!(desc, "void");
}
#[test]
fn test_type_system_is_character_type() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.is_character_type(&QualType::char()));
assert!(ts.is_character_type(&QualType::schar()));
assert!(ts.is_character_type(&QualType::uchar()));
assert!(!ts.is_character_type(&QualType::int()));
}
#[test]
fn test_type_system_is_object_type() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(ts.is_object_type(&QualType::int()));
assert!(!ts.is_object_type(&QualType::void()));
let fn_ty = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: Vec::new(),
is_vararg: false,
});
assert!(!ts.is_object_type(&fn_ty));
}
#[test]
fn test_type_system_to_signed_from_unsigned_types() {
let ts = TypeSystem::new(CLangStandard::C17);
assert!(matches!(
*ts.to_signed(&QualType::uint()).base,
TypeNode::Int
));
assert!(matches!(
*ts.to_signed(&QualType::ulong()).base,
TypeNode::Long
));
assert!(matches!(
*ts.to_signed(&QualType::ushort()).base,
TypeNode::Short
));
}
}