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