use std::cell::OnceCell;
use std::collections::HashSet;
use std::rc::Rc;
use tree_sitter::Node;
use super::receivers::{collect_member_facts, collect_type_facts, FactTable};
use super::refs::{member_qualifier_info, type_parameter_names};
use super::text::{declared_name, named_children, text};
use super::types::Fact;
pub(super) const MEMBER_SCOPE_NODES: &[&str] = &[
"method_declaration",
"constructor_declaration",
"destructor_declaration",
"operator_declaration",
"conversion_operator_declaration",
"property_declaration",
"indexer_declaration",
];
pub(super) struct Scope<'a> {
type_facts: Rc<FactTable>,
pub(super) type_params: Rc<HashSet<String>>,
type_name: Option<String>,
node: Option<Node<'a>>,
member_facts: OnceCell<Option<FactTable>>,
}
impl<'a> Scope<'a> {
pub(super) fn root() -> Self {
Scope {
type_facts: Rc::new(FactTable::new()),
type_params: Rc::new(HashSet::new()),
type_name: None,
node: None,
member_facts: OnceCell::new(),
}
}
pub(super) fn for_type(node: Node<'a>, src: &[u8]) -> Self {
let type_params = type_parameter_names(node, src);
let name = declared_name(node, src);
Scope {
type_facts: Rc::new(collect_type_facts(node, src, &type_params)),
type_params: Rc::new(type_params),
type_name: if name.is_empty() { None } else { Some(name) },
node: None,
member_facts: OnceCell::new(),
}
}
pub(super) fn for_member(&self, node: Node<'a>) -> Self {
Scope {
type_facts: Rc::clone(&self.type_facts),
type_params: Rc::clone(&self.type_params),
type_name: self.type_name.clone(),
node: Some(node),
member_facts: OnceCell::new(),
}
}
fn receiver_fact_for(&self, name: &str, src: &[u8]) -> Option<Fact> {
let locals = self.member_facts.get_or_init(|| {
self.node.map(|n| {
collect_member_facts(
n,
src,
&self.type_params,
&self.type_facts,
self.type_name.as_deref(),
)
})
});
if let Some(table) = locals {
if let Some(found) = table.get(name) {
return found.clone();
}
}
self.type_facts.get(name).cloned().flatten()
}
fn has_local_fact(&self, name: &str, src: &[u8]) -> bool {
let locals = self.member_facts.get_or_init(|| {
self.node.map(|n| {
collect_member_facts(
n,
src,
&self.type_params,
&self.type_facts,
self.type_name.as_deref(),
)
})
});
locals
.as_ref()
.is_some_and(|table| table.contains_key(name))
}
fn name_is_claimed(&self, name: &str, src: &[u8]) -> bool {
self.has_local_fact(name, src) || self.type_facts.contains_key(name)
}
}
pub(super) fn member_name_text(node: Option<Node>, src: &[u8]) -> Option<String> {
node.map(|n| {
if n.kind() == "generic_name" {
named_children(n)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| text(id, src))
.unwrap_or_default()
} else {
text(n, src)
}
})
}
pub(super) struct QualifierResolution {
pub(super) text: String,
pub(super) generic: bool,
pub(super) receiver: Option<Fact>,
pub(super) property_owner: Option<String>,
pub(super) receiver_base: bool,
pub(super) receiver_local: bool,
}
pub(super) fn resolve_member_qualifier(
qualifier: Option<Node>,
src: &[u8],
type_stack: &[String],
scope: &Scope,
) -> Option<QualifierResolution> {
let kind = qualifier?.kind();
let (qt, generic) = member_qualifier_info(qualifier, src, type_stack)?;
if kind == "this" || kind == "base" {
let receiver_args = if scope.type_params.is_empty() {
None
} else {
Some(vec!["*".to_string(); scope.type_params.len()])
};
return Some(QualifierResolution {
receiver: Some(Fact {
type_name: qt.clone(),
args: receiver_args,
call: None,
awaited: false,
is_array: false,
lambda: None,
}),
text: qt,
generic,
property_owner: None,
receiver_base: kind == "base",
receiver_local: false,
});
}
let dot_at = if generic { None } else { qt.find('.') };
let bare = dot_at.is_none() && !generic;
let receiver = if bare {
scope.receiver_fact_for(&qt, src)
} else {
None
};
let receiver_local = bare && scope.has_local_fact(&qt, src);
let property_owner = dot_at
.filter(|d| !qt[d + 1..].contains('.'))
.and_then(|d| scope.receiver_fact_for(&qt[..d], src))
.filter(|fact| fact.call.is_none() && fact.lambda.is_none())
.map(|fact| fact.type_name);
Some(QualifierResolution {
text: qt,
generic,
receiver,
property_owner,
receiver_base: false,
receiver_local,
})
}
fn chain_tail_receiver_type(a_text: &str, scope: &Scope, src: &[u8]) -> Option<String> {
match scope.receiver_fact_for(a_text, src) {
Some(Fact {
type_name,
call: None,
lambda: None,
..
}) => Some(type_name),
Some(_) => None,
None if scope.name_is_claimed(a_text, src) => None,
None => Some(a_text.to_string()),
}
}
pub(super) fn resolve_call_chain_tail(
qualifier: Node,
src: &[u8],
type_stack: &[String],
scope: &Scope,
) -> Option<(String, String, bool)> {
if qualifier.kind() != "invocation_expression" {
return None;
}
let function = qualifier.child_by_field_name("function")?;
if function.kind() != "member_access_expression" {
return None;
}
let head = function.child_by_field_name("expression");
let head_is_base = head.is_some_and(|h| h.kind() == "base");
let (a_text, a_generic) = member_qualifier_info(head, src, type_stack)?;
if a_generic || a_text.contains('.') {
return None;
}
let name_node = function.child_by_field_name("name")?;
let inner_member = if name_node.kind() == "generic_name" {
named_children(name_node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| text(id, src))?
} else {
text(name_node, src)
};
if inner_member.is_empty() {
return None;
}
let owner = chain_tail_receiver_type(&a_text, scope, src)?;
Some((owner, inner_member, head_is_base))
}