use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompilationIdentity {
pub project: String,
pub tfm: String,
pub configuration: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SymbolIdentity {
pub assembly: String,
pub declaring_type: String,
pub member: String,
pub generic_arity: u32,
pub overload_signature: String,
pub compilation: CompilationIdentity,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OccurrenceSpan {
pub file: String,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
}
#[derive(Debug, Clone)]
pub struct CompatibilityJoinKey {
pub file: String,
pub start_line: u32,
pub member: String,
}
impl CompatibilityJoinKey {
pub fn from_identity(identity: &SymbolIdentity, occurrence: &OccurrenceSpan) -> Self {
CompatibilityJoinKey {
file: occurrence.file.clone(),
start_line: occurrence.start_line,
member: identity.member.clone(),
}
}
pub fn joins(&self, other: &CompatibilityJoinKey) -> bool {
self.file == other.file
&& self.start_line == other.start_line
&& self.member == other.member
}
}
#[cfg(test)]
mod tests {
use super::*;
fn identity(member: &str, arity: u32, signature: &str) -> SymbolIdentity {
SymbolIdentity {
assembly: "Fixture".to_string(),
declaring_type: "Fixture.Cases".to_string(),
member: member.to_string(),
generic_arity: arity,
overload_signature: signature.to_string(),
compilation: CompilationIdentity {
project: "Cases".to_string(),
tfm: "net8.0".to_string(),
configuration: "Release".to_string(),
},
}
}
fn occurrence(start_col: u32, end_col: u32) -> OccurrenceSpan {
OccurrenceSpan {
file: "src/Cases.cs".to_string(),
start_line: 12,
start_col,
end_line: 12,
end_col,
}
}
#[test]
fn distinct_overloads_on_one_line_stay_distinct_identities() {
let int_call = identity("Ping", 0, "Ping(int)");
let string_call = identity("Ping", 0, "Ping(string)");
assert_ne!(int_call, string_call);
assert_ne!(occurrence(5, 12), occurrence(14, 22));
}
#[test]
fn a_symbol_identity_round_trips_through_json_unchanged() {
let original = identity("Ping", 0, "Ping(int)");
let json = serde_json::to_string(&original).unwrap();
let restored: SymbolIdentity = serde_json::from_str(&json).unwrap();
assert_eq!(original, restored);
}
#[test]
fn an_occurrence_span_round_trips_through_json_unchanged() {
let original = occurrence(5, 12);
let json = serde_json::to_string(&original).unwrap();
let restored: OccurrenceSpan = serde_json::from_str(&json).unwrap();
assert_eq!(original, restored);
}
#[test]
fn compatibility_join_key_collapses_what_full_identity_keeps_apart() {
let int_call = identity("Ping", 0, "Ping(int)");
let string_call = identity("Ping", 0, "Ping(string)");
let a = CompatibilityJoinKey::from_identity(&int_call, &occurrence(5, 12));
let b = CompatibilityJoinKey::from_identity(&string_call, &occurrence(14, 22));
assert!(
int_call != string_call,
"full identity distinguishes the overloads"
);
assert!(
a.joins(&b),
"the legacy (file, startLine, member) key does not"
);
}
}