use super::arity::{base_arity, method_arity_admits, resolve_ref_by_arity};
use super::index::{name_probe, DefIndex};
use super::ladder::Resolution;
use super::lambda_arity::lambda_arity_admits;
use super::receiver::nullable_unwrap_owns_member;
use super::scope::FileContext;
use crate::graph::FragRef;
use std::collections::{HashMap, HashSet};
pub(super) fn declares_member(
index: &DefIndex,
idx: usize,
member: Option<&str>,
arg_count: Option<usize>,
) -> bool {
let Some(member) = member else { return false };
match arg_count {
Some(n) => {
index.defs[idx].methods.iter().any(|m| m == member)
&& method_arity_admits(index, idx, member, n)
}
None => {
index.defs[idx].methods.iter().any(|m| m == member)
|| index.member_lists[idx]
.properties
.iter()
.any(|p| p == member)
|| index.member_lists[idx].fields.iter().any(|f| f == member)
}
}
}
pub(super) fn declares_member_any_visibility(
index: &DefIndex,
idx: usize,
member: Option<&str>,
arg_count: Option<usize>,
) -> bool {
if declares_member(index, idx, member, arg_count) {
return true;
}
let Some(member) = member else { return false };
if !index.member_lists[idx]
.non_public_methods
.iter()
.any(|m| m == member)
{
return false;
}
match arg_count {
Some(n) => method_arity_admits(index, idx, member, n),
None => true,
}
}
pub(super) fn declares_here_for_ref(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
idx: usize,
r: &FragRef,
this_shaped: bool,
) -> bool {
let visible = if this_shaped {
declares_member_any_visibility(index, idx, r.member.as_deref(), r.arg_count)
} else {
declares_member(index, idx, r.member.as_deref(), r.arg_count)
};
visible
&& lambda_arity_admits(
index,
file_contexts,
idx,
r.member.as_deref(),
r.lambda_arg_arity.as_deref(),
)
}
pub(super) fn typed_receiver_precise_target(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
ridx: usize,
r: &FragRef,
this_shaped: bool,
receiver_nullable: bool,
) -> (Option<usize>, bool) {
let nullable_veto = receiver_nullable
&& nullable_unwrap_owns_member(&index.defs[ridx].kind, r.member.as_deref());
let target = if nullable_veto {
None
} else if declares_here_for_ref(index, file_contexts, ridx, r, this_shaped) {
Some(ridx)
} else {
typed_receiver_base_member(index, file_contexts, ridx, r, this_shaped)
};
(target, nullable_veto)
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum MemberShape {
Call,
Read,
}
pub(super) fn member_shape(r: &FragRef) -> MemberShape {
if r.arg_count.is_some() {
MemberShape::Call
} else {
MemberShape::Read
}
}
pub(super) fn member_vouched(
index: &DefIndex,
idx: usize,
member: Option<&str>,
shape: MemberShape,
) -> bool {
let Some(member) = member else { return false };
let instance_vouches = match shape {
MemberShape::Call => index.defs[idx].methods.iter().any(|m| m == member),
MemberShape::Read => declares_member(index, idx, Some(member), None),
};
if instance_vouches {
return true;
}
index.member_lists[idx]
.extension_methods
.iter()
.any(|(name, ..)| name == member)
}
pub(super) fn inheritance_walk_find(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
mut matches: impl FnMut(usize) -> bool,
) -> Option<usize> {
let mut seen: HashSet<usize> = HashSet::from([start]);
let mut stack: Vec<usize> = vec![start];
while let Some(cur) = stack.pop() {
if matches(cur) {
return Some(cur);
}
let Some(ctx) = file_contexts.get(&index.defs[cur].file) else {
continue;
};
let ns = index.defs[cur].namespace.clone();
for base in &index.member_lists[cur].bases {
let probe = name_probe(base.clone(), &ns, Vec::new());
if let Resolution::Resolved(bidx, _) = resolve_ref_by_arity(
probe,
Some(base_arity(index, cur, base)),
&ctx.usings,
&ns,
index,
&ctx.aliases,
file_contexts,
) {
if seen.insert(bidx) {
stack.push(bidx);
}
}
}
}
None
}
pub(super) fn inheritance_walk_matches(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
matches: impl FnMut(usize) -> bool,
) -> bool {
inheritance_walk_find(index, file_contexts, start, matches).is_some()
}
pub(super) fn inherited_member_declared(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
r: &FragRef,
) -> bool {
inheritance_walk_matches(index, file_contexts, start, |idx| {
declares_member(index, idx, r.member.as_deref(), r.arg_count)
&& lambda_arity_admits(
index,
file_contexts,
idx,
r.member.as_deref(),
r.lambda_arg_arity.as_deref(),
)
})
}
fn declares_in_base_closure(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
cur: usize,
skip_interfaces: bool,
seen: &mut HashSet<usize>,
declares: &mut impl FnMut(&DefIndex, usize) -> bool,
) -> Option<usize> {
if declares(index, cur) {
return Some(cur);
}
let ctx = file_contexts.get(&index.defs[cur].file)?;
let ns = index.defs[cur].namespace.clone();
let mut classes: Vec<usize> = Vec::new();
let mut interfaces: Vec<usize> = Vec::new();
for base in &index.member_lists[cur].bases {
let probe = name_probe(base.clone(), &ns, Vec::new());
let Resolution::Resolved(bidx, _) = resolve_ref_by_arity(
probe,
Some(base_arity(index, cur, base)),
&ctx.usings,
&ns,
index,
&ctx.aliases,
file_contexts,
) else {
continue;
};
if !seen.insert(bidx) {
continue;
}
if index.defs[bidx].kind == "interface" {
if skip_interfaces {
continue;
}
interfaces.push(bidx);
} else {
classes.push(bidx);
}
}
for bidx in classes.into_iter().chain(interfaces) {
if let Some(found) =
declares_in_base_closure(index, file_contexts, bidx, skip_interfaces, seen, declares)
{
return Some(found);
}
}
None
}
pub(super) fn first_base_declaring(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
skip_interfaces: bool,
mut declares: impl FnMut(&DefIndex, usize) -> bool,
) -> Option<usize> {
let ctx = file_contexts.get(&index.defs[start].file)?;
let ns = index.defs[start].namespace.clone();
let mut classes: Vec<usize> = Vec::new();
let mut interfaces: Vec<usize> = Vec::new();
for base in &index.member_lists[start].bases {
let probe = name_probe(base.clone(), &ns, Vec::new());
let Resolution::Resolved(bidx, _) = resolve_ref_by_arity(
probe,
Some(base_arity(index, start, base)),
&ctx.usings,
&ns,
index,
&ctx.aliases,
file_contexts,
) else {
continue;
};
if index.defs[bidx].kind == "interface" {
if skip_interfaces {
continue;
}
interfaces.push(bidx);
} else {
classes.push(bidx);
}
}
for bidx in classes.into_iter().chain(interfaces) {
let mut seen: HashSet<usize> = HashSet::from([start, bidx]);
if let Some(found) = declares_in_base_closure(
index,
file_contexts,
bidx,
skip_interfaces,
&mut seen,
&mut declares,
) {
return Some(found);
}
}
None
}
pub(super) fn base_member_declared(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
member: Option<&str>,
arg_count: Option<usize>,
) -> Option<usize> {
first_base_declaring(index, file_contexts, start, true, |index, idx| {
declares_member_any_visibility(index, idx, member, arg_count)
})
}
pub(super) fn typed_receiver_base_member(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
r: &FragRef,
any_visibility: bool,
) -> Option<usize> {
let skip_interfaces = index.defs[start].kind != "interface";
first_base_declaring(
index,
file_contexts,
start,
skip_interfaces,
|index, idx| {
let visible = if any_visibility {
declares_member_any_visibility(index, idx, r.member.as_deref(), r.arg_count)
} else {
declares_member(index, idx, r.member.as_deref(), r.arg_count)
};
visible
&& lambda_arity_admits(
index,
file_contexts,
idx,
r.member.as_deref(),
r.lambda_arg_arity.as_deref(),
)
},
)
}
pub(super) fn extension_closure_key(
index: &DefIndex,
file_contexts: &HashMap<String, FileContext>,
start: usize,
member: &str,
) -> Option<(String, Option<Vec<String>>)> {
let mut found: Option<(String, Option<Vec<String>>)> = None;
inheritance_walk_find(index, file_contexts, start, |idx| {
let own_key = format!("{member} {}", index.defs[idx].name);
if index.extension_index.contains_key(&own_key) {
let type_params = index.member_lists[idx].type_params.len();
let args = if type_params == 0 {
None
} else {
Some(vec!["*".to_string(); type_params])
};
found = Some((own_key, args));
return true;
}
for base in &index.member_lists[idx].bases {
let base_key = format!("{member} {base}");
if index.extension_index.contains_key(&base_key) {
let args = index.member_lists[idx]
.base_generic_args
.iter()
.find(|(k, _)| k == base)
.map(|(_, v)| v.clone());
found = Some((base_key, args));
return true;
}
}
false
});
found
}