cairo_lang_filesystem/
ids.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use cairo_lang_utils::{Intern, LookupIntern, define_short_id};
6use path_clean::PathClean;
7use smol_str::SmolStr;
8
9use crate::db::{CORELIB_CRATE_NAME, FilesGroup};
10use crate::span::{TextOffset, TextSpan};
11
12pub const CAIRO_FILE_EXTENSION: &str = "cairo";
13
14/// A crate is a standalone file tree representing a single compilation unit.
15#[derive(Clone, Debug, Hash, PartialEq, Eq)]
16pub enum CrateLongId {
17    /// A crate that appears in crate_roots(), and on the filesystem.
18    Real { name: SmolStr, discriminator: Option<SmolStr> },
19    /// A virtual crate, not a part of the crate_roots(). Used mainly for tests.
20    Virtual { name: SmolStr, file_id: FileId, settings: String },
21}
22impl CrateLongId {
23    pub fn name(&self) -> SmolStr {
24        match self {
25            CrateLongId::Real { name, .. } | CrateLongId::Virtual { name, .. } => name.clone(),
26        }
27    }
28}
29define_short_id!(CrateId, CrateLongId, FilesGroup, lookup_intern_crate, intern_crate);
30impl CrateId {
31    /// Gets the crate id for a real crate by name, without a discriminator.
32    pub fn plain(
33        db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized),
34        name: &str,
35    ) -> Self {
36        CrateLongId::Real { name: name.into(), discriminator: None }.intern(db)
37    }
38
39    /// Gets the crate id for `core`.
40    pub fn core(db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized)) -> Self {
41        CrateLongId::Real { name: CORELIB_CRATE_NAME.into(), discriminator: None }.intern(db)
42    }
43
44    pub fn name(&self, db: &dyn FilesGroup) -> SmolStr {
45        self.lookup_intern(db).name()
46    }
47}
48
49/// A trait for getting the internal salsa::InternId of a short id object.
50///
51/// This id is unstable across runs and should not be used to anything that is externally visible.
52/// This is currently used to pick representative for strongly connected components.
53pub trait UnstableSalsaId {
54    fn get_internal_id(&self) -> &salsa::InternId;
55}
56impl UnstableSalsaId for CrateId {
57    fn get_internal_id(&self) -> &salsa::InternId {
58        &self.0
59    }
60}
61
62/// The long ID for a compilation flag.
63#[derive(Clone, Debug, Hash, PartialEq, Eq)]
64pub struct FlagLongId(pub SmolStr);
65define_short_id!(FlagId, FlagLongId, FilesGroup, lookup_intern_flag, intern_flag);
66impl FlagId {
67    pub fn new(db: &dyn FilesGroup, name: &str) -> Self {
68        FlagLongId(name.into()).intern(db)
69    }
70}
71
72/// We use a higher level FileId struct, because not all files are on disk. Some might be online.
73/// Some might be virtual/computed on demand.
74#[derive(Clone, Debug, Hash, PartialEq, Eq)]
75pub enum FileLongId {
76    OnDisk(PathBuf),
77    Virtual(VirtualFile),
78    External(salsa::InternId),
79}
80/// Whether the file holds syntax for a module or for an expression.
81#[derive(Clone, Debug, Hash, PartialEq, Eq)]
82pub enum FileKind {
83    Module,
84    Expr,
85}
86
87/// A mapping for a code rewrite.
88#[derive(Clone, Debug, Hash, PartialEq, Eq)]
89pub struct CodeMapping {
90    pub span: TextSpan,
91    pub origin: CodeOrigin,
92}
93impl CodeMapping {
94    pub fn translate(&self, span: TextSpan) -> Option<TextSpan> {
95        if self.span.contains(span) {
96            Some(match self.origin {
97                CodeOrigin::Start(origin_start) => {
98                    let start = origin_start.add_width(span.start - self.span.start);
99                    TextSpan { start, end: start.add_width(span.width()) }
100                }
101                CodeOrigin::Span(span) => span,
102            })
103        } else {
104            None
105        }
106    }
107}
108
109/// The origin of a code mapping.
110#[derive(Clone, Debug, Hash, PartialEq, Eq)]
111pub enum CodeOrigin {
112    /// The origin is a copied node staring at the given offset.
113    Start(TextOffset),
114    /// The origin was generated from this span, but there's no direct mapping.
115    Span(TextSpan),
116}
117
118#[derive(Clone, Debug, Hash, PartialEq, Eq)]
119pub struct VirtualFile {
120    pub parent: Option<FileId>,
121    pub name: SmolStr,
122    pub content: Arc<str>,
123    pub code_mappings: Arc<[CodeMapping]>,
124    pub kind: FileKind,
125}
126impl VirtualFile {
127    fn full_path(&self, db: &dyn FilesGroup) -> String {
128        if let Some(parent) = self.parent {
129            // TODO(yuval): consider a different path format for virtual files.
130            format!("{}[{}]", parent.full_path(db), self.name)
131        } else {
132            self.name.clone().into()
133        }
134    }
135}
136
137define_short_id!(FileId, FileLongId, FilesGroup, lookup_intern_file, intern_file);
138impl FileId {
139    pub fn new(db: &dyn FilesGroup, path: PathBuf) -> FileId {
140        FileLongId::OnDisk(path.clean()).intern(db)
141    }
142    pub fn file_name(self, db: &dyn FilesGroup) -> String {
143        match self.lookup_intern(db) {
144            FileLongId::OnDisk(path) => {
145                path.file_name().and_then(|x| x.to_str()).unwrap_or("<unknown>").to_string()
146            }
147            FileLongId::Virtual(vf) => vf.name.to_string(),
148            FileLongId::External(external_id) => db.ext_as_virtual(external_id).name.to_string(),
149        }
150    }
151    pub fn full_path(self, db: &dyn FilesGroup) -> String {
152        match self.lookup_intern(db) {
153            FileLongId::OnDisk(path) => path.to_str().unwrap_or("<unknown>").to_string(),
154            FileLongId::Virtual(vf) => vf.full_path(db),
155            FileLongId::External(external_id) => db.ext_as_virtual(external_id).full_path(db),
156        }
157    }
158    pub fn kind(self, db: &dyn FilesGroup) -> FileKind {
159        match self.lookup_intern(db) {
160            FileLongId::OnDisk(_) => FileKind::Module,
161            FileLongId::Virtual(vf) => vf.kind.clone(),
162            FileLongId::External(_) => FileKind::Module,
163        }
164    }
165}
166
167#[derive(Clone, Debug, Hash, PartialEq, Eq)]
168pub enum Directory {
169    /// A directory on the file system.
170    Real(PathBuf),
171    /// A virtual directory, not on the file system. Used mainly for virtual crates.
172    Virtual { files: BTreeMap<SmolStr, FileId>, dirs: BTreeMap<SmolStr, Box<Directory>> },
173}
174
175impl Directory {
176    /// Returns a file inside this directory. The file and directory don't necessarily exist on
177    /// the file system. These are ids/paths to them.
178    pub fn file(&self, db: &dyn FilesGroup, name: SmolStr) -> FileId {
179        match self {
180            Directory::Real(path) => FileId::new(db, path.join(name.to_string())),
181            Directory::Virtual { files, dirs: _ } => files
182                .get(&name)
183                .copied()
184                .unwrap_or_else(|| FileId::new(db, PathBuf::from(name.as_str()))),
185        }
186    }
187
188    /// Returns a sub directory inside this directory. These directories don't necessarily exist on
189    /// the file system. These are ids/paths to them.
190    pub fn subdir(&self, name: SmolStr) -> Directory {
191        match self {
192            Directory::Real(path) => Directory::Real(path.join(name.to_string())),
193            Directory::Virtual { files: _, dirs } => {
194                if let Some(dir) = dirs.get(&name) {
195                    dir.as_ref().clone()
196                } else {
197                    Directory::Virtual { files: BTreeMap::new(), dirs: BTreeMap::new() }
198                }
199            }
200        }
201    }
202}