Skip to main content

imping_ir/
validate.rs

1//! Semantic checks on an [`Ir`](crate::Ir): type references and type-table
2//! invariants.
3
4use crate::{Ir, Module, Primitive, TypeDef, TypeId, TypeKind};
5use core::fmt;
6use std::collections::HashMap;
7
8/// A single problem found by [`Ir::validate`](crate::Ir::validate).
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum ValidationError {
12    /// A referenced type id has no definition in its module.
13    UnknownType {
14        /// The module the reference is in.
15        module: String,
16        /// The undefined type id.
17        type_id: TypeId,
18        /// A human-readable description of where the reference occurs.
19        location: String,
20    },
21    /// Two type definitions in a module share the same id.
22    DuplicateTypeId {
23        /// The module the duplicate is in.
24        module: String,
25        /// The duplicated type id.
26        type_id: TypeId,
27    },
28    /// An enum's `underlying` type does not resolve to an integer primitive.
29    EnumUnderlyingNotInteger {
30        /// The module the enum is in.
31        module: String,
32        /// The id of the enum type whose underlying type is not an integer.
33        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/// The problems found by [`Ir::validate`](crate::Ir::validate); never empty.
64///
65/// Its [`Display`](fmt::Display) lists every problem.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ValidationErrors(Vec<ValidationError>);
68
69impl ValidationErrors {
70    /// Returns the individual errors.
71    pub fn errors(&self) -> &[ValidationError] {
72        &self.0
73    }
74
75    /// Consumes the collection, returning the individual errors.
76    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    /// Checks every module's type references and type-table invariants.
95    ///
96    /// Parsing doesn't run this. It reports every problem and leaves it to the
97    /// caller to decide which are fatal.
98    ///
99    /// # Errors
100    ///
101    /// [`ValidationErrors`] listing every problem, if there are any.
102    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                // A missing underlying type was just reported; don't report it twice.
161                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
228/// Joins a namespace path and name, e.g. `Engine::Entity::Update`.
229fn 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
237/// Follows typedefs to the primitive a type resolves to, if any.
238fn resolve_primitive(by_id: &HashMap<u32, &TypeDef>, start: TypeId) -> Option<Primitive> {
239    let mut id = start;
240    // Bounded, so a typedef cycle gives up instead of looping forever.
241    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}