1use std::collections::BTreeMap;
16use std::collections::BTreeSet;
17use std::sync::Arc;
18
19use ra_ap_hir::{DisplayTarget, Semantics};
20use ra_ap_ide::RootDatabase;
21use ra_ap_syntax::{AstNode, SyntaxKind, ast};
22
23use super::super::facts::DataFlowFact;
24use super::common::{FnContext, ParsedFile, format_type};
25use super::function_summary::{FunctionAnalysisSummary, FunctionSummaryData, run_detectors};
26use super::targets::{self, SemanticEdge};
27use super::{concurrency, reachability};
28
29pub(super) type FnEntry = (Box<str>, usize, usize, bool);
31
32pub struct SemanticFileAnalysis {
38 semantic_edges: Box<[SemanticEdge]>,
39 fn_entries: Box<[FnEntry]>,
40 reachable_names: BTreeSet<Box<str>>,
41 data_flows: Arc<[DataFlowFact]>,
42 fn_summaries: BTreeMap<Box<str>, FunctionSummaryData>,
43 resolved_types: BTreeMap<(usize, usize), Box<str>>,
45}
46
47impl SemanticFileAnalysis {
48 pub(super) fn build(pf: &ParsedFile<'_>) -> Self {
55 let mut call_graph_edges: Vec<(Box<str>, Box<str>)> = Vec::new();
56 let mut fn_entries: Vec<FnEntry> = Vec::new();
57 let mut fn_summaries: BTreeMap<Box<str>, FunctionSummaryData> = BTreeMap::new();
58 let mut all_flows: Vec<DataFlowFact> = Vec::new();
59 let mut resolved_types: BTreeMap<(usize, usize), Box<str>> = BTreeMap::new();
60
61 let display_target = Some(pf.display_target);
63
64 let mut fn_ranges: Vec<ra_ap_syntax::TextRange> = Vec::new();
66
67 for fn_node in pf.tree.syntax().descendants().filter_map(ast::Fn::cast) {
68 let fn_range = fn_node.syntax().text_range();
69 fn_ranges.push(fn_range);
70
71 let Some(ctx) = FnContext::build(pf, &fn_node) else {
72 continue;
73 };
74
75 ctx.extend_call_graph(&mut call_graph_edges);
77
78 let ranges = run_detectors(&ctx, &mut all_flows);
79
80 if let Some(dt) = display_target {
82 resolve_types_in_subtree(pf, fn_node.syntax(), dt, &mut resolved_types);
83 }
84
85 let (name, start, end, entry, lock_acquisitions) = ctx.into_entry_data();
86 fn_entries.push((Box::from(&*name), start, end, entry));
87 fn_summaries.insert(name, ranges.into_summary(lock_acquisitions));
88 }
89
90 call_graph_edges.sort();
91 call_graph_edges.dedup();
92 let call_graph = call_graph_edges.into_boxed_slice();
93 let fn_entries = fn_entries.into_boxed_slice();
94 let reachable_names = reachability::compute_reachable_names(&fn_entries, &call_graph);
95
96 let lock_ordering = concurrency::detect_lock_ordering(&fn_summaries);
98 all_flows.extend(lock_ordering.into_vec());
99
100 let data_flows: Arc<[DataFlowFact]> = all_flows.into();
101
102 if let Some(dt) = display_target {
104 resolve_module_level_types(pf, dt, &fn_ranges, &mut resolved_types);
105 }
106
107 Self {
108 semantic_edges: targets::collect(pf),
109 fn_entries,
110 reachable_names,
111 data_flows,
112 fn_summaries,
113 resolved_types,
114 }
115 }
116
117 pub(super) fn semantic_edges(&self) -> &[SemanticEdge] {
119 &self.semantic_edges
120 }
121
122 pub fn data_flows(&self) -> &Arc<[DataFlowFact]> {
127 &self.data_flows
128 }
129
130 pub fn function(&self, name: &str) -> Option<FunctionAnalysisSummary<'_>> {
135 self.fn_summaries
136 .get(name)
137 .map(|data| FunctionAnalysisSummary::new(data, &self.data_flows))
138 }
139
140 pub fn resolve_type(&self, line: usize, column: usize) -> Option<&str> {
145 self.resolved_types.get(&(line, column)).map(|s| &**s)
146 }
147
148 pub fn is_line_reachable(&self, line: usize) -> bool {
150 reachability::is_line_in_reachable_fn(&self.fn_entries, &self.reachable_names, line)
151 }
152
153 pub fn check_reachability_batch(&self, lines: &[usize]) -> Box<[bool]> {
155 lines
156 .iter()
157 .map(|&line| self.is_line_reachable(line))
158 .collect::<Vec<_>>()
159 .into_boxed_slice()
160 }
161}
162
163fn resolve_types_in_subtree(
165 pf: &ParsedFile<'_>,
166 root: &ra_ap_syntax::SyntaxNode,
167 display_target: DisplayTarget,
168 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
169) {
170 for node in root.descendants() {
171 resolve_type_bearing_node(pf, &node, display_target, resolved);
172 }
173}
174
175fn resolve_module_level_types(
179 pf: &ParsedFile<'_>,
180 display_target: DisplayTarget,
181 fn_ranges: &[ra_ap_syntax::TextRange],
182 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
183) {
184 for node in pf.tree.syntax().descendants() {
185 let range = node.text_range();
186 if fn_ranges.iter().any(|fr| fr.contains_range(range)) {
187 continue;
188 }
189 resolve_type_bearing_node(pf, &node, display_target, resolved);
190 }
191}
192
193fn resolve_type_bearing_node(
195 pf: &ParsedFile<'_>,
196 node: &ra_ap_syntax::SyntaxNode,
197 display_target: DisplayTarget,
198 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
199) {
200 match node.kind() {
201 SyntaxKind::LET_STMT => {
202 resolve_let_stmt_type(pf, node, display_target, resolved);
203 }
204 SyntaxKind::PATH_TYPE
205 | SyntaxKind::TUPLE_TYPE
206 | SyntaxKind::ARRAY_TYPE
207 | SyntaxKind::SLICE_TYPE
208 | SyntaxKind::REF_TYPE
209 | SyntaxKind::PTR_TYPE => {
210 resolve_type_node(pf, node, display_target, resolved);
211 }
212 _ => {}
213 }
214}
215
216fn resolve_let_stmt_type(
218 pf: &ParsedFile<'_>,
219 node: &ra_ap_syntax::SyntaxNode,
220 display_target: DisplayTarget,
221 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
222) {
223 let Some(let_stmt) = ast::LetStmt::cast(node.clone()) else {
224 return;
225 };
226
227 let init_resolved = let_stmt.initializer().and_then(|init| {
229 let type_str = resolve_expr_type(&pf.sema, &init, pf.db, display_target)?;
230 let lc = pf.line_index.line_col(init.syntax().text_range().start());
231 Some(((lc.line + 1) as usize, lc.col as usize, type_str))
232 });
233 if let Some((line, col, type_str)) = init_resolved {
234 resolved.insert((line, col), type_str);
235 }
236
237 let ann_resolved = let_stmt.ty().and_then(|ty| {
239 let type_str = resolve_ast_type(&pf.sema, &ty, pf.db, display_target)?;
240 let lc = pf.line_index.line_col(ty.syntax().text_range().start());
241 Some(((lc.line + 1) as usize, lc.col as usize, type_str))
242 });
243 if let Some((line, col, type_str)) = ann_resolved {
244 resolved.insert((line, col), type_str);
245 }
246}
247
248fn resolve_type_node(
250 pf: &ParsedFile<'_>,
251 node: &ra_ap_syntax::SyntaxNode,
252 display_target: DisplayTarget,
253 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
254) {
255 let Some(ty) = ast::Type::cast(node.clone()) else {
256 return;
257 };
258 let Some(type_str) = resolve_ast_type(&pf.sema, &ty, pf.db, display_target) else {
259 return;
260 };
261 let range = ty.syntax().text_range();
262 let lc = pf.line_index.line_col(range.start());
263 resolved.insert(((lc.line + 1) as usize, lc.col as usize), type_str);
264}
265
266fn resolve_expr_type(
268 sema: &Semantics<'_, RootDatabase>,
269 expr: &ast::Expr,
270 db: &RootDatabase,
271 display_target: DisplayTarget,
272) -> Option<Box<str>> {
273 let ty_info = sema.type_of_expr(expr)?;
274 Some(format_type(&ty_info.original, db, display_target))
275}
276
277fn resolve_ast_type(
279 sema: &Semantics<'_, RootDatabase>,
280 ty: &ast::Type,
281 db: &RootDatabase,
282 display_target: DisplayTarget,
283) -> Option<Box<str>> {
284 sema.resolve_type(ty)
285 .map(|resolved| format_type(&resolved, db, display_target))
286 .or_else(|| resolve_path_type(sema, ty, db, display_target))
287}
288
289fn resolve_path_type(
291 sema: &Semantics<'_, RootDatabase>,
292 ty: &ast::Type,
293 db: &RootDatabase,
294 display_target: DisplayTarget,
295) -> Option<Box<str>> {
296 let ast::Type::PathType(p) = ty else {
297 return None;
298 };
299 let path = p.path()?;
300 let resolution = sema.resolve_path(&path)?;
301 match resolution {
302 ra_ap_hir::PathResolution::Def(module_def) => {
303 resolve_module_def_type(module_def, db, display_target)
304 }
305 _ => None,
306 }
307}
308
309fn resolve_module_def_type(
311 def: ra_ap_hir::ModuleDef,
312 db: &RootDatabase,
313 display_target: DisplayTarget,
314) -> Option<Box<str>> {
315 match def {
316 ra_ap_hir::ModuleDef::TypeAlias(alias) => {
317 Some(format_type(&alias.ty(db), db, display_target))
318 }
319 ra_ap_hir::ModuleDef::Adt(adt) => Some(format_type(&adt.ty(db), db, display_target)),
320 ra_ap_hir::ModuleDef::BuiltinType(builtin) => Some(builtin.name().as_str().into()),
321 _ => None,
322 }
323}