cairo_lang_compiler/
db.rs

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