Skip to main content

ryo_executor/engine/impls/
struct_literal.rs

1//! ASTRegApply implementation for struct literal field mutations
2//!
3//! V2 implementation that operates directly on ASTRegistry.
4//! These mutations add/remove fields from struct literal expressions
5//! (e.g., `Config { field: value }`) throughout the codebase.
6//!
7//! # Design Notes
8//!
9//! Unlike function-specific mutations (like AddMatchArm), these mutations
10//! operate on ALL struct literals matching the target struct name across
11//! the entire codebase. This requires walking all symbols in the registry.
12//!
13//! ## Self Resolution
14//!
15//! Inside impl blocks, `Self { ... }` must be correctly resolved to the
16//! impl's target type. The `self_ty` parameter tracks this context.
17//!
18//! ## Future Improvements (TODO)
19//!
20//! - **Conflict detection**: Since this mutation affects ALL matching struct
21//!   literals globally, conflict detection is difficult. Consider decomposing
22//!   at Spec → Mutation conversion into individual symbol-targeted mutations.
23//!
24//! - **Granularity**: Current design is too coarse. Ideally:
25//!   Spec (AddStructLiteralField)
26//!   → Analyze (find all struct literals)
27//!   → Decompose into individual Mutations with symbol_id
28//!
29//! - **Type-aware matching**: Currently matches by struct name string.
30//!   Ideally would use type system for precise matching.
31//!
32//! - **Scope control**: Add ability to limit changes to specific files
33//!   or modules rather than global changes.
34
35use ryo_mutations::basic::{AddStructLiteralFieldMutation, RemoveStructLiteralFieldMutation};
36use ryo_mutations::MutationResult;
37use ryo_source::pure::macro_utils;
38use ryo_source::pure::{PureBlock, PureExpr, PureImplItem, PureItem, PureStmt, ToSynError};
39
40use crate::engine::{ASTMutationContext, ASTRegApply, ModificationType};
41
42// ============================================================================
43// ASTRegApply for AddStructLiteralFieldMutation
44// ============================================================================
45
46impl ASTRegApply for AddStructLiteralFieldMutation {
47    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
48        // Get struct name from registry via SymbolId
49        let struct_name = ctx
50            .symbol_registry
51            .path(self.struct_id)
52            .map(|p| p.name().to_string())
53            .unwrap_or_else(|| format!("{:?}", self.struct_id));
54
55        let mut total_changes = 0;
56
57        // Collect all symbol IDs first to avoid borrow conflict
58        let symbol_ids: Vec<_> = ctx.symbol_registry.iter().map(|(id, _)| id).collect();
59
60        for id in symbol_ids {
61            // Skip methods - they're handled via their parent Impl block
62            if let Some(kind) = ctx.symbol_registry.kind(id) {
63                if kind == ryo_symbol::SymbolKind::Method {
64                    continue;
65                }
66            }
67
68            let ast = match ctx.get_ast_mut(id) {
69                Some(ast) => ast,
70                None => continue,
71            };
72
73            let changes = match ast {
74                PureItem::Fn(f) => match walk_and_add_field(
75                    &mut f.body,
76                    &struct_name,
77                    &self.field_name,
78                    &self.value,
79                    None,
80                ) {
81                    Ok(c) => c,
82                    Err(e) => {
83                        return MutationResult {
84                            mutation_type: "AddStructLiteralField".to_string(),
85                            changes: 0,
86                            description: format!("Failed to serialize macro tokens: {}", e),
87                        };
88                    }
89                },
90                PureItem::Impl(impl_block) => {
91                    let self_ty = Some(impl_block.self_ty.as_str());
92                    let mut count = 0;
93                    for item in &mut impl_block.items {
94                        if let PureImplItem::Fn(method) = item {
95                            match walk_and_add_field(
96                                &mut method.body,
97                                &struct_name,
98                                &self.field_name,
99                                &self.value,
100                                self_ty,
101                            ) {
102                                Ok(c) => count += c,
103                                Err(e) => {
104                                    return MutationResult {
105                                        mutation_type: "AddStructLiteralField".to_string(),
106                                        changes: 0,
107                                        description: format!(
108                                            "Failed to serialize macro tokens: {}",
109                                            e
110                                        ),
111                                    };
112                                }
113                            }
114                        }
115                    }
116                    count
117                }
118                _ => 0,
119            };
120
121            if changes > 0 {
122                ctx.emit_modified(
123                    id,
124                    ModificationType::Other("StructLiteralFieldAdded".into()),
125                );
126                total_changes += changes;
127            }
128        }
129
130        MutationResult {
131            mutation_type: "AddStructLiteralField".to_string(),
132            changes: total_changes,
133            description: if total_changes > 0 {
134                format!(
135                    "Added field '{}' to {} struct literal(s) of '{}'",
136                    self.field_name, total_changes, struct_name
137                )
138            } else {
139                format!(
140                    "No struct literals of '{}' found or field already exists",
141                    struct_name
142                )
143            },
144        }
145    }
146}
147
148// ============================================================================
149// ASTRegApply for RemoveStructLiteralFieldMutation
150// ============================================================================
151
152impl ASTRegApply for RemoveStructLiteralFieldMutation {
153    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
154        // Get struct name from registry via SymbolId
155        let struct_name = ctx
156            .symbol_registry
157            .path(self.struct_id)
158            .map(|p| p.name().to_string())
159            .unwrap_or_else(|| format!("{:?}", self.struct_id));
160
161        let mut total_changes = 0;
162
163        // Collect all symbol IDs first to avoid borrow conflict
164        let symbol_ids: Vec<_> = ctx.symbol_registry.iter().map(|(id, _)| id).collect();
165
166        for id in symbol_ids {
167            // Skip methods - they're handled via their parent Impl block
168            if let Some(kind) = ctx.symbol_registry.kind(id) {
169                if kind == ryo_symbol::SymbolKind::Method {
170                    continue;
171                }
172            }
173
174            let ast = match ctx.get_ast_mut(id) {
175                Some(ast) => ast,
176                None => continue,
177            };
178
179            let changes = match ast {
180                PureItem::Fn(f) => {
181                    match walk_and_remove_field(&mut f.body, &struct_name, &self.field_name, None) {
182                        Ok(c) => c,
183                        Err(e) => {
184                            return MutationResult {
185                                mutation_type: "RemoveStructLiteralField".to_string(),
186                                changes: 0,
187                                description: format!("Failed to serialize macro tokens: {}", e),
188                            };
189                        }
190                    }
191                }
192                PureItem::Impl(impl_block) => {
193                    let self_ty = Some(impl_block.self_ty.as_str());
194                    let mut count = 0;
195                    for item in &mut impl_block.items {
196                        if let PureImplItem::Fn(method) = item {
197                            match walk_and_remove_field(
198                                &mut method.body,
199                                &struct_name,
200                                &self.field_name,
201                                self_ty,
202                            ) {
203                                Ok(c) => count += c,
204                                Err(e) => {
205                                    return MutationResult {
206                                        mutation_type: "RemoveStructLiteralField".to_string(),
207                                        changes: 0,
208                                        description: format!(
209                                            "Failed to serialize macro tokens: {}",
210                                            e
211                                        ),
212                                    };
213                                }
214                            }
215                        }
216                    }
217                    count
218                }
219                _ => 0,
220            };
221
222            if changes > 0 {
223                ctx.emit_modified(
224                    id,
225                    ModificationType::Other("StructLiteralFieldRemoved".into()),
226                );
227                total_changes += changes;
228            }
229        }
230
231        MutationResult {
232            mutation_type: "RemoveStructLiteralField".to_string(),
233            changes: total_changes,
234            description: if total_changes > 0 {
235                format!(
236                    "Removed field '{}' from {} struct literal(s) of '{}'",
237                    self.field_name, total_changes, struct_name
238                )
239            } else {
240                format!(
241                    "No struct literals of '{}' with field '{}' found",
242                    struct_name, self.field_name
243                )
244            },
245        }
246    }
247}
248
249// ============================================================================
250// Helper functions
251// ============================================================================
252
253/// Check if path matches target struct name
254fn matches_struct(path: &str, struct_name: &str, self_ty: Option<&str>) -> bool {
255    // Direct match
256    if path.ends_with(struct_name) || path == struct_name {
257        return true;
258    }
259    // Match Self if we're in an impl block for target struct
260    if path == "Self" {
261        if let Some(ty) = self_ty {
262            return ty.ends_with(struct_name) || ty == struct_name;
263        }
264    }
265    false
266}
267
268/// Parse value string into PureExpr
269fn parse_value(value: &str) -> PureExpr {
270    let value = value.trim();
271
272    // Handle None
273    if value == "None" {
274        return PureExpr::Path("None".to_string());
275    }
276
277    // Handle Some(...)
278    if value.starts_with("Some(") && value.ends_with(')') {
279        let inner = &value[5..value.len() - 1];
280        return PureExpr::Call {
281            func: Box::new(PureExpr::Path("Some".to_string())),
282            args: vec![PureExpr::Other(inner.to_string())],
283        };
284    }
285
286    // Handle Default::default()
287    if value == "Default::default()" {
288        return PureExpr::Call {
289            func: Box::new(PureExpr::Path("Default::default".to_string())),
290            args: vec![],
291        };
292    }
293
294    // Handle numeric literals
295    if value.parse::<i64>().is_ok() || value.parse::<f64>().is_ok() {
296        return PureExpr::Lit(value.to_string());
297    }
298
299    // Default: treat as Other expression
300    PureExpr::Other(value.to_string())
301}
302
303/// Walk block and add field to matching struct literals
304fn walk_and_add_field(
305    block: &mut PureBlock,
306    struct_name: &str,
307    field_name: &str,
308    value: &str,
309    self_ty: Option<&str>,
310) -> Result<usize, ToSynError> {
311    let mut count = 0;
312    for stmt in &mut block.stmts {
313        count += walk_stmt_and_add_field(stmt, struct_name, field_name, value, self_ty)?;
314    }
315    Ok(count)
316}
317
318/// Walk block and remove field from matching struct literals
319fn walk_and_remove_field(
320    block: &mut PureBlock,
321    struct_name: &str,
322    field_name: &str,
323    self_ty: Option<&str>,
324) -> Result<usize, ToSynError> {
325    let mut count = 0;
326    for stmt in &mut block.stmts {
327        count += walk_stmt_and_remove_field(stmt, struct_name, field_name, self_ty)?;
328    }
329    Ok(count)
330}
331
332fn walk_stmt_and_add_field(
333    stmt: &mut PureStmt,
334    struct_name: &str,
335    field_name: &str,
336    value: &str,
337    self_ty: Option<&str>,
338) -> Result<usize, ToSynError> {
339    match stmt {
340        PureStmt::Local {
341            init: Some(expr), ..
342        } => {
343            return walk_expr_and_add_field(expr, struct_name, field_name, value, self_ty);
344        }
345        PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
346            return walk_expr_and_add_field(expr, struct_name, field_name, value, self_ty);
347        }
348        _ => {}
349    }
350    Ok(0)
351}
352
353fn walk_stmt_and_remove_field(
354    stmt: &mut PureStmt,
355    struct_name: &str,
356    field_name: &str,
357    self_ty: Option<&str>,
358) -> Result<usize, ToSynError> {
359    match stmt {
360        PureStmt::Local {
361            init: Some(expr), ..
362        } => {
363            return walk_expr_and_remove_field(expr, struct_name, field_name, self_ty);
364        }
365        PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
366            return walk_expr_and_remove_field(expr, struct_name, field_name, self_ty);
367        }
368        _ => {}
369    }
370    Ok(0)
371}
372
373fn walk_expr_and_add_field(
374    expr: &mut PureExpr,
375    struct_name: &str,
376    field_name: &str,
377    value: &str,
378    self_ty: Option<&str>,
379) -> Result<usize, ToSynError> {
380    let mut count = 0;
381
382    // Check if this is a target struct literal
383    if let PureExpr::Struct { path, fields } = expr {
384        if matches_struct(path, struct_name, self_ty) {
385            // Check if field already exists
386            if !fields.iter().any(|(name, _)| name == field_name) {
387                fields.push((field_name.to_string(), parse_value(value)));
388                count += 1;
389            }
390        }
391    }
392
393    // Recursively walk children
394    count += walk_expr_children_and_add_field(expr, struct_name, field_name, value, self_ty)?;
395    Ok(count)
396}
397
398fn walk_expr_and_remove_field(
399    expr: &mut PureExpr,
400    struct_name: &str,
401    field_name: &str,
402    self_ty: Option<&str>,
403) -> Result<usize, ToSynError> {
404    let mut count = 0;
405
406    // Check if this is a target struct literal
407    if let PureExpr::Struct { path, fields } = expr {
408        if matches_struct(path, struct_name, self_ty) {
409            let original_len = fields.len();
410            fields.retain(|(name, _)| name != field_name);
411            if fields.len() < original_len {
412                count += 1;
413            }
414        }
415    }
416
417    // Recursively walk children
418    count += walk_expr_children_and_remove_field(expr, struct_name, field_name, self_ty)?;
419    Ok(count)
420}
421
422fn walk_expr_children_and_add_field(
423    expr: &mut PureExpr,
424    struct_name: &str,
425    field_name: &str,
426    value: &str,
427    self_ty: Option<&str>,
428) -> Result<usize, ToSynError> {
429    match expr {
430        PureExpr::Block { block, .. } => {
431            walk_and_add_field(block, struct_name, field_name, value, self_ty)
432        }
433        PureExpr::If {
434            cond,
435            then_branch,
436            else_branch,
437        } => {
438            let mut count = walk_expr_and_add_field(cond, struct_name, field_name, value, self_ty)?;
439            count += walk_and_add_field(then_branch, struct_name, field_name, value, self_ty)?;
440            if let Some(else_expr) = else_branch {
441                count +=
442                    walk_expr_and_add_field(else_expr, struct_name, field_name, value, self_ty)?;
443            }
444            Ok(count)
445        }
446        PureExpr::Match { expr: e, arms } => {
447            let mut count = walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?;
448            for arm in arms {
449                count += walk_expr_and_add_field(
450                    &mut arm.body,
451                    struct_name,
452                    field_name,
453                    value,
454                    self_ty,
455                )?;
456            }
457            Ok(count)
458        }
459        PureExpr::Loop { body: block, .. } | PureExpr::Unsafe(block) => {
460            walk_and_add_field(block, struct_name, field_name, value, self_ty)
461        }
462        PureExpr::While { cond, body, .. } => {
463            Ok(
464                walk_expr_and_add_field(cond, struct_name, field_name, value, self_ty)?
465                    + walk_and_add_field(body, struct_name, field_name, value, self_ty)?,
466            )
467        }
468        PureExpr::For { expr: e, body, .. } => {
469            Ok(
470                walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?
471                    + walk_and_add_field(body, struct_name, field_name, value, self_ty)?,
472            )
473        }
474        PureExpr::Async { body, .. } => {
475            walk_and_add_field(body, struct_name, field_name, value, self_ty)
476        }
477        PureExpr::Closure { body, .. } => {
478            walk_expr_and_add_field(body, struct_name, field_name, value, self_ty)
479        }
480        PureExpr::Call { func, args } => {
481            let mut count = walk_expr_and_add_field(func, struct_name, field_name, value, self_ty)?;
482            for arg in args {
483                count += walk_expr_and_add_field(arg, struct_name, field_name, value, self_ty)?;
484            }
485            Ok(count)
486        }
487        PureExpr::MethodCall { receiver, args, .. } => {
488            let mut count =
489                walk_expr_and_add_field(receiver, struct_name, field_name, value, self_ty)?;
490            for arg in args {
491                count += walk_expr_and_add_field(arg, struct_name, field_name, value, self_ty)?;
492            }
493            Ok(count)
494        }
495        PureExpr::Binary { left, right, .. } => {
496            Ok(
497                walk_expr_and_add_field(left, struct_name, field_name, value, self_ty)?
498                    + walk_expr_and_add_field(right, struct_name, field_name, value, self_ty)?,
499            )
500        }
501        PureExpr::Unary { expr: e, .. }
502        | PureExpr::Field { expr: e, .. }
503        | PureExpr::Await(e)
504        | PureExpr::Try(e) => walk_expr_and_add_field(e, struct_name, field_name, value, self_ty),
505        PureExpr::Index { expr: e, index } => {
506            Ok(
507                walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?
508                    + walk_expr_and_add_field(index, struct_name, field_name, value, self_ty)?,
509            )
510        }
511        PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
512            let mut count = 0;
513            for e in exprs {
514                count += walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?;
515            }
516            Ok(count)
517        }
518        PureExpr::Return(Some(e)) | PureExpr::Break { expr: Some(e), .. } => {
519            walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)
520        }
521        PureExpr::Let { expr: e, .. }
522        | PureExpr::Cast { expr: e, .. }
523        | PureExpr::Ref { expr: e, .. } => {
524            walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)
525        }
526        PureExpr::Struct { fields, .. } => {
527            // Recurse into field values (struct already handled above)
528            let mut count = 0;
529            for (_, field_expr) in fields {
530                count +=
531                    walk_expr_and_add_field(field_expr, struct_name, field_name, value, self_ty)?;
532            }
533            Ok(count)
534        }
535        PureExpr::Range { start, end, .. } => {
536            let mut count = 0;
537            if let Some(s) = start {
538                count += walk_expr_and_add_field(s, struct_name, field_name, value, self_ty)?;
539            }
540            if let Some(e) = end {
541                count += walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?;
542            }
543            Ok(count)
544        }
545        PureExpr::Repeat { expr: e, len } => {
546            Ok(
547                walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?
548                    + walk_expr_and_add_field(len, struct_name, field_name, value, self_ty)?,
549            )
550        }
551        PureExpr::Macro { name, tokens, .. } => {
552            // Try to parse and walk expressions inside known macros (vec![], etc.)
553            if let Some(mut exprs) = macro_utils::try_extract_exprs(name, tokens) {
554                let mut count = 0;
555                for e in &mut exprs {
556                    count += walk_expr_and_add_field(e, struct_name, field_name, value, self_ty)?;
557                }
558                if count > 0 {
559                    *tokens = macro_utils::exprs_to_tokens(&exprs)?;
560                }
561                Ok(count)
562            } else {
563                Ok(0)
564            }
565        }
566        _ => Ok(0),
567    }
568}
569
570fn walk_expr_children_and_remove_field(
571    expr: &mut PureExpr,
572    struct_name: &str,
573    field_name: &str,
574    self_ty: Option<&str>,
575) -> Result<usize, ToSynError> {
576    match expr {
577        PureExpr::Block { block, .. } => {
578            walk_and_remove_field(block, struct_name, field_name, self_ty)
579        }
580        PureExpr::If {
581            cond,
582            then_branch,
583            else_branch,
584        } => {
585            let mut count = walk_expr_and_remove_field(cond, struct_name, field_name, self_ty)?;
586            count += walk_and_remove_field(then_branch, struct_name, field_name, self_ty)?;
587            if let Some(else_expr) = else_branch {
588                count += walk_expr_and_remove_field(else_expr, struct_name, field_name, self_ty)?;
589            }
590            Ok(count)
591        }
592        PureExpr::Match { expr: e, arms } => {
593            let mut count = walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?;
594            for arm in arms {
595                count +=
596                    walk_expr_and_remove_field(&mut arm.body, struct_name, field_name, self_ty)?;
597            }
598            Ok(count)
599        }
600        PureExpr::Loop { body: block, .. } | PureExpr::Unsafe(block) => {
601            walk_and_remove_field(block, struct_name, field_name, self_ty)
602        }
603        PureExpr::While { cond, body, .. } => {
604            Ok(
605                walk_expr_and_remove_field(cond, struct_name, field_name, self_ty)?
606                    + walk_and_remove_field(body, struct_name, field_name, self_ty)?,
607            )
608        }
609        PureExpr::For { expr: e, body, .. } => {
610            Ok(
611                walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?
612                    + walk_and_remove_field(body, struct_name, field_name, self_ty)?,
613            )
614        }
615        PureExpr::Async { body, .. } => {
616            walk_and_remove_field(body, struct_name, field_name, self_ty)
617        }
618        PureExpr::Closure { body, .. } => {
619            walk_expr_and_remove_field(body, struct_name, field_name, self_ty)
620        }
621        PureExpr::Call { func, args } => {
622            let mut count = walk_expr_and_remove_field(func, struct_name, field_name, self_ty)?;
623            for arg in args {
624                count += walk_expr_and_remove_field(arg, struct_name, field_name, self_ty)?;
625            }
626            Ok(count)
627        }
628        PureExpr::MethodCall { receiver, args, .. } => {
629            let mut count = walk_expr_and_remove_field(receiver, struct_name, field_name, self_ty)?;
630            for arg in args {
631                count += walk_expr_and_remove_field(arg, struct_name, field_name, self_ty)?;
632            }
633            Ok(count)
634        }
635        PureExpr::Binary { left, right, .. } => {
636            Ok(
637                walk_expr_and_remove_field(left, struct_name, field_name, self_ty)?
638                    + walk_expr_and_remove_field(right, struct_name, field_name, self_ty)?,
639            )
640        }
641        PureExpr::Unary { expr: e, .. }
642        | PureExpr::Field { expr: e, .. }
643        | PureExpr::Await(e)
644        | PureExpr::Try(e) => walk_expr_and_remove_field(e, struct_name, field_name, self_ty),
645        PureExpr::Index { expr: e, index } => {
646            Ok(
647                walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?
648                    + walk_expr_and_remove_field(index, struct_name, field_name, self_ty)?,
649            )
650        }
651        PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
652            let mut count = 0;
653            for e in exprs {
654                count += walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?;
655            }
656            Ok(count)
657        }
658        PureExpr::Return(Some(e)) | PureExpr::Break { expr: Some(e), .. } => {
659            walk_expr_and_remove_field(e, struct_name, field_name, self_ty)
660        }
661        PureExpr::Let { expr: e, .. }
662        | PureExpr::Cast { expr: e, .. }
663        | PureExpr::Ref { expr: e, .. } => {
664            walk_expr_and_remove_field(e, struct_name, field_name, self_ty)
665        }
666        PureExpr::Struct { fields, .. } => {
667            // Recurse into field values (struct already handled above)
668            let mut count = 0;
669            for (_, field_expr) in fields {
670                count += walk_expr_and_remove_field(field_expr, struct_name, field_name, self_ty)?;
671            }
672            Ok(count)
673        }
674        PureExpr::Range { start, end, .. } => {
675            let mut count = 0;
676            if let Some(s) = start {
677                count += walk_expr_and_remove_field(s, struct_name, field_name, self_ty)?;
678            }
679            if let Some(e) = end {
680                count += walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?;
681            }
682            Ok(count)
683        }
684        PureExpr::Repeat { expr: e, len } => {
685            Ok(
686                walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?
687                    + walk_expr_and_remove_field(len, struct_name, field_name, self_ty)?,
688            )
689        }
690        PureExpr::Macro { name, tokens, .. } => {
691            // Try to parse and walk expressions inside known macros (vec![], etc.)
692            if let Some(mut exprs) = macro_utils::try_extract_exprs(name, tokens) {
693                let mut count = 0;
694                for e in &mut exprs {
695                    count += walk_expr_and_remove_field(e, struct_name, field_name, self_ty)?;
696                }
697                if count > 0 {
698                    *tokens = macro_utils::exprs_to_tokens(&exprs)?;
699                }
700                Ok(count)
701            } else {
702                Ok(0)
703            }
704        }
705        _ => Ok(0),
706    }
707}