1use std::fmt;
4
5#[derive(Debug, Clone, Default)]
7pub enum CheckResult {
8 #[default]
10 Ok,
11 Warning(Vec<CheckWarning>),
13 Error(Vec<CheckError>),
15}
16
17impl CheckResult {
18 #[inline]
20 pub fn is_ok(&self) -> bool {
21 matches!(self, Self::Ok | Self::Warning(_))
22 }
23
24 #[inline]
26 pub fn is_err(&self) -> bool {
27 matches!(self, Self::Error(_))
28 }
29
30 pub fn errors(&self) -> &[CheckError] {
32 match self {
33 Self::Error(errors) => errors,
34 _ => &[],
35 }
36 }
37
38 pub fn warnings(&self) -> &[CheckWarning] {
40 match self {
41 Self::Warning(warnings) => warnings,
42 _ => &[],
43 }
44 }
45
46 pub fn merge(self, other: Self) -> Self {
48 match (self, other) {
49 (Self::Ok, Self::Ok) => Self::Ok,
51 (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 (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#[derive(Debug, Clone)]
69pub struct CheckWarning {
70 pub message: String,
72 pub location: Option<String>,
74}
75
76impl CheckWarning {
77 pub fn new(message: impl Into<String>) -> Self {
79 Self {
80 message: message.into(),
81 location: None,
82 }
83 }
84
85 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 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 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#[derive(Debug, Clone)]
126pub enum CheckError {
127 UnresolvedRef {
129 name: String,
131 location: Option<String>,
133 suggestions: Vec<String>,
135 },
136
137 DeriveFailed {
139 target: String,
141 trait_name: String,
143 missing_impls: Vec<String>,
145 },
146
147 TypeNotFound {
149 type_name: String,
151 },
152
153 TraitNotImplemented {
155 type_name: String,
157 trait_name: String,
159 },
160
161 SimultaneousMutBorrow {
164 variable: String,
166 first_borrow_line: u32,
168 second_borrow_line: u32,
170 },
171
172 BorrowConflict {
174 variable: String,
176 existing_kind: String,
178 existing_line: u32,
180 new_kind: String,
182 new_line: u32,
184 },
185
186 UseAfterMove {
188 variable: String,
190 moved_at: u32,
192 used_at: u32,
194 },
195
196 DanglingReference {
198 reference: String,
200 source: String,
202 dropped_at: u32,
204 used_at: u32,
206 },
207
208 CannotMutateThroughSharedRef {
210 variable: String,
212 at_line: u32,
214 },
215
216 FieldNotFound {
219 type_name: String,
221 field_name: String,
223 available_fields: Vec<String>,
225 },
226
227 MethodNotFound {
229 type_name: String,
231 method_name: String,
233 available_methods: Vec<String>,
235 },
236
237 EnumVariantNotFound {
239 enum_name: String,
241 variant_name: String,
243 available_variants: Vec<String>,
245 },
246
247 MissingRequiredField {
249 struct_name: String,
251 missing_fields: Vec<String>,
253 },
254
255 ArgumentCountMismatch {
257 function_name: String,
259 expected: usize,
261 actual: usize,
263 },
264
265 AmbiguousTarget {
267 name: String,
269 candidates: Vec<String>,
271 },
272
273 TypeImpact {
275 description: String,
277 details: String,
279 },
280
281 ReferenceIntegrity {
283 description: String,
285 details: String,
287 },
288
289 Other {
291 message: String,
293 },
294}
295
296impl CheckError {
297 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 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 pub fn type_not_found(type_name: impl Into<String>) -> Self {
317 Self::TypeNotFound {
318 type_name: type_name.into(),
319 }
320 }
321
322 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}