use super::analysis::CapabilitySink;
use super::analysis::{
build_binding_method_calls, build_binding_method_in_tail, build_binding_refs_in_expr,
build_binding_stmt_refs, build_body_derived_state, build_lock_acquisitions, build_match_exprs,
};
use super::file::{ParsedFile, span_from_node};
use super::prelude::*;
pub(super) type BindingMethodIndex = BTreeMap<Box<str>, Box<[(usize, Box<str>)]>>;
pub(in crate::ir::semantic) struct CallSite {
pub(in crate::ir::semantic) callee: Box<str>,
pub(in crate::ir::semantic) args_hash: u64,
pub(in crate::ir::semantic) span: IrSpan,
}
pub(in crate::ir::semantic) struct LockAcquisition {
pub(in crate::ir::semantic) guard_name: Box<str>,
pub(in crate::ir::semantic) receiver_name: Box<str>,
pub(in crate::ir::semantic) span: IrSpan,
}
pub(in crate::ir::semantic) struct FnContext<'a, 'db> {
pub(in crate::ir::semantic) stmts: Box<[ast::Stmt]>,
pub(in crate::ir::semantic) tail_expr: Option<ast::Expr>,
pub(in crate::ir::semantic) is_async: bool,
pub(in crate::ir::semantic) sema: &'a Semantics<'db, RootDatabase>,
pub(in crate::ir::semantic) db: &'db RootDatabase,
pub(in crate::ir::semantic) line_index: &'db LineIndex,
pub(in crate::ir::semantic) binding_stmt_refs: BTreeMap<Box<str>, Box<[usize]>>,
pub(in crate::ir::semantic) binding_in_tail: BTreeSet<Box<str>>,
pub(in crate::ir::semantic) mutated_bindings: BTreeSet<Box<str>>,
pub(in crate::ir::semantic) returned_bindings: BTreeSet<Box<str>>,
pub(in crate::ir::semantic) mut_ref_bindings: BTreeSet<Box<str>>,
pub(in crate::ir::semantic) call_sites: Box<[CallSite]>,
pub(in crate::ir::semantic) alloc_in_loop_spans: Box<[IrSpan]>,
pub(in crate::ir::semantic) capability_sinks: Box<[CapabilitySink]>,
pub(in crate::ir::semantic) match_exprs: Box<[ast::MatchExpr]>,
pub(in crate::ir::semantic) binding_method_calls: BindingMethodIndex,
pub(in crate::ir::semantic) binding_method_in_tail: BTreeMap<Box<str>, Box<str>>,
pub(in crate::ir::semantic) lock_acquisitions: Box<[LockAcquisition]>,
pub(in crate::ir::semantic) fn_name: Box<str>,
pub(in crate::ir::semantic) start_line: usize,
pub(in crate::ir::semantic) end_line: usize,
pub(in crate::ir::semantic) is_entry_point: bool,
}
impl<'a, 'db> FnContext<'a, 'db> {
pub(in crate::ir::semantic) fn build(
pf: &'a ParsedFile<'db>,
fn_node: &ast::Fn,
) -> Option<Self> {
let name_node = fn_node.name()?;
let body = fn_node.body()?;
let stmt_list = body.stmt_list()?;
let stmts: Box<[ast::Stmt]> = stmt_list
.statements()
.collect::<Vec<_>>()
.into_boxed_slice();
let tail_expr = stmt_list.tail_expr();
let is_async = fn_node.async_token().is_some();
let binding_stmt_refs = build_binding_stmt_refs(&stmts);
let binding_in_tail = build_binding_refs_in_expr(tail_expr.as_ref());
let derived = build_body_derived_state(pf, &body, tail_expr.as_ref());
let lock_acquisitions = build_lock_acquisitions(pf.line_index, &stmts);
let match_exprs = build_match_exprs(&stmt_list);
let binding_method_calls = build_binding_method_calls(&stmts);
let binding_method_in_tail = build_binding_method_in_tail(tail_expr.as_ref());
let fn_name = Box::from(name_node.text().as_str());
let range = fn_node.syntax().text_range();
let start_lc = pf.line_index.line_col(range.start());
let end_lc = pf.line_index.line_col(range.end());
let is_pub = fn_node.visibility().is_some();
let is_main = &*fn_name == "main";
let is_test = fn_node.attrs().any(|attr: ast::Attr| {
attr.path()
.map(|p: ast::Path| p.syntax().text() == "test")
.unwrap_or(false)
});
Some(Self {
stmts,
tail_expr,
is_async,
sema: &pf.sema,
db: pf.db,
line_index: pf.line_index,
binding_stmt_refs,
binding_in_tail,
mutated_bindings: derived.mutated_bindings,
returned_bindings: derived.returned_bindings,
mut_ref_bindings: derived.mut_ref_bindings,
call_sites: derived.call_sites,
alloc_in_loop_spans: derived.alloc_in_loop_spans,
capability_sinks: derived.capability_sinks,
match_exprs,
binding_method_calls,
binding_method_in_tail,
lock_acquisitions,
fn_name,
start_line: (start_lc.line + 1) as usize,
end_line: (end_lc.line + 1) as usize,
is_entry_point: is_pub || is_main || is_test,
})
}
pub(in crate::ir::semantic) fn binding_used_after(
&self,
name: &str,
after_index: usize,
) -> bool {
self.binding_stmt_refs
.get(name)
.is_some_and(|indices| indices.partition_point(|&i| i <= after_index) < indices.len())
}
pub(in crate::ir::semantic) fn binding_used_in_tail(&self, name: &str) -> bool {
self.binding_in_tail.contains(name)
}
pub(in crate::ir::semantic) fn binding_is_mutated(&self, name: &str) -> bool {
self.mutated_bindings.contains(name)
}
pub(in crate::ir::semantic) fn binding_is_returned(&self, name: &str) -> bool {
self.returned_bindings.contains(name)
}
pub(in crate::ir::semantic) fn binding_passed_as_mut_ref(&self, name: &str) -> bool {
self.mut_ref_bindings.contains(name)
}
pub(in crate::ir::semantic) fn next_use_is_iter(&self, name: &str, after_index: usize) -> bool {
let in_stmts = self.binding_method_calls.get(name).is_some_and(|calls| {
calls
.iter()
.filter(|(idx, _)| *idx > after_index)
.any(|(_, method)| &**method == "iter" || &**method == "into_iter")
});
let in_tail = self
.binding_method_in_tail
.get(name)
.is_some_and(|m| &**m == "iter" || &**m == "into_iter");
in_stmts || in_tail
}
pub(in crate::ir::semantic) fn span(&self, node: &SyntaxNode) -> IrSpan {
span_from_node(node, self.line_index)
}
pub(in crate::ir::semantic) fn extend_call_graph(&self, edges: &mut Vec<(Box<str>, Box<str>)>) {
let caller: &str = &self.fn_name;
edges.extend(
self.call_sites
.iter()
.map(|cs| (Box::from(caller), cs.callee.clone())),
);
}
pub(in crate::ir::semantic) fn into_entry_data(
self,
) -> (Box<str>, usize, usize, bool, Box<[LockAcquisition]>) {
(
self.fn_name,
self.start_line,
self.end_line,
self.is_entry_point,
self.lock_acquisitions,
)
}
}