1use std::path::{Path, PathBuf};
8
9use serde::{Deserialize, Serialize};
10
11use super::InapplicableReason;
12use crate::compat::arg_shape::ArgShape;
13use crate::model::names::{
14 Arity, CommitSha, FieldName, FunctionName, GitRef, MacroName, ModuleName, ProjectName,
15 RecordName, RelativePath, TagName, TypeName,
16};
17use crate::model::resolver_coverage::ResolverClass;
18use crate::model::symbol::SymbolRef;
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
22#[serde(tag = "verdict", rename_all = "snake_case")]
23pub enum Verdict {
24 Compatible,
25 RequiresAdaptation { reasons: Vec<Reason> },
26 Incompatible { reasons: Vec<Reason> },
27 Inapplicable { reason: InapplicableReason },
28}
29
30impl Verdict {
31 pub fn from_reasons(reasons: Vec<Reason>) -> Self {
32 if reasons.is_empty() {
33 return Self::Compatible;
34 }
35 if reasons.iter().any(Reason::is_blocking) {
36 Self::Incompatible { reasons }
37 } else {
38 Self::RequiresAdaptation { reasons }
39 }
40 }
41
42 pub fn is_compatible(&self) -> bool {
43 matches!(self, Self::Compatible)
44 }
45
46 pub fn reasons(&self) -> &[Reason] {
47 match self {
48 Self::Compatible | Self::Inapplicable { .. } => &[],
49 Self::RequiresAdaptation { reasons } | Self::Incompatible { reasons } => reasons,
50 }
51 }
52}
53
54#[non_exhaustive]
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
57#[serde(tag = "kind", rename_all = "snake_case")]
58pub enum Reason {
59 MissingSymbol {
60 symbol: SymbolRef,
61 first_seen_at_tag: Option<TagName>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 needs_pin_at_least: Option<NeedsPin>,
67 suggested_replacement: Option<SymbolRef>,
68 },
69 ArityChanged {
70 module: ModuleName,
71 function: FunctionName,
72 expected: Arity,
73 found: Vec<Arity>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
78 expected_available_at: Option<TagName>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 needs_pin_at_least: Option<NeedsPin>,
81 },
82 SignatureChanged {
83 module: ModuleName,
84 function: FunctionName,
85 arity: Arity,
86 expected_spec: String,
87 found_spec: String,
88 },
89 FileAbsent {
90 path: PathBuf,
91 },
92 ContextDrift {
93 path: PathBuf,
94 hunk_index: usize,
95 },
96 PreimageDrifted {
102 path: PathBuf,
103 hunk_index: usize,
104 line_delta: isize,
105 },
106 PostimageCollision {
112 path: PathBuf,
113 hunk_index: usize,
114 },
115 PreimageMissing {
120 path: PathBuf,
121 hunk_index: usize,
122 preimage_excerpt: String,
123 },
124 DeprecatedUsage {
125 symbol: SymbolRef,
126 since: Option<TagName>,
127 replacement: Option<SymbolRef>,
128 },
129 NowHidden {
130 module: ModuleName,
131 },
132 RecordFieldsChanged {
133 record: RecordName,
134 expected: Vec<FieldName>,
135 found: Vec<FieldName>,
136 },
137 UnsupportedFileType {
138 path: PathBuf,
139 },
140 UntrackedModuleMissing {
141 module: ModuleName,
142 },
143 ClauseMismatch {
148 module: ModuleName,
149 function: FunctionName,
150 arity: Arity,
151 call_args: Vec<ArgShape>,
152 pin_clauses: Vec<Vec<ArgShape>>,
153 },
154 MissingPrereq {
158 symbol: SymbolRef,
159 self_branch: GitRef,
160 suggested_source_for_prereq: Option<CommitSha>,
161 },
162 SyntacticArtifact {
165 path: PathBuf,
166 line: usize,
167 artifact: ArtifactKind,
168 },
169 BehaviourCallbackSignatureChanged {
171 behaviour: ModuleName,
172 callback: FunctionName,
173 arity: Arity,
174 expected_after_patch: String,
175 implementer: ModuleName,
176 implementer_signature: String,
177 },
178 BehaviourCallbackRemoved {
180 behaviour: ModuleName,
181 callback: FunctionName,
182 arity: Arity,
183 implementer: ModuleName,
184 },
185 BehaviourCallbackAdded {
187 behaviour: ModuleName,
188 callback: FunctionName,
189 arity: Arity,
190 implementer: ModuleName,
191 },
192 ModuleRelocated {
194 module: ModuleName,
195 patch_path: PathBuf,
196 },
197 WireConstantChanged {
199 module: ModuleName,
200 macro_name: String,
201 before: String,
202 after: String,
203 },
204 HistoricalImplementationMissing {
206 module: ModuleName,
207 advertised_version_before: u32,
208 advertised_version_after: u32,
209 expected_historical_module: ModuleName,
210 },
211 WireContractBodyDrift {
213 module: ModuleName,
214 functions: Vec<FunctionName>,
215 advertised_version: u32,
216 },
217 WireContractRegression {
219 module: ModuleName,
220 pin_version: u32,
221 patch_version: u32,
222 },
223 ReturnShapeMismatch {
225 module: ModuleName,
226 function: FunctionName,
227 arity: Arity,
228 source_signature: String,
229 pin_signature: String,
230 },
231 MissingType {
236 module: ModuleName,
237 name: TypeName,
238 arity: Arity,
239 },
240 PathRename {
245 source_path: PathBuf,
246 target_path: PathBuf,
247 translation: TranslationSource,
248 },
249 TargetPathAbsent {
254 path: RelativePath,
255 },
256 TestModuleSymbolMissing {
269 suite_path: RelativePath,
270 missing_module: ModuleName,
271 call_sites: Vec<TestCallSite>,
272 },
273 BehaviourModuleMissing {
280 source_path: RelativePath,
281 behaviour: ModuleName,
282 },
283 HeaderFileMissing {
290 source_path: RelativePath,
291 include_directive: IncludeDirective,
292 attempted_paths: Vec<RelativePath>,
293 },
294 MacroUndefinedOnTarget {
300 source_path: RelativePath,
301 macro_name: String,
302 line: u32,
303 },
304 RecordUndefinedOnTarget {
309 source_path: RelativePath,
310 record_name: RecordName,
311 line: u32,
312 },
313 LocalCallUndefinedOnTarget {
318 source_path: RelativePath,
319 function: FunctionName,
320 arity: Arity,
321 line: u32,
322 },
323 QualifiedCallUndefinedOnTarget {
332 source_path: RelativePath,
333 module: ModuleName,
334 function: FunctionName,
335 arity: Arity,
336 line: u32,
337 },
338 QualifiedCallReturnShapeDrift {
346 source_path: RelativePath,
347 module: ModuleName,
348 function: FunctionName,
349 arity: Arity,
350 source_signature: String,
351 target_signature: String,
352 line: u32,
353 },
354 LocalCallReturnShapeDrift {
360 source_path: RelativePath,
361 function: FunctionName,
362 arity: Arity,
363 source_signature: String,
364 target_signature: String,
365 line: u32,
366 },
367 MacroValueDrift {
372 source_path: RelativePath,
373 macro_name: String,
374 source_value: String,
375 target_value: String,
376 line: u32,
377 },
378 BehaviourCallbackAddedOnTarget {
384 source_path: RelativePath,
385 behaviour: ModuleName,
386 callback: FunctionName,
387 arity: Arity,
388 line: u32,
389 },
390 BehaviourCallbackDriftOnTarget {
396 source_path: RelativePath,
397 behaviour: ModuleName,
398 callback: FunctionName,
399 arity: Arity,
400 source_signature: String,
401 target_signature: String,
402 line: u32,
403 },
404 VersionedMachineSnapshotMissing {
408 module: ModuleName,
409 side: SnapshotSide,
410 },
411 WireConstantBindingsMissing {
415 module: ModuleName,
416 macros: Vec<MacroName>,
417 side: SnapshotSide,
418 },
419 IndirectCallUndefinedOnTarget {
426 source_path: RelativePath,
427 module: ModuleName,
428 function: FunctionName,
429 arity: Arity,
430 via: IndirectCallForm,
431 line: u32,
432 },
433 ExportedTypeUndefinedOnTarget {
441 source_path: RelativePath,
442 type_name: TypeName,
443 arity: Arity,
444 line: u32,
445 },
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
450#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
451#[serde(rename_all = "snake_case")]
452pub enum SnapshotSide {
453 Source,
454 Target,
455 Both,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
462#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
463pub struct TestCallSite {
464 pub function: FunctionName,
465 pub arity: Arity,
466 pub line: u32,
467}
468
469#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
475#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
476#[serde(tag = "form", rename_all = "snake_case")]
477pub enum IncludeDirective {
478 Include { path: String },
479 IncludeLib { path: String },
480}
481
482impl IncludeDirective {
483 pub fn path(&self) -> &str {
484 match self {
485 Self::Include { path } | Self::IncludeLib { path } => path,
486 }
487 }
488}
489
490#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
491#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
492#[serde(tag = "kind", rename_all = "snake_case")]
493pub enum TranslationSource {
494 ConfigStanza { name: String },
495 ExternalFile { path: PathBuf, name: String },
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
499#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
500#[serde(tag = "artifact_kind", rename_all = "snake_case")]
501pub enum ArtifactKind {
502 ConflictMarker { marker: ConflictMarker },
505 ExportWithoutBody {
508 module: ModuleName,
509 function: FunctionName,
510 arity: Arity,
511 },
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
515#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
516#[serde(rename_all = "snake_case")]
517pub enum ConflictMarker {
518 Ours,
520 Divider,
522 Theirs,
524 Ancestor,
526}
527
528impl ConflictMarker {
529 pub fn detect(line: &str) -> Option<Self> {
536 Self::detect_with_marker(line, '<', Self::Ours)
537 .or_else(|| Self::detect_with_marker(line, '=', Self::Divider))
538 .or_else(|| Self::detect_with_marker(line, '>', Self::Theirs))
539 .or_else(|| Self::detect_with_marker(line, '|', Self::Ancestor))
540 }
541
542 fn detect_with_marker(line: &str, token: char, marker: Self) -> Option<Self> {
543 const MIN_RUN: usize = 7;
544 let mut count = 0usize;
545 for c in line.chars() {
546 if c == token {
547 count += 1;
548 continue;
549 }
550 return if count >= MIN_RUN && (c == ' ' || c == '\t') {
551 Some(marker)
552 } else {
553 None
554 };
555 }
556 if count >= MIN_RUN { Some(marker) } else { None }
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
564#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
565#[serde(rename_all = "snake_case")]
566pub enum ApplyConflictKind {
567 PostimageCollision,
568 PreimageMissing,
569 FileAbsent,
570}
571
572impl ApplyConflictKind {
573 #[must_use]
575 pub fn as_str(self) -> &'static str {
576 match self {
577 Self::PostimageCollision => "postimage_collision",
578 Self::PreimageMissing => "preimage_missing",
579 Self::FileAbsent => "file_absent",
580 }
581 }
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
588#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
589#[serde(rename_all = "snake_case")]
590pub enum IndirectCallForm {
591 MeckExpect,
592 RpcCall,
593 ErpcCall,
594 CtBrokerHelperRpc,
595 RabbitMiscRpcCall,
596}
597
598impl IndirectCallForm {
599 #[must_use]
602 pub fn display_form(self) -> &'static str {
603 match self {
604 Self::MeckExpect => "meck:expect",
605 Self::RpcCall => "rpc:call",
606 Self::ErpcCall => "erpc:call",
607 Self::CtBrokerHelperRpc => "rabbit_ct_broker_helpers:rpc",
608 Self::RabbitMiscRpcCall => "rabbit_misc:rpc_call",
609 }
610 }
611}
612
613impl Reason {
614 pub fn apply_conflict(&self) -> Option<(&Path, ApplyConflictKind)> {
619 match self {
620 Self::PostimageCollision { path, .. } => {
621 Some((path, ApplyConflictKind::PostimageCollision))
622 }
623 Self::PreimageMissing { path, .. } => Some((path, ApplyConflictKind::PreimageMissing)),
624 Self::FileAbsent { path } => Some((path, ApplyConflictKind::FileAbsent)),
625 Self::MissingSymbol { .. }
626 | Self::ArityChanged { .. }
627 | Self::SignatureChanged { .. }
628 | Self::ContextDrift { .. }
629 | Self::PreimageDrifted { .. }
630 | Self::DeprecatedUsage { .. }
631 | Self::NowHidden { .. }
632 | Self::RecordFieldsChanged { .. }
633 | Self::UnsupportedFileType { .. }
634 | Self::UntrackedModuleMissing { .. }
635 | Self::ClauseMismatch { .. }
636 | Self::MissingPrereq { .. }
637 | Self::SyntacticArtifact { .. }
638 | Self::BehaviourCallbackSignatureChanged { .. }
639 | Self::BehaviourCallbackRemoved { .. }
640 | Self::BehaviourCallbackAdded { .. }
641 | Self::ModuleRelocated { .. }
642 | Self::WireConstantChanged { .. }
643 | Self::HistoricalImplementationMissing { .. }
644 | Self::WireContractBodyDrift { .. }
645 | Self::WireContractRegression { .. }
646 | Self::ReturnShapeMismatch { .. }
647 | Self::MissingType { .. }
648 | Self::PathRename { .. }
649 | Self::TargetPathAbsent { .. }
650 | Self::TestModuleSymbolMissing { .. }
651 | Self::BehaviourModuleMissing { .. }
652 | Self::HeaderFileMissing { .. }
653 | Self::MacroUndefinedOnTarget { .. }
654 | Self::RecordUndefinedOnTarget { .. }
655 | Self::LocalCallUndefinedOnTarget { .. }
656 | Self::ExportedTypeUndefinedOnTarget { .. }
657 | Self::QualifiedCallUndefinedOnTarget { .. }
658 | Self::IndirectCallUndefinedOnTarget { .. }
659 | Self::QualifiedCallReturnShapeDrift { .. }
660 | Self::LocalCallReturnShapeDrift { .. }
661 | Self::MacroValueDrift { .. }
662 | Self::BehaviourCallbackAddedOnTarget { .. }
663 | Self::BehaviourCallbackDriftOnTarget { .. }
664 | Self::VersionedMachineSnapshotMissing { .. }
665 | Self::WireConstantBindingsMissing { .. } => None,
666 }
667 }
668
669 pub fn resolver_class(&self) -> Option<ResolverClass> {
673 match self {
674 Self::MissingSymbol { symbol, .. } | Self::MissingPrereq { symbol, .. } => {
675 Some(ResolverClass::of_symbol_kind(&symbol.kind))
676 }
677 Self::MissingType { .. } => Some(ResolverClass::Type),
678 Self::MacroUndefinedOnTarget { .. } | Self::MacroValueDrift { .. } => {
679 Some(ResolverClass::Macro)
680 }
681 Self::RecordUndefinedOnTarget { .. } => Some(ResolverClass::Record),
682 Self::ExportedTypeUndefinedOnTarget { .. } => Some(ResolverClass::Type),
683 Self::LocalCallUndefinedOnTarget { .. } | Self::LocalCallReturnShapeDrift { .. } => {
684 Some(ResolverClass::LocalCall)
685 }
686 Self::QualifiedCallUndefinedOnTarget { .. }
687 | Self::QualifiedCallReturnShapeDrift { .. } => Some(ResolverClass::QualifiedCall),
688 Self::IndirectCallUndefinedOnTarget { .. } => Some(ResolverClass::IndirectCall),
689 Self::HeaderFileMissing { .. } => Some(ResolverClass::Include),
690 Self::BehaviourModuleMissing { .. }
691 | Self::BehaviourCallbackAddedOnTarget { .. }
692 | Self::BehaviourCallbackDriftOnTarget { .. } => Some(ResolverClass::Behaviour),
693 Self::ArityChanged { .. }
694 | Self::SignatureChanged { .. }
695 | Self::FileAbsent { .. }
696 | Self::ContextDrift { .. }
697 | Self::PreimageDrifted { .. }
698 | Self::PostimageCollision { .. }
699 | Self::PreimageMissing { .. }
700 | Self::DeprecatedUsage { .. }
701 | Self::NowHidden { .. }
702 | Self::RecordFieldsChanged { .. }
703 | Self::UnsupportedFileType { .. }
704 | Self::UntrackedModuleMissing { .. }
705 | Self::ClauseMismatch { .. }
706 | Self::SyntacticArtifact { .. }
707 | Self::BehaviourCallbackSignatureChanged { .. }
708 | Self::BehaviourCallbackRemoved { .. }
709 | Self::BehaviourCallbackAdded { .. }
710 | Self::ModuleRelocated { .. }
711 | Self::WireConstantChanged { .. }
712 | Self::HistoricalImplementationMissing { .. }
713 | Self::WireContractBodyDrift { .. }
714 | Self::WireContractRegression { .. }
715 | Self::ReturnShapeMismatch { .. }
716 | Self::PathRename { .. }
717 | Self::TargetPathAbsent { .. }
718 | Self::TestModuleSymbolMissing { .. }
719 | Self::VersionedMachineSnapshotMissing { .. }
720 | Self::WireConstantBindingsMissing { .. } => None,
721 }
722 }
723
724 pub fn is_blocking(&self) -> bool {
726 match self {
727 Self::MissingSymbol {
729 first_seen_at_tag: Some(_),
730 ..
731 } => false,
732 Self::ArityChanged {
733 expected_available_at: Some(_),
734 ..
735 } => false,
736 Self::MissingSymbol { .. }
737 | Self::ArityChanged { .. }
738 | Self::SignatureChanged { .. }
739 | Self::FileAbsent { .. }
740 | Self::NowHidden { .. }
741 | Self::RecordFieldsChanged { .. }
742 | Self::UntrackedModuleMissing { .. }
743 | Self::ClauseMismatch { .. }
744 | Self::MissingPrereq { .. }
745 | Self::SyntacticArtifact { .. }
746 | Self::BehaviourCallbackSignatureChanged { .. }
747 | Self::BehaviourCallbackRemoved { .. }
748 | Self::BehaviourCallbackAdded { .. }
749 | Self::WireConstantChanged { .. }
750 | Self::HistoricalImplementationMissing { .. }
751 | Self::WireContractRegression { .. }
752 | Self::ReturnShapeMismatch { .. } => true,
753 Self::ContextDrift { .. }
754 | Self::PreimageDrifted { .. }
755 | Self::PreimageMissing { .. }
756 | Self::PostimageCollision { .. }
757 | Self::DeprecatedUsage { .. }
758 | Self::UnsupportedFileType { .. }
759 | Self::ModuleRelocated { .. }
760 | Self::WireContractBodyDrift { .. }
761 | Self::MissingType { .. }
762 | Self::PathRename { .. }
763 | Self::TargetPathAbsent { .. }
764 | Self::TestModuleSymbolMissing { .. }
765 | Self::BehaviourModuleMissing { .. }
766 | Self::HeaderFileMissing { .. }
767 | Self::MacroUndefinedOnTarget { .. }
768 | Self::RecordUndefinedOnTarget { .. }
769 | Self::LocalCallUndefinedOnTarget { .. }
770 | Self::ExportedTypeUndefinedOnTarget { .. }
771 | Self::QualifiedCallUndefinedOnTarget { .. }
772 | Self::IndirectCallUndefinedOnTarget { .. }
773 | Self::QualifiedCallReturnShapeDrift { .. }
774 | Self::LocalCallReturnShapeDrift { .. }
775 | Self::MacroValueDrift { .. }
776 | Self::BehaviourCallbackAddedOnTarget { .. }
777 | Self::BehaviourCallbackDriftOnTarget { .. }
778 | Self::VersionedMachineSnapshotMissing { .. }
779 | Self::WireConstantBindingsMissing { .. } => false,
780 }
781 }
782
783 pub fn is_path_scoped(&self) -> bool {
788 match self {
789 Self::FileAbsent { .. }
790 | Self::ModuleRelocated { .. }
791 | Self::PreimageDrifted { .. }
792 | Self::PreimageMissing { .. }
793 | Self::PostimageCollision { .. }
794 | Self::ContextDrift { .. }
795 | Self::PathRename { .. }
796 | Self::TargetPathAbsent { .. }
797 | Self::UnsupportedFileType { .. }
798 | Self::SyntacticArtifact { .. } => true,
799 Self::MissingSymbol { .. }
800 | Self::ArityChanged { .. }
801 | Self::SignatureChanged { .. }
802 | Self::DeprecatedUsage { .. }
803 | Self::NowHidden { .. }
804 | Self::RecordFieldsChanged { .. }
805 | Self::UntrackedModuleMissing { .. }
806 | Self::ClauseMismatch { .. }
807 | Self::MissingPrereq { .. }
808 | Self::BehaviourCallbackSignatureChanged { .. }
809 | Self::BehaviourCallbackRemoved { .. }
810 | Self::BehaviourCallbackAdded { .. }
811 | Self::WireConstantChanged { .. }
812 | Self::HistoricalImplementationMissing { .. }
813 | Self::WireContractBodyDrift { .. }
814 | Self::WireContractRegression { .. }
815 | Self::ReturnShapeMismatch { .. }
816 | Self::MissingType { .. }
817 | Self::TestModuleSymbolMissing { .. }
818 | Self::BehaviourModuleMissing { .. }
819 | Self::HeaderFileMissing { .. }
820 | Self::MacroUndefinedOnTarget { .. }
821 | Self::RecordUndefinedOnTarget { .. }
822 | Self::LocalCallUndefinedOnTarget { .. }
823 | Self::ExportedTypeUndefinedOnTarget { .. }
824 | Self::QualifiedCallUndefinedOnTarget { .. }
825 | Self::IndirectCallUndefinedOnTarget { .. }
826 | Self::QualifiedCallReturnShapeDrift { .. }
827 | Self::LocalCallReturnShapeDrift { .. }
828 | Self::MacroValueDrift { .. }
829 | Self::BehaviourCallbackAddedOnTarget { .. }
830 | Self::BehaviourCallbackDriftOnTarget { .. }
831 | Self::VersionedMachineSnapshotMissing { .. }
832 | Self::WireConstantBindingsMissing { .. } => false,
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
840#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
841pub struct NeedsPin {
842 pub project: ProjectName,
843 pub tag: TagName,
844}