1use core::fmt;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use cairo_lang_debug::DebugWithDb;
7use cairo_lang_proc_macros::HeapSize;
8use cairo_lang_utils::{Intern, define_short_id};
9use itertools::Itertools;
10use path_clean::PathClean;
11use salsa::Database;
12use serde::{Deserialize, Serialize};
13use smol_str::SmolStr;
14
15use crate::db::{CORELIB_CRATE_NAME, ext_as_virtual, get_originating_location};
16use crate::location_marks::get_location_marks;
17use crate::span::{TextOffset, TextSpan};
18
19pub const CAIRO_FILE_EXTENSION: &str = "cairo";
20
21#[derive(Clone, Debug, Hash, PartialEq, Eq)]
24pub enum CrateInput {
25 Real {
26 name: String,
27 discriminator: Option<String>,
28 },
29 Virtual {
30 name: String,
31 file_long_id: FileInput,
32 settings: String,
33 cache_file: Option<BlobLongId>,
34 },
35}
36
37impl CrateInput {
38 pub fn into_crate_long_id(self, db: &dyn Database) -> CrateLongId<'_> {
39 match self {
40 CrateInput::Real { name, discriminator } => {
41 CrateLongId::Real { name: SmolStrId::from(db, name), discriminator }
42 }
43 CrateInput::Virtual { name, file_long_id, settings, cache_file } => {
44 CrateLongId::Virtual {
45 name: SmolStrId::from(db, name),
46 file_id: file_long_id.into_file_long_id(db).intern(db),
47 settings,
48 cache_file: cache_file.map(|blob_long_id| blob_long_id.intern(db)),
49 }
50 }
51 }
52 }
53
54 pub fn into_crate_ids(
55 db: &dyn Database,
56 inputs: impl IntoIterator<Item = CrateInput>,
57 ) -> Vec<CrateId<'_>> {
58 inputs.into_iter().map(|input| input.into_crate_long_id(db).intern(db)).collect()
59 }
60}
61
62#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update, HeapSize)]
64pub enum CrateLongId<'db> {
65 Real { name: SmolStrId<'db>, discriminator: Option<String> },
67 Virtual {
69 name: SmolStrId<'db>,
70 file_id: FileId<'db>,
71 settings: String,
72 cache_file: Option<BlobId<'db>>,
73 },
74}
75impl<'db> CrateLongId<'db> {
76 pub fn name(&self) -> SmolStrId<'db> {
77 match self {
78 CrateLongId::Real { name, .. } | CrateLongId::Virtual { name, .. } => *name,
79 }
80 }
81
82 pub fn into_crate_input(self, db: &'db dyn Database) -> CrateInput {
83 match self {
84 CrateLongId::Real { name, discriminator } => {
85 CrateInput::Real { name: name.to_string(db), discriminator }
86 }
87 CrateLongId::Virtual { name, file_id, settings, cache_file } => CrateInput::Virtual {
88 name: name.to_string(db),
89 file_long_id: file_id.long(db).clone().into_file_input(db),
90 settings,
91 cache_file: cache_file.map(|blob_id| blob_id.long(db).clone()),
92 },
93 }
94 }
95
96 pub fn core(db: &'db dyn Database) -> Self {
97 CrateLongId::Real { name: SmolStrId::from(db, CORELIB_CRATE_NAME), discriminator: None }
98 }
99
100 pub fn plain(name: SmolStrId<'db>) -> Self {
101 CrateLongId::Real { name, discriminator: None }
102 }
103}
104define_short_id!(CrateId, CrateLongId<'db>);
105impl<'db> CrateId<'db> {
106 pub fn plain(db: &'db dyn Database, name: SmolStrId<'db>) -> Self {
108 CrateId::new(db, CrateLongId::plain(name))
109 }
110
111 pub fn core(db: &'db dyn Database) -> Self {
113 CrateId::new(db, CrateLongId::core(db))
114 }
115}
116
117pub trait UnstableSalsaId {
122 fn get_internal_id(&self) -> salsa::Id;
123}
124impl UnstableSalsaId for CrateId<'_> {
125 fn get_internal_id(&self) -> salsa::Id {
126 self.0
127 }
128}
129
130#[derive(Clone, Debug, Hash, PartialEq, Eq, HeapSize)]
132pub struct FlagLongId(pub String);
133define_short_id!(FlagId, FlagLongId);
134
135#[derive(Clone, Debug, Hash, PartialEq, Eq)]
138pub enum FileInput {
139 OnDisk(PathBuf),
140 Virtual(VirtualFileInput),
141 External(salsa::Id),
142}
143
144impl FileInput {
145 pub fn into_file_long_id(self, db: &dyn Database) -> FileLongId<'_> {
146 match self {
147 FileInput::OnDisk(path) => FileLongId::OnDisk(path),
148 FileInput::Virtual(vf) => FileLongId::Virtual(vf.into_virtual_file(db)),
149 FileInput::External(id) => FileLongId::External(id),
150 }
151 }
152}
153
154#[derive(Clone, Debug, Hash, PartialEq, Eq, HeapSize)]
157pub enum FileLongId<'db> {
158 OnDisk(PathBuf),
159 Virtual(VirtualFile<'db>),
160 External(salsa::Id),
161}
162#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, HeapSize)]
164pub enum FileKind {
165 Module,
166 Expr,
167 StatementList,
168}
169
170#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, HeapSize)]
172pub struct CodeMapping {
173 pub span: TextSpan,
174 pub origin: CodeOrigin,
175}
176impl CodeMapping {
177 pub fn translate(&self, span: TextSpan) -> Option<TextSpan> {
178 if self.span.contains(span) {
179 Some(match self.origin {
180 CodeOrigin::Start(origin_start) => {
181 let start = origin_start.add_width(span.start - self.span.start);
182 TextSpan::new_with_width(start, span.width())
183 }
184 CodeOrigin::Span(span) => span,
185 CodeOrigin::CallSite(span) => span,
186 })
187 } else {
188 None
189 }
190 }
191}
192
193#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, HeapSize)]
195pub enum CodeOrigin {
196 Start(TextOffset),
198 Span(TextSpan),
200 CallSite(TextSpan),
203}
204
205impl CodeOrigin {
206 pub fn as_span(&self) -> Option<TextSpan> {
207 match self {
208 CodeOrigin::Start(_) => None,
209 CodeOrigin::CallSite(_) => None,
210 CodeOrigin::Span(span) => Some(*span),
211 }
212 }
213
214 pub fn start(&self) -> TextOffset {
215 match self {
216 CodeOrigin::Start(start) => *start,
217 CodeOrigin::CallSite(span) => span.start,
218 CodeOrigin::Span(span) => span.start,
219 }
220 }
221}
222
223#[derive(Clone, Debug, Hash, PartialEq, Eq)]
226pub struct VirtualFileInput {
227 pub parent: Option<(Arc<FileInput>, TextSpan)>,
228 pub name: String,
229 pub content: Arc<str>,
230 pub code_mappings: Arc<[CodeMapping]>,
231 pub kind: FileKind,
232 pub original_item_removed: bool,
233}
234
235impl VirtualFileInput {
236 fn into_virtual_file(self, db: &dyn Database) -> VirtualFile<'_> {
237 VirtualFile {
238 parent: self.parent.map(|(id, span)| SpanInFile {
239 file_id: id.as_ref().clone().into_file_long_id(db).intern(db),
240 span,
241 }),
242 name: SmolStrId::from(db, self.name),
243 content: SmolStrId::from(db, self.content),
244 code_mappings: self.code_mappings,
245 kind: self.kind,
246 original_item_removed: self.original_item_removed,
247 }
248 }
249}
250
251#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update, HeapSize)]
252pub struct VirtualFile<'db> {
253 pub parent: Option<SpanInFile<'db>>,
254 pub name: SmolStrId<'db>,
255 pub content: SmolStrId<'db>,
256 pub code_mappings: Arc<[CodeMapping]>,
257 pub kind: FileKind,
258 pub original_item_removed: bool,
262}
263impl<'db> VirtualFile<'db> {
264 fn full_path(&self, db: &'db dyn Database) -> String {
265 if let Some(parent) = self.parent {
266 use std::fmt::Write;
267 let mut f = String::new();
268 parent.fmt_location(&mut f, db).unwrap();
269 write!(&mut f, "[{}]", self.name.long(db)).unwrap();
270 f
271 } else {
272 self.name.to_string(db)
273 }
274 }
275
276 fn into_virtual_file_input(self, db: &dyn Database) -> VirtualFileInput {
277 VirtualFileInput {
278 parent: self
279 .parent
280 .map(|loc| (Arc::new(loc.file_id.long(db).clone().into_file_input(db)), loc.span)),
281 name: self.name.to_string(db),
282 content: Arc::from(self.content.long(db).as_str()),
283 code_mappings: self.code_mappings,
284 kind: self.kind,
285 original_item_removed: self.original_item_removed,
286 }
287 }
288}
289
290impl<'db> FileLongId<'db> {
291 pub fn file_name(&self, db: &'db dyn Database) -> SmolStrId<'db> {
292 match self {
293 FileLongId::OnDisk(path) => SmolStrId::from(
294 db,
295 path.file_name().and_then(|x| x.to_str()).unwrap_or("<unknown>"),
296 ),
297 FileLongId::Virtual(vf) => vf.name,
298 FileLongId::External(external_id) => ext_as_virtual(db, *external_id).name,
299 }
300 }
301 pub fn full_path(&self, db: &'db dyn Database) -> String {
302 match self {
303 FileLongId::OnDisk(path) => path.to_str().unwrap_or("<unknown>").to_string(),
304 FileLongId::Virtual(vf) => vf.full_path(db),
305 FileLongId::External(external_id) => ext_as_virtual(db, *external_id).full_path(db),
306 }
307 }
308 pub fn kind(&self) -> FileKind {
309 match self {
310 FileLongId::OnDisk(_) => FileKind::Module,
311 FileLongId::Virtual(vf) => vf.kind,
312 FileLongId::External(_) => FileKind::Module,
313 }
314 }
315
316 pub fn into_file_input(&self, db: &dyn Database) -> FileInput {
317 match self {
318 FileLongId::OnDisk(path) => FileInput::OnDisk(path.clone()),
319 FileLongId::Virtual(vf) => FileInput::Virtual(vf.clone().into_virtual_file_input(db)),
320 FileLongId::External(id) => FileInput::External(*id),
321 }
322 }
323}
324
325define_short_id!(FileId, FileLongId<'db>);
326impl<'db> FileId<'db> {
327 pub fn new_on_disk(db: &'db dyn Database, path: PathBuf) -> FileId<'db> {
328 FileLongId::OnDisk(path.clean()).intern(db)
329 }
330
331 pub fn file_name(self, db: &'db dyn Database) -> SmolStrId<'db> {
332 self.long(db).file_name(db)
333 }
334
335 pub fn full_path(self, db: &dyn Database) -> String {
336 self.long(db).full_path(db)
337 }
338
339 pub fn kind(self, db: &dyn Database) -> FileKind {
340 self.long(db).kind()
341 }
342}
343
344#[derive(Clone, Debug, Hash, PartialEq, Eq)]
347pub enum DirectoryInput {
348 Real(PathBuf),
349 Virtual { files: BTreeMap<String, FileInput>, dirs: BTreeMap<String, Box<DirectoryInput>> },
350}
351
352impl DirectoryInput {
353 pub fn into_directory(self, db: &dyn Database) -> Directory<'_> {
355 match self {
356 DirectoryInput::Real(path) => Directory::Real(path),
357 DirectoryInput::Virtual { files, dirs } => Directory::Virtual {
358 files: files
359 .into_iter()
360 .map(|(name, file_input)| (name, file_input.into_file_long_id(db).intern(db)))
361 .collect(),
362 dirs: dirs
363 .into_iter()
364 .map(|(name, dir_input)| (name, Box::new(dir_input.into_directory(db))))
365 .collect(),
366 },
367 }
368 }
369}
370
371#[derive(Clone, Hash, PartialEq, Eq)]
372pub struct ArcStr(Arc<str>);
373
374impl ArcStr {
375 pub fn new(s: Arc<str>) -> Self {
376 ArcStr(s)
377 }
378}
379
380impl std::ops::Deref for ArcStr {
381 type Target = Arc<str>;
382
383 fn deref(&self) -> &Self::Target {
384 &self.0
385 }
386}
387
388impl std::fmt::Display for ArcStr {
389 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390 self.0.fmt(f)
391 }
392}
393
394impl std::fmt::Debug for ArcStr {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 self.0.fmt(f)
397 }
398}
399
400unsafe impl salsa::Update for ArcStr {
401 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
402 let old_str: &mut Self = unsafe { &mut *old_pointer };
403
404 if Arc::ptr_eq(&old_str.0, &new_value.0) {
406 return false;
407 }
408 if old_str.0 == new_value.0 {
410 return false;
411 }
412 *old_str = new_value;
414 true
415 }
416}
417
418define_short_id!(SmolStrId, SmolStr);
419
420pub trait DbJoin {
421 fn join(&self, db: &dyn Database, separator: &str) -> String;
422}
423
424impl<'db> DbJoin for Vec<SmolStrId<'db>> {
425 fn join(&self, db: &dyn Database, separator: &str) -> String {
426 self.iter().map(|id| id.long(db)).join(separator)
427 }
428}
429
430impl<'db> SmolStrId<'db> {
431 pub fn from(db: &'db dyn Database, content: impl Into<SmolStr>) -> Self {
432 SmolStrId::new(db, content.into())
433 }
434
435 pub fn from_arcstr(db: &'db dyn Database, content: &Arc<str>) -> Self {
436 SmolStrId::from(db, content.clone())
437 }
438
439 pub fn to_string(&self, db: &dyn Database) -> String {
440 self.long(db).to_string()
441 }
442}
443
444#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)]
445pub enum Directory<'db> {
446 Real(PathBuf),
448 Virtual { files: BTreeMap<String, FileId<'db>>, dirs: BTreeMap<String, Box<Directory<'db>>> },
450}
451
452impl<'db> Directory<'db> {
453 pub fn file(&self, db: &'db dyn Database, name: &str) -> FileId<'db> {
456 match self {
457 Directory::Real(path) => FileId::new_on_disk(db, path.join(name)),
458 Directory::Virtual { files, dirs: _ } => files
459 .get(name)
460 .copied()
461 .unwrap_or_else(|| FileId::new_on_disk(db, PathBuf::from(name))),
462 }
463 }
464
465 pub fn subdir(&self, name: &'db str) -> Directory<'db> {
468 match self {
469 Directory::Real(path) => Directory::Real(path.join(name)),
470 Directory::Virtual { files: _, dirs } => {
471 if let Some(dir) = dirs.get(name) {
472 dir.as_ref().clone()
473 } else {
474 Directory::Virtual { files: BTreeMap::new(), dirs: BTreeMap::new() }
475 }
476 }
477 }
478 }
479
480 pub fn into_directory_input(self, db: &dyn Database) -> DirectoryInput {
482 match self {
483 Directory::Real(path) => DirectoryInput::Real(path),
484 Directory::Virtual { files, dirs } => DirectoryInput::Virtual {
485 files: files
486 .into_iter()
487 .map(|(name, file_id)| (name, file_id.long(db).clone().into_file_input(db)))
488 .collect(),
489 dirs: dirs
490 .into_iter()
491 .map(|(name, dir)| (name, Box::new(dir.into_directory_input(db))))
492 .collect(),
493 },
494 }
495 }
496}
497
498#[derive(Clone, Debug, Hash, PartialEq, Eq, HeapSize)]
500pub enum BlobLongId {
501 OnDisk(PathBuf),
502 Virtual(Vec<u8>),
503}
504
505impl BlobLongId {
506 pub fn content(&self) -> Option<Vec<u8>> {
507 match self {
508 BlobLongId::OnDisk(path) => std::fs::read(path).ok(),
509 BlobLongId::Virtual(content) => Some(content.clone()),
510 }
511 }
512}
513
514define_short_id!(BlobId, BlobLongId);
515
516impl<'db> BlobId<'db> {
517 pub fn new_on_disk(db: &'db (dyn salsa::Database + 'db), path: PathBuf) -> Self {
518 BlobId::new(db, BlobLongId::OnDisk(path.clean()))
519 }
520}
521
522#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, HeapSize)]
524pub struct SpanInFile<'db> {
525 pub file_id: FileId<'db>,
526 pub span: TextSpan,
527}
528impl<'db> SpanInFile<'db> {
529 pub fn after(&self) -> Self {
531 Self { file_id: self.file_id, span: self.span.after() }
532 }
533 pub fn user_location(&self, db: &'db dyn Database) -> Self {
535 get_originating_location(db, *self, None)
536 }
537 pub fn fmt_location(&self, f: &mut impl fmt::Write, db: &'db dyn Database) -> fmt::Result {
539 let file_path = self.file_id.long(db).full_path(db);
540 let start = match self.span.start.position_in_file(db, self.file_id) {
541 Some(pos) => format!("{}:{}", pos.line + 1, pos.col + 1),
542 None => "?".into(),
543 };
544
545 let end = match self.span.end.position_in_file(db, self.file_id) {
546 Some(pos) => format!("{}:{}", pos.line + 1, pos.col + 1),
547 None => "?".into(),
548 };
549 write!(f, "{file_path}:{start}: {end}")
550 }
551}
552impl<'db> DebugWithDb<'db> for SpanInFile<'db> {
553 type Db = dyn Database;
554
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db dyn Database) -> std::fmt::Result {
556 let file_path = self.file_id.long(db).full_path(db);
557 let mut marks = String::new();
558 let mut ending_pos = String::new();
559 let starting_pos = match self.span.start.position_in_file(db, self.file_id) {
560 Some(starting_text_pos) => {
561 if let Some(ending_text_pos) = self.span.end.position_in_file(db, self.file_id)
562 && starting_text_pos.line != ending_text_pos.line
563 {
564 ending_pos = format!("-{}:{}", ending_text_pos.line + 1, ending_text_pos.col);
565 }
566 marks = get_location_marks(db, self, true);
567 format!("{}:{}", starting_text_pos.line + 1, starting_text_pos.col + 1)
568 }
569 None => "?".into(),
570 };
571 write!(f, "{file_path}:{starting_pos}{ending_pos}\n{marks}")
572 }
573}
574
575pub type Tracked = ();