sway_types/
source_engine.rs1use crate::{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 next_program_id: RwLock<u16>,
25 manifest_path_to_program_map: RwLock<HashMap<PathBuf, ProgramId>>,
26 manifest_path_to_package_info: RwLock<HashMap<PathBuf, (String, String)>>,
30 module_to_sources_map: RwLock<HashMap<ProgramId, BTreeSet<SourceId>>>,
31}
32
33impl Clone for SourceEngine {
34 fn clone(&self) -> Self {
35 SourceEngine {
36 next_source_id: RwLock::new(*self.next_source_id.read()),
37 path_to_source_map: RwLock::new(self.path_to_source_map.read().clone()),
38 source_to_path_map: RwLock::new(self.source_to_path_map.read().clone()),
39 next_program_id: RwLock::new(*self.next_program_id.read()),
40 manifest_path_to_program_map: RwLock::new(
41 self.manifest_path_to_program_map.read().clone(),
42 ),
43 manifest_path_to_package_info: RwLock::new(
44 self.manifest_path_to_package_info.read().clone(),
45 ),
46 module_to_sources_map: RwLock::new(self.module_to_sources_map.read().clone()),
47 }
48 }
49}
50
51impl SourceEngine {
52 const AUTOGENERATED_PATH: &'static str = "<autogenerated>";
53
54 pub fn is_span_in_autogenerated(&self, span: &crate::Span) -> Option<bool> {
55 span.source_id().map(|s| self.is_source_id_autogenerated(s))
56 }
57
58 pub fn is_source_id_autogenerated(&self, source_id: &SourceId) -> bool {
59 self.get_path(source_id)
60 .display()
61 .to_string()
62 .contains("<autogenerated>")
63 }
64
65 pub fn get_source_id(&self, path: &PathBuf) -> SourceId {
69 {
70 let source_map = self.path_to_source_map.read();
71 if source_map.contains_key(path) {
72 return source_map.get(path).copied().unwrap();
73 }
74 }
75
76 let program_id = self.get_or_create_program_id_from_manifest_path(path);
77 self.get_source_id_with_program_id(path, program_id)
78 }
79
80 pub fn get_source_id_with_program_id(&self, path: &PathBuf, program_id: ProgramId) -> SourceId {
81 {
82 let source_map = self.path_to_source_map.read();
83 if source_map.contains_key(path) {
84 return source_map.get(path).copied().unwrap();
85 }
86 }
87
88 let source_id = SourceId::new(program_id.0, *self.next_source_id.read());
89 {
90 let mut next_id = self.next_source_id.write();
91 *next_id += 1;
92
93 let mut source_map = self.path_to_source_map.write();
94 source_map.insert(path.clone(), source_id);
95
96 let mut path_map = self.source_to_path_map.write();
97 path_map.insert(source_id, path.clone());
98 }
99
100 let mut module_map = self.module_to_sources_map.write();
101 module_map.entry(program_id).or_default().insert(source_id);
102
103 source_id
104 }
105
106 pub fn get_associated_autogenerated_source_id(&self, source_id: &SourceId) -> Option<SourceId> {
111 let path = self.get_path(source_id);
112 let file_name = PathBuf::from_str(path.file_name()?.to_str()?).ok()?;
113 let path = path.with_file_name(format!(
114 "{}.{}.{}",
115 file_name.file_stem()?.to_str()?,
116 Self::AUTOGENERATED_PATH,
117 file_name.extension()?.to_str()?
118 ));
119 Some(self.get_source_id_with_program_id(&path, source_id.program_id()))
120 }
121
122 pub fn get_path(&self, source_id: &SourceId) -> PathBuf {
124 self.source_to_path_map
125 .read()
126 .get(source_id)
127 .unwrap()
128 .clone()
129 }
130
131 pub fn get_program_id_from_manifest_path(&self, path: &PathBuf) -> Option<ProgramId> {
133 let manifest_path = sway_utils::find_parent_manifest_dir(path).unwrap_or(path.clone());
134 self.manifest_path_to_program_map
135 .read()
136 .get(&manifest_path)
137 .copied()
138 }
139
140 pub fn get_or_create_program_id_from_manifest_path(&self, path: &PathBuf) -> ProgramId {
141 let manifest_path = sway_utils::find_parent_manifest_dir(path).unwrap_or(path.clone());
142 let mut module_map = self.manifest_path_to_program_map.write();
143 *module_map.entry(manifest_path.clone()).or_insert_with(|| {
144 let mut next_id = self.next_program_id.write();
145 *next_id += 1;
146 ProgramId::new(*next_id)
147 })
148 }
149
150 pub fn get_manifest_path_from_program_id(&self, program_id: &ProgramId) -> Option<PathBuf> {
152 let path_to_module_map = self.manifest_path_to_program_map.read();
153 path_to_module_map
154 .iter()
155 .find(|(_, &id)| id == *program_id)
156 .map(|(path, _)| path.clone())
157 }
158
159 pub fn get_file_name(&self, source_id: &SourceId) -> Option<String> {
161 self.get_path(source_id)
162 .as_path()
163 .file_name()
164 .map(|file_name| file_name.to_string_lossy())
165 .map(|file_name| file_name.to_string())
166 }
167
168 pub fn all_files(&self) -> Vec<PathBuf> {
169 let s = self.source_to_path_map.read();
170 let mut v = s.values().cloned().collect::<Vec<_>>();
171 v.sort();
172 v
173 }
174
175 pub fn get_source_ids_from_program_id(
176 &self,
177 program_id: ProgramId,
178 ) -> Option<BTreeSet<SourceId>> {
179 let s = self.module_to_sources_map.read();
180 s.get(&program_id).cloned()
181 }
182
183 fn get_package_name_and_version(&self, manifest_path: &Path) -> (String, String) {
188 fn get_fallback_package_name_and_version(manifest_path: &Path) -> (String, String) {
189 let package_dir_name = manifest_path
193 .iter()
194 .next_back()
195 .map(|p| p.to_string_lossy().to_string())
196 .unwrap_or_else(|| "<unknown>".to_string());
197 (package_dir_name, String::new())
198 }
199
200 fn get_project_field(toml: &Table, field_name: &str) -> Option<String> {
201 toml.get("project")
202 .and_then(|v| v.get(field_name))
203 .and_then(|field| field.as_str())
204 .map(|value| value.to_string())
205 }
206
207 let forc_toml_path = manifest_path.join("Forc.toml");
208 if !forc_toml_path.exists() {
209 return get_fallback_package_name_and_version(manifest_path);
210 }
211
212 let content = match std::fs::read_to_string(&forc_toml_path) {
213 Ok(content) => content,
214 Err(_) => return get_fallback_package_name_and_version(manifest_path),
215 };
216
217 let toml = match content.parse::<Table>() {
218 Ok(toml) => toml,
219 Err(_) => return get_fallback_package_name_and_version(manifest_path),
220 };
221
222 let package_name = get_project_field(&toml, "name").unwrap_or("<unknown>".to_string());
223 let package_version = get_project_field(&toml, "version").unwrap_or_default();
224
225 (package_name, package_version)
226 }
227
228 pub fn get_source_location(&self, span: &Span) -> SourceLocation {
229 let Some(source_id) = span.source_id() else {
230 return SourceLocation::unknown();
231 };
232
233 let source_file = self.get_path(source_id);
234
235 let program_id = self
237 .get_program_id_from_manifest_path(&source_file)
238 .expect("the `source_file` is retrieved from the `SourceEngine::get_path` function so the manifest path and program id must exist");
239 let manifest_path = self.get_manifest_path_from_program_id(&program_id).expect(
240 "the `program_id` is retrieved from the `SourceEngine` so the manifest path must exist",
241 );
242
243 let mut package_infos = self.manifest_path_to_package_info.write();
246 let (package_name, package_version) = &package_infos
247 .entry(manifest_path.clone())
248 .or_insert_with(|| self.get_package_name_and_version(&manifest_path));
249
250 let pkg = if package_version.is_empty() {
251 package_name.clone()
252 } else {
253 format!("{}@{}", package_name, package_version)
254 };
255
256 let source_file = source_file
258 .strip_prefix(&manifest_path)
259 .expect("the `manifest_path` is a parent of the `source_file`")
260 .to_string_lossy();
261 let source_file = if let Some(source_file_no_leading_slash) = source_file.strip_prefix("/")
262 {
263 source_file_no_leading_slash.to_string()
264 } else {
265 source_file.to_string()
266 };
267
268 SourceLocation {
269 pkg,
270 file: source_file,
271 loc: span.start_line_col_one_index(),
272 }
273 }
274}
275
276#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
279pub struct SourceLocation {
280 pub pkg: String,
287 pub file: String,
291 pub loc: LineCol,
292}
293
294impl SourceLocation {
295 pub fn unknown() -> Self {
298 Self {
299 pkg: "<unknown>".to_string(),
300 file: "<unknown>".to_string(),
301 loc: LineCol::default(),
302 }
303 }
304}