cairo_lang_sierra_generator/
statements_locations.rs1use std::ops::Add;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::diagnostic_utils::StableLocation;
5use cairo_lang_diagnostics::ToOption;
6use cairo_lang_filesystem::ids::{FileId, FileLongId, VirtualFile};
7use cairo_lang_sierra::program::StatementIdx;
8use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode};
9use cairo_lang_utils::LookupIntern;
10use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
11use itertools::Itertools;
12
13use crate::statements_code_locations::{
14 SourceCodeLocation, SourceCodeSpan, SourceFileFullPath, StatementsSourceCodeLocations,
15};
16use crate::statements_functions::StatementsFunctions;
17
18#[cfg(test)]
19#[path = "statements_locations_test.rs"]
20mod test;
21
22pub fn maybe_containing_function_identifier(
27 db: &dyn DefsGroup,
28 location: StableLocation,
29) -> Option<String> {
30 let file_id = location.file_id(db.upcast());
31 let absolute_semantic_path_to_file_module = file_module_absolute_identifier(db, file_id)?;
32
33 let relative_semantic_path = function_identifier_relative_to_file_module(db, location);
34 if relative_semantic_path.is_empty() {
35 None
39 } else {
40 Some(absolute_semantic_path_to_file_module.add("::").add(&relative_semantic_path))
41 }
42}
43
44pub fn maybe_containing_function_identifier_for_tests(
52 db: &dyn DefsGroup,
53 location: StableLocation,
54) -> Option<String> {
55 let file_id = location.file_id(db.upcast());
56 let absolute_semantic_path_to_file_module = file_module_absolute_identifier(db, file_id)
57 .unwrap_or_else(|| file_id.file_name(db.upcast()));
58
59 let relative_semantic_path = function_identifier_relative_to_file_module(db, location);
60 if relative_semantic_path.is_empty() {
61 None
66 } else {
67 Some(absolute_semantic_path_to_file_module.add("::").add(&relative_semantic_path))
68 }
69}
70
71pub fn function_identifier_relative_to_file_module(
74 db: &dyn DefsGroup,
75 location: StableLocation,
76) -> String {
77 let syntax_db = db.upcast();
78 let mut relative_semantic_path_segments: Vec<String> = vec![];
79 let mut syntax_node = location.syntax_node(db);
80 let mut statement_located_in_function = false;
81 loop {
82 match syntax_node.kind(syntax_db) {
85 cairo_lang_syntax::node::kind::SyntaxKind::FunctionWithBody => {
86 let function_name =
87 cairo_lang_syntax::node::ast::FunctionWithBody::from_syntax_node(
88 syntax_db,
89 syntax_node.clone(),
90 )
91 .declaration(syntax_db)
92 .name(syntax_db)
93 .text(syntax_db);
94
95 if relative_semantic_path_segments.is_empty() {
96 statement_located_in_function = true;
97 }
98
99 relative_semantic_path_segments.push(function_name.to_string());
100 }
101 cairo_lang_syntax::node::kind::SyntaxKind::ItemImpl => {
102 let impl_name = cairo_lang_syntax::node::ast::ItemImpl::from_syntax_node(
103 syntax_db,
104 syntax_node.clone(),
105 )
106 .name(syntax_db)
107 .text(syntax_db);
108 relative_semantic_path_segments.push(impl_name.to_string());
109 }
110 cairo_lang_syntax::node::kind::SyntaxKind::ItemModule => {
111 let module_name = cairo_lang_syntax::node::ast::ItemModule::from_syntax_node(
112 syntax_db,
113 syntax_node.clone(),
114 )
115 .name(syntax_db)
116 .text(syntax_db);
117 relative_semantic_path_segments.push(module_name.to_string());
118 }
119 _ => {}
120 }
121 if let Some(parent) = syntax_node.parent() {
122 syntax_node = parent;
123 } else {
124 break;
125 }
126 }
127
128 let file_id = location.file_id(db.upcast());
131 if !statement_located_in_function
132 && matches!(
133 file_id.lookup_intern(db),
134 FileLongId::Virtual(VirtualFile { parent: Some(_), .. })
135 )
136 {
137 relative_semantic_path_segments.insert(0, file_id.file_name(db.upcast()));
138 }
139
140 relative_semantic_path_segments.into_iter().rev().join("::")
141}
142
143pub fn maybe_code_location(
146 db: &dyn DefsGroup,
147 location: StableLocation,
148) -> Option<(SourceFileFullPath, SourceCodeSpan)> {
149 let location = location.diagnostic_location(db.upcast()).user_location(db.upcast());
150 let file_full_path = location.file_id.full_path(db.upcast());
151 let position = location.span.position_in_file(db.upcast(), location.file_id)?;
152 let source_location = SourceCodeSpan {
153 start: SourceCodeLocation { col: position.start.col, line: position.start.line },
154 end: SourceCodeLocation { col: position.end.col, line: position.end.line },
155 };
156
157 Some((SourceFileFullPath(file_full_path), source_location))
158}
159
160pub fn file_module_absolute_identifier(db: &dyn DefsGroup, mut file_id: FileId) -> Option<String> {
164 while let FileLongId::Virtual(VirtualFile { parent: Some(parent), .. }) =
168 file_id.lookup_intern(db)
169 {
170 file_id = parent;
171 }
172
173 let file_modules = db.file_modules(file_id).to_option()?;
174 let full_path = file_modules.first().unwrap().full_path(db.upcast());
175
176 Some(full_path)
177}
178
179#[derive(Clone, Debug, Default, Eq, PartialEq)]
181pub struct StatementsLocations {
182 pub locations: UnorderedHashMap<StatementIdx, Vec<StableLocation>>,
183}
184
185impl StatementsLocations {
186 pub fn from_locations_vec(locations_vec: &[Vec<StableLocation>]) -> Self {
188 let mut locations = UnorderedHashMap::default();
189 for (idx, stmt_locations) in locations_vec.iter().enumerate() {
190 if !stmt_locations.is_empty() {
191 locations.insert(StatementIdx(idx), stmt_locations.clone());
192 }
193 }
194 Self { locations }
195 }
196 pub fn get_statements_functions_map_for_tests(
201 &self,
202 db: &dyn DefsGroup,
203 ) -> UnorderedHashMap<StatementIdx, String> {
204 self.locations
205 .iter_sorted()
206 .filter_map(|(statement_idx, stable_locations)| {
207 maybe_containing_function_identifier_for_tests(
208 db,
209 *stable_locations.first().unwrap(),
210 )
211 .map(|function_identifier| (*statement_idx, function_identifier))
212 })
213 .collect()
214 }
215
216 pub fn extract_statements_functions(&self, db: &dyn DefsGroup) -> StatementsFunctions {
218 StatementsFunctions {
219 statements_to_functions_map: self
220 .locations
221 .iter_sorted()
222 .map(|(statement_idx, stable_locations)| {
223 (
224 *statement_idx,
225 stable_locations
226 .iter()
227 .filter_map(|s| maybe_containing_function_identifier(db, *s))
228 .collect(),
229 )
230 })
231 .collect(),
232 }
233 }
234
235 pub fn extract_statements_source_code_locations(
238 &self,
239 db: &dyn DefsGroup,
240 ) -> StatementsSourceCodeLocations {
241 StatementsSourceCodeLocations {
242 statements_to_code_location_map: self
243 .locations
244 .iter_sorted()
245 .map(|(statement_idx, stable_locations)| {
246 (
247 *statement_idx,
248 stable_locations
249 .iter()
250 .filter_map(|s| maybe_code_location(db, *s))
251 .collect(),
252 )
253 })
254 .collect(),
255 }
256 }
257}