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 {
281 source_path: RelativePath,
282 behaviour: ModuleName,
283 },
284 HeaderFileMissing {
291 source_path: RelativePath,
292 include_directive: IncludeDirective,
293 attempted_paths: Vec<RelativePath>,
294 },
295 MacroUndefinedOnTarget {
301 source_path: RelativePath,
302 macro_name: String,
303 line: u32,
304 },
305 RecordUndefinedOnTarget {
310 source_path: RelativePath,
311 record_name: RecordName,
312 line: u32,
313 },
314 LocalCallUndefinedOnTarget {
319 source_path: RelativePath,
320 function: FunctionName,
321 arity: Arity,
322 line: u32,
323 },
324 QualifiedCallUndefinedOnTarget {
333 source_path: RelativePath,
334 module: ModuleName,
335 function: FunctionName,
336 arity: Arity,
337 line: u32,
338 },
339 QualifiedCallReturnShapeDrift {
347 source_path: RelativePath,
348 module: ModuleName,
349 function: FunctionName,
350 arity: Arity,
351 source_signature: String,
352 target_signature: String,
353 line: u32,
354 },
355 LocalCallReturnShapeDrift {
361 source_path: RelativePath,
362 function: FunctionName,
363 arity: Arity,
364 source_signature: String,
365 target_signature: String,
366 line: u32,
367 },
368 MacroValueDrift {
373 source_path: RelativePath,
374 macro_name: String,
375 source_value: String,
376 target_value: String,
377 line: u32,
378 },
379 BehaviourCallbackAddedOnTarget {
385 source_path: RelativePath,
386 behaviour: ModuleName,
387 callback: FunctionName,
388 arity: Arity,
389 line: u32,
390 },
391 BehaviourCallbackDriftOnTarget {
397 source_path: RelativePath,
398 behaviour: ModuleName,
399 callback: FunctionName,
400 arity: Arity,
401 source_signature: String,
402 target_signature: String,
403 line: u32,
404 },
405 VersionedMachineSnapshotMissing {
409 module: ModuleName,
410 side: SnapshotSide,
411 },
412 WireConstantBindingsMissing {
416 module: ModuleName,
417 macros: Vec<MacroName>,
418 side: SnapshotSide,
419 },
420 IndirectCallUndefinedOnTarget {
427 source_path: RelativePath,
428 module: ModuleName,
429 function: FunctionName,
430 arity: Arity,
431 via: IndirectCallForm,
432 line: u32,
433 },
434 ExportedTypeUndefinedOnTarget {
442 source_path: RelativePath,
443 type_name: TypeName,
444 arity: Arity,
445 line: u32,
446 },
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
451#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
452#[serde(rename_all = "snake_case")]
453pub enum SnapshotSide {
454 Source,
455 Target,
456 Both,
457}
458
459#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
463#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
464pub struct TestCallSite {
465 pub function: FunctionName,
466 pub arity: Arity,
467 pub line: u32,
468}
469
470#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
476#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
477#[serde(tag = "form", rename_all = "snake_case")]
478pub enum IncludeDirective {
479 Include { path: String },
480 IncludeLib { path: String },
481}
482
483impl IncludeDirective {
484 pub fn path(&self) -> &str {
485 match self {
486 Self::Include { path } | Self::IncludeLib { path } => path,
487 }
488 }
489}
490
491#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
492#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
493#[serde(tag = "kind", rename_all = "snake_case")]
494pub enum TranslationSource {
495 ConfigStanza { name: String },
496 ExternalFile { path: PathBuf, name: String },
497}
498
499#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
500#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
501#[serde(tag = "artifact_kind", rename_all = "snake_case")]
502pub enum ArtifactKind {
503 ConflictMarker { marker: ConflictMarker },
506 ExportWithoutBody {
509 module: ModuleName,
510 function: FunctionName,
511 arity: Arity,
512 },
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
516#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
517#[serde(rename_all = "snake_case")]
518pub enum ConflictMarker {
519 Ours,
521 Divider,
523 Theirs,
525 Ancestor,
527}
528
529impl ConflictMarker {
530 pub fn detect(line: &str) -> Option<Self> {
537 Self::detect_with_marker(line, '<', Self::Ours)
538 .or_else(|| Self::detect_with_marker(line, '=', Self::Divider))
539 .or_else(|| Self::detect_with_marker(line, '>', Self::Theirs))
540 .or_else(|| Self::detect_with_marker(line, '|', Self::Ancestor))
541 }
542
543 fn detect_with_marker(line: &str, token: char, marker: Self) -> Option<Self> {
544 const MIN_RUN: usize = 7;
545 let mut count = 0usize;
546 for c in line.chars() {
547 if c == token {
548 count += 1;
549 continue;
550 }
551 return if count >= MIN_RUN && (c == ' ' || c == '\t') {
552 Some(marker)
553 } else {
554 None
555 };
556 }
557 if count >= MIN_RUN { Some(marker) } else { None }
558 }
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
565#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
566#[serde(rename_all = "snake_case")]
567pub enum ApplyConflictKind {
568 PostimageCollision,
569 PreimageMissing,
570 FileAbsent,
571}
572
573impl ApplyConflictKind {
574 #[must_use]
576 pub fn as_str(self) -> &'static str {
577 match self {
578 Self::PostimageCollision => "postimage_collision",
579 Self::PreimageMissing => "preimage_missing",
580 Self::FileAbsent => "file_absent",
581 }
582 }
583}
584
585#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
589#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
590#[serde(rename_all = "snake_case")]
591pub enum IndirectCallForm {
592 MeckExpect,
593 RpcCall,
594 ErpcCall,
595 CtBrokerHelperRpc,
596 RabbitMiscRpcCall,
597}
598
599impl IndirectCallForm {
600 #[must_use]
603 pub fn display_form(self) -> &'static str {
604 match self {
605 Self::MeckExpect => "meck:expect",
606 Self::RpcCall => "rpc:call",
607 Self::ErpcCall => "erpc:call",
608 Self::CtBrokerHelperRpc => "rabbit_ct_broker_helpers:rpc",
609 Self::RabbitMiscRpcCall => "rabbit_misc:rpc_call",
610 }
611 }
612}
613
614impl Reason {
615 pub fn apply_conflict(&self) -> Option<(&Path, ApplyConflictKind)> {
620 match self {
621 Self::PostimageCollision { path, .. } => {
622 Some((path, ApplyConflictKind::PostimageCollision))
623 }
624 Self::PreimageMissing { path, .. } => Some((path, ApplyConflictKind::PreimageMissing)),
625 Self::FileAbsent { path } => Some((path, ApplyConflictKind::FileAbsent)),
626 Self::MissingSymbol { .. }
627 | Self::ArityChanged { .. }
628 | Self::SignatureChanged { .. }
629 | Self::ContextDrift { .. }
630 | Self::PreimageDrifted { .. }
631 | Self::DeprecatedUsage { .. }
632 | Self::NowHidden { .. }
633 | Self::RecordFieldsChanged { .. }
634 | Self::UnsupportedFileType { .. }
635 | Self::UntrackedModuleMissing { .. }
636 | Self::ClauseMismatch { .. }
637 | Self::MissingPrereq { .. }
638 | Self::SyntacticArtifact { .. }
639 | Self::BehaviourCallbackSignatureChanged { .. }
640 | Self::BehaviourCallbackRemoved { .. }
641 | Self::BehaviourCallbackAdded { .. }
642 | Self::ModuleRelocated { .. }
643 | Self::WireConstantChanged { .. }
644 | Self::HistoricalImplementationMissing { .. }
645 | Self::WireContractBodyDrift { .. }
646 | Self::WireContractRegression { .. }
647 | Self::ReturnShapeMismatch { .. }
648 | Self::MissingType { .. }
649 | Self::PathRename { .. }
650 | Self::TargetPathAbsent { .. }
651 | Self::TestModuleSymbolMissing { .. }
652 | Self::BehaviourModuleMissing { .. }
653 | Self::HeaderFileMissing { .. }
654 | Self::MacroUndefinedOnTarget { .. }
655 | Self::RecordUndefinedOnTarget { .. }
656 | Self::LocalCallUndefinedOnTarget { .. }
657 | Self::ExportedTypeUndefinedOnTarget { .. }
658 | Self::QualifiedCallUndefinedOnTarget { .. }
659 | Self::IndirectCallUndefinedOnTarget { .. }
660 | Self::QualifiedCallReturnShapeDrift { .. }
661 | Self::LocalCallReturnShapeDrift { .. }
662 | Self::MacroValueDrift { .. }
663 | Self::BehaviourCallbackAddedOnTarget { .. }
664 | Self::BehaviourCallbackDriftOnTarget { .. }
665 | Self::VersionedMachineSnapshotMissing { .. }
666 | Self::WireConstantBindingsMissing { .. } => None,
667 }
668 }
669
670 pub fn resolver_class(&self) -> Option<ResolverClass> {
674 match self {
675 Self::MissingSymbol { symbol, .. } | Self::MissingPrereq { symbol, .. } => {
676 Some(ResolverClass::of_symbol_kind(&symbol.kind))
677 }
678 Self::MissingType { .. } => Some(ResolverClass::Type),
679 Self::MacroUndefinedOnTarget { .. } | Self::MacroValueDrift { .. } => {
680 Some(ResolverClass::Macro)
681 }
682 Self::RecordUndefinedOnTarget { .. } => Some(ResolverClass::Record),
683 Self::ExportedTypeUndefinedOnTarget { .. } => Some(ResolverClass::Type),
684 Self::LocalCallUndefinedOnTarget { .. } | Self::LocalCallReturnShapeDrift { .. } => {
685 Some(ResolverClass::LocalCall)
686 }
687 Self::QualifiedCallUndefinedOnTarget { .. }
688 | Self::QualifiedCallReturnShapeDrift { .. } => Some(ResolverClass::QualifiedCall),
689 Self::IndirectCallUndefinedOnTarget { .. } => Some(ResolverClass::IndirectCall),
690 Self::HeaderFileMissing { .. } => Some(ResolverClass::Include),
691 Self::BehaviourModuleMissing { .. }
692 | Self::BehaviourCallbackAddedOnTarget { .. }
693 | Self::BehaviourCallbackDriftOnTarget { .. } => Some(ResolverClass::Behaviour),
694 Self::ArityChanged { .. }
695 | Self::SignatureChanged { .. }
696 | Self::FileAbsent { .. }
697 | Self::ContextDrift { .. }
698 | Self::PreimageDrifted { .. }
699 | Self::PostimageCollision { .. }
700 | Self::PreimageMissing { .. }
701 | Self::DeprecatedUsage { .. }
702 | Self::NowHidden { .. }
703 | Self::RecordFieldsChanged { .. }
704 | Self::UnsupportedFileType { .. }
705 | Self::UntrackedModuleMissing { .. }
706 | Self::ClauseMismatch { .. }
707 | Self::SyntacticArtifact { .. }
708 | Self::BehaviourCallbackSignatureChanged { .. }
709 | Self::BehaviourCallbackRemoved { .. }
710 | Self::BehaviourCallbackAdded { .. }
711 | Self::ModuleRelocated { .. }
712 | Self::WireConstantChanged { .. }
713 | Self::HistoricalImplementationMissing { .. }
714 | Self::WireContractBodyDrift { .. }
715 | Self::WireContractRegression { .. }
716 | Self::ReturnShapeMismatch { .. }
717 | Self::PathRename { .. }
718 | Self::TargetPathAbsent { .. }
719 | Self::TestModuleSymbolMissing { .. }
720 | Self::VersionedMachineSnapshotMissing { .. }
721 | Self::WireConstantBindingsMissing { .. } => None,
722 }
723 }
724
725 pub fn is_blocking(&self) -> bool {
727 match self {
728 Self::MissingSymbol {
731 first_seen_at_tag: Some(_),
732 ..
733 } => false,
734 Self::ArityChanged {
735 expected_available_at: Some(_),
736 ..
737 } => false,
738 Self::MissingSymbol { .. }
739 | Self::ArityChanged { .. }
740 | Self::SignatureChanged { .. }
741 | Self::FileAbsent { .. }
742 | Self::NowHidden { .. }
743 | Self::RecordFieldsChanged { .. }
744 | Self::UntrackedModuleMissing { .. }
745 | Self::ClauseMismatch { .. }
746 | Self::MissingPrereq { .. }
747 | Self::SyntacticArtifact { .. }
748 | Self::BehaviourCallbackSignatureChanged { .. }
749 | Self::BehaviourCallbackRemoved { .. }
750 | Self::BehaviourCallbackAdded { .. }
751 | Self::WireConstantChanged { .. }
752 | Self::HistoricalImplementationMissing { .. }
753 | Self::WireContractRegression { .. }
754 | Self::ReturnShapeMismatch { .. } => true,
755 Self::ContextDrift { .. }
756 | Self::PreimageDrifted { .. }
757 | Self::PreimageMissing { .. }
758 | Self::PostimageCollision { .. }
759 | Self::DeprecatedUsage { .. }
760 | Self::UnsupportedFileType { .. }
761 | Self::ModuleRelocated { .. }
762 | Self::WireContractBodyDrift { .. }
763 | Self::MissingType { .. }
764 | Self::PathRename { .. }
765 | Self::TargetPathAbsent { .. }
766 | Self::TestModuleSymbolMissing { .. }
767 | Self::BehaviourModuleMissing { .. }
768 | Self::HeaderFileMissing { .. }
769 | Self::MacroUndefinedOnTarget { .. }
770 | Self::RecordUndefinedOnTarget { .. }
771 | Self::LocalCallUndefinedOnTarget { .. }
772 | Self::ExportedTypeUndefinedOnTarget { .. }
773 | Self::QualifiedCallUndefinedOnTarget { .. }
774 | Self::IndirectCallUndefinedOnTarget { .. }
775 | Self::QualifiedCallReturnShapeDrift { .. }
776 | Self::LocalCallReturnShapeDrift { .. }
777 | Self::MacroValueDrift { .. }
778 | Self::BehaviourCallbackAddedOnTarget { .. }
779 | Self::BehaviourCallbackDriftOnTarget { .. }
780 | Self::VersionedMachineSnapshotMissing { .. }
781 | Self::WireConstantBindingsMissing { .. } => false,
782 }
783 }
784
785 pub fn is_path_scoped(&self) -> bool {
790 match self {
791 Self::FileAbsent { .. }
792 | Self::ModuleRelocated { .. }
793 | Self::PreimageDrifted { .. }
794 | Self::PreimageMissing { .. }
795 | Self::PostimageCollision { .. }
796 | Self::ContextDrift { .. }
797 | Self::PathRename { .. }
798 | Self::TargetPathAbsent { .. }
799 | Self::UnsupportedFileType { .. }
800 | Self::SyntacticArtifact { .. } => true,
801 Self::MissingSymbol { .. }
802 | Self::ArityChanged { .. }
803 | Self::SignatureChanged { .. }
804 | Self::DeprecatedUsage { .. }
805 | Self::NowHidden { .. }
806 | Self::RecordFieldsChanged { .. }
807 | Self::UntrackedModuleMissing { .. }
808 | Self::ClauseMismatch { .. }
809 | Self::MissingPrereq { .. }
810 | Self::BehaviourCallbackSignatureChanged { .. }
811 | Self::BehaviourCallbackRemoved { .. }
812 | Self::BehaviourCallbackAdded { .. }
813 | Self::WireConstantChanged { .. }
814 | Self::HistoricalImplementationMissing { .. }
815 | Self::WireContractBodyDrift { .. }
816 | Self::WireContractRegression { .. }
817 | Self::ReturnShapeMismatch { .. }
818 | Self::MissingType { .. }
819 | Self::TestModuleSymbolMissing { .. }
820 | Self::BehaviourModuleMissing { .. }
821 | Self::HeaderFileMissing { .. }
822 | Self::MacroUndefinedOnTarget { .. }
823 | Self::RecordUndefinedOnTarget { .. }
824 | Self::LocalCallUndefinedOnTarget { .. }
825 | Self::ExportedTypeUndefinedOnTarget { .. }
826 | Self::QualifiedCallUndefinedOnTarget { .. }
827 | Self::IndirectCallUndefinedOnTarget { .. }
828 | Self::QualifiedCallReturnShapeDrift { .. }
829 | Self::LocalCallReturnShapeDrift { .. }
830 | Self::MacroValueDrift { .. }
831 | Self::BehaviourCallbackAddedOnTarget { .. }
832 | Self::BehaviourCallbackDriftOnTarget { .. }
833 | Self::VersionedMachineSnapshotMissing { .. }
834 | Self::WireConstantBindingsMissing { .. } => false,
835 }
836 }
837}
838
839#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
842#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
843pub struct NeedsPin {
844 pub project: ProjectName,
845 pub tag: TagName,
846}