Skip to main content

airl_patch/
apply.rs

1//! Patch application: transform a Module by applying a Patch.
2
3use airl_ir::module::{FuncDef, Module};
4use airl_ir::version::VersionId;
5
6use crate::impact::{self, Impact};
7use crate::ops::{Patch, PatchOp};
8use crate::traverse;
9use crate::validate;
10use crate::PatchError;
11
12/// Result of applying a patch.
13#[derive(Clone, Debug)]
14pub struct PatchResult {
15    /// The new module after patch application.
16    pub new_module: Module,
17    /// Content hash of the new module state.
18    pub new_version: String,
19    /// Impact analysis: which functions/types were affected.
20    pub impact: Impact,
21}
22
23/// Apply a patch to a module, producing a new module.
24///
25/// Validates the patch first, then applies operations sequentially.
26/// Returns the new module, its version hash, and impact analysis.
27pub fn apply_patch(module: &Module, patch: &Patch) -> Result<PatchResult, PatchError> {
28    // Validate before applying
29    validate::validate_patch(module, patch)?;
30
31    // Compute impact before mutation
32    let impact_result = impact::analyze_impact(module, &patch.operations);
33
34    // Clone module and apply operations sequentially
35    let mut new_module = module.clone();
36
37    for op in &patch.operations {
38        apply_op(&mut new_module, op)?;
39    }
40
41    // Compute new version hash
42    let version = VersionId::compute(&new_module);
43    let version_hex = version.to_hex();
44
45    Ok(PatchResult {
46        new_module,
47        new_version: version_hex,
48        impact: impact_result,
49    })
50}
51
52/// Apply a single patch operation to a module (mutating it in place).
53fn apply_op(module: &mut Module, op: &PatchOp) -> Result<(), PatchError> {
54    match op {
55        PatchOp::ReplaceNode {
56            target,
57            replacement,
58        } => {
59            // Find which function contains this node and replace it
60            let mut found = false;
61            let new_functions: Vec<FuncDef> = module
62                .module
63                .functions
64                .iter()
65                .map(|func| {
66                    if let Some(new_body) =
67                        traverse::replace_node_in_tree(&func.body, target, replacement)
68                    {
69                        found = true;
70                        FuncDef {
71                            body: new_body,
72                            ..func.clone()
73                        }
74                    } else {
75                        func.clone()
76                    }
77                })
78                .collect();
79
80            if !found {
81                return Err(PatchError::NodeNotFound {
82                    node_id: target.to_string(),
83                });
84            }
85            module.module.functions = new_functions;
86            Ok(())
87        }
88
89        PatchOp::AddFunction { func } => {
90            module.module.functions.push(func.clone());
91            Ok(())
92        }
93
94        PatchOp::RemoveFunction { func_id } => {
95            module.module.functions.retain(|f| &f.id != func_id);
96            Ok(())
97        }
98
99        PatchOp::AddImport { import } => {
100            module.module.imports.push(import.clone());
101            Ok(())
102        }
103
104        PatchOp::RemoveImport { import } => {
105            module
106                .module
107                .imports
108                .retain(|i| i.module != import.module || i.items != import.items);
109            Ok(())
110        }
111
112        PatchOp::RenameSymbol {
113            old_name,
114            new_name,
115            scope,
116        } => {
117            let new_functions: Vec<FuncDef> = module
118                .module
119                .functions
120                .iter()
121                .map(|func| {
122                    let in_scope = scope.as_ref().is_none_or(|s| &func.id == s);
123                    if in_scope {
124                        let new_body = traverse::rename_in_tree(&func.body, old_name, new_name);
125                        let new_name_field = if func.name == *old_name {
126                            new_name.clone()
127                        } else {
128                            func.name.clone()
129                        };
130                        FuncDef {
131                            name: new_name_field,
132                            body: new_body,
133                            ..func.clone()
134                        }
135                    } else {
136                        func.clone()
137                    }
138                })
139                .collect();
140            module.module.functions = new_functions;
141            Ok(())
142        }
143
144        PatchOp::AddEffect { func_id, effect } => {
145            for func in &mut module.module.functions {
146                if &func.id == func_id {
147                    if !func.effects.contains(effect) {
148                        func.effects.push(effect.clone());
149                    }
150                    return Ok(());
151                }
152            }
153            Err(PatchError::FunctionNotFound {
154                func_id: func_id.to_string(),
155            })
156        }
157
158        PatchOp::RemoveEffect { func_id, effect } => {
159            for func in &mut module.module.functions {
160                if &func.id == func_id {
161                    func.effects.retain(|e| e != effect);
162                    return Ok(());
163                }
164            }
165            Err(PatchError::FunctionNotFound {
166                func_id: func_id.to_string(),
167            })
168        }
169    }
170}