Skip to main content

ryo_analysis/check/
result.rs

1//! Check result types for LightGraphCheck.
2
3use std::fmt;
4
5/// Result of a lightweight graph-based check.
6#[derive(Debug, Clone, Default)]
7pub enum CheckResult {
8    /// Check passed.
9    #[default]
10    Ok,
11    /// Check passed with warnings (proceed with caution).
12    Warning(Vec<CheckWarning>),
13    /// Check failed (mutation should be aborted or cascaded).
14    Error(Vec<CheckError>),
15}
16
17impl CheckResult {
18    /// Returns true if the check passed (Ok or Warning).
19    #[inline]
20    pub fn is_ok(&self) -> bool {
21        matches!(self, Self::Ok | Self::Warning(_))
22    }
23
24    /// Returns true if the check failed.
25    #[inline]
26    pub fn is_err(&self) -> bool {
27        matches!(self, Self::Error(_))
28    }
29
30    /// Get errors if any.
31    pub fn errors(&self) -> &[CheckError] {
32        match self {
33            Self::Error(errors) => errors,
34            _ => &[],
35        }
36    }
37
38    /// Get warnings if any.
39    pub fn warnings(&self) -> &[CheckWarning] {
40        match self {
41            Self::Warning(warnings) => warnings,
42            _ => &[],
43        }
44    }
45
46    /// Merge two results, keeping the worse outcome.
47    pub fn merge(self, other: Self) -> Self {
48        match (self, other) {
49            // Both Ok
50            (Self::Ok, Self::Ok) => Self::Ok,
51            // Error takes precedence
52            (Self::Error(mut e1), Self::Error(e2)) => {
53                e1.extend(e2);
54                Self::Error(e1)
55            }
56            (Self::Error(e), _) | (_, Self::Error(e)) => Self::Error(e),
57            // Warning merges
58            (Self::Warning(mut w1), Self::Warning(w2)) => {
59                w1.extend(w2);
60                Self::Warning(w1)
61            }
62            (Self::Warning(w), Self::Ok) | (Self::Ok, Self::Warning(w)) => Self::Warning(w),
63        }
64    }
65}
66
67/// Warning from a light check.
68#[derive(Debug, Clone)]
69pub struct CheckWarning {
70    /// Warning message.
71    pub message: String,
72    /// Location context (if available).
73    pub location: Option<String>,
74}
75
76impl CheckWarning {
77    /// Create a new warning.
78    pub fn new(message: impl Into<String>) -> Self {
79        Self {
80            message: message.into(),
81            location: None,
82        }
83    }
84
85    /// Create a warning with location context.
86    pub fn with_location(message: impl Into<String>, location: impl Into<String>) -> Self {
87        Self {
88            message: message.into(),
89            location: Some(location.into()),
90        }
91    }
92
93    /// Create an unused symbol warning.
94    pub fn unused_symbol(name: impl Into<String>, reason: impl Into<String>) -> Self {
95        Self {
96            message: format!("unused symbol '{}': {}", name.into(), reason.into()),
97            location: None,
98        }
99    }
100
101    /// Create a warning for symbol that would become unused.
102    pub fn would_become_unused(name: impl Into<String>, reason: impl Into<String>) -> Self {
103        Self {
104            message: format!(
105                "symbol '{}' would become unused: {}",
106                name.into(),
107                reason.into()
108            ),
109            location: None,
110        }
111    }
112}
113
114impl fmt::Display for CheckWarning {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        if let Some(loc) = &self.location {
117            write!(f, "{}: {}", loc, self.message)
118        } else {
119            write!(f, "{}", self.message)
120        }
121    }
122}
123
124/// Error from a light check.
125#[derive(Debug, Clone)]
126pub enum CheckError {
127    /// Unresolved symbol reference.
128    UnresolvedRef {
129        /// The symbol name that couldn't be resolved.
130        name: String,
131        /// Location context (if available).
132        location: Option<String>,
133        /// Suggested alternatives (if any).
134        suggestions: Vec<String>,
135    },
136
137    /// Derive macro cannot be applied.
138    DeriveFailed {
139        /// Target type name.
140        target: String,
141        /// Trait that cannot be derived.
142        trait_name: String,
143        /// Field types missing the required trait implementation.
144        missing_impls: Vec<String>,
145    },
146
147    /// Type not found in registry.
148    TypeNotFound {
149        /// The type name that wasn't found.
150        type_name: String,
151    },
152
153    /// Trait not implemented for type.
154    TraitNotImplemented {
155        /// Type that doesn't implement the trait.
156        type_name: String,
157        /// Trait that isn't implemented.
158        trait_name: String,
159    },
160
161    // === Borrow Checking Errors (Rust-specific) ===
162    /// Simultaneous mutable borrows of the same variable.
163    SimultaneousMutBorrow {
164        /// Variable name.
165        variable: String,
166        /// Line of the first mutable borrow.
167        first_borrow_line: u32,
168        /// Line of the second mutable borrow.
169        second_borrow_line: u32,
170    },
171
172    /// Conflict between mutable and shared borrows.
173    BorrowConflict {
174        /// Variable name.
175        variable: String,
176        /// Existing borrow kind ("mutable" or "shared").
177        existing_kind: String,
178        /// Line of the existing borrow.
179        existing_line: u32,
180        /// New borrow kind.
181        new_kind: String,
182        /// Line of the new borrow.
183        new_line: u32,
184    },
185
186    /// Use of a variable after it has been moved.
187    UseAfterMove {
188        /// Variable name.
189        variable: String,
190        /// Line where the move occurred.
191        moved_at: u32,
192        /// Line where the invalid use occurred.
193        used_at: u32,
194    },
195
196    /// Dangling reference (reference to dropped value).
197    DanglingReference {
198        /// Reference variable name.
199        reference: String,
200        /// Source variable name (the dropped value).
201        source: String,
202        /// Line where the source was dropped.
203        dropped_at: u32,
204        /// Line where the reference was used.
205        used_at: u32,
206    },
207
208    /// Cannot mutate through a shared reference.
209    CannotMutateThroughSharedRef {
210        /// Variable name.
211        variable: String,
212        /// Line where mutation was attempted.
213        at_line: u32,
214    },
215
216    // === Member Access Errors ===
217    /// Field not found on a type.
218    FieldNotFound {
219        /// Type that was accessed.
220        type_name: String,
221        /// Field name that wasn't found.
222        field_name: String,
223        /// Available fields (for suggestions).
224        available_fields: Vec<String>,
225    },
226
227    /// Method not found on a type.
228    MethodNotFound {
229        /// Type that was accessed.
230        type_name: String,
231        /// Method name that wasn't found.
232        method_name: String,
233        /// Available methods (for suggestions).
234        available_methods: Vec<String>,
235    },
236
237    /// Enum variant not found.
238    EnumVariantNotFound {
239        /// Enum type name.
240        enum_name: String,
241        /// Variant name that wasn't found.
242        variant_name: String,
243        /// Available variants (for suggestions).
244        available_variants: Vec<String>,
245    },
246
247    /// Missing required field in struct literal.
248    MissingRequiredField {
249        /// Struct type name.
250        struct_name: String,
251        /// Missing field names.
252        missing_fields: Vec<String>,
253    },
254
255    /// Function argument count mismatch.
256    ArgumentCountMismatch {
257        /// Function name.
258        function_name: String,
259        /// Expected argument count.
260        expected: usize,
261        /// Actual argument count.
262        actual: usize,
263    },
264
265    /// Ambiguous target - multiple symbols match the given name.
266    AmbiguousTarget {
267        /// The name that matched multiple symbols.
268        name: String,
269        /// The matching symbol paths.
270        candidates: Vec<String>,
271    },
272
273    /// Type change has impact on other parts of the codebase.
274    TypeImpact {
275        /// Description of the impact.
276        description: String,
277        /// Details about the affected areas.
278        details: String,
279    },
280
281    /// Reference integrity issue (dangling references, missing fields, etc.).
282    ReferenceIntegrity {
283        /// Description of the issue.
284        description: String,
285        /// Details about the affected areas.
286        details: String,
287    },
288
289    /// Generic check failure.
290    Other {
291        /// Error message.
292        message: String,
293    },
294}
295
296impl CheckError {
297    /// Create an unresolved reference error.
298    pub fn unresolved(name: impl Into<String>) -> Self {
299        Self::UnresolvedRef {
300            name: name.into(),
301            location: None,
302            suggestions: Vec::new(),
303        }
304    }
305
306    /// Create an unresolved reference error with location.
307    pub fn unresolved_at(name: impl Into<String>, location: impl Into<String>) -> Self {
308        Self::UnresolvedRef {
309            name: name.into(),
310            location: Some(location.into()),
311            suggestions: Vec::new(),
312        }
313    }
314
315    /// Create a type not found error.
316    pub fn type_not_found(type_name: impl Into<String>) -> Self {
317        Self::TypeNotFound {
318            type_name: type_name.into(),
319        }
320    }
321
322    /// Create a derive failed error.
323    pub fn derive_failed(
324        target: impl Into<String>,
325        trait_name: impl Into<String>,
326        missing_impls: Vec<String>,
327    ) -> Self {
328        Self::DeriveFailed {
329            target: target.into(),
330            trait_name: trait_name.into(),
331            missing_impls,
332        }
333    }
334
335    /// Create a trait not implemented error.
336    pub fn trait_not_impl(type_name: impl Into<String>, trait_name: impl Into<String>) -> Self {
337        Self::TraitNotImplemented {
338            type_name: type_name.into(),
339            trait_name: trait_name.into(),
340        }
341    }
342
343    // === Borrow Error Constructors ===
344
345    /// Create a simultaneous mutable borrow error.
346    pub fn simultaneous_mut_borrow(
347        variable: impl Into<String>,
348        first_borrow_line: u32,
349        second_borrow_line: u32,
350    ) -> Self {
351        Self::SimultaneousMutBorrow {
352            variable: variable.into(),
353            first_borrow_line,
354            second_borrow_line,
355        }
356    }
357
358    /// Create a borrow conflict error.
359    pub fn borrow_conflict(
360        variable: impl Into<String>,
361        existing_kind: impl Into<String>,
362        existing_line: u32,
363        new_kind: impl Into<String>,
364        new_line: u32,
365    ) -> Self {
366        Self::BorrowConflict {
367            variable: variable.into(),
368            existing_kind: existing_kind.into(),
369            existing_line,
370            new_kind: new_kind.into(),
371            new_line,
372        }
373    }
374
375    /// Create a use after move error.
376    pub fn use_after_move(variable: impl Into<String>, moved_at: u32, used_at: u32) -> Self {
377        Self::UseAfterMove {
378            variable: variable.into(),
379            moved_at,
380            used_at,
381        }
382    }
383
384    /// Create a dangling reference error.
385    pub fn dangling_reference(
386        reference: impl Into<String>,
387        source: impl Into<String>,
388        dropped_at: u32,
389        used_at: u32,
390    ) -> Self {
391        Self::DanglingReference {
392            reference: reference.into(),
393            source: source.into(),
394            dropped_at,
395            used_at,
396        }
397    }
398
399    /// Create a cannot mutate through shared ref error.
400    pub fn cannot_mutate_shared(variable: impl Into<String>, at_line: u32) -> Self {
401        Self::CannotMutateThroughSharedRef {
402            variable: variable.into(),
403            at_line,
404        }
405    }
406
407    // === Member Access Error Constructors ===
408
409    /// Create a field not found error.
410    pub fn field_not_found(
411        type_name: impl Into<String>,
412        field_name: impl Into<String>,
413        available_fields: Vec<String>,
414    ) -> Self {
415        Self::FieldNotFound {
416            type_name: type_name.into(),
417            field_name: field_name.into(),
418            available_fields,
419        }
420    }
421
422    /// Create a method not found error.
423    pub fn method_not_found(
424        type_name: impl Into<String>,
425        method_name: impl Into<String>,
426        available_methods: Vec<String>,
427    ) -> Self {
428        Self::MethodNotFound {
429            type_name: type_name.into(),
430            method_name: method_name.into(),
431            available_methods,
432        }
433    }
434
435    /// Create an enum variant not found error.
436    pub fn variant_not_found(
437        enum_name: impl Into<String>,
438        variant_name: impl Into<String>,
439        available_variants: Vec<String>,
440    ) -> Self {
441        Self::EnumVariantNotFound {
442            enum_name: enum_name.into(),
443            variant_name: variant_name.into(),
444            available_variants,
445        }
446    }
447
448    /// Create a missing required field error.
449    pub fn missing_fields(struct_name: impl Into<String>, missing_fields: Vec<String>) -> Self {
450        Self::MissingRequiredField {
451            struct_name: struct_name.into(),
452            missing_fields,
453        }
454    }
455
456    /// Create an argument count mismatch error.
457    pub fn arg_count_mismatch(
458        function_name: impl Into<String>,
459        expected: usize,
460        actual: usize,
461    ) -> Self {
462        Self::ArgumentCountMismatch {
463            function_name: function_name.into(),
464            expected,
465            actual,
466        }
467    }
468
469    /// Create an ambiguous target error.
470    pub fn ambiguous_target(name: impl Into<String>, candidates: Vec<String>) -> Self {
471        Self::AmbiguousTarget {
472            name: name.into(),
473            candidates,
474        }
475    }
476
477    /// Create a type impact error.
478    pub fn type_impact(description: impl Into<String>, details: impl Into<String>) -> Self {
479        Self::TypeImpact {
480            description: description.into(),
481            details: details.into(),
482        }
483    }
484
485    /// Create a reference integrity error.
486    pub fn reference_integrity(description: impl Into<String>, details: impl Into<String>) -> Self {
487        Self::ReferenceIntegrity {
488            description: description.into(),
489            details: details.into(),
490        }
491    }
492}
493
494impl fmt::Display for CheckError {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        match self {
497            Self::UnresolvedRef {
498                name,
499                location,
500                suggestions,
501            } => {
502                if let Some(loc) = location {
503                    write!(f, "{}: ", loc)?;
504                }
505                write!(f, "unresolved reference: `{}`", name)?;
506                if !suggestions.is_empty() {
507                    write!(f, " (did you mean: {}?)", suggestions.join(", "))?;
508                }
509                Ok(())
510            }
511            Self::DeriveFailed {
512                target,
513                trait_name,
514                missing_impls,
515            } => {
516                write!(
517                    f,
518                    "cannot derive `{}` for `{}`: missing impl on {}",
519                    trait_name,
520                    target,
521                    missing_impls.join(", ")
522                )
523            }
524            Self::TypeNotFound { type_name } => {
525                write!(f, "type not found: `{}`", type_name)
526            }
527            Self::TraitNotImplemented {
528                type_name,
529                trait_name,
530            } => {
531                write!(f, "`{}` does not implement `{}`", type_name, trait_name)
532            }
533
534            // === Borrow Errors ===
535            Self::SimultaneousMutBorrow {
536                variable,
537                first_borrow_line,
538                second_borrow_line,
539            } => {
540                write!(
541                    f,
542                    "cannot borrow `{}` as mutable more than once: \
543                     first borrow at line {}, second borrow at line {}",
544                    variable, first_borrow_line, second_borrow_line
545                )
546            }
547            Self::BorrowConflict {
548                variable,
549                existing_kind,
550                existing_line,
551                new_kind,
552                new_line,
553            } => {
554                write!(
555                    f,
556                    "cannot borrow `{}` as {} because it is already borrowed as {}: \
557                     existing borrow at line {}, new borrow at line {}",
558                    variable, new_kind, existing_kind, existing_line, new_line
559                )
560            }
561            Self::UseAfterMove {
562                variable,
563                moved_at,
564                used_at,
565            } => {
566                write!(
567                    f,
568                    "use of moved value `{}`: moved at line {}, used at line {}",
569                    variable, moved_at, used_at
570                )
571            }
572            Self::DanglingReference {
573                reference,
574                source,
575                dropped_at,
576                used_at,
577            } => {
578                write!(
579                    f,
580                    "dangling reference `{}`: source `{}` dropped at line {}, \
581                     reference used at line {}",
582                    reference, source, dropped_at, used_at
583                )
584            }
585            Self::CannotMutateThroughSharedRef { variable, at_line } => {
586                write!(
587                    f,
588                    "cannot mutate `{}` through a shared reference at line {}",
589                    variable, at_line
590                )
591            }
592
593            // === Member Access Errors ===
594            Self::FieldNotFound {
595                type_name,
596                field_name,
597                available_fields,
598            } => {
599                write!(
600                    f,
601                    "field `{}` not found on type `{}`",
602                    field_name, type_name
603                )?;
604                if !available_fields.is_empty() {
605                    write!(f, " (available: {})", available_fields.join(", "))?;
606                }
607                Ok(())
608            }
609            Self::MethodNotFound {
610                type_name,
611                method_name,
612                available_methods,
613            } => {
614                write!(
615                    f,
616                    "method `{}` not found on type `{}`",
617                    method_name, type_name
618                )?;
619                if !available_methods.is_empty() {
620                    write!(f, " (available: {})", available_methods.join(", "))?;
621                }
622                Ok(())
623            }
624            Self::EnumVariantNotFound {
625                enum_name,
626                variant_name,
627                available_variants,
628            } => {
629                write!(
630                    f,
631                    "variant `{}` not found in enum `{}`",
632                    variant_name, enum_name
633                )?;
634                if !available_variants.is_empty() {
635                    write!(f, " (available: {})", available_variants.join(", "))?;
636                }
637                Ok(())
638            }
639            Self::MissingRequiredField {
640                struct_name,
641                missing_fields,
642            } => {
643                write!(
644                    f,
645                    "missing required field(s) in struct `{}`: {}",
646                    struct_name,
647                    missing_fields.join(", ")
648                )
649            }
650            Self::ArgumentCountMismatch {
651                function_name,
652                expected,
653                actual,
654            } => {
655                write!(
656                    f,
657                    "function `{}` expects {} argument(s), but {} provided",
658                    function_name, expected, actual
659                )
660            }
661
662            Self::AmbiguousTarget { name, candidates } => {
663                write!(
664                    f,
665                    "ambiguous target `{}`: multiple symbols found ({})",
666                    name,
667                    candidates.join(", ")
668                )
669            }
670
671            Self::TypeImpact {
672                description,
673                details,
674            } => {
675                write!(f, "type impact: {} ({})", description, details)
676            }
677
678            Self::ReferenceIntegrity {
679                description,
680                details,
681            } => {
682                write!(f, "reference integrity: {} ({})", description, details)
683            }
684
685            Self::Other { message } => write!(f, "{}", message),
686        }
687    }
688}
689
690impl std::error::Error for CheckError {}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695
696    #[test]
697    fn test_check_result_is_ok() {
698        assert!(CheckResult::Ok.is_ok());
699        assert!(CheckResult::Warning(vec![]).is_ok());
700        assert!(!CheckResult::Error(vec![]).is_ok());
701    }
702
703    #[test]
704    fn test_check_result_merge() {
705        let ok1 = CheckResult::Ok;
706        let ok2 = CheckResult::Ok;
707        assert!(matches!(ok1.merge(ok2), CheckResult::Ok));
708
709        let ok = CheckResult::Ok;
710        let err = CheckResult::Error(vec![CheckError::type_not_found("Foo")]);
711        assert!(matches!(ok.merge(err), CheckResult::Error(_)));
712
713        let warn1 = CheckResult::Warning(vec![CheckWarning::new("w1")]);
714        let warn2 = CheckResult::Warning(vec![CheckWarning::new("w2")]);
715        let merged = warn1.merge(warn2);
716        assert!(matches!(merged, CheckResult::Warning(ref w) if w.len() == 2));
717    }
718
719    #[test]
720    fn test_check_error_display() {
721        let err = CheckError::unresolved("foo");
722        assert_eq!(format!("{}", err), "unresolved reference: `foo`");
723
724        let err = CheckError::derive_failed("MyStruct", "Default", vec!["SomeField".to_string()]);
725        assert!(format!("{}", err).contains("cannot derive"));
726    }
727}