use tree_sitter::Node;
pub(super) fn is_reference(node: Node<'_>) -> bool {
let Some(parent) = node.parent() else {
return true;
};
match parent.kind() {
"attribute" => parent.child_by_field_name("attribute") != Some(node),
"keyword_argument" => parent.child_by_field_name("name") != Some(node),
_ => true,
}
}
pub(super) fn declared_idents<'tree>(node: Node<'tree>, src: &[u8]) -> Vec<Node<'tree>> {
let mut out = Vec::new();
match node.kind() {
"parameters" | "lambda_parameters" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
param_ident(child, &mut out);
}
}
"function_definition" | "class_definition" => {
if let Some(name) = node.child_by_field_name("name") {
out.push(name);
}
}
"assignment" | "for_statement" | "for_in_clause" | "named_expression" | "as_pattern" => {
let field = match node.kind() {
"named_expression" => "name",
"as_pattern" => "alias",
_ => "left",
};
if let Some(target) = node.child_by_field_name(field) {
target_idents(target, &mut out);
}
}
_ => {}
}
out.retain(|ident| !freed(*ident, src));
out
}
fn freed(ident: Node<'_>, src: &[u8]) -> bool {
let Ok(name) = ident.utf8_text(src) else {
return false;
};
let mut current = ident.parent();
while let Some(parent) = current {
if matches!(parent.kind(), "function_definition" | "lambda") {
return body_declares_freed(parent, name, src);
}
current = parent.parent();
}
false
}
fn body_declares_freed(node: Node<'_>, name: &str, src: &[u8]) -> bool {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"global_statement" | "nonlocal_statement" => {
let mut inner = child.walk();
if child
.children(&mut inner)
.any(|id| id.kind() == "identifier" && id.utf8_text(src) == Ok(name))
{
return true;
}
}
"function_definition" | "lambda" | "class_definition" => {}
_ => {
if body_declares_freed(child, name, src) {
return true;
}
}
}
}
false
}
pub(super) fn escapes_scope(node: Node<'_>) -> bool {
is_definition_name(node) || in_param_default(node) || in_leading_iterable(node)
}
fn is_definition_name(node: Node<'_>) -> bool {
node.parent().is_some_and(|parent| {
matches!(parent.kind(), "function_definition" | "class_definition")
&& parent.child_by_field_name("name") == Some(node)
})
}
pub(super) fn binds_past(node: Node<'_>, scope_kind: &str) -> bool {
matches!(
scope_kind,
"list_comprehension"
| "set_comprehension"
| "dictionary_comprehension"
| "generator_expression"
) && is_walrus_target(node)
}
fn is_walrus_target(node: Node<'_>) -> bool {
node.parent().is_some_and(|parent| {
parent.kind() == "named_expression" && parent.child_by_field_name("name") == Some(node)
})
}
fn in_param_default(node: Node<'_>) -> bool {
let start = node.start_byte();
let mut current = node.parent();
while let Some(parent) = current {
match parent.kind() {
"default_parameter" | "typed_default_parameter" => {
return parent
.child_by_field_name("value")
.is_some_and(|value| value.start_byte() <= start && start < value.end_byte());
}
"function_definition"
| "lambda"
| "list_comprehension"
| "set_comprehension"
| "dictionary_comprehension"
| "generator_expression" => return false,
_ => {}
}
current = parent.parent();
}
false
}
fn in_leading_iterable(node: Node<'_>) -> bool {
let start = node.start_byte();
let mut current = node.parent();
while let Some(parent) = current {
match parent.kind() {
"for_in_clause" => {
let Some(right) = parent.child_by_field_name("right") else {
return false;
};
if !(right.start_byte() <= start && start < right.end_byte()) {
return false;
}
let Some(comprehension) = parent.parent() else {
return false;
};
let mut cursor = comprehension.walk();
return comprehension
.children(&mut cursor)
.find(|child| child.kind() == "for_in_clause")
.is_some_and(|first| first.id() == parent.id());
}
"function_definition"
| "lambda"
| "list_comprehension"
| "set_comprehension"
| "dictionary_comprehension"
| "generator_expression" => return false,
_ => {}
}
current = parent.parent();
}
false
}
fn param_ident<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) {
match node.kind() {
"identifier" => out.push(node),
"default_parameter" | "typed_default_parameter" => {
if let Some(name) = node.child_by_field_name("name") {
target_idents(name, out);
}
}
"typed_parameter" | "list_splat_pattern" | "dictionary_splat_pattern" => {
let mut cursor = node.walk();
if let Some(ident) = node
.named_children(&mut cursor)
.find(|child| child.kind() == "identifier")
{
out.push(ident);
}
}
_ => {}
}
}
fn target_idents<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) {
match node.kind() {
"identifier" => out.push(node),
"pattern_list"
| "tuple_pattern"
| "list_pattern"
| "as_pattern_target"
| "list_splat_pattern"
| "dictionary_splat_pattern" => {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
target_idents(child, out);
}
}
_ => {}
}
}