#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VisibleClass<'a> {
pub package: &'a str,
pub nested_short_name: &'a str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReconciledIdentity {
pub package: String,
pub owner_chain: String,
pub member: String,
}
impl ReconciledIdentity {
pub fn fq_name(&self) -> String {
let short = format!("{}.{}", self.owner_chain, self.member);
if self.package.is_empty() {
short
} else {
format!("{}.{}", self.package, short)
}
}
}
pub fn reconcile_out_of_line_member_identity(
owner_segments: &[&str],
member: &str,
namespace_candidates: &[&str],
class_table: &[VisibleClass<'_>],
) -> Option<ReconciledIdentity> {
if owner_segments.is_empty() || member.is_empty() {
return None;
}
for split in 0..owner_segments.len() {
let chain = owner_segments[split..].join("$");
let namespace_prefix = &owner_segments[..split];
let mut confirmed: Option<ReconciledIdentity> = None;
for namespace in namespace_candidates {
let package = join_namespace(namespace, namespace_prefix);
let matches = class_table
.iter()
.any(|visible| visible.package == package && visible.nested_short_name == chain);
if !matches {
continue;
}
let candidate = ReconciledIdentity {
package,
owner_chain: chain.clone(),
member: member.to_string(),
};
match &confirmed {
Some(existing) if existing != &candidate => return None,
Some(_) => {}
None => confirmed = Some(candidate),
}
}
if let Some(identity) = confirmed {
return Some(identity);
}
}
None
}
fn join_namespace(namespace: &str, extra_segments: &[&str]) -> String {
let extra = extra_segments.join("::");
match (namespace.is_empty(), extra.is_empty()) {
(true, true) => String::new(),
(true, false) => extra,
(false, true) => namespace.to_string(),
(false, false) => format!("{namespace}::{extra}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn identity(package: &str, owner_chain: &str, member: &str) -> ReconciledIdentity {
ReconciledIdentity {
package: package.to_string(),
owner_chain: owner_chain.to_string(),
member: member.to_string(),
}
}
#[test]
fn file_scope_using_directive_shape_recovers_namespace_and_chain() {
let table = [
VisibleClass {
package: "log4cxx",
nested_short_name: "Outer",
},
VisibleClass {
package: "log4cxx",
nested_short_name: "Outer$Inner",
},
];
let reconciled = reconcile_out_of_line_member_identity(
&["Outer", "Inner"],
"method",
&["", "log4cxx"],
&table,
);
assert_eq!(
reconciled,
Some(identity("log4cxx", "Outer$Inner", "method"))
);
}
#[test]
fn template_shape_inside_namespace_block_folds_outer_into_chain() {
let table = [VisibleClass {
package: "ns",
nested_short_name: "Outer$Inner",
}];
let reconciled =
reconcile_out_of_line_member_identity(&["Outer", "Inner"], "method", &["ns"], &table);
assert_eq!(reconciled, Some(identity("ns", "Outer$Inner", "method")));
}
#[test]
fn genuine_namespace_chain_keeps_namespace_reading() {
let table = [VisibleClass {
package: "ns1::ns2",
nested_short_name: "Klass",
}];
let reconciled = reconcile_out_of_line_member_identity(
&["ns1", "ns2", "Klass"],
"method",
&[""],
&table,
);
assert_eq!(reconciled, Some(identity("ns1::ns2", "Klass", "method")));
}
#[test]
fn prefers_longest_confirmed_class_chain() {
let table = [
VisibleClass {
package: "a::Outer",
nested_short_name: "Inner",
},
VisibleClass {
package: "a",
nested_short_name: "Outer$Inner",
},
];
let reconciled =
reconcile_out_of_line_member_identity(&["Outer", "Inner"], "method", &["a"], &table);
assert_eq!(reconciled, Some(identity("a", "Outer$Inner", "method")));
}
#[test]
fn no_visible_class_returns_none() {
let reconciled = reconcile_out_of_line_member_identity(
&["Outer", "Inner"],
"method",
&["", "log4cxx"],
&[],
);
assert_eq!(reconciled, None);
}
#[test]
fn ambiguous_equal_depth_readings_return_none() {
let table = [
VisibleClass {
package: "one",
nested_short_name: "Outer$Inner",
},
VisibleClass {
package: "two",
nested_short_name: "Outer$Inner",
},
];
let reconciled = reconcile_out_of_line_member_identity(
&["Outer", "Inner"],
"method",
&["one", "two"],
&table,
);
assert_eq!(reconciled, None);
}
#[test]
fn degenerate_inputs_return_none() {
assert_eq!(
reconcile_out_of_line_member_identity(&[], "method", &["ns"], &[]),
None
);
assert_eq!(
reconcile_out_of_line_member_identity(&["Outer"], "", &["ns"], &[]),
None
);
}
}