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(self.as_dyn_database(), node_descendant_files, node)
116 }
117}
118
119impl<T: Database + ?Sized> CommonGroup for T {}
120
121#[salsa::tracked]
123fn find_syntax_node_at_offset<'db>(
124 db: &'db dyn Database,
125 file: FileId<'db>,
126 offset: TextOffset,
127) -> Option<SyntaxNode<'db>> {
128 Some(db.file_syntax(file).to_option()?.lookup_offset(db, offset))
129}
130
131#[salsa::tracked(returns(ref))]
132fn file_and_subfiles_with_corresponding_modules_without_inline<'db>(
133 db: &'db dyn Database,
134 file: FileId<'db>,
135) -> Option<(OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
136 let file_modules = db.file_modules(file).ok()?;
138
139 let mut modules: OrderedHashSet<_> = file_modules.iter().copied().collect();
140 let mut files = OrderedHashSet::from_iter([file]);
141 let mut modules_queue: VecDeque<_> = modules.iter().copied().collect();
157 while let Some(module_id) = modules_queue.pop_front() {
158 let Ok(module_files) = db.module_files(module_id) else {
159 continue;
160 };
161
162 for &file_id in module_files {
163 if files.insert(file_id)
164 && let Ok(file_modules) = db.file_modules(file_id)
165 {
166 for module_id in file_modules {
167 if modules.insert(*module_id) {
168 modules_queue.push_back(*module_id);
169 }
170 }
171 }
172 }
173 }
174 Some((files, modules))
175}
176
177#[salsa::tracked(returns(ref))]
178fn file_and_subfiles_with_corresponding_modules<'db>(
179 db: &'db dyn Database,
180 file: FileId<'db>,
181) -> Option<(OrderedHashSet<FileId<'db>>, OrderedHashSet<ModuleId<'db>>)> {
182 let (mut files, modules) = db
183 .file_and_subfiles_with_corresponding_modules_without_inline(file)?
184 .clone();
185
186 for &module_id in modules.iter() {
187 let inline_macro_files = db.inline_macro_expansion_files(module_id);
188
189 let mut path_buffer = vec![];
190
191 for &inline_macro_file in inline_macro_files {
192 let mut inline_macro_file = inline_macro_file;
193 while let Some((parent, _)) = get_parent_and_mapping(db, inline_macro_file) {
194 path_buffer.push(inline_macro_file);
195
196 if files.contains(&parent.file_id) {
197 files.extend(path_buffer.iter().copied());
198 break;
199 }
200
201 inline_macro_file = parent.file_id;
202 }
203 path_buffer.clear();
204 }
205 }
206 Some((files, modules))
207}
208
209#[tracing::instrument(skip_all)]
210#[salsa::tracked(returns(ref))]
211fn get_node_resultants<'db>(
212 db: &'db dyn Database,
213 _: (),
214 node: SyntaxNode<'db>,
215) -> Option<Vec<SyntaxNode<'db>>> {
216 let main_file = node.stable_ptr(db).file_id(db);
217
218 let (files, _) = db.file_and_subfiles_with_corresponding_modules(main_file)?;
219
220 let files: Arc<[FileId]> = files
221 .iter()
222 .filter(|file| **file != main_file)
223 .copied()
224 .collect();
225 let resultants = db.find_generated_nodes(files, node);
226
227 Some(resultants.into_iter().copied().collect())
228}
229
230#[tracing::instrument(skip_all)]
231#[salsa::tracked(returns(ref))]
232fn find_generated_nodes<'db>(
234 db: &'db dyn Database,
235 node_descendant_files: Arc<[FileId<'db>]>,
236 node: SyntaxNode<'db>,
237) -> OrderedHashSet<SyntaxNode<'db>> {
238 let start_file = node.stable_ptr(db).file_id(db);
239
240 let mut result = OrderedHashSet::default();
241
242 let mut is_replaced = false;
243
244 for file in node_descendant_files.iter().cloned() {
245 let Some((parent, mappings)) = get_parent_and_mapping(db, file) else {
246 continue;
247 };
248
249 if parent.file_id != start_file {
250 continue;
251 }
252
253 let Ok(file_syntax) = db.file_syntax(file) else {
254 continue;
255 };
256
257 let mappings: Vec<_> = mappings
258 .iter()
259 .filter(|mapping| match mapping.origin {
260 CodeOrigin::CallSite(_) => false,
263 CodeOrigin::Start(start) => start == node.span(db).start,
264 CodeOrigin::Span(span) => node.span(db).contains(span),
265 })
266 .cloned()
267 .collect();
268 if mappings.is_empty() {
269 continue;
270 }
271
272 let is_replacing_og_item = match file.long(db) {
273 FileLongId::Virtual(vfs) => vfs.original_item_removed,
274 FileLongId::External(id) => ext_as_virtual(db, *id).original_item_removed,
275 _ => unreachable!(),
276 };
277
278 let mut new_nodes: OrderedHashSet<_> = Default::default();
279
280 for mapping in &mappings {
281 let node_by_offset = file_syntax.lookup_offset(db, mapping.span.start);
282 let node_parent = node_by_offset.parent(db);
283 node_parent
284 .unwrap_or(node_by_offset)
285 .for_each_terminal(db, |terminal| {
286 if terminal.kind(db) == SyntaxKind::TerminalEndOfFile {
291 return;
292 }
293 let nodes: Vec<_> = terminal
294 .ancestors_with_self(db)
295 .map_while(|new_node| {
296 translate_location(&mappings, new_node.span_without_trivia(db))
297 .map(|span_in_parent| (new_node, span_in_parent))
298 })
299 .take_while(|(_, span_in_parent)| {
300 node.span_without_trivia(db).contains(*span_in_parent)
301 })
302 .collect();
303
304 if let Some((last_node, _)) = nodes.last().cloned() {
305 let (new_node, _) = nodes
306 .into_iter()
307 .rev()
308 .take_while(|(node, _)| node.span(db) == last_node.span(db))
309 .last()
310 .unwrap();
311
312 new_nodes.insert(new_node);
313 }
314 });
315 }
316
317 if !new_nodes.is_empty() {
319 is_replaced = is_replaced || is_replacing_og_item;
320 }
321
322 for new_node in new_nodes {
323 result.extend(
324 find_generated_nodes(db, Arc::clone(&node_descendant_files), new_node)
325 .into_iter()
326 .copied(),
327 );
328 }
329 }
330
331 if !is_replaced {
332 result.insert(node);
333 }
334
335 result
336}