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(), ¶m.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}