use super::arity::generic_args_unify;
use super::index::{name_probe, DefIndex};
use super::ladder::{capped_candidates, resolve_ref, Resolution};
use super::scope::FileContext;
use crate::graph::{Candidate, Def, Edge, FragRef, HeuristicTier};
use std::collections::{HashMap, HashSet};
pub(super) fn build_implementor_index(index: &DefIndex) -> HashMap<String, Vec<usize>> {
let mut map: HashMap<String, Vec<usize>> = HashMap::new();
for (idx, lists) in index.member_lists.iter().enumerate() {
for base_name in &lists.bases {
map.entry(base_name.clone()).or_default().push(idx);
}
}
map
}
fn is_infra_namespace(name: &str) -> bool {
name == "System"
|| name == "Microsoft"
|| name.starts_with("System.")
|| name.starts_with("Microsoft.")
}
pub(super) enum CtorDiResolution {
Plain(usize),
Closed(usize),
OpenGeneric(usize),
Ambiguous(Vec<usize>),
Infra,
Unresolved,
}
impl CtorDiResolution {
pub(super) fn edge_parts(
self,
index: &DefIndex,
) -> (&'static str, Option<String>, Vec<Candidate>) {
match self {
CtorDiResolution::Plain(i) => ("plain", Some(index.defs[i].id.clone()), Vec::new()),
CtorDiResolution::Closed(i) => ("closed", Some(index.defs[i].id.clone()), Vec::new()),
CtorDiResolution::OpenGeneric(i) => {
("open-generic", Some(index.defs[i].id.clone()), Vec::new())
}
CtorDiResolution::Ambiguous(idxs) => {
("ambiguous", None, capped_candidates(index, idxs))
}
CtorDiResolution::Infra => ("infra", None, Vec::new()),
CtorDiResolution::Unresolved => ("unresolved", None, Vec::new()),
}
}
}
pub(super) fn resolve_ctor_param(
ref_: &FragRef,
usings: &HashSet<String>,
ns: &str,
index: &DefIndex,
aliases: &HashMap<String, String>,
file_contexts: &HashMap<String, FileContext>,
implementors_by_base_name: &HashMap<String, Vec<usize>>,
) -> CtorDiResolution {
match resolve_ref(ref_, usings, ns, index, aliases, file_contexts) {
Resolution::Ambiguous(candidate_indices, _) => {
CtorDiResolution::Ambiguous(candidate_indices)
}
Resolution::External => {
if usings.iter().any(|u| is_infra_namespace(u)) {
CtorDiResolution::Infra
} else {
CtorDiResolution::Unresolved
}
}
Resolution::Resolved(iface_idx, _) => {
let base_name = &ref_.name;
let raw_candidates = implementors_by_base_name
.get(base_name)
.cloned()
.unwrap_or_default();
let mut closed_or_plain: Vec<usize> = Vec::new();
let mut open_generic: Vec<usize> = Vec::new();
for cand in raw_candidates {
if cand == iface_idx {
continue;
}
let cand_def = &index.defs[cand];
let Some(cand_ctx) = file_contexts.get(&cand_def.file) else {
continue;
};
let probe = name_probe(base_name.clone(), &cand_def.namespace, Vec::new());
let base_res = resolve_ref(
&probe,
&cand_ctx.usings,
&cand_def.namespace,
index,
&cand_ctx.aliases,
file_contexts,
);
let Resolution::Resolved(resolved_iface, _) = base_res else {
continue;
};
if resolved_iface != iface_idx {
continue;
}
let cand_args = index.member_lists[cand]
.base_generic_args
.iter()
.find(|(k, _)| k == base_name)
.map(|(_, v)| v);
if !generic_args_unify(cand_args, ref_.args.as_ref()) {
continue;
}
if cand_args
.map(|a| a.iter().any(|x| x == "*"))
.unwrap_or(false)
{
open_generic.push(cand);
} else {
closed_or_plain.push(cand);
}
}
if closed_or_plain.len() == 1 {
return if ref_.args.is_some() {
CtorDiResolution::Closed(closed_or_plain[0])
} else {
CtorDiResolution::Plain(closed_or_plain[0])
};
}
if closed_or_plain.len() > 1 {
return CtorDiResolution::Ambiguous(closed_or_plain);
}
if open_generic.len() == 1 {
return CtorDiResolution::OpenGeneric(open_generic[0]);
}
if open_generic.len() > 1 {
return CtorDiResolution::Ambiguous(open_generic);
}
CtorDiResolution::Unresolved
}
}
}
pub(super) fn type_probe(raw: &str, ns: &str) -> FragRef {
let (name, qualified) = match raw.rfind('.') {
Some(dot) => (raw[dot + 1..].to_string(), Some(raw.to_string())),
None => (raw.to_string(), None),
};
FragRef {
kind: "uses-type".to_string(),
name,
qualified,
member: None,
line: 0,
namespace: Some(ns.to_string()),
type_arg_count: None,
generic: false,
receiver_type: None,
arg_count: None,
receiver_args: None,
outer_types: Vec::new(),
args: None,
receiver_property_owner: None,
receiver_call_owner: None,
receiver_call_member: None,
receiver_base: false,
receiver_awaited: false,
receiver_local: false,
receiver_lambda: None,
}
}
pub(super) fn type_edge(kind: &str, file: &str, line: usize, target: &Def) -> Edge {
let from_file = file.to_string();
let to = target.id.clone();
let to_file = target.file.clone();
match kind {
"inherits" => Edge::Inherits {
from_file,
from_line: line,
to,
to_file,
heuristic: false,
},
_ => Edge::UsesType {
from_file,
from_line: line,
to,
to_file,
heuristic: false,
},
}
}
pub(super) fn heuristic_edge_key(e: &Edge) -> Option<String> {
let (kind, from_file, from_line, to, to_file, tier, member) = match e {
Edge::Inherits {
from_file,
from_line,
to,
to_file,
heuristic: true,
} => ("inherits", from_file, from_line, to, to_file, None, None),
Edge::UsesType {
from_file,
from_line,
to,
to_file,
heuristic: true,
} => ("uses-type", from_file, from_line, to, to_file, None, None),
Edge::UsesMember {
from_file,
from_line,
to,
to_file,
heuristic: true,
tier,
member,
} => (
"uses-member",
from_file,
from_line,
to,
to_file,
*tier,
member.as_deref(),
),
_ => return None,
};
let tier = match tier {
Some(HeuristicTier::Ext) => "ext",
Some(HeuristicTier::Guess) => "guess",
None => "-",
};
let member = member.unwrap_or("-");
Some(format!(
"{kind} {from_file} {from_line} {to} {to_file} {tier} {member}"
))
}