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