Skip to main content

draxl_rust/
merge_semantics.rs

1use crate::merge_context::{LetRegion, TreeContext};
2use draxl_ast::{Expr, Stmt};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum SemanticOwner {
6    Binding {
7        let_id: String,
8        binding_id: String,
9    },
10    Parameter {
11        fn_id: String,
12        param_id: String,
13        param_name: String,
14    },
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SemanticRegion {
19    BindingName,
20    BindingInitializer,
21    ParameterTypeContract,
22    ParameterBodyInterpretation,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct SemanticChange {
27    pub owner: SemanticOwner,
28    pub region: SemanticRegion,
29    pub op_index: usize,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum SemanticPatchNode {
34    Expr(Expr),
35    Stmt(Stmt),
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum SemanticSlotOwner {
40    File,
41    Node(String),
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct SemanticSlotRef {
46    pub owner: SemanticSlotOwner,
47    pub slot: String,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum SemanticOp {
52    Set {
53        node_id: String,
54        path: Vec<String>,
55        ident_value: bool,
56    },
57    Clear {
58        node_id: String,
59    },
60    Put {
61        slot: SemanticSlotRef,
62        node: Option<SemanticPatchNode>,
63    },
64    Replace {
65        target_id: String,
66        replacement: Option<SemanticPatchNode>,
67    },
68    Delete {
69        target_id: String,
70    },
71    Move {
72        target_id: String,
73        dest_slot: Option<SemanticSlotRef>,
74    },
75    Other,
76}
77
78pub fn extract_semantic_changes(ops: &[SemanticOp], context: &TreeContext) -> Vec<SemanticChange> {
79    let mut changes = Vec::new();
80
81    for (op_index, op) in ops.iter().enumerate() {
82        changes.extend(semantic_changes_for_op(op_index, op, context));
83    }
84
85    changes
86}
87
88fn semantic_changes_for_op(
89    op_index: usize,
90    op: &SemanticOp,
91    context: &TreeContext,
92) -> Vec<SemanticChange> {
93    let mut changes = Vec::new();
94
95    if let Some(owner) = binding_name_owner(op, context) {
96        changes.push(SemanticChange {
97            owner,
98            region: SemanticRegion::BindingName,
99            op_index,
100        });
101    }
102
103    if let Some(owner) = binding_initializer_owner(op, context) {
104        changes.push(SemanticChange {
105            owner,
106            region: SemanticRegion::BindingInitializer,
107            op_index,
108        });
109    }
110
111    if let Some(owner) = parameter_type_contract_owner(op, context) {
112        changes.push(SemanticChange {
113            owner,
114            region: SemanticRegion::ParameterTypeContract,
115            op_index,
116        });
117    }
118
119    changes.extend(parameter_body_interpretation_changes(op_index, op, context));
120
121    changes
122}
123
124fn binding_name_owner(op: &SemanticOp, context: &TreeContext) -> Option<SemanticOwner> {
125    match op {
126        SemanticOp::Set {
127            node_id,
128            path,
129            ident_value,
130        } if path.as_slice() == ["name"] => {
131            if !ident_value {
132                return None;
133            }
134            let node = context.node(node_id)?;
135            if !node.is_let_binding {
136                return None;
137            }
138            binding_owner_for_let(node.enclosing_let.as_deref()?, context)
139        }
140        _ => None,
141    }
142}
143
144fn binding_initializer_owner(op: &SemanticOp, context: &TreeContext) -> Option<SemanticOwner> {
145    match op {
146        SemanticOp::Put { slot, .. } => init_slot_binding_owner(slot, context),
147        SemanticOp::Move {
148            target_id,
149            dest_slot: Some(slot),
150        } => init_slot_binding_owner(slot, context)
151            .or_else(|| node_in_init_region_binding_owner(target_id, context)),
152        SemanticOp::Replace { target_id, .. }
153        | SemanticOp::Delete { target_id }
154        | SemanticOp::Move { target_id, .. } => {
155            node_in_init_region_binding_owner(target_id, context)
156        }
157        SemanticOp::Set { node_id, .. } | SemanticOp::Clear { node_id } => {
158            node_in_init_region_binding_owner(node_id, context)
159        }
160        _ => None,
161    }
162}
163
164fn init_slot_binding_owner(slot: &SemanticSlotRef, context: &TreeContext) -> Option<SemanticOwner> {
165    if slot.slot != "init" {
166        return None;
167    }
168
169    let SemanticSlotOwner::Node(owner_id) = &slot.owner else {
170        return None;
171    };
172
173    let node = context.node(owner_id)?;
174    if !node.is_let_stmt {
175        return None;
176    }
177
178    binding_owner_for_let(owner_id, context)
179}
180
181fn node_in_init_region_binding_owner(
182    node_id: &str,
183    context: &TreeContext,
184) -> Option<SemanticOwner> {
185    let node = context.node(node_id)?;
186    if node.let_region != Some(LetRegion::Init) {
187        return None;
188    }
189
190    binding_owner_for_let(node.enclosing_let.as_deref()?, context)
191}
192
193fn binding_owner_for_let(let_id: &str, context: &TreeContext) -> Option<SemanticOwner> {
194    Some(SemanticOwner::Binding {
195        let_id: let_id.to_owned(),
196        binding_id: context.binding_id_for_let(let_id)?.to_owned(),
197    })
198}
199
200fn parameter_type_contract_owner(op: &SemanticOp, context: &TreeContext) -> Option<SemanticOwner> {
201    match op {
202        SemanticOp::Put { slot, .. } => param_type_slot_owner(slot, context),
203        SemanticOp::Move {
204            target_id,
205            dest_slot: Some(slot),
206        } => param_type_slot_owner(slot, context)
207            .or_else(|| node_in_param_type_region_owner(target_id, context)),
208        SemanticOp::Replace { target_id, .. }
209        | SemanticOp::Delete { target_id }
210        | SemanticOp::Move { target_id, .. } => node_in_param_type_region_owner(target_id, context),
211        SemanticOp::Set { node_id, .. } | SemanticOp::Clear { node_id } => {
212            node_in_param_type_region_owner(node_id, context)
213        }
214        _ => None,
215    }
216}
217
218fn param_type_slot_owner(slot: &SemanticSlotRef, context: &TreeContext) -> Option<SemanticOwner> {
219    if slot.slot != "ty" {
220        return None;
221    }
222
223    let SemanticSlotOwner::Node(owner_id) = &slot.owner else {
224        return None;
225    };
226
227    parameter_owner_from_param_id(owner_id, context)
228}
229
230fn node_in_param_type_region_owner(node_id: &str, context: &TreeContext) -> Option<SemanticOwner> {
231    let node = context.node(node_id)?;
232    if !node.param_type_region {
233        return None;
234    }
235
236    parameter_owner_from_param_id(node.enclosing_param.as_deref()?, context)
237}
238
239fn parameter_owner_from_param_id(param_id: &str, context: &TreeContext) -> Option<SemanticOwner> {
240    let node = context.node(param_id)?;
241    Some(SemanticOwner::Parameter {
242        fn_id: node.enclosing_fn.clone()?,
243        param_id: param_id.to_owned(),
244        param_name: node.param_name.clone()?,
245    })
246}
247
248fn parameter_body_interpretation_changes(
249    op_index: usize,
250    op: &SemanticOp,
251    context: &TreeContext,
252) -> Vec<SemanticChange> {
253    let (fn_id, node) = match op {
254        SemanticOp::Replace {
255            target_id,
256            replacement,
257        } => {
258            let Some(fn_id) = function_body_owner_id_for_node(target_id, context) else {
259                return Vec::new();
260            };
261            (fn_id, replacement)
262        }
263        SemanticOp::Put { slot, node } => {
264            let Some(fn_id) = function_body_owner_id_for_slot(slot, context) else {
265                return Vec::new();
266            };
267            (fn_id, node)
268        }
269        _ => return Vec::new(),
270    };
271
272    context
273        .params_in_fn(&fn_id)
274        .iter()
275        .filter(|param| patch_node_mentions_name(node.as_ref(), &param.name))
276        .map(|param| SemanticChange {
277            owner: SemanticOwner::Parameter {
278                fn_id: fn_id.clone(),
279                param_id: param.id.clone(),
280                param_name: param.name.clone(),
281            },
282            region: SemanticRegion::ParameterBodyInterpretation,
283            op_index,
284        })
285        .collect()
286}
287
288fn function_body_owner_id_for_node(node_id: &str, context: &TreeContext) -> Option<String> {
289    let node = context.node(node_id)?;
290    if !node.in_fn_body {
291        return None;
292    }
293
294    node.enclosing_fn.clone()
295}
296
297fn function_body_owner_id_for_slot(
298    slot: &SemanticSlotRef,
299    context: &TreeContext,
300) -> Option<String> {
301    let SemanticSlotOwner::Node(owner_id) = &slot.owner else {
302        return None;
303    };
304
305    function_body_owner_id_for_node(owner_id, context)
306}
307
308fn patch_node_mentions_name(node: Option<&SemanticPatchNode>, name: &str) -> bool {
309    match node {
310        Some(SemanticPatchNode::Expr(expr)) => expr_mentions_name(expr, name),
311        Some(SemanticPatchNode::Stmt(stmt)) => stmt_mentions_name(stmt, name),
312        _ => false,
313    }
314}
315
316fn stmt_mentions_name(stmt: &Stmt, name: &str) -> bool {
317    match stmt {
318        Stmt::Let(node) => expr_mentions_name(&node.value, name),
319        Stmt::Expr(node) => expr_mentions_name(&node.expr, name),
320        Stmt::Item(_) | Stmt::Doc(_) | Stmt::Comment(_) => false,
321    }
322}
323
324fn expr_mentions_name(expr: &Expr, name: &str) -> bool {
325    match expr {
326        Expr::Path(node) => node.path.segments.len() == 1 && node.path.segments[0] == name,
327        Expr::Lit(_) => false,
328        Expr::Group(node) => expr_mentions_name(&node.expr, name),
329        Expr::Binary(node) => {
330            expr_mentions_name(&node.lhs, name) || expr_mentions_name(&node.rhs, name)
331        }
332        Expr::Unary(node) => expr_mentions_name(&node.expr, name),
333        Expr::Call(node) => {
334            expr_mentions_name(&node.callee, name)
335                || node.args.iter().any(|arg| expr_mentions_name(arg, name))
336        }
337        Expr::Match(node) => {
338            expr_mentions_name(&node.scrutinee, name)
339                || node
340                    .arms
341                    .iter()
342                    .any(|arm| expr_mentions_name(&arm.body, name))
343                || node
344                    .arms
345                    .iter()
346                    .filter_map(|arm| arm.guard.as_ref())
347                    .any(|guard| expr_mentions_name(guard, name))
348        }
349        Expr::Block(block) => block
350            .stmts
351            .iter()
352            .any(|stmt| stmt_mentions_name(stmt, name)),
353    }
354}