cairo_lang_compiler/
db.rs

1use std::sync::Arc;
2
3use anyhow::{Result, anyhow, bail};
4use cairo_lang_defs::db::{DefsDatabase, DefsGroup, init_defs_group, try_ext_as_virtual_impl};
5use cairo_lang_filesystem::cfg::CfgSet;
6use cairo_lang_filesystem::db::{
7    AsFilesGroupMut, CORELIB_VERSION, ExternalFiles, FilesDatabase, FilesGroup, FilesGroupEx,
8    init_dev_corelib, init_files_group,
9};
10use cairo_lang_filesystem::detect::detect_corelib;
11use cairo_lang_filesystem::flag::Flag;
12use cairo_lang_filesystem::ids::{CrateId, FlagId, VirtualFile};
13use cairo_lang_lowering::db::{LoweringDatabase, LoweringGroup, init_lowering_group};
14use cairo_lang_parser::db::{ParserDatabase, ParserGroup};
15use cairo_lang_project::ProjectConfig;
16use cairo_lang_semantic::db::{
17    PluginSuiteInput, SemanticDatabase, SemanticGroup, init_semantic_group,
18};
19use cairo_lang_semantic::inline_macros::get_default_plugin_suite;
20use cairo_lang_semantic::plugin::PluginSuite;
21use cairo_lang_sierra_generator::db::{SierraGenDatabase, SierraGenGroup};
22use cairo_lang_syntax::node::db::{SyntaxDatabase, SyntaxGroup};
23use cairo_lang_utils::Upcast;
24
25use crate::InliningStrategy;
26use crate::project::update_crate_roots_from_project_config;
27
28#[salsa::database(
29    DefsDatabase,
30    FilesDatabase,
31    LoweringDatabase,
32    ParserDatabase,
33    SemanticDatabase,
34    SierraGenDatabase,
35    SyntaxDatabase
36)]
37pub struct RootDatabase {
38    storage: salsa::Storage<RootDatabase>,
39}
40impl salsa::Database for RootDatabase {}
41impl ExternalFiles for RootDatabase {
42    fn try_ext_as_virtual(&self, external_id: salsa::InternId) -> Option<VirtualFile> {
43        try_ext_as_virtual_impl(self.upcast(), external_id)
44    }
45}
46impl salsa::ParallelDatabase for RootDatabase {
47    fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
48        salsa::Snapshot::new(RootDatabase { storage: self.storage.snapshot() })
49    }
50}
51impl RootDatabase {
52    fn new(default_plugin_suite: PluginSuite, inlining_strategy: InliningStrategy) -> Self {
53        let mut res = Self { storage: Default::default() };
54        init_files_group(&mut res);
55        init_lowering_group(&mut res, inlining_strategy);
56        init_defs_group(&mut res);
57        init_semantic_group(&mut res);
58
59        let suite = res.intern_plugin_suite(default_plugin_suite);
60        res.set_default_plugins_from_suite(suite);
61
62        res
63    }
64
65    pub fn empty() -> Self {
66        Self::builder().clear_plugins().build().unwrap()
67    }
68
69    pub fn builder() -> RootDatabaseBuilder {
70        RootDatabaseBuilder::new()
71    }
72
73    /// Snapshots the db for read only.
74    pub fn snapshot(&self) -> RootDatabase {
75        RootDatabase { storage: self.storage.snapshot() }
76    }
77}
78
79impl Default for RootDatabase {
80    fn default() -> Self {
81        Self::builder().build().unwrap()
82    }
83}
84
85#[derive(Clone, Debug)]
86pub struct RootDatabaseBuilder {
87    default_plugin_suite: PluginSuite,
88    detect_corelib: bool,
89    auto_withdraw_gas: bool,
90    panic_backtrace: bool,
91    project_config: Option<Box<ProjectConfig>>,
92    cfg_set: Option<CfgSet>,
93    inlining_strategy: InliningStrategy,
94}
95
96impl RootDatabaseBuilder {
97    fn new() -> Self {
98        Self {
99            default_plugin_suite: get_default_plugin_suite(),
100            detect_corelib: false,
101            auto_withdraw_gas: true,
102            panic_backtrace: false,
103            project_config: None,
104            cfg_set: None,
105            inlining_strategy: InliningStrategy::Default,
106        }
107    }
108
109    pub fn with_default_plugin_suite(&mut self, suite: PluginSuite) -> &mut Self {
110        self.default_plugin_suite.add(suite);
111        self
112    }
113
114    pub fn clear_plugins(&mut self) -> &mut Self {
115        self.default_plugin_suite = get_default_plugin_suite();
116        self
117    }
118
119    pub fn with_inlining_strategy(&mut self, inlining_strategy: InliningStrategy) -> &mut Self {
120        self.inlining_strategy = inlining_strategy;
121        self
122    }
123
124    pub fn detect_corelib(&mut self) -> &mut Self {
125        self.detect_corelib = true;
126        self
127    }
128
129    pub fn with_project_config(&mut self, config: ProjectConfig) -> &mut Self {
130        self.project_config = Some(Box::new(config));
131        self
132    }
133
134    pub fn with_cfg(&mut self, cfg_set: impl Into<CfgSet>) -> &mut Self {
135        self.cfg_set = Some(cfg_set.into());
136        self
137    }
138
139    pub fn skip_auto_withdraw_gas(&mut self) -> &mut Self {
140        self.auto_withdraw_gas = false;
141        self
142    }
143
144    pub fn with_panic_backtrace(&mut self) -> &mut Self {
145        self.panic_backtrace = true;
146        self
147    }
148
149    pub fn build(&mut self) -> Result<RootDatabase> {
150        // NOTE: Order of operations matters here!
151        //   Errors if something is not OK are very subtle, mostly this results in missing
152        //   identifier diagnostics, or panics regarding lack of corelib items.
153
154        let mut db = RootDatabase::new(self.default_plugin_suite.clone(), self.inlining_strategy);
155
156        if let Some(cfg_set) = &self.cfg_set {
157            db.use_cfg(cfg_set);
158        }
159
160        if self.detect_corelib {
161            let path =
162                detect_corelib().ok_or_else(|| anyhow!("Failed to find development corelib."))?;
163            init_dev_corelib(&mut db, path)
164        }
165
166        let add_withdraw_gas_flag_id = FlagId::new(db.upcast(), "add_withdraw_gas");
167        db.set_flag(
168            add_withdraw_gas_flag_id,
169            Some(Arc::new(Flag::AddWithdrawGas(self.auto_withdraw_gas))),
170        );
171        let panic_backtrace_flag_id = FlagId::new(db.upcast(), "panic_backtrace");
172        db.set_flag(
173            panic_backtrace_flag_id,
174            Some(Arc::new(Flag::PanicBacktrace(self.panic_backtrace))),
175        );
176
177        if let Some(config) = &self.project_config {
178            update_crate_roots_from_project_config(&mut db, config.as_ref());
179        }
180        validate_corelib(&db)?;
181
182        Ok(db)
183    }
184}
185
186/// Validates that the corelib version matches the expected one.
187pub fn validate_corelib(db: &(dyn FilesGroup + 'static)) -> Result<()> {
188    let Some(config) = db.crate_config(CrateId::core(db)) else {
189        return Ok(());
190    };
191    let Some(found) = config.settings.version else {
192        return Ok(());
193    };
194    let Ok(expected) = semver::Version::parse(CORELIB_VERSION) else {
195        return Ok(());
196    };
197    if found == expected {
198        return Ok(());
199    }
200    let path_part = match config.root {
201        cairo_lang_filesystem::ids::Directory::Real(path) => {
202            format!(" for `{}`", path.to_string_lossy())
203        }
204        cairo_lang_filesystem::ids::Directory::Virtual { .. } => "".to_string(),
205    };
206    bail!("Corelib version mismatch: expected `{expected}`, found `{found}`{path_part}.");
207}
208
209impl AsFilesGroupMut for RootDatabase {
210    fn as_files_group_mut(&mut self) -> &mut (dyn FilesGroup + 'static) {
211        self
212    }
213}
214impl Upcast<dyn FilesGroup> for RootDatabase {
215    fn upcast(&self) -> &(dyn FilesGroup + 'static) {
216        self
217    }
218}
219impl Upcast<dyn SyntaxGroup> for RootDatabase {
220    fn upcast(&self) -> &(dyn SyntaxGroup + 'static) {
221        self
222    }
223}
224impl Upcast<dyn DefsGroup> for RootDatabase {
225    fn upcast(&self) -> &(dyn DefsGroup + 'static) {
226        self
227    }
228}
229impl Upcast<dyn SemanticGroup> for RootDatabase {
230    fn upcast(&self) -> &(dyn SemanticGroup + 'static) {
231        self
232    }
233}
234impl Upcast<dyn LoweringGroup> for RootDatabase {
235    fn upcast(&self) -> &(dyn LoweringGroup + 'static) {
236        self
237    }
238}
239impl Upcast<dyn SierraGenGroup> for RootDatabase {
240    fn upcast(&self) -> &(dyn SierraGenGroup + 'static) {
241        self
242    }
243}
244impl Upcast<dyn ParserGroup> for RootDatabase {
245    fn upcast(&self) -> &(dyn ParserGroup + 'static) {
246        self
247    }
248}