1use crate::{span, LineCol, ProgramId, SourceId, Span};
2use parking_lot::RwLock;
3use std::{
4 collections::{BTreeSet, HashMap},
5 path::{Path, PathBuf},
6 str::FromStr,
7};
8use toml::Table;
9
10#[derive(Debug, Default)]
20pub struct SourceEngine {
21 next_source_id: RwLock<u32>,
22 path_to_source_map: RwLock<HashMap<PathBuf, SourceId>>,
23 source_to_path_map: RwLock<HashMap<SourceId, PathBuf>>,
24 source_to_buffer_map: RwLock<HashMap<SourceId, span::Source>>,
25 next_program_id: RwLock<u16>,
26 manifest_path_to_program_map: RwLock<HashMap<PathBuf, ProgramId>>,
27 manifest_path_to_package_info: RwLock<HashMap<PathBuf, (String, String)>>,
31 module_to_sources_map: RwLock<HashMap<ProgramId, BTreeSet<SourceId>>>,
32}
33
34impl Clone for SourceEngine {
35 fn clone(&self) -> Self {
36 SourceEngine {
37 next_source_id: RwLock::new(*self.next_source_id.read()),
38 path_to_source_map: RwLock::new(self.path_to_source_map.read().clone()),
39 source_to_path_map: RwLock::new(self.source_to_path_map.read().clone()),
40 source_to_buffer_map: RwLock::new(self.source_to_buffer_map.read().clone()),
41 next_program_id: RwLock::new(*self.next_program_id.read()),
42 manifest_path_to_program_map: RwLock::new(
43 self.manifest_path_to_program_map.read().clone(),
44 ),
45 manifest_path_to_package_info: RwLock::new(
46 self.manifest_path_to_package_info.read().clone(),
47 ),
48 module_to_sources_map: RwLock::new(self.module_to_sources_map.read().clone()),
49 }
50 }
51}
52
53impl SourceEngine {
54 const AUTOGENERATED_PATH: &'static str = "<autogenerated>";
55
56 pub fn is_span_in_autogenerated(&self, span: &crate::Span) -> Option<bool> {
57 span.source_id().map(|s| self.is_source_id_autogenerated(s))
58 }
59
60 pub fn is_source_id_autogenerated(&self, source_id: &SourceId) -> bool {
61 self.get_path(source_id)
62 .display()
63 .to_string()
64 .contains("<autogenerated>")
65 }
66
67 pub fn get_or_create_source_buffer(
70 &self,
71 source_id: &SourceId,
72 source: span::Source,
73 ) -> span::Source {
74 let mut map = self.source_to_buffer_map.write();
75
76 if let Some(existing) = map.get_mut(source_id) {
77 if existing.text != source.text {
80 *existing = source;
81 }
82 existing.clone()
83 } else {
84 map.insert(*source_id, source.clone());
85 source
86 }
87 }
88
89 pub fn get_source_id(&self, path: &PathBuf) -> SourceId {
93 {
94 let source_map = self.path_to_source_map.read();
95 if source_map.contains_key(path) {
96 return source_map.get(path).copied().unwrap();
97 }
98 }
99
100 let program_id = self.get_or_create_program_id_from_manifest_path(path);
101 self.get_source_id_with_program_id(path, program_id)
102 }
103
104 pub fn get_source_id_with_program_id(&self, path: &PathBuf, program_id: ProgramId) -> SourceId {
105 {
106 let source_map = self.path_to_source_map.read();
107 if source_map.contains_key(path) {
108 return source_map.get(path).copied().unwrap();
109 }
110 }
111
112 let source_id = SourceId::new(program_id.0, *self.next_source_id.read());
113 {
114 let mut next_id = self.next_source_id.write();
115 *next_id += 1;
116
117 let mut source_map = self.path_to_source_map.write();
118 source_map.insert(path.clone(), source_id);
119
120 let mut path_map = self.source_to_path_map.write();
121 path_map.insert(source_id, path.clone());
122 }
123
124 let mut module_map = self.module_to_sources_map.write();
125 module_map.entry(program_id).or_default().insert(source_id);
126
127 source_id
128 }
129
130 pub fn get_associated_autogenerated_source_id(&self, source_id: &SourceId) -> Option<SourceId> {
135 let path = self.get_path(source_id);
136 let file_name = PathBuf::from_str(path.file_name()?.to_str()?).ok()?;
137 let path = path.with_file_name(format!(
138 "{}.{}.{}",
139 file_name.file_stem()?.to_str()?,
140 Self::AUTOGENERATED_PATH,
141 file_name.extension()?.to_str()?
142 ));
143 Some(self.get_source_id_with_program_id(&path, source_id.program_id()))
144 }
145
146 pub fn get_path(&self, source_id: &SourceId) -> PathBuf {
148 self.source_to_path_map
149 .read()
150 .get(source_id)
151 .unwrap()
152 .clone()
153 }
154
155 pub fn get_program_id_from_manifest_path(&self, path: impl AsRef<Path>) -> Option<ProgramId> {
157 let manifest_path = sway_utils::find_parent_manifest_dir(&path)
158 .unwrap_or_else(|| path.as_ref().to_path_buf());
159 self.manifest_path_to_program_map
160 .read()
161 .get(&manifest_path)
162 .copied()
163 }
164
165 pub fn get_or_create_program_id_from_manifest_path(&self, path: &PathBuf) -> ProgramId {
166 let manifest_path = sway_utils::find_parent_manifest_dir(path).unwrap_or(path.clone());
167 let mut module_map = self.manifest_path_to_program_map.write();
168 *module_map.entry(manifest_path.clone()).or_insert_with(|| {
169 let mut next_id = self.next_program_id.write();
170 *next_id += 1;
171 ProgramId::new(*next_id)
172 })
173 }
174
175 pub fn get_manifest_path_from_program_id(&self, program_id: &ProgramId) -> Option<PathBuf> {
177 let path_to_module_map = self.manifest_path_to_program_map.read();
178 path_to_module_map
179 .iter()
180 .find(|(_, &id)| id == *program_id)
181 .map(|(path, _)| path.clone())
182 }
183
184 pub fn get_file_name(&self, source_id: &SourceId) -> Option<String> {
186 self.get_path(source_id)
187 .as_path()
188 .file_name()
189 .map(|file_name| file_name.to_string_lossy())
190 .map(|file_name| file_name.to_string())
191 }
192
193 pub fn all_files(&self) -> Vec<PathBuf> {
194 let s = self.source_to_path_map.read();
195 let mut v = s.values().cloned().collect::<Vec<_>>();
196 v.sort();
197 v
198 }
199
200 pub fn get_source_ids_from_program_id(
201 &self,
202 program_id: ProgramId,
203 ) -> Option<BTreeSet<SourceId>> {
204 let s = self.module_to_sources_map.read();
205 s.get(&program_id).cloned()
206 }
207
208 fn get_package_name_and_version(&self, manifest_path: &Path) -> (String, String) {
213 fn get_fallback_package_name_and_version(manifest_path: &Path) -> (String, String) {
214 let package_dir_name = manifest_path
218 .iter()
219 .next_back()
220 .map(|p| p.to_string_lossy().to_string())
221 .unwrap_or_else(|| "<unknown>".to_string());
222 (package_dir_name, String::new())
223 }
224
225 fn get_project_field(toml: &Table, field_name: &str) -> Option<String> {
226 toml.get("project")
227 .and_then(|v| v.get(field_name))
228 .and_then(|field| field.as_str())
229 .map(|value| value.to_string())
230 }
231
232 let forc_toml_path = manifest_path.join("Forc.toml");
233 if !forc_toml_path.exists() {
234 return get_fallback_package_name_and_version(manifest_path);
235 }
236
237 let content = match std::fs::read_to_string(&forc_toml_path) {
238 Ok(content) => content,
239 Err(_) => return get_fallback_package_name_and_version(manifest_path),
240 };
241
242 let toml = match content.parse::<Table>() {
243 Ok(toml) => toml,
244 Err(_) => return get_fallback_package_name_and_version(manifest_path),
245 };
246
247 let package_name = get_project_field(&toml, "name").unwrap_or("<unknown>".to_string());
248 let package_version = get_project_field(&toml, "version").unwrap_or_default();
249
250 (package_name, package_version)
251 }
252
253 pub fn get_source_location(&self, span: &Span) -> SourceLocation {
254 let Some(source_id) = span.source_id() else {
255 return SourceLocation::unknown();
256 };
257
258 let source_file = self.get_path(source_id);
259
260 let program_id = self
262 .get_program_id_from_manifest_path(&source_file)
263 .expect("the `source_file` is retrieved from the `SourceEngine::get_path` function so the manifest path and program id must exist");
264 let manifest_path = self.get_manifest_path_from_program_id(&program_id).expect(
265 "the `program_id` is retrieved from the `SourceEngine` so the manifest path must exist",
266 );
267
268 let mut package_infos = self.manifest_path_to_package_info.write();
271 let (package_name, package_version) = &package_infos
272 .entry(manifest_path.clone())
273 .or_insert_with(|| self.get_package_name_and_version(&manifest_path));
274
275 let pkg = if package_version.is_empty() {
276 package_name.clone()
277 } else {
278 format!("{package_name}@{package_version}")
279 };
280
281 let source_file = source_file
283 .strip_prefix(&manifest_path)
284 .expect("the `manifest_path` is a parent of the `source_file`")
285 .to_string_lossy();
286 let source_file = if let Some(source_file_no_leading_slash) = source_file.strip_prefix("/")
287 {
288 source_file_no_leading_slash.to_string()
289 } else {
290 source_file.to_string()
291 };
292
293 SourceLocation {
294 pkg,
295 file: source_file,
296 loc: span.start_line_col_one_index(),
297 }
298 }
299}
300
301#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
304pub struct SourceLocation {
305 pub pkg: String,
312 pub file: String,
316 pub loc: LineCol,
317}
318
319impl SourceLocation {
320 pub fn unknown() -> Self {
323 Self {
324 pkg: "<unknown>".to_string(),
325 file: "<unknown>".to_string(),
326 loc: LineCol::default(),
327 }
328 }
329}