use tree_sitter::Node;
use super::scope::RustFile;
pub(super) fn guard_rhs_blocks(fm: &RustFile, rhs: Node) -> bool {
chain_has_guard_method(fm, rhs)
}
fn chain_has_guard_method(fm: &RustFile, n: Node) -> bool {
match n.kind() {
"method_call_expression" => method_call_has_guard(fm, n),
"call_expression" => call_expr_has_guard(fm, n),
"await_expression" | "try_expression" | "parenthesized_expression" => n
.named_child(0)
.is_some_and(|c| chain_has_guard_method(fm, c)),
_ => false,
}
}
fn method_call_has_guard(fm: &RustFile, n: Node) -> bool {
is_guard_producing_method(
n.child_by_field_name("name")
.map(|name| fm.text(name))
.unwrap_or(""),
) || n
.child_by_field_name("receiver")
.is_some_and(|r| chain_has_guard_method(fm, r))
}
fn call_expr_has_guard(fm: &RustFile, n: Node) -> bool {
let Some(func) = n.child_by_field_name("function") else {
return false;
};
if func.kind() == "field_expression" {
if is_guard_producing_method(
func.child_by_field_name("field")
.map(|f| fm.text(f))
.unwrap_or(""),
) {
return true;
}
return func
.child_by_field_name("value")
.is_some_and(|v| chain_has_guard_method(fm, v));
}
chain_has_guard_method(fm, func)
}
fn is_guard_producing_method(name: &str) -> bool {
matches!(
name,
"borrow"
| "borrow_mut"
| "try_borrow"
| "try_borrow_mut"
| "lock"
| "try_lock"
| "read"
| "write"
| "try_read"
| "try_write"
)
}
pub(super) fn lifetime_guard_blocks(fm: &RustFile, rhs: Node, read_byte: usize) -> bool {
if !rhs_is_callish(rhs) {
return false;
}
let Some(ident) = fm
.tree
.root_node()
.descendant_for_byte_range(read_byte, read_byte)
else {
return false;
};
let Some(borrow_call) = borrow_method_on_ident(fm, ident) else {
return false;
};
bound_borrow_result(borrow_call)
}
fn rhs_is_callish(n: Node) -> bool {
match n.kind() {
"call_expression" | "method_call_expression" | "await_expression" | "try_expression" => {
true
}
"parenthesized_expression" => n.named_child(0).is_some_and(rhs_is_callish),
_ => false,
}
}
fn borrow_method_on_ident<'t>(fm: &RustFile, ident: Node<'t>) -> Option<Node<'t>> {
let parent = ident.parent()?;
match parent.kind() {
"method_call_expression" => method_call_borrow(fm, ident, parent),
"field_expression" => field_call_borrow(fm, ident, parent),
_ => None,
}
}
fn method_call_borrow<'t>(fm: &RustFile, ident: Node<'t>, call: Node<'t>) -> Option<Node<'t>> {
let receiver = call.child_by_field_name("receiver")?;
if receiver.id() != ident.id() {
return None;
}
let name = call.child_by_field_name("name")?;
is_borrow_extending_method(fm.text(name)).then_some(call)
}
fn field_call_borrow<'t>(fm: &RustFile, ident: Node<'t>, field_expr: Node<'t>) -> Option<Node<'t>> {
let value = field_expr.child_by_field_name("value")?;
if value.id() != ident.id() {
return None;
}
let field = field_expr.child_by_field_name("field")?;
if !is_borrow_extending_method(fm.text(field)) {
return None;
}
outer_call_or_field(field_expr)
}
fn outer_call_or_field(field_expr: Node) -> Option<Node> {
let Some(call) = field_expr.parent() else {
return Some(field_expr);
};
if call.kind() != "call_expression" {
return Some(field_expr);
}
let func = call.child_by_field_name("function")?;
(func.id() == field_expr.id()).then_some(call)
}
fn is_borrow_extending_method(name: &str) -> bool {
is_guard_producing_method(name)
|| matches!(
name,
"as_str"
| "as_bytes"
| "as_os_str"
| "as_path"
| "as_ref"
| "as_mut"
| "as_deref"
| "as_deref_mut"
| "as_slice"
| "as_mut_slice"
| "to_string_lossy"
| "deref"
| "deref_mut"
)
}
fn bound_borrow_result(mut call: Node) -> bool {
while let Some(parent) = call.parent() {
match parent.kind() {
"method_call_expression" | "field_expression" | "call_expression"
| "try_expression" | "await_expression" | "reference_expression"
| "parenthesized_expression" | "let_condition" | "condition" => {
call = parent;
}
"let_declaration" => {
return parent
.child_by_field_name("value")
.is_some_and(|v| node_contains(v, call));
}
"if_let_expression" | "while_let_expression" | "match_expression" => {
return parent
.child_by_field_name("value")
.or_else(|| parent.child_by_field_name("condition"))
.is_some_and(|v| node_contains(v, call));
}
"else_clause" | "block" | "expression_statement" | "arguments" => return false,
_ => return false,
}
}
false
}
fn node_contains(outer: Node, inner: Node) -> bool {
if outer.id() == inner.id() {
return true;
}
let mut cur = inner.parent();
while let Some(n) = cur {
if n.id() == outer.id() {
return true;
}
cur = n.parent();
}
false
}