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