use std::collections::HashMap;
use tree_sitter::Node;
use super::qualifiers::MEMBER_SCOPE_NODES;
use super::receivers::{add_fact, FactTable};
use super::text::{named_children, text};
use super::types::Fact;
type Span = (usize, usize);
const WHOLE_MEMBER: Span = (0, usize::MAX);
fn span(node: Node) -> Option<Span> {
Some((node.start_byte(), node.end_byte()))
}
pub(super) struct MemberTable {
pub(super) facts: FactTable,
constructs: HashMap<String, Vec<Span>>,
misread_loops: Vec<(String, Span)>,
}
impl MemberTable {
pub(super) fn new() -> Self {
MemberTable {
facts: FactTable::new(),
constructs: HashMap::new(),
misread_loops: Vec::new(),
}
}
pub(super) fn declare(&mut self, name: Option<String>, fact: Option<Fact>, decl: Node) {
let Some(name) = name.filter(|n| !n.is_empty()) else {
return;
};
let fact = if self.reserve(&name, decl) {
fact
} else {
None
};
add_fact(&mut self.facts, Some(name), fact);
}
pub(super) fn reserve(&mut self, name: &str, decl: Node) -> bool {
let placed = declaring_construct(decl);
self.constructs
.entry(name.to_string())
.or_default()
.push(placed.unwrap_or(WHOLE_MEMBER));
if placed.is_none() {
add_fact(&mut self.facts, Some(name.to_string()), None);
}
placed.is_some()
}
pub(super) fn note_misread_loop(&mut self, node: Node, src: &[u8]) {
if node.kind() != "foreach_statement" {
return;
}
let name = node
.child_by_field_name("left")
.and_then(|l| misparsed_generic_variable(l, src));
if let (Some(name), Some(loop_span)) = (name, span(node)) {
self.misread_loops.push((name, loop_span));
}
}
pub(super) fn settle_misread_loops(&mut self) {
for (name, loop_span) in std::mem::take(&mut self.misread_loops) {
if !self.constructs.contains_key(&name) {
add_fact(&mut self.facts, Some(name.clone()), None);
}
self.constructs.entry(name).or_default().push(loop_span);
}
}
pub(super) fn settle(&mut self, name: &str, fact: Option<Fact>) {
add_fact(&mut self.facts, Some(name.to_string()), fact);
}
pub(super) fn contains(&self, name: &str) -> bool {
self.facts.contains_key(name)
}
pub(super) fn withholds(&self, name: &str, at: usize) -> bool {
self.facts.contains_key(name)
&& !self
.constructs
.get(name)
.is_some_and(|spans| spans.iter().any(|&(s, e)| s <= at && at < e))
}
pub(super) fn lookup(&self, name: &str, at: usize) -> Option<Option<Fact>> {
let found = self.facts.get(name)?;
if self.withholds(name, at) {
return Some(None);
}
Some(found.clone())
}
}
fn is_member(node: Node) -> bool {
MEMBER_SCOPE_NODES.contains(&node.kind())
}
fn declaring_construct(decl: Node) -> Option<Span> {
match decl.kind() {
"parameter" => {
let owner = decl.parent()?.parent()?;
match owner.kind() {
"lambda_expression"
| "anonymous_method_expression"
| "local_function_statement" => span(owner),
_ if is_member(owner) => Some(WHOLE_MEMBER),
_ => None,
}
}
"implicit_parameter" => decl
.parent()
.filter(|p| p.kind() == "lambda_expression")
.and_then(span),
"variable_declaration" => {
let parent = decl.parent()?;
match parent.kind() {
"for_statement" | "using_statement" | "fixed_statement" => span(parent),
"local_declaration_statement" => statement_block(parent),
_ => None,
}
}
"foreach_statement" => span(decl),
"catch_declaration" => decl
.parent()
.filter(|p| p.kind() == "catch_clause")
.and_then(span),
"from_clause" | "join_clause" | "join_into_clause" | "let_clause" | "query_expression" => {
let mut node = Some(decl);
while let Some(n) = node {
if n.kind() == "query_expression" {
return span(n);
}
node = n.parent();
}
None
}
_ => designation_construct(decl),
}
}
fn statement_block(statement: Node) -> Option<Span> {
let mut node = statement.parent();
while let Some(n) = node {
match n.kind() {
"block" => return span(n),
"switch_section" => return n.parent().and_then(span),
_ if is_member(n) => return None,
_ => node = n.parent(),
}
}
None
}
fn designation_construct(decl: Node) -> Option<Span> {
let mut child = decl;
while let Some(n) = child.parent() {
match n.kind() {
"block" | "switch_section" => return span(n),
"foreach_statement"
if n.child_by_field_name("left")
.is_some_and(|l| l.id() == child.id()) =>
{
return span(n)
}
_ if is_member(n) => return Some(WHOLE_MEMBER),
_ => child = n,
}
}
Some(WHOLE_MEMBER)
}
pub(super) fn untyped_declared_names(node: Node, src: &[u8]) -> Vec<String> {
match node.kind() {
"variable_declaration" => named_children(node)
.into_iter()
.filter(|d| {
d.kind() == "variable_declarator" && d.child_by_field_name("name").is_none()
})
.flat_map(|d| designated_names(d, src))
.collect(),
"foreach_statement" => node
.child_by_field_name("left")
.filter(|l| l.kind() == "tuple_pattern")
.map(|l| designated_names(l, src))
.unwrap_or_default(),
"invocation_expression" => misparsed_var_pattern_names(node, src),
"declaration_pattern" => pattern_designated_names(node, src),
"var_pattern" => designated_names(node, src),
"recursive_pattern" => node
.child_by_field_name("name")
.map(|n| text(n, src))
.into_iter()
.collect(),
_ => query_binding_names(node, src),
}
}
fn designated_names(node: Node, src: &[u8]) -> Vec<String> {
let mut names = Vec::new();
let mut cursor = node.walk();
for named in node.children_by_field_name("name", &mut cursor) {
names.push(text(named, src));
}
for c in named_children(node) {
if matches!(
c.kind(),
"tuple_pattern" | "parenthesized_variable_designation"
) {
names.extend(designated_names(c, src));
}
}
names
}
fn misparsed_generic_variable(left: Node, src: &[u8]) -> Option<String> {
let is_op = |n: Node, op: &str| {
n.kind() == "binary_expression"
&& n.child_by_field_name("operator")
.is_some_and(|o| o.kind() == op)
};
let opened = left.child_by_field_name("left")?;
let name = left.child_by_field_name("right")?;
(is_op(left, ">") && is_op(opened, "<") && name.kind() == "identifier").then(|| text(name, src))
}
fn misparsed_var_pattern_names(node: Node, src: &[u8]) -> Vec<String> {
let is_var_test = node
.child_by_field_name("function")
.filter(|f| f.kind() == "is_expression")
.and_then(|f| f.child_by_field_name("right"))
.is_some_and(|t| t.kind() == "implicit_type");
match node.child_by_field_name("arguments") {
Some(args) if is_var_test => argument_identifiers(args, src),
_ => Vec::new(),
}
}
fn argument_identifiers(list: Node, src: &[u8]) -> Vec<String> {
let mut names = Vec::new();
for arg in named_children(list) {
for c in named_children(arg) {
match c.kind() {
"identifier" if text(c, src) != "_" => names.push(text(c, src)),
"tuple_expression" => names.extend(argument_identifiers(c, src)),
_ => {}
}
}
}
names
}
fn pattern_designated_names(node: Node, src: &[u8]) -> Vec<String> {
named_children(node)
.into_iter()
.filter(|c| c.kind() == "parenthesized_variable_designation")
.flat_map(|c| designated_names(c, src))
.collect()
}
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(),
}
}