use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
use super::lambdas::{
lambda_argument_slot, lambda_first_arg_receiver, lambda_receiver_element_fact,
lambda_slot_fact, DeferredLambdaParam,
};
use super::refs::{
base_type_identifier, generic_arg_descriptors, member_qualifier_info, type_parameter_names,
};
use super::text::{declared_name, named_children, text};
use super::types::Fact;
pub(super) type FactTable = HashMap<String, Option<Fact>>;
fn add_fact(table: &mut FactTable, name: Option<String>, fact: Option<Fact>) {
let Some(name) = name.filter(|n| !n.is_empty()) else {
return;
};
match table.get(&name) {
None => {
table.insert(name, fact);
}
Some(existing) => {
if *existing != fact {
table.insert(name, None);
}
}
}
}
pub(super) fn type_fact(
type_node: Option<Node>,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<Fact> {
let type_name = base_type_identifier(type_node, src, false)?;
let args = generic_arg_descriptors(type_node, src, type_params);
let is_array = type_node.is_some_and(|n| n.kind() == "array_type");
Some(Fact {
type_name,
args,
call: None,
awaited: false,
is_array,
lambda: None,
})
}
fn find_child_through_await<'a>(node: Node<'a>, target: &str) -> Option<(Node<'a>, bool)> {
for c in named_children(node) {
if c.kind() == target {
return Some((c, false));
}
if c.kind() == "await_expression" {
if let Some(inner) = named_children(c).into_iter().find(|g| g.kind() == target) {
return Some((inner, true));
}
}
}
None
}
fn new_expression_fact(
declarator: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<Fact> {
let (init, _awaited) = find_child_through_await(declarator, "object_creation_expression")?;
type_fact(init.child_by_field_name("type"), src, type_params)
}
fn cast_expression_fact(
declarator: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<Fact> {
let cast = named_children(declarator)
.into_iter()
.find(|c| c.kind() == "cast_expression")?;
type_fact(cast.child_by_field_name("type"), src, type_params)
}
pub(super) fn collect_type_facts(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> FactTable {
let mut table = FactTable::new();
for kid in named_children(node) {
if kid.kind() != "parameter_list" {
continue;
}
for p in named_children(kid) {
if p.kind() != "parameter" {
continue;
}
add_fact(
&mut table,
p.child_by_field_name("name").map(|n| text(n, src)),
type_fact(p.child_by_field_name("type"), src, type_params),
);
}
}
let Some(body) = node.child_by_field_name("body") else {
return table;
};
collect_declared_member_facts(&named_children(body), src, type_params, &mut table);
table
}
fn collect_declared_member_facts(
members: &[Node],
src: &[u8],
type_params: &HashSet<String>,
table: &mut FactTable,
) {
for c in members {
if c.kind() == "property_declaration" {
let name = declared_name(*c, src);
add_fact(
table,
Some(name),
type_fact(c.child_by_field_name("type"), src, type_params),
);
continue;
}
if c.kind() != "field_declaration" {
continue;
}
let Some(vd) = named_children(*c)
.into_iter()
.find(|k| k.kind() == "variable_declaration")
else {
continue;
};
let fact = type_fact(vd.child_by_field_name("type"), src, type_params);
for decl in named_children(vd) {
if decl.kind() != "variable_declarator" {
continue;
}
add_fact(
table,
decl.child_by_field_name("name").map(|n| text(n, src)),
fact.clone(),
);
}
}
}
pub(super) fn collect_member_facts(
node: Node,
src: &[u8],
class_type_params: &HashSet<String>,
type_facts: &FactTable,
enclosing_type: Option<&str>,
) -> FactTable {
let mut type_params = class_type_params.clone();
type_params.extend(type_parameter_names(node, src));
let mut table = FactTable::new();
let mut deferred: Vec<DeferredCall> = Vec::new();
let mut deferred_foreach: Vec<DeferredForeach> = Vec::new();
let mut deferred_lambda: Vec<DeferredLambdaParam> = Vec::new();
visit_member_facts(
node,
src,
&type_params,
&mut table,
&mut deferred,
&mut deferred_foreach,
&mut deferred_lambda,
);
if !deferred.is_empty() {
let pending: HashSet<&str> = deferred.iter().map(|d| d.name.as_str()).collect();
for d in &deferred {
let owner = if pending.contains(d.qualifier.as_str()) {
None
} else {
qualifier_type_name(&table, type_facts, &d.qualifier)
};
let fact = owner.map(|type_name| Fact {
type_name,
args: None,
call: Some(d.member.clone()),
awaited: d.awaited,
is_array: false,
lambda: None,
});
add_fact(&mut table, Some(d.name.clone()), fact);
}
}
for d in &deferred_foreach {
let fact = collection_element_fact(&table, type_facts, &d.collection);
add_fact(&mut table, Some(d.name.clone()), fact);
}
let pending_lambda: HashSet<&str> = deferred_lambda.iter().map(|d| d.name.as_str()).collect();
for d in &deferred_lambda {
let fact = d
.collection
.as_deref()
.and_then(|c| lambda_receiver_element_fact(&table, type_facts, c))
.or_else(|| {
d.slot.as_ref().and_then(|s| {
lambda_slot_fact(&table, type_facts, enclosing_type, &pending_lambda, s)
})
});
add_fact(&mut table, Some(d.name.clone()), fact);
}
table
}
struct DeferredCall {
name: String,
qualifier: String,
member: String,
awaited: bool,
}
struct DeferredForeach {
name: String,
collection: String,
}
fn query_binding_names(n: Node, src: &[u8]) -> Vec<String> {
match n.kind() {
"from_clause" => n
.child_by_field_name("name")
.map(|x| text(x, src))
.into_iter()
.collect(),
"join_clause" | "let_clause" | "join_into_clause" => {
let type_id = n.child_by_field_name("type").map(|t| t.id());
named_children(n)
.into_iter()
.find(|c| c.kind() == "identifier" && Some(c.id()) != type_id)
.map(|c| text(c, src))
.into_iter()
.collect()
}
"query_expression" => named_children(n)
.into_iter()
.filter(|c| c.kind() == "identifier")
.map(|c| text(c, src))
.collect(),
_ => Vec::new(),
}
}
#[allow(
clippy::too_many_lines,
reason = "one dispatch over every member-fact shape a node can carry; each shape's deferral rule only makes sense read against the others"
)]
fn visit_member_facts(
n: Node,
src: &[u8],
type_params: &HashSet<String>,
table: &mut FactTable,
deferred: &mut Vec<DeferredCall>,
deferred_foreach: &mut Vec<DeferredForeach>,
deferred_lambda: &mut Vec<DeferredLambdaParam>,
) {
if n.kind() == "parameter" {
let name = n.child_by_field_name("name").map(|x| text(x, src));
let receiver = lambda_first_arg_receiver(n);
let slot = lambda_argument_slot(n, src);
if receiver.is_some() || slot.is_some() {
if let Some(name) = name.filter(|nm| !nm.is_empty()) {
deferred_lambda.push(DeferredLambdaParam {
name,
collection: receiver.map(|r| text(r, src)),
slot,
});
}
} else {
add_fact(
table,
name,
type_fact(n.child_by_field_name("type"), src, type_params),
);
}
} else if n.kind() == "implicit_parameter" {
let name = text(n, src);
let receiver = lambda_first_arg_receiver(n);
let slot = lambda_argument_slot(n, src);
if receiver.is_some() || slot.is_some() {
if !name.is_empty() {
deferred_lambda.push(DeferredLambdaParam {
name,
collection: receiver.map(|r| text(r, src)),
slot,
});
}
} else {
add_fact(table, Some(name), None);
}
} else if n.kind() == "variable_declaration" {
let type_node = n.child_by_field_name("type");
let is_var = type_node.map(|t| t.kind()) == Some("implicit_type");
let declared = if is_var {
None
} else {
type_fact(type_node, src, type_params)
};
for decl in named_children(n) {
if decl.kind() != "variable_declarator" {
continue;
}
let name = decl.child_by_field_name("name").map(|x| text(x, src));
let fact = if is_var {
new_expression_fact(decl, src, type_params)
.or_else(|| cast_expression_fact(decl, src, type_params))
} else {
declared.clone()
};
let call = match (is_var && fact.is_none(), name.as_deref()) {
(true, Some(n)) if !n.is_empty() => invocation_call(decl, src),
_ => None,
};
match call {
Some((qualifier, member, awaited)) => deferred.push(DeferredCall {
name: name.unwrap_or_default(),
qualifier,
member,
awaited,
}),
None => add_fact(table, name, fact),
}
}
} else if n.kind() == "foreach_statement" {
let left = n.child_by_field_name("left");
let name = left
.filter(|l| l.kind() == "identifier")
.map(|l| text(l, src));
if let Some(name) = name {
let type_node = n.child_by_field_name("type");
let is_var = type_node
.map(|t| t.kind() == "implicit_type")
.unwrap_or(true);
if !is_var {
add_fact(table, Some(name), type_fact(type_node, src, type_params));
} else {
let right = n.child_by_field_name("right");
match right.filter(|r| r.kind() == "identifier") {
Some(r) => deferred_foreach.push(DeferredForeach {
name,
collection: text(r, src),
}),
None => add_fact(table, Some(name), None),
}
}
}
} else if matches!(
n.kind(),
"declaration_pattern" | "declaration_expression" | "catch_declaration"
) {
add_fact(
table,
n.child_by_field_name("name").map(|x| text(x, src)),
type_fact(n.child_by_field_name("type"), src, type_params),
);
} else if matches!(
n.kind(),
"from_clause" | "join_clause" | "join_into_clause" | "let_clause" | "query_expression"
) {
for name in query_binding_names(n, src) {
add_fact(table, Some(name), None);
}
}
for c in named_children(n) {
visit_member_facts(
c,
src,
type_params,
table,
deferred,
deferred_foreach,
deferred_lambda,
);
}
}
fn invocation_call(declarator: Node, src: &[u8]) -> Option<(String, String, bool)> {
let (init, awaited) = find_child_through_await(declarator, "invocation_expression")?;
let function = init.child_by_field_name("function")?;
if function.kind() != "member_access_expression" {
return None;
}
let (qualifier, generic) =
member_qualifier_info(function.child_by_field_name("expression"), src, &[])?;
if generic || qualifier.contains('.') {
return None;
}
let name_node = function.child_by_field_name("name")?;
let 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 member.is_empty() {
return None;
}
Some((qualifier, member, awaited))
}
pub(super) fn qualifier_type_name(
locals: &FactTable,
type_facts: &FactTable,
name: &str,
) -> Option<String> {
match locals.get(name).or_else(|| type_facts.get(name)) {
None => Some(name.to_string()),
Some(Some(fact)) if fact.call.is_none() && fact.lambda.is_none() => {
Some(fact.type_name.clone())
}
Some(_) => None,
}
}
fn collection_element_fact(locals: &FactTable, type_facts: &FactTable, name: &str) -> Option<Fact> {
match locals.get(name).or_else(|| type_facts.get(name)) {
Some(Some(fact)) => match fact.args.as_deref() {
Some([arg]) if arg != "*" => Some(Fact {
type_name: arg.clone(),
args: None,
call: None,
awaited: false,
is_array: false,
lambda: None,
}),
_ => None,
},
_ => None,
}
}