1use std::collections::BTreeMap;
2use std::fs;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
7use cairo_lang_utils::{LookupIntern, Upcast};
8use salsa::Durability;
9use semver::Version;
10use serde::{Deserialize, Serialize};
11use smol_str::{SmolStr, ToSmolStr};
12
13use crate::cfg::CfgSet;
14use crate::flag::Flag;
15use crate::ids::{
16 CodeMapping, CrateId, CrateLongId, Directory, FileId, FileLongId, FlagId, FlagLongId,
17 VirtualFile,
18};
19use crate::span::{FileSummary, TextOffset, TextSpan, TextWidth};
20
21#[cfg(test)]
22#[path = "db_test.rs"]
23mod test;
24
25pub const CORELIB_CRATE_NAME: &str = "core";
26pub const CORELIB_VERSION: &str = env!("CARGO_PKG_VERSION");
27
28#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
33pub struct CrateIdentifier(SmolStr);
34
35impl<T: ToSmolStr> From<T> for CrateIdentifier {
36 fn from(value: T) -> Self {
37 Self(value.to_smolstr())
38 }
39}
40
41impl From<CrateIdentifier> for SmolStr {
42 fn from(value: CrateIdentifier) -> Self {
43 value.0
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct CrateConfiguration {
50 pub root: Directory,
52 pub settings: CrateSettings,
53}
54impl CrateConfiguration {
55 pub fn default_for_root(root: Directory) -> Self {
57 Self { root, settings: CrateSettings::default() }
58 }
59}
60
61#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
63pub struct CrateSettings {
64 pub name: Option<SmolStr>,
67 pub edition: Edition,
69 pub version: Option<Version>,
81 pub cfg_set: Option<CfgSet>,
83 #[serde(default)]
85 pub dependencies: BTreeMap<String, DependencySettings>,
86
87 #[serde(default)]
88 pub experimental_features: ExperimentalFeaturesConfig,
89}
90
91#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
99pub enum Edition {
100 #[default]
102 #[serde(rename = "2023_01")]
103 V2023_01,
104 #[serde(rename = "2023_10")]
105 V2023_10,
106 #[serde(rename = "2023_11")]
107 V2023_11,
108 #[serde(rename = "2024_07")]
109 V2024_07,
110}
111impl Edition {
112 pub const fn latest() -> Self {
117 Self::V2024_07
118 }
119
120 pub fn prelude_submodule_name(&self) -> &str {
122 match self {
123 Self::V2023_01 => "v2023_01",
124 Self::V2023_10 | Self::V2023_11 => "v2023_10",
125 Self::V2024_07 => "v2024_07",
126 }
127 }
128
129 pub fn ignore_visibility(&self) -> bool {
131 match self {
132 Self::V2023_01 | Self::V2023_10 => true,
133 Self::V2023_11 | Self::V2024_07 => false,
134 }
135 }
136}
137
138#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
140pub struct DependencySettings {
141 pub discriminator: Option<SmolStr>,
148}
149
150#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
152pub struct ExperimentalFeaturesConfig {
153 pub negative_impls: bool,
154 pub associated_item_constraints: bool,
156 #[serde(default)]
161 pub coupons: bool,
162}
163
164pub trait ExternalFiles {
166 fn ext_as_virtual(&self, external_id: salsa::InternId) -> VirtualFile {
168 self.try_ext_as_virtual(external_id).unwrap()
169 }
170
171 fn try_ext_as_virtual(&self, _external_id: salsa::InternId) -> Option<VirtualFile> {
173 panic!("Should not be called, unless specifically implemented!");
174 }
175}
176
177#[salsa::query_group(FilesDatabase)]
179pub trait FilesGroup: ExternalFiles {
180 #[salsa::interned]
181 fn intern_crate(&self, crt: CrateLongId) -> CrateId;
182 #[salsa::interned]
183 fn intern_file(&self, file: FileLongId) -> FileId;
184 #[salsa::interned]
185 fn intern_flag(&self, flag: FlagLongId) -> FlagId;
186
187 #[salsa::input]
189 fn crate_configs(&self) -> Arc<OrderedHashMap<CrateId, CrateConfiguration>>;
190
191 #[salsa::input]
197 fn file_overrides(&self) -> Arc<OrderedHashMap<FileId, Arc<str>>>;
198
199 #[salsa::input]
202 fn flags(&self) -> Arc<OrderedHashMap<FlagId, Arc<Flag>>>;
203 #[salsa::input]
205 fn cfg_set(&self) -> Arc<CfgSet>;
206
207 fn crates(&self) -> Vec<CrateId>;
209 fn crate_config(&self, crate_id: CrateId) -> Option<CrateConfiguration>;
211
212 fn priv_raw_file_content(&self, file_id: FileId) -> Option<Arc<str>>;
214 fn file_content(&self, file_id: FileId) -> Option<Arc<str>>;
216 fn file_summary(&self, file_id: FileId) -> Option<Arc<FileSummary>>;
217
218 fn get_flag(&self, id: FlagId) -> Option<Arc<Flag>>;
220}
221
222pub fn init_files_group(db: &mut (dyn FilesGroup + 'static)) {
223 db.set_file_overrides(Arc::new(OrderedHashMap::default()));
225 db.set_crate_configs(Arc::new(OrderedHashMap::default()));
226 db.set_flags(Arc::new(OrderedHashMap::default()));
227 db.set_cfg_set(Arc::new(CfgSet::new()));
228}
229
230pub fn init_dev_corelib(db: &mut (dyn FilesGroup + 'static), core_lib_dir: PathBuf) {
231 db.set_crate_config(
232 CrateId::core(db),
233 Some(CrateConfiguration {
234 root: Directory::Real(core_lib_dir),
235 settings: CrateSettings {
236 name: None,
237 edition: Edition::V2024_07,
238 version: Version::parse(CORELIB_VERSION).ok(),
239 cfg_set: Default::default(),
240 dependencies: Default::default(),
241 experimental_features: ExperimentalFeaturesConfig {
242 negative_impls: true,
243 associated_item_constraints: true,
244 coupons: true,
245 },
246 },
247 }),
248 );
249}
250
251impl AsFilesGroupMut for dyn FilesGroup {
252 fn as_files_group_mut(&mut self) -> &mut (dyn FilesGroup + 'static) {
253 self
254 }
255}
256
257pub trait FilesGroupEx: Upcast<dyn FilesGroup> + AsFilesGroupMut {
258 fn override_file_content(&mut self, file: FileId, content: Option<Arc<str>>) {
260 let mut overrides = Upcast::upcast(self).file_overrides().as_ref().clone();
261 match content {
262 Some(content) => overrides.insert(file, content),
263 None => overrides.swap_remove(&file),
264 };
265 self.as_files_group_mut().set_file_overrides(Arc::new(overrides));
266 }
267 fn set_crate_config(&mut self, crt: CrateId, root: Option<CrateConfiguration>) {
269 let mut crate_configs = Upcast::upcast(self).crate_configs().as_ref().clone();
270 match root {
271 Some(root) => crate_configs.insert(crt, root),
272 None => crate_configs.swap_remove(&crt),
273 };
274 self.as_files_group_mut().set_crate_configs(Arc::new(crate_configs));
275 }
276 fn set_flag(&mut self, id: FlagId, value: Option<Arc<Flag>>) {
278 let mut flags = Upcast::upcast(self).flags().as_ref().clone();
279 match value {
280 Some(value) => flags.insert(id, value),
281 None => flags.swap_remove(&id),
282 };
283 self.as_files_group_mut().set_flags(Arc::new(flags));
284 }
285 fn use_cfg(&mut self, cfg_set: &CfgSet) {
287 let existing = Upcast::upcast(self).cfg_set();
288 let merged = existing.union(cfg_set);
289 self.as_files_group_mut().set_cfg_set(Arc::new(merged));
290 }
291}
292impl<T: Upcast<dyn FilesGroup> + AsFilesGroupMut + ?Sized> FilesGroupEx for T {}
293
294pub trait AsFilesGroupMut {
295 fn as_files_group_mut(&mut self) -> &mut (dyn FilesGroup + 'static);
296}
297
298fn crates(db: &dyn FilesGroup) -> Vec<CrateId> {
299 db.crate_configs().keys().copied().collect()
301}
302fn crate_config(db: &dyn FilesGroup, crt: CrateId) -> Option<CrateConfiguration> {
303 match crt.lookup_intern(db) {
304 CrateLongId::Real { .. } => db.crate_configs().get(&crt).cloned(),
305 CrateLongId::Virtual { name: _, file_id, settings } => Some(CrateConfiguration {
306 root: Directory::Virtual {
307 files: BTreeMap::from([("lib.cairo".into(), file_id)]),
308 dirs: Default::default(),
309 },
310 settings: toml::from_str(&settings).expect("Failed to parse virtual crate settings."),
311 }),
312 }
313}
314
315fn priv_raw_file_content(db: &dyn FilesGroup, file: FileId) -> Option<Arc<str>> {
316 match file.lookup_intern(db) {
317 FileLongId::OnDisk(path) => {
318 db.salsa_runtime().report_synthetic_read(Durability::LOW);
321
322 match fs::read_to_string(path) {
323 Ok(content) => Some(content.into()),
324 Err(_) => None,
325 }
326 }
327 FileLongId::Virtual(virt) => Some(virt.content),
328 FileLongId::External(external_id) => Some(db.ext_as_virtual(external_id).content),
329 }
330}
331fn file_content(db: &dyn FilesGroup, file: FileId) -> Option<Arc<str>> {
332 let overrides = db.file_overrides();
333 overrides.get(&file).cloned().or_else(|| db.priv_raw_file_content(file))
334}
335fn file_summary(db: &dyn FilesGroup, file: FileId) -> Option<Arc<FileSummary>> {
336 let content = db.file_content(file)?;
337 let mut line_offsets = vec![TextOffset::START];
338 let mut offset = TextOffset::START;
339 for ch in content.chars() {
340 offset = offset.add_width(TextWidth::from_char(ch));
341 if ch == '\n' {
342 line_offsets.push(offset);
343 }
344 }
345 Some(Arc::new(FileSummary { line_offsets, last_offset: offset }))
346}
347fn get_flag(db: &dyn FilesGroup, id: FlagId) -> Option<Arc<Flag>> {
348 db.flags().get(&id).cloned()
349}
350
351pub fn get_originating_location(
353 db: &dyn FilesGroup,
354 mut file_id: FileId,
355 mut span: TextSpan,
356 mut parent_files: Option<&mut Vec<FileId>>,
357) -> (FileId, TextSpan) {
358 if let Some(ref mut parent_files) = parent_files {
359 parent_files.push(file_id);
360 }
361 while let Some((parent, code_mappings)) = get_parent_and_mapping(db, file_id) {
362 if let Some(origin) = code_mappings.iter().find_map(|mapping| mapping.translate(span)) {
363 span = origin;
364 file_id = parent;
365 if let Some(ref mut parent_files) = parent_files {
366 parent_files.push(file_id);
367 }
368 } else {
369 break;
370 }
371 }
372 (file_id, span)
373}
374
375fn get_parent_and_mapping(
377 db: &dyn FilesGroup,
378 file_id: FileId,
379) -> Option<(FileId, Arc<[CodeMapping]>)> {
380 let vf = match file_id.lookup_intern(db) {
381 FileLongId::OnDisk(_) => return None,
382 FileLongId::Virtual(vf) => vf,
383 FileLongId::External(id) => db.ext_as_virtual(id),
384 };
385 Some((vf.parent?, vf.code_mappings))
386}