use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RecvKind {
None,
This,
#[serde(rename = "SELF")]
SelfRecv,
NamedVariable,
TypeQualified,
FieldOfThis,
FieldOfSelf,
FieldOfVariable,
Super,
StaticType,
#[default]
Unknown,
}
impl RecvKind {
pub fn as_str(self) -> &'static str {
match self {
RecvKind::None => "NONE",
RecvKind::This => "THIS",
RecvKind::SelfRecv => "SELF",
RecvKind::NamedVariable => "NAMED_VARIABLE",
RecvKind::TypeQualified => "TYPE_QUALIFIED",
RecvKind::FieldOfThis => "FIELD_OF_THIS",
RecvKind::FieldOfSelf => "FIELD_OF_SELF",
RecvKind::FieldOfVariable => "FIELD_OF_VARIABLE",
RecvKind::Super => "SUPER",
RecvKind::StaticType => "STATIC_TYPE",
RecvKind::Unknown => "UNKNOWN_RECEIVER",
}
}
pub fn is_instance_self(self) -> bool {
matches!(self, RecvKind::This | RecvKind::SelfRecv)
}
pub fn is_field_chain(self) -> bool {
matches!(
self,
RecvKind::FieldOfThis | RecvKind::FieldOfSelf | RecvKind::FieldOfVariable
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ResolutionClass {
ResolvedInternal,
UnresolvedLikelyInternal,
ConfirmedExternal,
#[default]
Unknown,
}
impl ResolutionClass {
pub fn as_str(self) -> &'static str {
match self {
ResolutionClass::ResolvedInternal => "resolved_internal",
ResolutionClass::UnresolvedLikelyInternal => "unresolved_likely_internal",
ResolutionClass::ConfirmedExternal => "confirmed_external",
ResolutionClass::Unknown => "unknown",
}
}
}
pub mod confidence {
pub const LSP_EXACT: f64 = 0.99;
pub const SCIP_EXACT: f64 = 0.99;
pub const UNIQUE_PIN: f64 = 0.99;
pub const SIBLING_METHOD: f64 = 0.98;
pub const NAMESPACE_PIN: f64 = 0.97;
pub const IMPORTED_MEMBER: f64 = 0.95;
pub const TYPED_RECEIVER: f64 = 0.9;
pub const CONFIRMED_EXTERNAL: f64 = 0.8;
pub const STRONG_LINK: f64 = 0.8;
pub const SEEDED_ROOT: f64 = 0.55;
pub const UNRESOLVED_FALLBACK: f64 = 0.5;
pub const UNRESOLVED_LIKELY_INTERNAL: f64 = 0.4;
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, schemars::JsonSchema)]
pub struct AnalysisQuality {
pub calls: CallQuality,
pub files: FileQuality,
#[serde(default, skip_serializing_if = "is_zero")]
pub matched_doc_mentions: u32,
#[serde(default, skip_serializing_if = "is_zero")]
pub unmatched_doc_mentions: u32,
}
fn is_zero(n: &u32) -> bool {
*n == 0
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, schemars::JsonSchema)]
pub struct CallQuality {
pub resolved: u32,
#[serde(default, skip_serializing_if = "is_zero")]
pub precise: u32,
#[serde(default, skip_serializing_if = "is_zero")]
pub heuristic: u32,
pub likely_internal_unresolved: u32,
pub external: u32,
#[serde(default, skip_serializing_if = "is_zero")]
pub unknown: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, schemars::JsonSchema)]
pub struct FileQuality {
pub parsed: u32,
#[serde(default, skip_serializing_if = "is_zero")]
pub unsupported: u32,
}
impl AnalysisQuality {
pub fn record_call(&mut self, class: ResolutionClass, precise: bool) {
match class {
ResolutionClass::ResolvedInternal => {
self.calls.resolved += 1;
if precise {
self.calls.precise += 1;
} else {
self.calls.heuristic += 1;
}
}
ResolutionClass::UnresolvedLikelyInternal => {
self.calls.likely_internal_unresolved += 1
}
ResolutionClass::ConfirmedExternal => self.calls.external += 1,
ResolutionClass::Unknown => self.calls.unknown += 1,
}
}
pub fn merge(&mut self, other: &AnalysisQuality) {
self.calls.resolved += other.calls.resolved;
self.calls.precise += other.calls.precise;
self.calls.heuristic += other.calls.heuristic;
self.calls.likely_internal_unresolved += other.calls.likely_internal_unresolved;
self.calls.external += other.calls.external;
self.calls.unknown += other.calls.unknown;
self.files.parsed += other.files.parsed;
self.files.unsupported += other.files.unsupported;
self.matched_doc_mentions += other.matched_doc_mentions;
self.unmatched_doc_mentions += other.unmatched_doc_mentions;
}
pub fn compact_line(&self) -> String {
let mut line = format!(
"calls: resolved={} precise={} heuristic={} likely_internal_unresolved={} external={} | files: parsed={} unsupported={}",
self.calls.resolved,
self.calls.precise,
self.calls.heuristic,
self.calls.likely_internal_unresolved,
self.calls.external,
self.files.parsed,
self.files.unsupported,
);
if self.matched_doc_mentions > 0 || self.unmatched_doc_mentions > 0 {
line.push_str(&format!(
" | mentions: matched={} unmatched={}",
self.matched_doc_mentions, self.unmatched_doc_mentions
));
}
line
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RepresentationKind {
Exact,
Structural,
Signatures,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepresentationChoice {
pub kind: RepresentationKind,
pub reason: String,
pub exact_cost: usize,
pub structural_cost: usize,
}
pub fn choose_representation(exact_cost: usize, structural_cost: usize) -> RepresentationChoice {
if exact_cost <= structural_cost {
RepresentationChoice {
kind: RepresentationKind::Exact,
reason: "exact_body_cheaper_than_structural".into(),
exact_cost,
structural_cost,
}
} else {
let saved = structural_cost
.saturating_mul(100)
.checked_div(exact_cost)
.map(|pct| 100usize.saturating_sub(pct))
.unwrap_or(0);
RepresentationChoice {
kind: RepresentationKind::Structural,
reason: format!("structural_saved_{saved}_percent"),
exact_cost,
structural_cost,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_wins_when_not_more_expensive() {
let c = choose_representation(40, 40);
assert_eq!(c.kind, RepresentationKind::Exact);
assert_eq!(c.reason, "exact_body_cheaper_than_structural");
let cheaper = choose_representation(10, 80);
assert_eq!(cheaper.kind, RepresentationKind::Exact);
}
#[test]
fn structural_wins_when_it_saves_tokens() {
let c = choose_representation(100, 29);
assert_eq!(c.kind, RepresentationKind::Structural);
assert!(c.reason.contains("structural_saved_"), "{}", c.reason);
}
#[test]
fn record_call_buckets_are_honest() {
let mut q = AnalysisQuality::default();
q.record_call(ResolutionClass::ResolvedInternal, true);
q.record_call(ResolutionClass::ResolvedInternal, false);
q.record_call(ResolutionClass::ConfirmedExternal, false);
q.record_call(ResolutionClass::UnresolvedLikelyInternal, false);
q.record_call(ResolutionClass::Unknown, false);
assert_eq!(q.calls.resolved, 2);
assert_eq!(q.calls.precise, 1);
assert_eq!(q.calls.heuristic, 1);
assert_eq!(q.calls.external, 1);
assert_eq!(q.calls.likely_internal_unresolved, 1);
assert_eq!(q.calls.unknown, 1);
assert!(q.compact_line().contains("precise=1"));
assert!(q.compact_line().contains("external=1"));
}
}