1use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10
11use super::{AlreadyPresent, AlreadyPresentSkipped, DepPinDivergence, PinBump, Unanalyzed};
12use crate::model::names::{CommitSha, ModuleName, ProjectName, RecordName, RelativePath};
13
14#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
18pub struct Diagnostics {
19 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
20 pub untracked_calls: BTreeMap<ModuleName, usize>,
21 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
22 pub untracked_records: BTreeMap<RecordName, usize>,
23 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
26 pub context_refs_missing: BTreeMap<ModuleName, usize>,
27 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
31 pub unattributed_paths: BTreeMap<String, usize>,
32 #[serde(default, skip_serializing_if = "Unanalyzed::is_empty")]
33 pub unanalyzed: Unanalyzed,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub suggested_suites: Vec<String>,
39 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
49 pub missing_test_modules: BTreeMap<RelativePath, BTreeMap<ModuleName, usize>>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub already_present: Option<AlreadyPresent>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub already_present_skipped: Option<AlreadyPresentSkipped>,
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
60 pub dep_pin_divergence: Vec<DepPinDivergence>,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 pub pin_bumps: Vec<PinBump>,
65 #[serde(default, skip_serializing_if = "ShapeCheckTally::is_empty")]
68 pub qualified_call_shape_checks: ShapeCheckTally,
69 #[serde(default, skip_serializing_if = "ShapeCheckTally::is_empty")]
73 pub local_call_shape_checks: ShapeCheckTally,
74 #[serde(default, skip_serializing_if = "MacroValueTally::is_empty")]
77 pub macro_value_checks: MacroValueTally,
78 #[serde(default, skip_serializing_if = "IndirectCallTally::is_empty")]
82 pub indirect_call_checks: IndirectCallTally,
83}
84
85impl Diagnostics {
86 pub fn is_empty(&self) -> bool {
87 let Self {
89 untracked_calls,
90 untracked_records,
91 context_refs_missing,
92 unattributed_paths,
93 unanalyzed,
94 suggested_suites,
95 missing_test_modules,
96 already_present,
97 already_present_skipped,
98 dep_pin_divergence,
99 pin_bumps,
100 qualified_call_shape_checks,
101 local_call_shape_checks,
102 macro_value_checks,
103 indirect_call_checks,
104 } = self;
105 untracked_calls.is_empty()
106 && untracked_records.is_empty()
107 && context_refs_missing.is_empty()
108 && unattributed_paths.is_empty()
109 && unanalyzed.is_empty()
110 && suggested_suites.is_empty()
111 && missing_test_modules.is_empty()
112 && already_present.is_none()
113 && already_present_skipped.is_none()
114 && dep_pin_divergence.is_empty()
115 && pin_bumps.is_empty()
116 && qualified_call_shape_checks.is_empty()
117 && local_call_shape_checks.is_empty()
118 && macro_value_checks.is_empty()
119 && indirect_call_checks.is_empty()
120 }
121
122 pub fn record_missing_test_module(&mut self, suite: RelativePath, helper: ModuleName) {
125 let inner = self.missing_test_modules.entry(suite).or_default();
126 *inner.entry(helper).or_insert(0) += 1;
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
133#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
134#[serde(rename_all = "snake_case")]
135pub enum TargetMatchKind {
136 TrailerIntersection,
139 TrailerAncestry,
142 PatchId,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
152pub struct TargetMatch {
153 pub commit: CommitSha,
154 pub via: TargetMatchKind,
155 pub subject: String,
156}
157
158#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
163#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
164pub struct ShapeCheckTally {
165 #[serde(default, skip_serializing_if = "is_zero")]
169 pub compared: usize,
170 #[serde(default, skip_serializing_if = "is_zero")]
172 pub withheld_no_spec: usize,
173 #[serde(default, skip_serializing_if = "is_zero")]
176 pub withheld_unknown_type: usize,
177 #[serde(default, skip_serializing_if = "is_zero")]
182 pub withheld_no_source: usize,
183 #[serde(default, skip_serializing_if = "is_zero")]
187 pub withheld_imported: usize,
188}
189
190#[allow(clippy::trivially_copy_pass_by_ref)]
191fn is_zero(n: &usize) -> bool {
192 *n == 0
193}
194
195impl ShapeCheckTally {
196 pub fn is_empty(&self) -> bool {
197 *self == Self::default()
198 }
199
200 pub fn merge(&mut self, other: Self) {
203 self.compared += other.compared;
204 self.withheld_no_spec += other.withheld_no_spec;
205 self.withheld_unknown_type += other.withheld_unknown_type;
206 self.withheld_no_source += other.withheld_no_source;
207 self.withheld_imported += other.withheld_imported;
208 }
209}
210
211#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
215#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
216pub struct MacroValueTally {
217 #[serde(default, skip_serializing_if = "is_zero")]
220 pub compared: usize,
221 #[serde(default, skip_serializing_if = "is_zero")]
224 pub withheld_definition_elsewhere: usize,
225 #[serde(default, skip_serializing_if = "is_zero")]
228 pub withheld_multiple_defines: usize,
229 #[serde(default, skip_serializing_if = "is_zero")]
231 pub withheld_no_source: usize,
232}
233
234impl MacroValueTally {
235 pub fn is_empty(&self) -> bool {
236 *self == Self::default()
237 }
238
239 pub fn merge(&mut self, other: Self) {
242 self.compared += other.compared;
243 self.withheld_definition_elsewhere += other.withheld_definition_elsewhere;
244 self.withheld_multiple_defines += other.withheld_multiple_defines;
245 self.withheld_no_source += other.withheld_no_source;
246 }
247}
248
249#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
254#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
255pub struct IndirectCallTally {
256 #[serde(default, skip_serializing_if = "is_zero")]
259 pub checked: usize,
260 #[serde(default, skip_serializing_if = "is_zero")]
264 pub withheld_dynamic: usize,
265}
266
267impl IndirectCallTally {
268 pub fn is_empty(&self) -> bool {
269 *self == Self::default()
270 }
271
272 pub fn merge(&mut self, other: Self) {
275 self.checked += other.checked;
276 self.withheld_dynamic += other.withheld_dynamic;
277 }
278}
279
280#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
282#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
283pub struct HunkTally {
284 pub applied: usize,
285 pub considered: usize,
286 pub ambiguous: usize,
287 pub low_confidence: usize,
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
295pub struct ContentPresence {
296 pub pin: ProjectName,
300 pub hunks_already_applied: usize,
301 pub hunks_considered: usize,
302 pub hunks_ambiguous: usize,
303 pub hunks_low_confidence: usize,
304 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
305 pub per_file: BTreeMap<RelativePath, HunkTally>,
306}