devscout_rs/truth/
identity.rs1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CompilationIdentity {
19 pub project: String,
21 pub tfm: String,
23 pub configuration: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct SymbolIdentity {
35 pub assembly: String,
37 pub declaring_type: String,
39 pub member: String,
41 pub generic_arity: u32,
43 pub overload_signature: String,
46 pub compilation: CompilationIdentity,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct OccurrenceSpan {
58 pub file: String,
60 pub start_line: u32,
62 pub start_col: u32,
64 pub end_line: u32,
66 pub end_col: u32,
68}
69
70#[derive(Debug, Clone)]
81pub struct CompatibilityJoinKey {
82 pub file: String,
84 pub start_line: u32,
86 pub member: String,
88}
89
90impl CompatibilityJoinKey {
91 pub fn from_identity(identity: &SymbolIdentity, occurrence: &OccurrenceSpan) -> Self {
93 CompatibilityJoinKey {
94 file: occurrence.file.clone(),
95 start_line: occurrence.start_line,
96 member: identity.member.clone(),
97 }
98 }
99
100 pub fn joins(&self, other: &CompatibilityJoinKey) -> bool {
104 self.file == other.file
105 && self.start_line == other.start_line
106 && self.member == other.member
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn identity(member: &str, arity: u32, signature: &str) -> SymbolIdentity {
115 SymbolIdentity {
116 assembly: "Fixture".to_string(),
117 declaring_type: "Fixture.Cases".to_string(),
118 member: member.to_string(),
119 generic_arity: arity,
120 overload_signature: signature.to_string(),
121 compilation: CompilationIdentity {
122 project: "Cases".to_string(),
123 tfm: "net8.0".to_string(),
124 configuration: "Release".to_string(),
125 },
126 }
127 }
128
129 fn occurrence(start_col: u32, end_col: u32) -> OccurrenceSpan {
130 OccurrenceSpan {
131 file: "src/Cases.cs".to_string(),
132 start_line: 12,
133 start_col,
134 end_line: 12,
135 end_col,
136 }
137 }
138
139 #[test]
140 fn distinct_overloads_on_one_line_stay_distinct_identities() {
141 let int_call = identity("Ping", 0, "Ping(int)");
142 let string_call = identity("Ping", 0, "Ping(string)");
143 assert_ne!(int_call, string_call);
144 assert_ne!(occurrence(5, 12), occurrence(14, 22));
145 }
146
147 #[test]
148 fn a_symbol_identity_round_trips_through_json_unchanged() {
149 let original = identity("Ping", 0, "Ping(int)");
150 let json = serde_json::to_string(&original).unwrap();
151 let restored: SymbolIdentity = serde_json::from_str(&json).unwrap();
152 assert_eq!(original, restored);
153 }
154
155 #[test]
156 fn an_occurrence_span_round_trips_through_json_unchanged() {
157 let original = occurrence(5, 12);
158 let json = serde_json::to_string(&original).unwrap();
159 let restored: OccurrenceSpan = serde_json::from_str(&json).unwrap();
160 assert_eq!(original, restored);
161 }
162
163 #[test]
164 fn compatibility_join_key_collapses_what_full_identity_keeps_apart() {
165 let int_call = identity("Ping", 0, "Ping(int)");
166 let string_call = identity("Ping", 0, "Ping(string)");
167 let a = CompatibilityJoinKey::from_identity(&int_call, &occurrence(5, 12));
168 let b = CompatibilityJoinKey::from_identity(&string_call, &occurrence(14, 22));
169 assert!(
170 int_call != string_call,
171 "full identity distinguishes the overloads"
172 );
173 assert!(
174 a.joins(&b),
175 "the legacy (file, startLine, member) key does not"
176 );
177 }
178}