1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use cairo_lang_utils::{Intern, define_short_id};
6use itertools::Itertools;
7use path_clean::PathClean;
8use salsa::Database;
9use serde::{Deserialize, Serialize};
10use smol_str::SmolStr;
11
12use crate::db::{CORELIB_CRATE_NAME, ext_as_virtual};
13use crate::span::{TextOffset, TextSpan};
14
15pub const CAIRO_FILE_EXTENSION: &str = "cairo";
16
17#[derive(Clone, Debug, Hash, PartialEq, Eq)]
20pub enum CrateInput {
21 Real {
22 name: String,
23 discriminator: Option<String>,
24 },
25 Virtual {
26 name: String,
27 file_long_id: FileInput,
28 settings: String,
29 cache_file: Option<BlobLongId>,
30 },
31}
32
33impl CrateInput {
34 pub fn into_crate_long_id(self, db: &dyn Database) -> CrateLongId<'_> {
35 match self {
36 CrateInput::Real { name, discriminator } => {
37 CrateLongId::Real { name: SmolStrId::from(db, name), discriminator }
38 }
39 CrateInput::Virtual { name, file_long_id, settings, cache_file } => {
40 CrateLongId::Virtual {
41 name: SmolStrId::from(db, name),
42 file_id: file_long_id.into_file_long_id(db).intern(db),
43 settings,
44 cache_file: cache_file.map(|blob_long_id| blob_long_id.intern(db)),
45 }
46 }
47 }
48 }
49
50 pub fn into_crate_ids(
51 db: &dyn Database,
52 inputs: impl IntoIterator<Item = CrateInput>,
53 ) -> Vec<CrateId<'_>> {
54 inputs.into_iter().map(|input| input.into_crate_long_id(db).intern(db)).collect()
55 }
56}
57
58#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
60pub enum CrateLongId<'db> {
61 Real { name: SmolStrId<'db>, discriminator: Option<String> },
63 Virtual {
65 name: SmolStrId<'db>,
66 file_id: FileId<'db>,
67 settings: String,
68 cache_file: Option<BlobId<'db>>,
69 },
70}
71impl<'db> CrateLongId<'db> {
72 pub fn name(&self) -> SmolStrId<'db> {
73 match self {
74 CrateLongId::Real { name, .. } | CrateLongId::Virtual { name, .. } => *name,
75 }
76 }
77
78 pub fn into_crate_input(self, db: &'db dyn Database) -> CrateInput {
79 match self {
80 CrateLongId::Real { name, discriminator } => {
81 CrateInput::Real { name: name.to_string(db), discriminator }
82 }
83 CrateLongId::Virtual { name, file_id, settings, cache_file } => CrateInput::Virtual {
84 name: name.to_string(db),
85 file_long_id: file_id.long(db).clone().into_file_input(db),
86 settings,
87 cache_file: cache_file.map(|blob_id| blob_id.long(db).clone()),
88 },
89 }
90 }
91
92 pub fn core(db: &'db dyn Database) -> Self {
93 CrateLongId::Real { name: SmolStrId::from(db, CORELIB_CRATE_NAME), discriminator: None }
94 }
95
96 pub fn plain(name: SmolStrId<'db>) -> Self {
97 CrateLongId::Real { name, discriminator: None }
98 }
99}
100define_short_id!(CrateId, CrateLongId<'db>);
101impl<'db> CrateId<'db> {
102 pub fn plain(db: &'db dyn Database, name: SmolStrId<'db>) -> Self {
104 CrateId::new(db, CrateLongId::plain(name))
105 }
106
107 pub fn core(db: &'db dyn Database) -> Self {
109 CrateId::new(db, CrateLongId::core(db))
110 }
111}
112
113pub trait UnstableSalsaId {
118 fn get_internal_id(&self) -> salsa::Id;
119}
120impl UnstableSalsaId for CrateId<'_> {
121 fn get_internal_id(&self) -> salsa::Id {
122 self.0
123 }
124}
125
126#[derive(Clone, Debug, Hash, PartialEq, Eq)]
128pub struct FlagLongId(pub String);
129define_short_id!(FlagId, FlagLongId);
130
131#[derive(Clone, Debug, Hash, PartialEq, Eq)]
134pub enum FileInput {
135 OnDisk(PathBuf),
136 Virtual(VirtualFileInput),
137 External(salsa::Id),
138}
139
140impl FileInput {
141 pub fn into_file_long_id(self, db: &dyn Database) -> FileLongId<'_> {
142 match self {
143 FileInput::OnDisk(path) => FileLongId::OnDisk(path),
144 FileInput::Virtual(vf) => FileLongId::Virtual(vf.into_virtual_file(db)),
145 FileInput::External(id) => FileLongId::External(id),
146 }
147 }
148}
149
150#[derive(Clone, Debug, Hash, PartialEq, Eq)]
153pub enum FileLongId<'db> {
154 OnDisk(PathBuf),
155 Virtual(VirtualFile<'db>),
156 External(salsa::Id),
157}
158#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
160pub enum FileKind {
161 Module,
162 Expr,
163 StatementList,
164}
165
166#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
168pub struct CodeMapping {
169 pub span: TextSpan,
170 pub origin: CodeOrigin,
171}
172impl CodeMapping {
173 pub fn translate(&self, span: TextSpan) -> Option<TextSpan> {
174 if self.span.contains(span) {
175 Some(match self.origin {
176 CodeOrigin::Start(origin_start) => {
177 let start = origin_start.add_width(span.start - self.span.start);
178 TextSpan::new_with_width(start, span.width())
179 }
180 CodeOrigin::Span(span) => span,
181 CodeOrigin::CallSite(span) => span,
182 })
183 } else {
184 None
185 }
186 }
187}
188
189#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
191pub enum CodeOrigin {
192 Start(TextOffset),
194 Span(TextSpan),
196 CallSite(TextSpan),
199}
200
201impl CodeOrigin {
202 pub fn as_span(&self) -> Option<TextSpan> {
203 match self {
204 CodeOrigin::Start(_) => None,
205 CodeOrigin::CallSite(_) => None,
206 CodeOrigin::Span(span) => Some(*span),
207 }
208 }
209
210 pub fn start(&self) -> TextOffset {
211 match self {
212 CodeOrigin::Start(start) => *start,
213 CodeOrigin::CallSite(span) => span.start,
214 CodeOrigin::Span(span) => span.start,
215 }
216 }
217}
218
219#[derive(Clone, Debug, Hash, PartialEq, Eq)]
222pub struct VirtualFileInput {
223 pub parent: Option<Arc<FileInput>>,
224 pub name: String,
225 pub content: Arc<str>,
226 pub code_mappings: Arc<[CodeMapping]>,
227 pub kind: FileKind,
228 pub original_item_removed: bool,
229}
230
231impl VirtualFileInput {
232 fn into_virtual_file(self, db: &dyn Database) -> VirtualFile<'_> {
233 VirtualFile {
234 parent: self.parent.map(|id| id.as_ref().clone().into_file_long_id(db).intern(db)),
235 name: SmolStrId::from(db, self.name),
236 content: SmolStrId::from(db, self.content),
237 code_mappings: self.code_mappings,
238 kind: self.kind,
239 original_item_removed: self.original_item_removed,
240 }
241 }
242}
243
244#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
245pub struct VirtualFile<'db> {
246 pub parent: Option<FileId<'db>>,
247 pub name: SmolStrId<'db>,
248 pub content: SmolStrId<'db>,
249 pub code_mappings: Arc<[CodeMapping]>,
250 pub kind: FileKind,
251 pub original_item_removed: bool,
255}
256impl<'db> VirtualFile<'db> {
257 fn full_path(&self, db: &'db dyn Database) -> String {
258 if let Some(parent) = self.parent {
259 format!("{}[{}]", parent.full_path(db), self.name.long(db))
261 } else {
262 self.name.to_string(db)
263 }
264 }
265
266 fn into_virtual_file_input(self, db: &dyn Database) -> VirtualFileInput {
267 VirtualFileInput {
268 parent: self.parent.map(|id| Arc::new(id.long(db).clone().into_file_input(db))),
269 name: self.name.to_string(db),
270 content: Arc::from(self.content.long(db).as_str()),
271 code_mappings: self.code_mappings,
272 kind: self.kind,
273 original_item_removed: self.original_item_removed,
274 }
275 }
276}
277
278impl<'db> FileLongId<'db> {
279 pub fn file_name(&self, db: &'db dyn Database) -> SmolStrId<'db> {
280 match self {
281 FileLongId::OnDisk(path) => SmolStrId::from(
282 db,
283 path.file_name().and_then(|x| x.to_str()).unwrap_or("<unknown>"),
284 ),
285 FileLongId::Virtual(vf) => vf.name,
286 FileLongId::External(external_id) => ext_as_virtual(db, *external_id).name,
287 }
288 }
289 pub fn full_path(&self, db: &'db dyn Database) -> String {
290 match self {
291 FileLongId::OnDisk(path) => path.to_str().unwrap_or("<unknown>").to_string(),
292 FileLongId::Virtual(vf) => vf.full_path(db),
293 FileLongId::External(external_id) => ext_as_virtual(db, *external_id).full_path(db),
294 }
295 }
296 pub fn kind(&self) -> FileKind {
297 match self {
298 FileLongId::OnDisk(_) => FileKind::Module,
299 FileLongId::Virtual(vf) => vf.kind,
300 FileLongId::External(_) => FileKind::Module,
301 }
302 }
303
304 pub fn into_file_input(&self, db: &dyn Database) -> FileInput {
305 match self {
306 FileLongId::OnDisk(path) => FileInput::OnDisk(path.clone()),
307 FileLongId::Virtual(vf) => FileInput::Virtual(vf.clone().into_virtual_file_input(db)),
308 FileLongId::External(id) => FileInput::External(*id),
309 }
310 }
311}
312
313define_short_id!(FileId, FileLongId<'db>);
314impl<'db> FileId<'db> {
315 pub fn new_on_disk(db: &'db dyn Database, path: PathBuf) -> FileId<'db> {
316 FileLongId::OnDisk(path.clean()).intern(db)
317 }
318
319 pub fn file_name(self, db: &'db dyn Database) -> SmolStrId<'db> {
320 self.long(db).file_name(db)
321 }
322
323 pub fn full_path(self, db: &dyn Database) -> String {
324 self.long(db).full_path(db)
325 }
326
327 pub fn kind(self, db: &dyn Database) -> FileKind {
328 self.long(db).kind()
329 }
330}
331
332#[derive(Clone, Debug, Hash, PartialEq, Eq)]
335pub enum DirectoryInput {
336 Real(PathBuf),
337 Virtual { files: BTreeMap<String, FileInput>, dirs: BTreeMap<String, Box<DirectoryInput>> },
338}
339
340impl DirectoryInput {
341 pub fn into_directory(self, db: &dyn Database) -> Directory<'_> {
343 match self {
344 DirectoryInput::Real(path) => Directory::Real(path),
345 DirectoryInput::Virtual { files, dirs } => Directory::Virtual {
346 files: files
347 .into_iter()
348 .map(|(name, file_input)| (name, file_input.into_file_long_id(db).intern(db)))
349 .collect(),
350 dirs: dirs
351 .into_iter()
352 .map(|(name, dir_input)| (name, Box::new(dir_input.into_directory(db))))
353 .collect(),
354 },
355 }
356 }
357}
358
359#[derive(Clone, Hash, PartialEq, Eq)]
360pub struct ArcStr(Arc<str>);
361
362impl ArcStr {
363 pub fn new(s: Arc<str>) -> Self {
364 ArcStr(s)
365 }
366}
367
368impl std::ops::Deref for ArcStr {
369 type Target = Arc<str>;
370
371 fn deref(&self) -> &Self::Target {
372 &self.0
373 }
374}
375
376impl std::fmt::Display for ArcStr {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 self.0.fmt(f)
379 }
380}
381
382impl std::fmt::Debug for ArcStr {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 self.0.fmt(f)
385 }
386}
387
388unsafe impl salsa::Update for ArcStr {
389 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
390 let old_str: &mut Self = unsafe { &mut *old_pointer };
391
392 if Arc::ptr_eq(&old_str.0, &new_value.0) {
394 return false;
395 }
396 if old_str.0 == new_value.0 {
398 return false;
399 }
400 *old_str = new_value;
402 true
403 }
404}
405
406define_short_id!(SmolStrId, SmolStr);
407
408pub trait DbJoin {
409 fn join(&self, db: &dyn Database, separator: &str) -> String;
410}
411
412impl<'db> DbJoin for Vec<SmolStrId<'db>> {
413 fn join(&self, db: &dyn Database, separator: &str) -> String {
414 self.iter().map(|id| id.long(db)).join(separator)
415 }
416}
417
418impl<'db> SmolStrId<'db> {
419 pub fn from(db: &'db dyn Database, content: impl Into<SmolStr>) -> Self {
420 SmolStrId::new(db, content.into())
421 }
422
423 pub fn from_arcstr(db: &'db dyn Database, content: &Arc<str>) -> Self {
424 SmolStrId::from(db, content.clone())
425 }
426
427 pub fn to_string(&self, db: &dyn Database) -> String {
428 self.long(db).to_string()
429 }
430}
431
432#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
433pub enum Directory<'db> {
434 Real(PathBuf),
436 Virtual { files: BTreeMap<String, FileId<'db>>, dirs: BTreeMap<String, Box<Directory<'db>>> },
438}
439
440impl<'db> Directory<'db> {
441 pub fn file(&self, db: &'db dyn Database, name: &str) -> FileId<'db> {
444 match self {
445 Directory::Real(path) => FileId::new_on_disk(db, path.join(name)),
446 Directory::Virtual { files, dirs: _ } => files
447 .get(name)
448 .copied()
449 .unwrap_or_else(|| FileId::new_on_disk(db, PathBuf::from(name))),
450 }
451 }
452
453 pub fn subdir(&self, name: &'db str) -> Directory<'db> {
456 match self {
457 Directory::Real(path) => Directory::Real(path.join(name)),
458 Directory::Virtual { files: _, dirs } => {
459 if let Some(dir) = dirs.get(name) {
460 dir.as_ref().clone()
461 } else {
462 Directory::Virtual { files: BTreeMap::new(), dirs: BTreeMap::new() }
463 }
464 }
465 }
466 }
467
468 pub fn into_directory_input(self, db: &dyn Database) -> DirectoryInput {
470 match self {
471 Directory::Real(path) => DirectoryInput::Real(path),
472 Directory::Virtual { files, dirs } => DirectoryInput::Virtual {
473 files: files
474 .into_iter()
475 .map(|(name, file_id)| (name, file_id.long(db).clone().into_file_input(db)))
476 .collect(),
477 dirs: dirs
478 .into_iter()
479 .map(|(name, dir)| (name, Box::new(dir.into_directory_input(db))))
480 .collect(),
481 },
482 }
483 }
484}
485
486#[derive(Clone, Debug, Hash, PartialEq, Eq)]
488pub enum BlobLongId {
489 OnDisk(PathBuf),
490 Virtual(Vec<u8>),
491}
492
493impl BlobLongId {
494 pub fn content(&self) -> Option<Vec<u8>> {
495 match self {
496 BlobLongId::OnDisk(path) => std::fs::read(path).ok(),
497 BlobLongId::Virtual(content) => Some(content.clone()),
498 }
499 }
500}
501
502define_short_id!(BlobId, BlobLongId);
503
504impl<'db> BlobId<'db> {
505 pub fn new_on_disk(db: &'db (dyn salsa::Database + 'db), path: PathBuf) -> Self {
506 BlobId::new(db, BlobLongId::OnDisk(path.clean()))
507 }
508}
509
510pub type Tracked = ();