cairo_language_common/
db.rs1use std::collections::VecDeque;
2use std::sync::Arc;
3
4use crate::syntax_ext::SyntaxNodeExt;
5use cairo_lang_defs::db::DefsGroup;
6use cairo_lang_defs::ids::ModuleId;
7use cairo_lang_diagnostics::ToOption;
8use cairo_lang_filesystem::db::{ext_as_virtual, get_parent_and_mapping, translate_location};
9use cairo_lang_filesystem::ids::{CodeOrigin, FileId, FileLongId};
10use cairo_lang_filesystem::span::TextOffset;
11use cairo_lang_parser::db::ParserGroup;
12use cairo_lang_semantic::lsp_helpers::LspHelpers;
13use cairo_lang_syntax::node::SyntaxNode;
14use cairo_lang_syntax::node::kind::SyntaxKind;
15use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
16use salsa::Database;
17
18pub trait CommonGroup: Database {
19 fn find_syntax_node_at_offset<'db>(
21 &'db self,
22 file: FileId<'db>,
23 offset: TextOffset,
24 ) -> Option<SyntaxNode<'db>> {
25 find_syntax_node_at_offset(self.as_dyn_database(), file, offset)
26 }
27
28 fn file_and_subfiles_with_corresponding_modules_without_inline<'db>(
30 &'db self,
31 file: FileId<'db>,
32 ) -> Option<&'db (OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
33 file_and_subfiles_with_corresponding_modules_without_inline(self.as_dyn_database(), file)
34 .as_ref()
35 }
36
37 fn file_and_subfiles_with_corresponding_modules<'db>(
39 &'db self,
40 file: FileId<'db>,
41 ) -> Option<&'db (OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
42 file_and_subfiles_with_corresponding_modules(self.as_dyn_database(), file).as_ref()
43 }
44
45 fn get_node_resultants<'db>(
104 &'db self,
105 node: SyntaxNode<'db>,
106 ) -> Option<&'db Vec<SyntaxNode<'db>>> {
107 get_node_resultants(self.as_dyn_database(), (), node).as_ref()
108 }
109
110 fn find_generated_nodes<'db>(
111 &'db self,
112 node_descendant_files: Arc<[FileId<'db>]>,
113 node: SyntaxNode<'db>,
114 ) -> &'db OrderedHashSet<SyntaxNode<'db>> {
115 find_generated_nodes(
116 self.as_dyn_database(),
117 node_descendant_files.iter().cloned().collect(),
118 node,
119 )
120 }
121}
122
123impl<T: Database + ?Sized> CommonGroup for T {}
124
125#[salsa::tracked]
127fn find_syntax_node_at_offset<'db>(
128 db: &'db dyn Database,
129 file: FileId<'db>,
130 offset: TextOffset,
131) -> Option<SyntaxNode<'db>> {
132 Some(db.file_syntax(file).to_option()?.lookup_offset(db, offset))
133}
134
135#[salsa::tracked(returns(ref))]
136fn file_and_subfiles_with_corresponding_modules_without_inline<'db>(
137 db: &'db dyn Database,
138 file: FileId<'db>,
139) -> Option<(OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
140 let file_modules = db.file_modules(file).ok()?;
142
143 let mut modules: OrderedHashSet<_> = file_modules.iter().copied().collect();
144 let mut files = OrderedHashSet::from_iter([file]);
145 let mut modules_queue: VecDeque<_> = modules.iter().copied().collect();
161 while let Some(module_id) = modules_queue.pop_front() {
162 let Ok(module_files) = db.module_files(module_id) else {
163 continue;
164 };
165
166 for &file_id in module_files {
167 if files.insert(file_id)
168 && let Ok(file_modules) = db.file_modules(file_id)
169 {
170 for module_id in file_modules {
171 if modules.insert(*module_id) {
172 modules_queue.push_back(*module_id);
173 }
174 }
175 }
176 }
177 }
178 Some((files, modules))
179}
180
181#[salsa::tracked(returns(ref))]
182fn file_and_subfiles_with_corresponding_modules<'db>(
183 db: &'db dyn Database,
184 file: FileId<'db>,
185) -> Option<(OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
186 let (mut files, modules) = db
187 .file_and_subfiles_with_corresponding_modules_without_inline(file)?
188 .clone();
189
190 for &module_id in modules.iter() {
191 let inline_macro_files = db.inline_macro_expansion_files(module_id);
192
193 let mut path_buffer = vec![];
194
195 for &inline_macro_file in inline_macro_files {
196 let mut inline_macro_file = inline_macro_file;
197 while let Some((parent, _)) = get_parent_and_mapping(db, inline_macro_file) {
198 path_buffer.push(inline_macro_file);
199
200 if files.contains(&parent.file_id) {
201 files.extend(path_buffer.iter().copied());
202 break;
203 }
204
205 inline_macro_file = parent.file_id;
206 }
207 path_buffer.clear();
208 }
209 }
210 Some((files, modules))
211}
212
213#[tracing::instrument(skip_all)]
214#[salsa::tracked(returns(ref))]
215fn get_node_resultants<'db>(
216 db: &'db dyn Database,
217 _: (),
218 node: SyntaxNode<'db>,
219) -> Option<Vec<SyntaxNode<'db>>> {
220 let main_file = node.stable_ptr(db).file_id(db);
221
222 let (files, _) = db.file_and_subfiles_with_corresponding_modules(main_file)?;
223
224 let files: Arc<[FileId]> = files
225 .iter()
226 .filter(|file| **file != main_file)
227 .copied()
228 .collect();
229 let resultants = db.find_generated_nodes(files, node);
230
231 Some(resultants.into_iter().copied().collect())
232}
233
234#[tracing::instrument(skip_all)]
235#[salsa::tracked(returns(ref))]
236fn find_generated_nodes<'db>(
238 db: &'db dyn Database,
239 node_descendant_files: Vec<FileId<'db>>,
240 node: SyntaxNode<'db>,
241) -> OrderedHashSet<SyntaxNode<'db>> {
242 let start_file = node.stable_ptr(db).file_id(db);
243
244 let mut result = OrderedHashSet::default();
245
246 let mut is_replaced = false;
247
248 for file in node_descendant_files.iter().cloned() {
249 let Some((parent, mappings)) = get_parent_and_mapping(db, file) else {
250 continue;
251 };
252
253 if parent.file_id != start_file {
254 continue;
255 }
256
257 let Ok(file_syntax) = db.file_syntax(file) else {
258 continue;
259 };
260
261 let mappings: Vec<_> = mappings
262 .iter()
263 .filter(|mapping| match mapping.origin {
264 CodeOrigin::CallSite(_) => false,
267 CodeOrigin::Start(start) => start == node.span(db).start,
268 CodeOrigin::Span(span) => node.span(db).contains(span),
269 })
270 .cloned()
271 .collect();
272 if mappings.is_empty() {
273 continue;
274 }
275
276 let is_replacing_og_item = match file.long(db) {
277 FileLongId::Virtual(vfs) => vfs.original_item_removed,
278 FileLongId::External(id) => ext_as_virtual(db, *id).original_item_removed,
279 _ => unreachable!(),
280 };
281
282 let mut new_nodes: OrderedHashSet<_> = Default::default();
283
284 for mapping in &mappings {
285 let node_by_offset = file_syntax.lookup_offset(db, mapping.span.start);
286 let node_parent = node_by_offset.parent(db);
287 node_parent
288 .unwrap_or(node_by_offset)
289 .for_each_terminal(db, |terminal| {
290 if terminal.kind(db) == SyntaxKind::TerminalEndOfFile {
295 return;
296 }
297 let nodes: Vec<_> = terminal
298 .ancestors_with_self(db)
299 .map_while(|new_node| {
300 translate_location(&mappings, new_node.span_without_trivia(db))
301 .map(|span_in_parent| (new_node, span_in_parent))
302 })
303 .take_while(|(_, span_in_parent)| {
304 node.span_without_trivia(db).contains(*span_in_parent)
305 })
306 .collect();
307
308 if let Some((last_node, _)) = nodes.last().cloned() {
309 let (new_node, _) = nodes
310 .into_iter()
311 .rev()
312 .take_while(|(node, _)| node.span(db) == last_node.span(db))
313 .last()
314 .unwrap();
315
316 new_nodes.insert(new_node);
317 }
318 });
319 }
320
321 if !new_nodes.is_empty() {
323 is_replaced = is_replaced || is_replacing_og_item;
324 }
325
326 for new_node in new_nodes {
327 result.extend(
328 find_generated_nodes(db, node_descendant_files.clone(), new_node)
329 .into_iter()
330 .copied(),
331 );
332 }
333 }
334
335 if !is_replaced {
336 result.insert(node);
337 }
338
339 result
340}