1use crate::{Ir, Module, Primitive, TypeDef, TypeId, TypeKind};
5use core::fmt;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum ValidationError {
12 UnknownType {
14 module: String,
16 type_id: TypeId,
18 location: String,
20 },
21 DuplicateTypeId {
23 module: String,
25 type_id: TypeId,
27 },
28 EnumUnderlyingNotInteger {
30 module: String,
32 type_id: TypeId,
34 },
35}
36
37impl fmt::Display for ValidationError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 ValidationError::UnknownType {
41 module,
42 type_id,
43 location,
44 } => write!(
45 f,
46 "module \"{module}\": {location} references undefined type id {}",
47 type_id.0
48 ),
49 ValidationError::DuplicateTypeId { module, type_id } => {
50 write!(f, "module \"{module}\": duplicate type id {}", type_id.0)
51 }
52 ValidationError::EnumUnderlyingNotInteger { module, type_id } => write!(
53 f,
54 "module \"{module}\": enum type id {} has a non-integer underlying type",
55 type_id.0
56 ),
57 }
58 }
59}
60
61impl core::error::Error for ValidationError {}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ValidationErrors(Vec<ValidationError>);
68
69impl ValidationErrors {
70 pub fn errors(&self) -> &[ValidationError] {
72 &self.0
73 }
74
75 pub fn into_errors(self) -> Vec<ValidationError> {
77 self.0
78 }
79}
80
81impl fmt::Display for ValidationErrors {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{} IR validation error(s)", self.0.len())?;
84 for error in &self.0 {
85 write!(f, "\n - {error}")?;
86 }
87 Ok(())
88 }
89}
90
91impl core::error::Error for ValidationErrors {}
92
93impl Ir {
94 pub fn validate(&self) -> core::result::Result<(), ValidationErrors> {
103 let mut errors = Vec::new();
104 for module in &self.modules {
105 validate_module(module, &mut errors);
106 }
107 if errors.is_empty() {
108 Ok(())
109 } else {
110 Err(ValidationErrors(errors))
111 }
112 }
113}
114
115fn validate_module(module: &Module, errors: &mut Vec<ValidationError>) {
116 let mut by_id: HashMap<u32, &TypeDef> = HashMap::with_capacity(module.types.len());
117 for def in &module.types {
118 if by_id.insert(def.id.0, def).is_some() {
119 errors.push(ValidationError::DuplicateTypeId {
120 module: module.name.clone(),
121 type_id: def.id,
122 });
123 }
124 }
125
126 let check = |id: TypeId, location: String, errors: &mut Vec<ValidationError>| {
127 if !by_id.contains_key(&id.0) {
128 errors.push(ValidationError::UnknownType {
129 module: module.name.clone(),
130 type_id: id,
131 location,
132 });
133 }
134 };
135
136 for def in &module.types {
137 match &def.kind {
138 TypeKind::Primitive { .. } | TypeKind::Opaque { .. } => {}
139 TypeKind::Pointer { pointee, .. } => {
140 check(*pointee, format!("type `{}` pointee", def.name), errors);
141 }
142 TypeKind::Array { element, .. } => {
143 check(*element, format!("type `{}` element", def.name), errors);
144 }
145 TypeKind::Struct { fields, .. } | TypeKind::Union { fields, .. } => {
146 for field in fields {
147 check(
148 field.type_id,
149 format!("type `{}` field `{}`", def.name, field.name),
150 errors,
151 );
152 }
153 }
154 TypeKind::Enum { underlying, .. } => {
155 check(
156 *underlying,
157 format!("type `{}` underlying", def.name),
158 errors,
159 );
160 if by_id.contains_key(&underlying.0)
162 && !matches!(resolve_primitive(&by_id, *underlying), Some(p) if is_integer(p))
163 {
164 errors.push(ValidationError::EnumUnderlyingNotInteger {
165 module: module.name.clone(),
166 type_id: def.id,
167 });
168 }
169 }
170 TypeKind::Function {
171 parameters,
172 return_type,
173 ..
174 } => {
175 for (index, param) in parameters.iter().enumerate() {
176 check(
177 *param,
178 format!("type `{}` parameter {index}", def.name),
179 errors,
180 );
181 }
182 check(
183 *return_type,
184 format!("type `{}` return type", def.name),
185 errors,
186 );
187 }
188 TypeKind::Typedef { target } => {
189 check(*target, format!("type `{}` target", def.name), errors);
190 }
191 }
192 }
193
194 for function in &module.functions {
195 let name = qualify(&function.namespace, &function.name);
196 check(
197 function.return_type,
198 format!("function `{name}` return type"),
199 errors,
200 );
201 for (index, param) in function.parameters.iter().enumerate() {
202 check(
203 param.type_id,
204 format!("function `{name}` parameter {index}"),
205 errors,
206 );
207 }
208 }
209
210 for global in &module.globals {
211 let name = qualify(&global.namespace, &global.name);
212 check(global.type_id, format!("global `{name}`"), errors);
213 }
214
215 for vtable in &module.vtables {
216 for entry in &vtable.entries {
217 if let Some(signature) = entry.signature {
218 check(
219 signature,
220 format!("vtable `{}` entry {}", vtable.name, entry.index),
221 errors,
222 );
223 }
224 }
225 }
226}
227
228fn qualify(namespace: &[String], name: &str) -> String {
230 if namespace.is_empty() {
231 name.to_string()
232 } else {
233 format!("{}::{name}", namespace.join("::"))
234 }
235}
236
237fn resolve_primitive(by_id: &HashMap<u32, &TypeDef>, start: TypeId) -> Option<Primitive> {
239 let mut id = start;
240 for _ in 0..64 {
242 match &by_id.get(&id.0)?.kind {
243 TypeKind::Primitive { primitive } => return Some(*primitive),
244 TypeKind::Typedef { target } => id = *target,
245 _ => return None,
246 }
247 }
248 None
249}
250
251fn is_integer(primitive: Primitive) -> bool {
252 matches!(
253 primitive,
254 Primitive::U8
255 | Primitive::U16
256 | Primitive::U32
257 | Primitive::U64
258 | Primitive::I8
259 | Primitive::I16
260 | Primitive::I32
261 | Primitive::I64
262 )
263}