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