diff --git a/crates/cairo-lang-compiler/src/db.rs b/crates/cairo-lang-compiler/src/db.rs index 97affd22fe9..91949c6714d 100644 --- a/crates/cairo-lang-compiler/src/db.rs +++ b/crates/cairo-lang-compiler/src/db.rs @@ -2,7 +2,10 @@ use anyhow::{Result, anyhow, bail}; use cairo_lang_defs::db::{init_defs_group, init_external_files}; use cairo_lang_diagnostics::Maybe; use cairo_lang_filesystem::cfg::CfgSet; -use cairo_lang_filesystem::db::{CORELIB_VERSION, FilesGroup, init_dev_corelib, init_files_group}; +use cairo_lang_filesystem::db::{ + CORELIB_VERSION, FileContentStorage, FileContentView, FilesGroup, init_dev_corelib, + init_files_group, register_files_group_view, +}; use cairo_lang_filesystem::detect::detect_corelib; use cairo_lang_filesystem::flag::{Flag, FlagsGroup}; use cairo_lang_filesystem::ids::{CrateId, FlagLongId}; @@ -69,9 +72,15 @@ fn estimate_code_size( #[derive(Clone)] pub struct RootDatabase { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl salsa::Database for RootDatabase {} +impl FileContentView for RootDatabase { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl CloneableDatabase for RootDatabase { fn dyn_clone(&self) -> Box { Box::new(self.clone()) @@ -80,7 +89,8 @@ impl CloneableDatabase for RootDatabase { impl RootDatabase { fn new(default_plugin_suite: PluginSuite, optimizations: Optimizations) -> Self { - let mut res = Self { storage: Default::default() }; + let mut res = Self { storage: Default::default(), file_contents: Default::default() }; + register_files_group_view(&res); init_external_files(&mut res); init_files_group(&mut res); init_lowering_group(&mut res, optimizations, Some(estimate_code_size)); @@ -103,7 +113,7 @@ impl RootDatabase { /// Snapshots the db for read only. pub fn snapshot(&self) -> RootDatabase { - RootDatabase { storage: self.storage.clone() } + RootDatabase { storage: self.storage.clone(), file_contents: self.file_contents.clone() } } } diff --git a/crates/cairo-lang-compiler/src/project.rs b/crates/cairo-lang-compiler/src/project.rs index 6298464e449..32df7adc702 100644 --- a/crates/cairo-lang-compiler/src/project.rs +++ b/crates/cairo-lang-compiler/src/project.rs @@ -5,9 +5,10 @@ use cairo_lang_defs::db::DefsGroup; use cairo_lang_defs::ids::ModuleId; use cairo_lang_filesystem::db::{ CORELIB_CRATE_NAME, CrateConfiguration, CrateIdentifier, CrateSettings, FilesGroup, + set_generated_file_content_for_input, }; use cairo_lang_filesystem::ids::{CrateId, CrateInput, CrateLongId, Directory, SmolStrId}; -use cairo_lang_filesystem::{override_file_content, set_crate_config}; +use cairo_lang_filesystem::set_crate_config; pub use cairo_lang_project::*; use cairo_lang_utils::Intern; use salsa::Database; @@ -55,17 +56,26 @@ pub fn setup_single_file_project( Ok(crate_id.long(db).clone().into_crate_input(db)) } else { // If file_stem is not lib, create a fake lib file. - let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem)); - set_crate_config!( + { + let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem)); + set_crate_config!( + db, + crate_id, + Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf()))) + ); + } + let file_input = { + let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem)); + let module_id = ModuleId::CrateRoot(crate_id); + let file_id = db.module_main_file(module_id).unwrap(); + db.file_input(file_id).clone() + }; + set_generated_file_content_for_input( db, - crate_id, - Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf()))) + file_input, + Some(format!("mod {file_stem};").into()), ); let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem)); - let module_id = ModuleId::CrateRoot(crate_id); - let file_id = db.module_main_file(module_id).unwrap(); - override_file_content!(db, file_id, Some(format!("mod {file_stem};").into())); - let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem)); Ok(crate_id.long(db).clone().into_crate_input(db)) } } diff --git a/crates/cairo-lang-defs/src/test.rs b/crates/cairo-lang-defs/src/test.rs index c54b3fb2adc..4810ec70d7c 100644 --- a/crates/cairo-lang-defs/src/test.rs +++ b/crates/cairo-lang-defs/src/test.rs @@ -2,9 +2,12 @@ use std::fmt::Write as _; use std::sync::Arc; use cairo_lang_debug::debug::DebugWithDb; -use cairo_lang_filesystem::db::{CrateConfiguration, FilesGroup, init_files_group}; +use cairo_lang_filesystem::db::{ + CrateConfiguration, FileContentStorage, FileContentView, FilesGroup, init_files_group, + override_file_content_for_input, register_files_group_view, +}; use cairo_lang_filesystem::ids::{CrateId, Directory, FileLongId, SmolStrId}; -use cairo_lang_filesystem::{override_file_content, set_crate_config}; +use cairo_lang_filesystem::set_crate_config; use cairo_lang_parser::db::ParserGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::kind::SyntaxKind; @@ -28,13 +31,20 @@ use crate::plugin::{ #[derive(Clone)] pub struct DatabaseForTesting { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl salsa::Database for DatabaseForTesting {} +impl FileContentView for DatabaseForTesting { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl Default for DatabaseForTesting { fn default() -> Self { - let mut res = Self { storage: Default::default() }; + let mut res = Self { storage: Default::default(), file_contents: Default::default() }; + register_files_group_view(&res); init_external_files(&mut res); init_files_group(&mut res); init_defs_group(&mut res); @@ -109,9 +119,12 @@ pub fn setup_test_module(db: &mut dyn Database, content: &str) { let crate_id = get_crate_id(db); let directory = Directory::Real("src".into()); set_crate_config!(db, crate_id, Some(CrateConfiguration::default_for_root(directory))); - let crate_id = get_crate_id(db); - let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); - override_file_content!(db, file, Some(content.into())); + let file_input = { + let crate_id = get_crate_id(db); + let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); + db.file_input(file).clone() + }; + override_file_content_for_input(db, file_input, Some(content.into())); let crate_id = get_crate_id(db); let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); let syntax_diagnostics = db.file_syntax_diagnostics(file).format(db); @@ -148,8 +161,11 @@ fn test_module_file() { macro_rules! set_file_content { ($db:expr, $path:expr, $content:expr) => { - let file_id = FileLongId::OnDisk($path.into()).intern($db); - override_file_content!($db, file_id, Some($content.into())); + let file_input = { + let file_id = FileLongId::OnDisk($path.into()).intern($db); + $db.file_input(file_id).clone() + }; + override_file_content_for_input($db, file_input, Some($content.into())); }; } diff --git a/crates/cairo-lang-doc/src/tests/test_utils.rs b/crates/cairo-lang-doc/src/tests/test_utils.rs index 865b6336558..228591f0955 100644 --- a/crates/cairo-lang-doc/src/tests/test_utils.rs +++ b/crates/cairo-lang-doc/src/tests/test_utils.rs @@ -2,11 +2,12 @@ use anyhow::{Result, anyhow}; use cairo_lang_defs::db::{DefsGroup, init_defs_group}; use cairo_lang_defs::ids::ModuleId; use cairo_lang_filesystem::db::{ - CrateConfiguration, FilesGroup, init_dev_corelib, init_files_group, + CrateConfiguration, FileContentStorage, FileContentView, FilesGroup, init_dev_corelib, + init_files_group, override_file_content_for_input, register_files_group_view, }; use cairo_lang_filesystem::detect::detect_corelib; use cairo_lang_filesystem::ids::{CrateId, Directory, FileLongId, SmolStrId}; -use cairo_lang_filesystem::{override_file_content, set_crate_config}; +use cairo_lang_filesystem::set_crate_config; use cairo_lang_parser::db::ParserGroup; use cairo_lang_semantic::db::{PluginSuiteInput, init_semantic_group}; use cairo_lang_semantic::plugin::PluginSuite; @@ -17,13 +18,20 @@ use salsa::Database; #[derive(Clone)] pub struct TestDatabase { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl salsa::Database for TestDatabase {} +impl FileContentView for TestDatabase { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl Default for TestDatabase { fn default() -> Self { - let mut res = Self { storage: Default::default() }; + let mut res = Self { storage: Default::default(), file_contents: Default::default() }; + register_files_group_view(&res); init_files_group(&mut res); init_defs_group(&mut res); init_semantic_group(&mut res); @@ -49,9 +57,12 @@ pub fn setup_test_module(db: &mut dyn Database, content: &str) { let crate_id = test_crate_id(db); let directory = Directory::Real("src".into()); set_crate_config!(db, crate_id, Some(CrateConfiguration::default_for_root(directory))); - let crate_id = test_crate_id(db); - let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); - override_file_content!(db, file, Some(content.into())); + let file_input = { + let crate_id = test_crate_id(db); + let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); + db.file_input(file).clone() + }; + override_file_content_for_input(db, file_input, Some(content.into())); let crate_id = test_crate_id(db); let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); let syntax_diagnostics = db.file_syntax_diagnostics(file).format(db); @@ -66,12 +77,18 @@ pub fn setup_test_module_without_syntax_diagnostics(db: &mut dyn Database, conte let crate_id = test_crate_id(db); let directory = Directory::Real("src".into()); set_crate_config!(db, crate_id, Some(CrateConfiguration::default_for_root(directory))); - let crate_id = test_crate_id(db); - let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); - override_file_content!(db, file, Some(content.into())); + let file_input = { + let crate_id = test_crate_id(db); + let file = db.module_main_file(ModuleId::CrateRoot(crate_id)).unwrap(); + db.file_input(file).clone() + }; + override_file_content_for_input(db, file_input, Some(content.into())); } pub fn set_file_content(db: &mut TestDatabase, path: &str, content: &str) { - let file_id = FileLongId::OnDisk(path.into()).intern(db); - override_file_content!(db, file_id, Some(content.into())); + let file_input = { + let file_id = FileLongId::OnDisk(path.into()).intern(db); + db.file_input(file_id).clone() + }; + override_file_content_for_input(db, file_input, Some(content.into())); } diff --git a/crates/cairo-lang-filesystem/src/db.rs b/crates/cairo-lang-filesystem/src/db.rs index a585135098e..e4c889f15f7 100644 --- a/crates/cairo-lang-filesystem/src/db.rs +++ b/crates/cairo-lang-filesystem/src/db.rs @@ -1,11 +1,14 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::fs; +use std::panic::{AssertUnwindSafe, catch_unwind}; use std::path::PathBuf; -use std::sync::Arc; +use std::ptr::NonNull; +use std::sync::{Arc, RwLock}; use cairo_lang_utils::Intern; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use salsa::{Database, Setter}; +use salsa::plumbing::views; +use salsa::{Database, Durability, Setter}; use semver::Version; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; @@ -231,9 +234,16 @@ pub struct FilesGroupInput { /// Main input of the project. Lists all the crates configurations. #[returns(ref)] pub crate_configs: Option>, - /// Overrides for file content. Mostly used by language server and tests. - #[returns(ref)] - pub file_overrides: Option>>, + /// Structural revision bumped each time a new [`FileContentOverride`] handle is first created + /// for a previously-unregistered file. + /// + /// Salsa only invalidates tracked functions based on dependencies they recorded during their + /// last execution. A [`file_content`] call for a file with no handle records no dependency on + /// that file's future [`FileContentOverride`] — so if a handle is later created, Salsa has no + /// recorded dependency to invalidate. This field provides the missing signal: [`file_content`] + /// reads it for handleless files, ensuring it re-executes when any new handle is created and + /// can then record the fine-grained per-file dependency going forward. + pub file_contents_revision: u64, // TODO(yuval): consider moving this to a separate crate, or rename this crate. /// The compilation flags. #[returns(ref)] @@ -247,7 +257,81 @@ pub struct FilesGroupInput { #[salsa::tracked] pub fn files_group_input(db: &dyn Database) -> FilesGroupInput { - FilesGroupInput::new(db, None, None, None, None, None) + FilesGroupInput::new(db, None, 0, None, None, None) +} + +/// Input used for overriding the content of a single file. +/// Useful for tools like CairoLS to manage contents of files that are opened in the editor. +/// +/// Notice that this structure holds a content override for a single file only — not the whole +/// hashmap of overrides for all overridden files. This is done to ensure granular invalidation of +/// results of the [`file_content`] tracked function — so that we don't invalidate everything when +/// a single override changes. +#[salsa::input] +pub struct FileContentOverride { + #[returns(ref)] + pub content: Option, +} + +/// Side-table mapping each file to its [`FileContentOverride`] Salsa input. +/// +/// Lives on the concrete database struct (outside Salsa storage) so that new handles can be +/// created with `&mut dyn Database` while existing ones are read from within tracked functions +/// via [`FileContentView`]. +pub type FileContentStorage = Arc>>; + +/// View trait for accessing the [`FileContentStorage`] side-table from within tracked functions. +/// Implement this on the concrete DB struct and register it with [`register_files_group_view`]. +pub trait FileContentView: Database { + /// Returns the [`FileContentStorage`] side-table, or `None` if this database does not hold + /// file content overrides. + fn file_content_storage(&self) -> Option<&FileContentStorage> { + None + } + + /// Returns the [`FileContentOverride`] handle for the given file input, if one has been + /// registered. + fn file_contents_for_input(&self, file_input: &FileInput) -> Option { + self.file_content_storage()?.read().unwrap().get(file_input).copied() + } + + /// Returns the overridden content for the given file, if one exists. + fn file_content_override<'db>(&'db self, file_id: FileId<'db>) -> Option<&'db ArcStr> { + let file_input = self.file_input(file_id).clone(); + self.file_contents_for_input(&file_input)?.content(self).as_ref() + } +} + +/// Downcasts `db` to `&dyn FileContentView` through the registered Salsa view. +/// Panics if [`register_files_group_view`] was not called for this database type. +fn file_content_view(db: &dyn Database) -> &dyn FileContentView { + let caster = + catch_unwind(AssertUnwindSafe(|| *views(db).downcaster_for::())).ok(); + + let caster = caster.expect("file content view is not registered"); + // SAFETY: The downcaster was fetched for the concrete type backing `db`. + unsafe { caster.downcast_unchecked(db.into()) } +} + +/// Downcasts `db` to `&dyn FileContentView`, or returns `None` if +/// [`register_files_group_view`] was not called for this database type. +fn maybe_file_content_view(db: &dyn Database) -> Option<&dyn FileContentView> { + catch_unwind(AssertUnwindSafe(|| file_content_view(db))).ok() +} + +/// Registers the concrete database type `Db` as a [`FileContentView`]. +/// +/// Must be called once during database initialization for any database struct that holds a +/// [`FileContentStorage`]. After registration, tracked functions can call +/// `maybe_file_content_view(db)` to reach the side-table through the type-erased view. +pub fn register_files_group_view(db: &Db) +where + Db: Database + FileContentView + 'static, +{ + views(db).add::(|db: NonNull| { + // SAFETY: `db` points to a live `Db` of the registered concrete database type. + NonNull::from(unsafe { db.as_ref() } as &dyn FileContentView) + }); } /// Queries over the files group. @@ -257,11 +341,6 @@ pub trait FilesGroup: Database { crate_configs(self.as_dyn_database()) } - /// Interned version of `file_overrides_input`. - fn file_overrides<'db>(&'db self) -> &'db OrderedHashMap, ArcStr> { - file_overrides(self.as_dyn_database()) - } - /// List of crates in the project. fn crates<'db>(&'db self) -> &'db [CrateId<'db>] { crates(self.as_dyn_database()) @@ -318,12 +397,19 @@ impl FilesGroup for T {} pub fn init_files_group<'db>(db: &mut (dyn Database + 'db)) { // Initialize inputs. let inp = files_group_input(db); - inp.set_file_overrides(db).to(Some(Default::default())); inp.set_crate_configs(db).to(Some(Default::default())); + inp.set_file_contents_revision(db).to(0); inp.set_flags(db).to(Some(Default::default())); inp.set_cfg_set(db).to(Some(Default::default())); } +/// Increments [`FilesGroupInput::file_contents_revision`] to signal that a new +/// [`FileContentOverride`] handle has been created for a previously-unregistered file. +fn bump_file_contents_revision(db: &mut dyn Database) { + let next = files_group_input(db).file_contents_revision(db).saturating_add(1); + files_group_input(db).set_file_contents_revision(db).to(next); +} + pub fn set_crate_configs_input( db: &mut dyn Database, crate_configs: Option>, @@ -331,16 +417,6 @@ pub fn set_crate_configs_input( files_group_input(db).set_crate_configs(db).to(crate_configs); } -#[salsa::tracked(returns(ref))] -pub fn file_overrides<'db>(db: &'db dyn Database) -> OrderedHashMap, ArcStr> { - let inp = files_group_input(db).file_overrides(db).as_ref().expect("file_overrides is not set"); - inp.iter() - .map(|(file_id, content)| { - (file_id.clone().into_file_long_id(db).intern(db), ArcStr::new(content.clone())) - }) - .collect() -} - #[salsa::tracked(returns(ref))] pub fn crate_configs<'db>( db: &'db dyn Database, @@ -431,32 +507,71 @@ macro_rules! set_crate_config { }; } -/// Updates file overrides input for standalone use. -pub fn update_file_overrides_input_helper( - db: &dyn Database, - file: FileInput, +fn ensure_file_contents_handle_for_input( + db: &mut dyn Database, + file_input: FileInput, +) -> FileContentOverride { + if let Some(handle) = file_content_view(db).file_contents_for_input(&file_input) { + return handle; + } + + let handle = FileContentOverride::new(db, None); + file_content_view(db) + .file_content_storage() + .expect("file content storage is not registered") + .write() + .unwrap() + .insert(file_input, handle); + bump_file_contents_revision(db); + handle +} + +/// Overrides the on-disk content of a file as seen by the compiler. +/// Used by CairoLS to make the compiler aware of unsaved edits. +pub fn override_file_content_for_input( + db: &mut dyn Database, + file_input: FileInput, content: Option>, -) -> OrderedHashMap> { - let db_ref: &dyn Database = db; - let mut overrides = files_group_input(db_ref).file_overrides(db_ref).clone().unwrap(); - match content { - Some(content) => overrides.insert(file, content), - None => overrides.swap_remove(&file), - }; - overrides +) { + let handle = ensure_file_contents_handle_for_input(db, file_input); + // LOW durability: on-disk overrides change frequently for files opened in editor in CairoLS. + // For regular compiling use cases the durability does not matter, as the inputs do not change + // during compilation. + handle.set_content(db).with_durability(Durability::LOW).to(content.map(ArcStr::new)); } -/// Overrides file content. None value removes the override. -#[macro_export] -macro_rules! override_file_content { - ($self:expr, $file:expr, $content:expr) => { - let file = $self.file_input($file).clone(); - let overrides = $crate::db::update_file_overrides_input_helper($self, file, $content); - salsa::Setter::to( - $crate::db::files_group_input($self).set_file_overrides($self), - Some(overrides), - ); +/// Sets content for a virtual file that does not exist on disk (e.g. a `tests/lib.cairo` +/// synthesized by Scarb when the file is absent and integration tests are defined). +pub fn set_generated_file_content_for_input( + db: &mut dyn Database, + file_input: FileInput, + content: Option>, +) { + let handle = ensure_file_contents_handle_for_input(db, file_input); + // HIGH durability: set rarely. + handle.set_content(db).with_durability(Durability::HIGH).to(content.map(ArcStr::new)); +} + +/// Snapshot of all per-file content overrides currently registered in the database. +pub type FileContentSnapshot = OrderedHashMap>>; + +/// Returns a snapshot of all per-file content overrides registered in this database. +/// Useful for copying overrides from one database to another. +pub fn snapshot_file_contents(db: &dyn Database) -> FileContentSnapshot { + let Some(view) = maybe_file_content_view(db) else { + return Default::default(); + }; + let Some(storage) = view.file_content_storage() else { + return Default::default(); }; + storage + .read() + .unwrap() + .iter() + .map(|(file_input, handle)| { + (file_input.clone(), handle.content(db).as_ref().map(|c| (**c).clone())) + }) + .collect() } fn cfg_set_helper(db: &dyn Database) -> &CfgSet { @@ -535,12 +650,31 @@ fn file_summary_helper<'db>(db: &'db dyn Database, file: FileId<'db>) -> Option< } /// Query implementation of [FilesGroup::file_content]. +/// +/// # Invalidation +/// +/// For files that already have a [`FileContentOverride`] handle, depends directly on +/// [`FileContentOverride::content`] — only that file's query is invalidated when its content +/// changes. +/// +/// For files without a handle, reads [`FilesGroupInput::file_contents_revision`] so the query +/// re-executes when a handle is first created for that file, switching to the per-file dependency +/// from that point on. #[salsa::tracked(returns(ref))] fn file_content<'db>(db: &'db dyn Database, file_id: FileId<'db>) -> Option> { - let overrides = db.file_overrides(); - overrides.get(&file_id).map(|content| (**content).clone()).or_else(|| { - priv_raw_file_content(db, file_id).map(|content| content.long(db).clone().into()) - }) + maybe_file_content_view(db) + .and_then(|view| { + let file_input = view.file_input(file_id); + if let Some(file_contents) = view.file_contents_for_input(file_input) { + file_contents.content(view).as_ref().map(|content| (**content).clone()) + } else { + let _ = files_group_input(db).file_contents_revision(db); + None + } + }) + .or_else(|| { + priv_raw_file_content(db, file_id).map(|content| content.long(db).clone().into()) + }) } /// Returns a reference to the content of a file as a string. diff --git a/crates/cairo-lang-filesystem/src/db_test.rs b/crates/cairo-lang-filesystem/src/db_test.rs index a816340d786..2bf2f70c9df 100644 --- a/crates/cairo-lang-filesystem/src/db_test.rs +++ b/crates/cairo-lang-filesystem/src/db_test.rs @@ -3,11 +3,11 @@ use cairo_lang_utils::Intern; use super::FilesGroup; use crate::cfg::{Cfg, CfgSet}; -use crate::db::CrateConfiguration; +use crate::db::{CrateConfiguration, override_file_content_for_input}; use crate::flag::{Flag, FlagsGroup}; use crate::ids::{CrateLongId, Directory, FlagId, FlagLongId, SmolStrId}; +use crate::set_crate_config; use crate::test_utils::FilesDatabaseForTesting; -use crate::{override_file_content, set_crate_config}; #[test] fn test_filesystem() { @@ -16,8 +16,11 @@ fn test_filesystem() { let directory = Directory::Real("src".into()); let child_str = "child.cairo"; let db_ref = &mut db; - let file_id = directory.file(db_ref, child_str); - override_file_content!(db_ref, file_id, Some("content\n".into())); + let file_input = { + let file_id = directory.file(db_ref, child_str); + db_ref.file_input(file_id).clone() + }; + override_file_content_for_input(db_ref, file_input, Some("content\n".into())); let config = CrateConfiguration::default_for_root(directory.clone()); let crt = CrateLongId::plain(SmolStrId::from(db_ref, "my_crate")).intern(db_ref); diff --git a/crates/cairo-lang-filesystem/src/test_utils.rs b/crates/cairo-lang-filesystem/src/test_utils.rs index 1b4537a3b35..d8c44558b1f 100644 --- a/crates/cairo-lang-filesystem/src/test_utils.rs +++ b/crates/cairo-lang-filesystem/src/test_utils.rs @@ -1,18 +1,25 @@ -use crate::db::init_files_group; +use crate::db::{FileContentStorage, FileContentView, init_files_group, register_files_group_view}; // Test salsa database. #[salsa::db] #[derive(Clone)] pub struct FilesDatabaseForTesting { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl salsa::Database for FilesDatabaseForTesting {} +impl FileContentView for FilesDatabaseForTesting { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl Default for FilesDatabaseForTesting { fn default() -> Self { - let mut res = Self { storage: Default::default() }; + let mut res = Self { storage: Default::default(), file_contents: Default::default() }; + register_files_group_view(&res); init_files_group(&mut res); res } diff --git a/crates/cairo-lang-lowering/src/test_utils.rs b/crates/cairo-lang-lowering/src/test_utils.rs index 263f3dabfde..b0f589bed03 100644 --- a/crates/cairo-lang-lowering/src/test_utils.rs +++ b/crates/cairo-lang-lowering/src/test_utils.rs @@ -2,7 +2,7 @@ use std::sync::{LazyLock, Mutex}; use cairo_lang_debug::DebugWithDb; use cairo_lang_defs::db::{init_defs_group, init_external_files}; -use cairo_lang_filesystem::db::{init_dev_corelib, init_files_group}; +use cairo_lang_filesystem::db::{FileContentView, init_dev_corelib, init_files_group}; use cairo_lang_filesystem::detect::detect_corelib; use cairo_lang_filesystem::flag::{Flag, FlagsGroup}; use cairo_lang_filesystem::ids::FlagLongId; @@ -23,6 +23,7 @@ pub struct LoweringDatabaseForTesting { } #[salsa::db] impl salsa::Database for LoweringDatabaseForTesting {} +impl FileContentView for LoweringDatabaseForTesting {} impl LoweringDatabaseForTesting { pub fn new() -> Self { diff --git a/crates/cairo-lang-parser/src/utils.rs b/crates/cairo-lang-parser/src/utils.rs index a5f5d6e5db9..2c073d461c3 100644 --- a/crates/cairo-lang-parser/src/utils.rs +++ b/crates/cairo-lang-parser/src/utils.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder}; -use cairo_lang_filesystem::db::{FilesGroup, init_files_group}; +use cairo_lang_filesystem::db::{FileContentView, FilesGroup, init_files_group}; use cairo_lang_filesystem::ids::{FileId, FileKind, FileLongId, SmolStrId, VirtualFile}; use cairo_lang_filesystem::span::{TextOffset, TextWidth}; use cairo_lang_primitive_token::{PrimitiveToken, ToPrimitiveTokenStream}; @@ -21,6 +21,7 @@ pub struct SimpleParserDatabase { } #[salsa::db] impl salsa::Database for SimpleParserDatabase {} +impl FileContentView for SimpleParserDatabase {} impl Default for SimpleParserDatabase { fn default() -> Self { let mut res = Self { storage: Default::default() }; diff --git a/crates/cairo-lang-plugins/src/test.rs b/crates/cairo-lang-plugins/src/test.rs index 873c06e49fa..2c9da43b495 100644 --- a/crates/cairo-lang-plugins/src/test.rs +++ b/crates/cairo-lang-plugins/src/test.rs @@ -8,12 +8,12 @@ use cairo_lang_defs::plugin::{ }; use cairo_lang_filesystem::cfg::CfgSet; use cairo_lang_filesystem::db::{ - CrateConfiguration, FilesGroup, files_group_input, init_files_group, + CrateConfiguration, FileContentStorage, FileContentView, FilesGroup, files_group_input, + init_files_group, override_file_content_for_input, register_files_group_view, }; use cairo_lang_filesystem::ids::{ CodeMapping, CodeOrigin, CrateId, Directory, FileLongId, SmolStrId, }; -use cairo_lang_filesystem::override_file_content; use cairo_lang_filesystem::span::TextSpan; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{TypedSyntaxNode, ast}; @@ -56,13 +56,20 @@ cairo_lang_test_utils::test_file_test!( #[derive(Clone)] pub struct DatabaseForTesting { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl salsa::Database for DatabaseForTesting {} +impl FileContentView for DatabaseForTesting { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl Default for DatabaseForTesting { fn default() -> Self { - let mut res = Self { storage: Default::default() }; + let mut res = Self { storage: Default::default(), file_contents: Default::default() }; + register_files_group_view(&res); init_external_files(&mut res); init_files_group(&mut res); init_defs_group(&mut res); @@ -121,8 +128,11 @@ pub fn test_expand_plugin_inner( ); // Main module file. - let file_id = FileLongId::OnDisk("test_src/lib.cairo".into()).intern(db_ref); - override_file_content!(db_ref, file_id, Some(format!("{cairo_code}\n").into())); + let file_input = { + let file_id = FileLongId::OnDisk("test_src/lib.cairo".into()).intern(db_ref); + db_ref.file_input(file_id).clone() + }; + override_file_content_for_input(db_ref, file_input, Some(format!("{cairo_code}\n").into())); let crate_id = CrateId::plain(&db, SmolStrId::from(&db, "test")); let mut diagnostic_items = vec![]; diff --git a/crates/cairo-lang-semantic/src/resolve/test.rs b/crates/cairo-lang-semantic/src/resolve/test.rs index b2811bad052..735b1701592 100644 --- a/crates/cairo-lang-semantic/src/resolve/test.rs +++ b/crates/cairo-lang-semantic/src/resolve/test.rs @@ -1,9 +1,9 @@ use cairo_lang_debug::DebugWithDb; use cairo_lang_defs::ids::{FunctionWithBodyId, ModuleId, ModuleItemId}; use cairo_lang_diagnostics::ToOption; -use cairo_lang_filesystem::db::{CrateConfiguration, FilesGroup}; +use cairo_lang_filesystem::db::{CrateConfiguration, FilesGroup, override_file_content_for_input}; use cairo_lang_filesystem::ids::{CrateId, Directory, FileLongId, SmolStrId}; -use cairo_lang_filesystem::{override_file_content, set_crate_config}; +use cairo_lang_filesystem::set_crate_config; use cairo_lang_test_utils::test; use cairo_lang_utils::{Intern, extract_matches}; use indoc::indoc; @@ -53,8 +53,11 @@ fn test_resolve_path() { } fn set_file_content(db: &mut dyn Database, path: &str, content: &str) { - let file_id = FileLongId::OnDisk(path.into()).intern(db); - override_file_content!(db, file_id, Some(content.into())); + let file_input = { + let file_id = FileLongId::OnDisk(path.into()).intern(db); + db.file_input(file_id).clone() + }; + override_file_content_for_input(db, file_input, Some(content.into())); } #[test] diff --git a/crates/cairo-lang-semantic/src/test_utils.rs b/crates/cairo-lang-semantic/src/test_utils.rs index 91295f26cf3..d93c481ef23 100644 --- a/crates/cairo-lang-semantic/src/test_utils.rs +++ b/crates/cairo-lang-semantic/src/test_utils.rs @@ -4,7 +4,8 @@ use cairo_lang_defs::db::{DefsGroup, init_defs_group, init_external_files}; use cairo_lang_defs::ids::{FunctionWithBodyId, ModuleId}; use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder}; use cairo_lang_filesystem::db::{ - CrateSettings, Edition, ExperimentalFeaturesConfig, init_dev_corelib, init_files_group, + CrateSettings, Edition, ExperimentalFeaturesConfig, FileContentStorage, FileContentView, + init_dev_corelib, init_files_group, register_files_group_view, }; use cairo_lang_filesystem::detect::detect_corelib; use cairo_lang_filesystem::ids::{ @@ -29,10 +30,16 @@ use crate::{ConcreteFunctionWithBodyId, SemanticDiagnostic, semantic}; #[derive(Clone)] pub struct SemanticDatabaseForTesting { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl Database for SemanticDatabaseForTesting {} +impl FileContentView for SemanticDatabaseForTesting { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} impl SemanticDatabaseForTesting { pub fn new_empty() -> Self { @@ -41,7 +48,11 @@ impl SemanticDatabaseForTesting { } pub fn with_plugin_suite(suite: PluginSuite) -> Self { - let mut res = SemanticDatabaseForTesting { storage: Default::default() }; + let mut res = SemanticDatabaseForTesting { + storage: Default::default(), + file_contents: Default::default(), + }; + register_files_group_view(&res); init_external_files(&mut res); init_files_group(&mut res); init_defs_group(&mut res); diff --git a/crates/cairo-lang-sierra-generator/src/test_utils.rs b/crates/cairo-lang-sierra-generator/src/test_utils.rs index dc6e8636612..84b71c3c2b0 100644 --- a/crates/cairo-lang-sierra-generator/src/test_utils.rs +++ b/crates/cairo-lang-sierra-generator/src/test_utils.rs @@ -3,7 +3,10 @@ use std::sync::{LazyLock, Mutex}; use cairo_lang_defs as defs; use cairo_lang_defs::db::{DefsGroup, init_defs_group, init_external_files}; use cairo_lang_defs::ids::ModuleId; -use cairo_lang_filesystem::db::{init_dev_corelib, init_files_group}; +use cairo_lang_filesystem::db::{ + FileContentStorage, FileContentView, init_dev_corelib, init_files_group, + register_files_group_view, +}; use cairo_lang_filesystem::detect::detect_corelib; use cairo_lang_filesystem::flag::{Flag, FlagsGroup}; use cairo_lang_filesystem::ids::FlagLongId; @@ -32,9 +35,17 @@ use crate::utils::{jump_statement, return_statement, simple_statement}; #[derive(Clone)] pub struct SierraGenDatabaseForTesting { storage: salsa::Storage, + file_contents: FileContentStorage, } #[salsa::db] impl Database for SierraGenDatabaseForTesting {} + +impl FileContentView for SierraGenDatabaseForTesting { + fn file_content_storage(&self) -> Option<&FileContentStorage> { + Some(&self.file_contents) + } +} + impl CloneableDatabase for SierraGenDatabaseForTesting { fn dyn_clone(&self) -> Box { Box::new(self.clone()) @@ -60,7 +71,11 @@ pub static SHARED_DB_WITHOUT_ADD_WITHDRAW_GAS_FUTURE_SIERRA: LazyLock< }); impl SierraGenDatabaseForTesting { pub fn new_empty() -> Self { - let mut res = SierraGenDatabaseForTesting { storage: Default::default() }; + let mut res = SierraGenDatabaseForTesting { + storage: Default::default(), + file_contents: Default::default(), + }; + register_files_group_view(&res); init_external_files(&mut res); init_files_group(&mut res); init_defs_group(&mut res); @@ -85,7 +100,10 @@ impl SierraGenDatabaseForTesting { } /// Snapshots the db for read only. pub fn snapshot(&self) -> SierraGenDatabaseForTesting { - SierraGenDatabaseForTesting { storage: self.storage.clone() } + SierraGenDatabaseForTesting { + storage: self.storage.clone(), + file_contents: self.file_contents.clone(), + } } } impl Default for SierraGenDatabaseForTesting {