Skip to main content

airl_patch/
validate.rs

1//! Patch validation: verify a patch can be safely applied.
2
3use airl_ir::module::Module;
4
5use crate::ops::{Patch, PatchOp};
6use crate::traverse;
7use crate::PatchError;
8
9/// Validate that a patch can be applied to the given module.
10/// Returns Ok(()) if valid, Err with details if not.
11pub fn validate_patch(module: &Module, patch: &Patch) -> Result<(), PatchError> {
12    for (i, op) in patch.operations.iter().enumerate() {
13        validate_op(module, op).map_err(|e| PatchError::ValidationFailed {
14            op_index: i,
15            message: e.to_string(),
16        })?;
17    }
18    Ok(())
19}
20
21fn validate_op(module: &Module, op: &PatchOp) -> Result<(), PatchError> {
22    match op {
23        PatchOp::ReplaceNode { target, .. } => {
24            // Target node must exist in some function
25            if traverse::find_containing_function(module, target).is_none() {
26                return Err(PatchError::NodeNotFound {
27                    node_id: target.to_string(),
28                });
29            }
30            Ok(())
31        }
32        PatchOp::RemoveFunction { func_id } => {
33            if module.find_function_by_id(func_id).is_none() {
34                return Err(PatchError::FunctionNotFound {
35                    func_id: func_id.to_string(),
36                });
37            }
38            Ok(())
39        }
40        PatchOp::AddFunction { func } => {
41            // Check that function name doesn't already exist
42            if module.find_function(&func.name).is_some() {
43                return Err(PatchError::DuplicateFunction {
44                    name: func.name.clone(),
45                });
46            }
47            Ok(())
48        }
49        PatchOp::AddEffect { func_id, .. } | PatchOp::RemoveEffect { func_id, .. } => {
50            if module.find_function_by_id(func_id).is_none() {
51                return Err(PatchError::FunctionNotFound {
52                    func_id: func_id.to_string(),
53                });
54            }
55            Ok(())
56        }
57        // RenameSymbol, AddImport, RemoveImport are always structurally valid
58        PatchOp::RenameSymbol { .. } | PatchOp::AddImport { .. } | PatchOp::RemoveImport { .. } => {
59            Ok(())
60        }
61    }
62}