diff --git a/crates/common2/src/input.rs b/crates/common2/src/input.rs index 8a9ef33e7..da387998f 100644 --- a/crates/common2/src/input.rs +++ b/crates/common2/src/input.rs @@ -75,9 +75,6 @@ impl InputIngot { #[salsa::input] pub struct InputFile { - /// A ingot id which the file belongs to. - pub ingot: InputIngot, - /// A path to the file from the ingot root directory. #[return_ref] pub path: Utf8PathBuf, @@ -87,8 +84,8 @@ pub struct InputFile { } impl InputFile { - pub fn abs_path(&self, db: &dyn InputDb) -> Utf8PathBuf { - self.ingot(db).path(db).join(self.path(db)) + pub fn abs_path(&self, db: &dyn InputDb, ingot: InputIngot) -> Utf8PathBuf { + ingot.path(db).join(self.path(db)) } } diff --git a/crates/driver2/src/lib.rs b/crates/driver2/src/lib.rs index 9ed5c9b0c..57173281a 100644 --- a/crates/driver2/src/lib.rs +++ b/crates/driver2/src/lib.rs @@ -69,7 +69,7 @@ impl DriverDataBase { DiagnosticsCollection(pass_manager.run_on_module(top_mod)) } - pub fn standalone(&mut self, file_path: &path::Path, source: &str) -> InputFile { + pub fn standalone(&mut self, file_path: &path::Path, source: &str) -> (InputIngot, InputFile) { let kind = IngotKind::StandAlone; // We set the ingot version to 0.0.0 for stand-alone file. @@ -84,14 +84,14 @@ impl DriverDataBase { ); let file_name = root_file.file_name().unwrap().to_str().unwrap(); - let input_file = InputFile::new(self, ingot, file_name.into(), source.to_string()); + let input_file = InputFile::new(self, file_name.into(), source.to_string()); ingot.set_root_file(self, input_file); ingot.set_files(self, [input_file].into_iter().collect()); - input_file + (ingot, input_file) } - pub fn top_mod(&self, input: InputFile) -> TopLevelMod { - map_file_to_mod(self, input) + pub fn top_mod(&self, ingot: InputIngot, input: InputFile) -> TopLevelMod { + map_file_to_mod(self, ingot, input) } } diff --git a/crates/driver2/src/main.rs b/crates/driver2/src/main.rs index 1ccababa4..7f8c727d0 100644 --- a/crates/driver2/src/main.rs +++ b/crates/driver2/src/main.rs @@ -24,8 +24,8 @@ pub fn main() { let source = std::fs::read_to_string(&args.file_path).unwrap(); let mut db = DriverDataBase::default(); - let input_file = db.standalone(path, &source); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, &source); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); diags.emit(&db); diff --git a/crates/hir-analysis/tests/constraints.rs b/crates/hir-analysis/tests/constraints.rs index 3a5509c64..428b79a87 100644 --- a/crates/hir-analysis/tests/constraints.rs +++ b/crates/hir-analysis/tests/constraints.rs @@ -12,7 +12,7 @@ fn constraints_standalone(fixture: Fixture<&str>) { let mut db = HirAnalysisTestDb::default(); let path = Path::new(fixture.path()); let file_name = path.file_name().and_then(|file| file.to_str()).unwrap(); - let input = db.new_stand_alone(file_name, fixture.content()); - let (top_mod, _) = db.top_mod(input); + let (ingot, file) = db.new_stand_alone(file_name, fixture.content()); + let (top_mod, _) = db.top_mod(ingot, file); db.assert_no_diags(top_mod); } diff --git a/crates/hir-analysis/tests/def_analysis.rs b/crates/hir-analysis/tests/def_analysis.rs index 35bd581d2..c8496264d 100644 --- a/crates/hir-analysis/tests/def_analysis.rs +++ b/crates/hir-analysis/tests/def_analysis.rs @@ -12,7 +12,7 @@ fn def_analysis_standalone(fixture: Fixture<&str>) { let mut db = HirAnalysisTestDb::default(); let path = Path::new(fixture.path()); let file_name = path.file_name().and_then(|file| file.to_str()).unwrap(); - let input = db.new_stand_alone(file_name, fixture.content()); - let (top_mod, _) = db.top_mod(input); + let (ingot, file) = db.new_stand_alone(file_name, fixture.content()); + let (top_mod, _) = db.top_mod(ingot, file); db.assert_no_diags(top_mod); } diff --git a/crates/hir-analysis/tests/early_path_resolution.rs b/crates/hir-analysis/tests/early_path_resolution.rs index 94d15defa..984cab0f0 100644 --- a/crates/hir-analysis/tests/early_path_resolution.rs +++ b/crates/hir-analysis/tests/early_path_resolution.rs @@ -19,8 +19,8 @@ fn early_path_resolution_standalone(fixture: Fixture<&str>) { let mut db = HirAnalysisTestDb::default(); let path = Path::new(fixture.path()); let file_name = path.file_name().and_then(|file| file.to_str()).unwrap(); - let input = db.new_stand_alone(file_name, fixture.content()); - let (top_mod, mut prop_formatter) = db.top_mod(input); + let (ingot, file) = db.new_stand_alone(file_name, fixture.content()); + let (top_mod, mut prop_formatter) = db.top_mod(ingot, file); db.assert_no_diags(top_mod); let mut ctxt = VisitorCtxt::with_top_mod(db.as_hir_db(), top_mod); diff --git a/crates/hir-analysis/tests/import.rs b/crates/hir-analysis/tests/import.rs index 7ed8156db..452ca1c82 100644 --- a/crates/hir-analysis/tests/import.rs +++ b/crates/hir-analysis/tests/import.rs @@ -16,8 +16,8 @@ fn import_standalone(fixture: Fixture<&str>) { let mut db = HirAnalysisTestDb::default(); let path = Path::new(fixture.path()); let file_name = path.file_name().and_then(|file| file.to_str()).unwrap(); - let input = db.new_stand_alone(file_name, fixture.content()); - let (top_mod, mut prop_formatter) = db.top_mod(input); + let (ingot, file) = db.new_stand_alone(file_name, fixture.content()); + let (top_mod, mut prop_formatter) = db.top_mod(ingot, file); db.assert_no_diags(top_mod); diff --git a/crates/hir-analysis/tests/repeated_updates.rs b/crates/hir-analysis/tests/repeated_updates.rs index 9206652de..b57c5721d 100644 --- a/crates/hir-analysis/tests/repeated_updates.rs +++ b/crates/hir-analysis/tests/repeated_updates.rs @@ -22,17 +22,17 @@ fn test_updated() { fn foo() {}"#, ]; - let input = db.new_stand_alone(file_name, versions[0]); + let (ingot, file) = db.new_stand_alone(file_name, versions[0]); for version in versions { { - let top_mod = map_file_to_mod(db.as_lower_hir_db(), input); + let top_mod = map_file_to_mod(db.as_lower_hir_db(), ingot, file); let mut pass_manager = initialize_pass_manager(&db); let _ = pass_manager.run_on_module(top_mod); } { - input.set_text(&mut db).to(version.into()); + file.set_text(&mut db).to(version.into()); } } } diff --git a/crates/hir-analysis/tests/test_db.rs b/crates/hir-analysis/tests/test_db.rs index 5317ec792..d10b1b3d3 100644 --- a/crates/hir-analysis/tests/test_db.rs +++ b/crates/hir-analysis/tests/test_db.rs @@ -53,19 +53,23 @@ impl_db_traits!( // https://github.com/rust-lang/rust/issues/46379 #[allow(dead_code)] impl HirAnalysisTestDb { - pub fn new_stand_alone(&mut self, file_name: &str, text: &str) -> InputFile { + pub fn new_stand_alone(&mut self, file_name: &str, text: &str) -> (InputIngot, InputFile) { let kind = IngotKind::StandAlone; let version = Version::new(0, 0, 1); let ingot = InputIngot::new(self, file_name, kind, version, IndexSet::default()); - let root = InputFile::new(self, ingot, file_name.into(), text.to_string()); + let root = InputFile::new(self, file_name.into(), text.to_string()); ingot.set_root_file(self, root); ingot.set_files(self, [root].into_iter().collect()); - root + (ingot, root) } - pub fn top_mod(&self, input: InputFile) -> (TopLevelMod, HirPropertyFormatter) { + pub fn top_mod( + &self, + ingot: InputIngot, + input: InputFile, + ) -> (TopLevelMod, HirPropertyFormatter) { let mut prop_formatter = HirPropertyFormatter::default(); - let top_mod = self.register_file(&mut prop_formatter, input); + let top_mod = self.register_file(&mut prop_formatter, ingot, input); (top_mod, prop_formatter) } @@ -104,9 +108,10 @@ impl HirAnalysisTestDb { fn register_file<'db>( &'db self, prop_formatter: &mut HirPropertyFormatter<'db>, + ingot: InputIngot, input_file: InputFile, ) -> TopLevelMod<'db> { - let top_mod = lower::map_file_to_mod(self, input_file); + let top_mod = lower::map_file_to_mod(self, ingot, input_file); let path = input_file.path(self); let text = input_file.text(self); prop_formatter.register_top_mod(path.as_str(), text, top_mod); diff --git a/crates/hir-analysis/tests/ty_check.rs b/crates/hir-analysis/tests/ty_check.rs index ae3cf25a7..bb1f3f55a 100644 --- a/crates/hir-analysis/tests/ty_check.rs +++ b/crates/hir-analysis/tests/ty_check.rs @@ -14,8 +14,8 @@ fn ty_check_standalone(fixture: Fixture<&str>) { let mut db = HirAnalysisTestDb::default(); let path = Path::new(fixture.path()); let file_name = path.file_name().and_then(|file| file.to_str()).unwrap(); - let input = db.new_stand_alone(file_name, fixture.content()); - let (top_mod, mut prop_formatter) = db.top_mod(input); + let (ingot, file) = db.new_stand_alone(file_name, fixture.content()); + let (top_mod, mut prop_formatter) = db.top_mod(ingot, file); db.assert_no_diags(top_mod); diff --git a/crates/hir/src/hir_def/module_tree.rs b/crates/hir/src/hir_def/module_tree.rs index b72f3becd..b57702220 100644 --- a/crates/hir/src/hir_def/module_tree.rs +++ b/crates/hir/src/hir_def/module_tree.rs @@ -266,13 +266,13 @@ mod tests { Version::new(0, 0, 1), Default::default(), ); - let local_root = InputFile::new(&db, local_ingot, "src/lib.fe".into(), "".into()); - let mod1 = InputFile::new(&db, local_ingot, "src/mod1.fe".into(), "".into()); - let mod2 = InputFile::new(&db, local_ingot, "src/mod2.fe".into(), "".into()); - let foo = InputFile::new(&db, local_ingot, "src/mod1/foo.fe".into(), "".into()); - let bar = InputFile::new(&db, local_ingot, "src/mod2/bar.fe".into(), "".into()); - let baz = InputFile::new(&db, local_ingot, "src/mod2/baz.fe".into(), "".into()); - let floating = InputFile::new(&db, local_ingot, "src/mod3/floating.fe".into(), "".into()); + let local_root = InputFile::new(&db, "src/lib.fe".into(), "".into()); + let mod1 = InputFile::new(&db, "src/mod1.fe".into(), "".into()); + let mod2 = InputFile::new(&db, "src/mod2.fe".into(), "".into()); + let foo = InputFile::new(&db, "src/mod1/foo.fe".into(), "".into()); + let bar = InputFile::new(&db, "src/mod2/bar.fe".into(), "".into()); + let baz = InputFile::new(&db, "src/mod2/baz.fe".into(), "".into()); + let floating = InputFile::new(&db, "src/mod3/floating.fe".into(), "".into()); local_ingot.set_root_file(&mut db, local_root); local_ingot.set_files( &mut db, @@ -281,12 +281,12 @@ mod tests { .collect(), ); - let local_root_mod = lower::map_file_to_mod(&db, local_root); - let mod1_mod = lower::map_file_to_mod(&db, mod1); - let mod2_mod = lower::map_file_to_mod(&db, mod2); - let foo_mod = lower::map_file_to_mod(&db, foo); - let bar_mod = lower::map_file_to_mod(&db, bar); - let baz_mod = lower::map_file_to_mod(&db, baz); + let local_root_mod = lower::map_file_to_mod(&db, local_ingot, local_root); + let mod1_mod = lower::map_file_to_mod(&db, local_ingot, mod1); + let mod2_mod = lower::map_file_to_mod(&db, local_ingot, mod2); + let foo_mod = lower::map_file_to_mod(&db, local_ingot, foo); + let bar_mod = lower::map_file_to_mod(&db, local_ingot, bar); + let baz_mod = lower::map_file_to_mod(&db, local_ingot, baz); let local_tree = lower::module_tree(&db, local_ingot); let root_node = local_tree.root_data(); diff --git a/crates/hir/src/hir_def/scope_graph.rs b/crates/hir/src/hir_def/scope_graph.rs index 1f11ca4fa..32c3d95c2 100644 --- a/crates/hir/src/hir_def/scope_graph.rs +++ b/crates/hir/src/hir_def/scope_graph.rs @@ -624,8 +624,8 @@ mod tests { } "#; - let file = db.standalone_file(text); - let scope_graph = db.parse_source(file); + let (ingot, file) = db.standalone_file(text); + let scope_graph = db.parse_source(ingot, file); assert_eq!(scope_graph.items_dfs(&db).count(), 8); for (i, item) in scope_graph.items_dfs(&db).enumerate() { @@ -653,8 +653,8 @@ mod tests { } "#; - let file = db.standalone_file(text); - let scope_graph = db.parse_source(file); + let (ingot, file) = db.standalone_file(text); + let scope_graph = db.parse_source(ingot, file); let root = scope_graph.top_mod.scope(); let enum_ = scope_graph.children(root).next().unwrap(); assert!(matches!(enum_.item(), ItemKind::Enum(_))); diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 01bb89e10..5ac188f79 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -119,28 +119,28 @@ mod test_db { impl_db_traits!(TestDb, InputDb, HirDb, LowerHirDb, SpannedHirDb); impl TestDb { - pub fn parse_source(&self, file: InputFile) -> &ScopeGraph { - let top_mod = map_file_to_mod(self, file); + pub fn parse_source(&self, ingot: InputIngot, file: InputFile) -> &ScopeGraph { + let top_mod = map_file_to_mod(self, ingot, file); scope_graph(self, top_mod) } /// Parses the given source text and returns the first inner item in the /// file. - pub fn expect_item<'db, T>(&'db self, input: InputFile) -> T + pub fn expect_item<'db, T>(&'db self, ingot: InputIngot, input: InputFile) -> T where ItemKind<'db>: TryInto, { - let tree = self.parse_source(input); + let tree = self.parse_source(ingot, input); tree.items_dfs(self) .find_map(|it| it.try_into().ok()) .unwrap() } - pub fn expect_items<'db, T>(&'db self, input: InputFile) -> Vec + pub fn expect_items<'db, T>(&'db self, ingot: InputIngot, input: InputFile) -> Vec where ItemKind<'db>: TryInto, { - let tree = self.parse_source(input); + let tree = self.parse_source(ingot, input); tree.items_dfs(self) .filter_map(|it| it.try_into().ok()) .collect() @@ -153,15 +153,15 @@ mod test_db { &text[range.start().into()..range.end().into()] } - pub fn standalone_file(&mut self, text: &str) -> InputFile { + pub fn standalone_file(&mut self, text: &str) -> (InputIngot, InputFile) { let path = "hir_test"; let kind = IngotKind::StandAlone; let version = Version::new(0, 0, 1); let ingot = InputIngot::new(self, path, kind, version, IndexSet::default()); - let file = InputFile::new(self, ingot, "test_file.fe".into(), text.to_string()); + let file = InputFile::new(self, "test_file.fe".into(), text.to_string()); ingot.set_root_file(self, file); ingot.set_files(self, [file].into_iter().collect()); - file + (ingot, file) } } } diff --git a/crates/hir/src/lower/mod.rs b/crates/hir/src/lower/mod.rs index 089635759..cd2b4832f 100644 --- a/crates/hir/src/lower/mod.rs +++ b/crates/hir/src/lower/mod.rs @@ -33,8 +33,8 @@ mod use_tree; /// This function just maps the file to a top-level module, and doesn't perform /// any parsing or lowering. /// To perform the actual lowering, use [`scope_graph`] instead. -pub fn map_file_to_mod(db: &dyn LowerHirDb, file: InputFile) -> TopLevelMod { - let ingot = module_tree_impl(db.as_hir_db(), file.ingot(db.as_input_db())).ingot; +pub fn map_file_to_mod(db: &dyn LowerHirDb, ingot: InputIngot, file: InputFile) -> TopLevelMod { + let ingot = module_tree_impl(db.as_hir_db(), ingot).ingot; map_file_to_mod_impl(db.as_hir_db(), ingot, file) } diff --git a/crates/hir/src/span/expr.rs b/crates/hir/src/span/expr.rs index c28b40187..cbb3a150c 100644 --- a/crates/hir/src/span/expr.rs +++ b/crates/hir/src/span/expr.rs @@ -233,8 +233,8 @@ mod tests { } }"#; - let input = db.standalone_file(text); - let body: Body = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let body: Body = db.expect_item::(ingot, file); let bin_expr = match body.exprs(db.as_hir_db()).values().nth(2).unwrap().unwrap() { Expr::AugAssign(lhs, rhs, bin_op) => (*lhs, *rhs, *bin_op), _ => unreachable!(), diff --git a/crates/hir/src/span/item.rs b/crates/hir/src/span/item.rs index 043cb957d..395447362 100644 --- a/crates/hir/src/span/item.rs +++ b/crates/hir/src/span/item.rs @@ -381,8 +381,8 @@ mod tests { } "#; - let input = db.standalone_file(text); - let item_tree = db.parse_source(input); + let (ingot, file) = db.standalone_file(text); + let item_tree = db.parse_source(ingot, file); let top_mod = item_tree.top_mod; assert_eq!(text, db.text_at(top_mod, &top_mod.lazy_span())); } @@ -398,8 +398,8 @@ mod tests { } "#; - let input = db.standalone_file(text); - let mod_ = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let mod_ = db.expect_item::(ingot, file); let top_mod = mod_.top_mod(db.as_hir_db()); let mod_span = mod_.lazy_span(); assert_eq!( @@ -420,9 +420,9 @@ mod tests { where U: Add "#; - let input = db.standalone_file(text); + let (ingot, file) = db.standalone_file(text); - let fn_ = db.expect_item::(input); + let fn_ = db.expect_item::(ingot, file); let top_mod = fn_.top_mod(db.as_hir_db()); let fn_span = fn_.lazy_span(); assert_eq!("my_func", db.text_at(top_mod, &fn_span.name())); @@ -473,8 +473,8 @@ mod tests { pub y: foo::Bar<2> }"#; - let input = db.standalone_file(text); - let struct_ = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let struct_ = db.expect_item::(ingot, file); let top_mod = struct_.top_mod(db.as_hir_db()); let struct_span = struct_.lazy_span(); assert_eq!("Foo", db.text_at(top_mod, &struct_span.name())); @@ -505,8 +505,8 @@ mod tests { } }"#; - let input = db.standalone_file(text); - let enum_ = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let enum_ = db.expect_item::(ingot, file); let top_mod = enum_.top_mod(db.as_hir_db()); let enum_span = enum_.lazy_span(); assert_eq!("Foo", db.text_at(top_mod, &enum_span.name())); @@ -531,8 +531,8 @@ mod tests { pub type Foo = u32 "#; - let input = db.standalone_file(text); - let type_alias = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let type_alias = db.expect_item::(ingot, file); let top_mod = type_alias.top_mod(db.as_hir_db()); let type_alias_span = type_alias.lazy_span(); assert_eq!("Foo", db.text_at(top_mod, &type_alias_span.alias())); @@ -548,8 +548,8 @@ mod tests { use foo::bar::baz::Trait as _ "#; - let input = db.standalone_file(text); - let use_ = db.expect_item::(input); + let (ingot, file) = db.standalone_file(text); + let use_ = db.expect_item::(ingot, file); let top_mod = use_.top_mod(db.as_hir_db()); let use_span = use_.lazy_span(); @@ -570,8 +570,8 @@ mod tests { use foo::bar::{baz::*, qux as Alias} "#; - let input = db.standalone_file(text); - let uses = db.expect_items::(input); + let (ingot, file) = db.standalone_file(text); + let uses = db.expect_items::(ingot, file); assert_eq!(uses.len(), 2); let top_mod = uses[0].top_mod(db.as_hir_db()); diff --git a/crates/hir/src/visitor.rs b/crates/hir/src/visitor.rs index 69d701bf2..9383c0041 100644 --- a/crates/hir/src/visitor.rs +++ b/crates/hir/src/visitor.rs @@ -2291,9 +2291,9 @@ mod tests { 42 }"#; - let input = db.standalone_file(text); + let (ingot, file) = db.standalone_file(text); - let func = db.expect_item::(input); + let func = db.expect_item::(ingot, file); let top_mod = func.top_mod(&db); let mut visitor = MyVisitor { diff --git a/crates/language-server/src/backend/workspace.rs b/crates/language-server/src/backend/workspace.rs index 037033f1c..894fef948 100644 --- a/crates/language-server/src/backend/workspace.rs +++ b/crates/language-server/src/backend/workspace.rs @@ -7,7 +7,7 @@ use common::{ input::{IngotKind, Version}, InputFile, InputIngot, }; -use hir::{hir_def::TopLevelMod, lower::map_file_to_mod, LowerHirDb}; + use patricia_tree::StringPatriciaMap; use salsa::Setter; use tracing::info; @@ -27,23 +27,22 @@ fn ingot_directory_key(path: String) -> String { } pub trait IngotFileContext { - fn get_input_for_file_path(&self, path: &str) -> Option; + fn get_input_for_file_path(&self, path: &str) -> Option<(InputIngot, InputFile)>; + fn touch_input_for_file_path( &mut self, db: &mut LanguageServerDatabase, path: &str, - ) -> Option; + ) -> Option<(InputIngot, InputFile)>; + fn get_ingot_for_file_path(&self, path: &str) -> Option; + fn touch_ingot_for_file_path( &mut self, db: &mut LanguageServerDatabase, path: &str, ) -> Option; - fn top_mod_from_file_path<'a>( - &self, - db: &'a dyn LowerHirDb, - path: &str, - ) -> Option>; + fn remove_input_for_file_path( &mut self, db: &mut LanguageServerDatabase, @@ -104,22 +103,22 @@ impl IngotFileContext for LocalIngotContext { &mut self, db: &mut LanguageServerDatabase, path: &str, - ) -> Option { + ) -> Option<(InputIngot, InputFile)> { let ingot = self.touch_ingot_for_file_path(db, path)?; - let input = self.files.get(path).map_or_else( - || { - let file = InputFile::new(db, ingot, path.into(), String::new()); - Some(file) - }, - |file| Some(*file), - ); - self.files.insert(path, input.unwrap()); + let input = self + .files + .get(path) + .copied() + .unwrap_or_else(|| InputFile::new(db, path.into(), String::new())); + self.files.insert(path, input); ingot.set_files(db, self.files.values().copied().collect()); - input + Some((ingot, input)) } - fn get_input_for_file_path(&self, path: &str) -> Option { - self.files.get(path).copied() + fn get_input_for_file_path(&self, path: &str) -> Option<(InputIngot, InputFile)> { + let ingot = self.get_ingot_for_file_path(path)?; + let file = self.files.get(path).copied()?; + Some((ingot, file)) } fn touch_ingot_for_file_path( @@ -134,15 +133,6 @@ impl IngotFileContext for LocalIngotContext { Some(self.ingot) } - fn top_mod_from_file_path<'a>( - &self, - db: &'a dyn LowerHirDb, - path: &str, - ) -> Option> { - let file = self.get_input_for_file_path(path)?; - Some(map_file_to_mod(db, file)) - } - fn remove_input_for_file_path( &mut self, db: &mut LanguageServerDatabase, @@ -184,25 +174,25 @@ impl IngotFileContext for StandaloneIngotContext { &mut self, db: &mut LanguageServerDatabase, path: &str, - ) -> Option { + ) -> Option<(InputIngot, InputFile)> { let ingot = self.touch_ingot_for_file_path(db, path)?; - let input_file = self.files.get(path).map_or_else( - || { - let file = InputFile::new(db, ingot, path.into(), String::new()); - Some(file) - }, - |file| Some(*file), - ); + let input_file = self + .files + .get(path) + .copied() + .unwrap_or_else(|| InputFile::new(db, path.into(), String::new())); let mut files = IndexSet::new(); - files.insert(input_file.unwrap()); + files.insert(input_file); ingot.set_files(db, files); - ingot.set_root_file(db, input_file.unwrap()); - self.files.insert(path, input_file.unwrap()); - input_file + ingot.set_root_file(db, input_file); + self.files.insert(path, input_file); + Some((ingot, input_file)) } - fn get_input_for_file_path(&self, path: &str) -> Option { - self.files.get(path).copied() + fn get_input_for_file_path(&self, path: &str) -> Option<(InputIngot, InputFile)> { + let ingot = self.get_ingot_for_file_path(path)?; + let file = self.files.get(path).copied()?; + Some((ingot, file)) } fn touch_ingot_for_file_path( @@ -234,15 +224,6 @@ impl IngotFileContext for StandaloneIngotContext { get_containing_ingot(&self.ingots, path).copied() } - fn top_mod_from_file_path<'a>( - &self, - db: &'a dyn LowerHirDb, - path: &str, - ) -> Option> { - let file = self.get_input_for_file_path(path)?; - Some(map_file_to_mod(db, file)) - } - fn remove_input_for_file_path( &mut self, _db: &mut LanguageServerDatabase, @@ -306,8 +287,8 @@ impl Workspace { if let Some(file) = StdLib::get(path_str) { let contents = String::from_utf8(file.data.as_ref().to_vec()); if let Ok(contents) = contents { - if let Some(input) = self.touch_input_for_file_path(db, &std_path) { - input.set_text(db).to(contents); + if let Some((_ingot, file)) = self.touch_input_for_file_path(db, &std_path) { + file.set_text(db).to(contents); } } } @@ -394,7 +375,7 @@ impl Workspace { for path in actual_paths { if !previous_ingot_context_file_keys.contains(&path) { - if let Some(file) = ingot_context.touch_input_for_file_path(db, &path) { + if let Some((_ingot, file)) = ingot_context.touch_input_for_file_path(db, &path) { if let Ok(contents) = std::fs::read_to_string(&path) { file.set_text(db).to(contents); } @@ -431,7 +412,7 @@ impl IngotFileContext for Workspace { &mut self, db: &mut LanguageServerDatabase, path: &str, - ) -> Option { + ) -> Option<(InputIngot, InputFile)> { let ctx = get_containing_ingot_mut(&mut self.ingot_contexts, path); if let Some(ctx) = ctx { ctx.touch_input_for_file_path(db, path) @@ -441,7 +422,7 @@ impl IngotFileContext for Workspace { } } - fn get_input_for_file_path(&self, path: &str) -> Option { + fn get_input_for_file_path(&self, path: &str) -> Option<(InputIngot, InputFile)> { let ctx = get_containing_ingot(&self.ingot_contexts, path); if let Some(ctx) = ctx { ctx.get_input_for_file_path(path) @@ -473,20 +454,6 @@ impl IngotFileContext for Workspace { } } - fn top_mod_from_file_path<'a>( - &self, - db: &'a dyn LowerHirDb, - path: &str, - ) -> Option> { - let ctx = get_containing_ingot(&self.ingot_contexts, path); - if let Some(ctx) = ctx { - ctx.top_mod_from_file_path(db, path) - } else { - self.standalone_ingot_context - .top_mod_from_file_path(db, path) - } - } - fn remove_input_for_file_path( &mut self, db: &mut LanguageServerDatabase, @@ -537,6 +504,8 @@ impl SyncableIngotFileContext for Workspace { #[cfg(test)] mod tests { + use hir::lower::map_file_to_mod; + use crate::backend::workspace::{ get_containing_ingot_mut, IngotFileContext, Workspace, FE_CONFIG_SUFFIX, }; @@ -560,7 +529,6 @@ mod tests { ingot.unwrap().kind(&db), common::input::IngotKind::StandAlone ); - assert_eq!(ingot.unwrap(), file.unwrap().ingot(&db)); } #[test] @@ -620,20 +588,16 @@ mod tests { }; let file_path = "tests/data/ingot1/src/main.fe"; - let file = workspace.touch_input_for_file_path(&mut db, file_path); - assert!(file.is_some()); - - let ingot = workspace.touch_ingot_for_file_path(&mut db, file_path); - assert!(ingot.is_some()); - - assert_eq!(file.map(|f| f.ingot(&db)).unwrap(), ingot.unwrap()); + let (ingot, _file) = workspace + .touch_input_for_file_path(&mut db, file_path) + .unwrap(); assert_eq!( ingot_context_ingot.unwrap().kind(&db), common::input::IngotKind::Local ); - assert_eq!(ingot.unwrap().kind(&db), common::input::IngotKind::Local); - assert_eq!(ingot_context_ingot.unwrap(), ingot.unwrap()); + assert_eq!(ingot.kind(&db), common::input::IngotKind::Local); + assert_eq!(ingot_context_ingot.unwrap(), ingot); } #[test] @@ -653,9 +617,10 @@ mod tests { assert_eq!(workspace.ingot_contexts.len(), 1); let fe_source_path = ingot_base_dir.join("src/main.fe"); - let input = workspace.touch_input_for_file_path(&mut db, fe_source_path.to_str().unwrap()); - assert!(input.is_some()); - assert!(input.unwrap().ingot(&db).kind(&db) == common::input::IngotKind::Local); + let (ingot, _file) = workspace + .touch_input_for_file_path(&mut db, fe_source_path.to_str().unwrap()) + .unwrap(); + assert!(ingot.kind(&db) == common::input::IngotKind::Local); } #[test] @@ -693,7 +658,8 @@ mod tests { // file.sync(&mut db, None); // this would panic if a file has been added to multiple ingots - let _top_mod = workspace.top_mod_from_file_path(&db, src_path.as_str()); + let (ingot, file) = workspace.get_input_for_file_path(&src_path).unwrap(); + let _top_mod = map_file_to_mod(&db, ingot, file); } } @@ -727,7 +693,7 @@ mod tests { let foo_files = foo_context.files.keys().collect::>(); for file in foo_files { let contents = std::fs::read_to_string(&file).unwrap(); - let file = foo_context + let (_ingot, file) = foo_context .touch_input_for_file_path(&mut db, &file) .unwrap(); @@ -745,14 +711,11 @@ mod tests { let mut db = crate::backend::db::LanguageServerDatabase::default(); workspace.sync_local_ingots(&mut db, &messy_workspace_path); - let dangling_file = workspace + let (d_ingot, _file) = workspace .touch_input_for_file_path(&mut db, &dangling_path) .unwrap(); - assert_eq!( - dangling_file.ingot(&db).kind(&db), - common::input::IngotKind::StandAlone - ); + assert_eq!(d_ingot.kind(&db), common::input::IngotKind::StandAlone); // TODO: make it easier to go both ways between an ingot root path and its config path let ingot_paths = workspace @@ -766,13 +729,10 @@ mod tests { } let non_dangling_file_path = format!("{crate_dir}/test_files/messy/foo/bar/src/main.fe"); - let non_dangling_input = workspace + let (n_ingot, _file) = workspace .touch_input_for_file_path(&mut db, &non_dangling_file_path) .unwrap(); - assert_eq!( - non_dangling_input.ingot(&db).kind(&db), - common::input::IngotKind::Local - ); + assert_eq!(n_ingot.kind(&db), common::input::IngotKind::Local); } } diff --git a/crates/language-server/src/functionality/diagnostics.rs b/crates/language-server/src/functionality/diagnostics.rs index 117465c02..97b9bf57a 100644 --- a/crates/language-server/src/functionality/diagnostics.rs +++ b/crates/language-server/src/functionality/diagnostics.rs @@ -102,7 +102,7 @@ impl LanguageServerDatabase { ) .or_default(); - let top_mod = map_file_to_mod(self, *file); + let top_mod = map_file_to_mod(self, ingot, *file); let diagnostics = pass_manager.run_on_module(top_mod); let mut finalized_diags: Vec = diagnostics .iter() @@ -113,7 +113,7 @@ impl LanguageServerDatabase { ord => ord, }); for diag in finalized_diags { - let lsp_diags = diag_to_lsp(diag, self.as_input_db()).clone(); + let lsp_diags = diag_to_lsp(self.as_input_db(), ingot, diag).clone(); for (uri, more_diags) in lsp_diags { let diags = result.entry(uri.clone()).or_insert_with(Vec::new); diags.extend(more_diags); diff --git a/crates/language-server/src/functionality/goto.rs b/crates/language-server/src/functionality/goto.rs index 406d260d4..b5f635986 100644 --- a/crates/language-server/src/functionality/goto.rs +++ b/crates/language-server/src/functionality/goto.rs @@ -1,9 +1,10 @@ use async_lsp::ResponseError; use hir::{ hir_def::{scope_graph::ScopeId, ItemKind, PathId, TopLevelMod}, + lower::map_file_to_mod, span::DynLazySpan, visitor::{prelude::LazyPathSpan, Visitor, VisitorCtxt}, - LowerHirDb, SpannedHirDb, + SpannedHirDb, }; use hir_analysis::name_resolution::{resolve_path, PathResErrorKind}; @@ -131,17 +132,20 @@ pub async fn handle_goto_definition( // Get the module and the goto info let file_path = params.text_document.uri.path(); - let top_mod = backend + let (ingot, file) = backend .workspace - .top_mod_from_file_path(backend.db.as_lower_hir_db(), file_path) + .get_input_for_file_path(file_path) .unwrap(); + let top_mod = map_file_to_mod(&backend.db, ingot, file); let scopes = get_goto_target_scopes_for_cursor(&backend.db, top_mod, cursor).unwrap_or_default(); let locations = scopes .iter() - .map(|scope| to_lsp_location_from_scope(*scope, backend.db.as_spanned_hir_db())) + .map(|scope| { + to_lsp_location_from_scope(backend.db.as_spanned_hir_db(), ingot, file, *scope) + }) .collect::>(); let result: Result, ()> = @@ -282,19 +286,17 @@ mod tests { let fe_source_path = ingot_base_dir.join(fixture.path()); let fe_source_path = fe_source_path.to_str().unwrap(); - let input = workspace.touch_input_for_file_path(&mut db, fixture.path()); - assert_eq!(input.unwrap().ingot(&db).kind(&db), IngotKind::Local); + let (ingot, file) = workspace + .touch_input_for_file_path(&mut db, fixture.path()) + .unwrap(); + assert_eq!(ingot.kind(&db), IngotKind::Local); - input - .unwrap() - .set_text(&mut db) - .to((*fixture.content()).to_string()); + file.set_text(&mut db).to((*fixture.content()).to_string()); // Introduce a new scope to limit the lifetime of `top_mod` { - let top_mod = workspace - .top_mod_from_file_path(db.as_lower_hir_db(), fe_source_path) - .unwrap(); + let (ingot, file) = workspace.get_input_for_file_path(fe_source_path).unwrap(); + let top_mod = map_file_to_mod(db.as_lower_hir_db(), ingot, file); let snapshot = make_goto_cursors_snapshot(&db, &fixture, top_mod); snap_test!(snapshot, fixture.path()); @@ -311,13 +313,11 @@ mod tests { fn test_goto_cursor_target(fixture: Fixture<&str>) { let db = &mut LanguageServerDatabase::default(); let workspace = &mut Workspace::default(); - let input = workspace + let (ingot, file) = workspace .touch_input_for_file_path(db, fixture.path()) .unwrap(); - input.set_text(db).to((*fixture.content()).to_string()); - let top_mod = workspace - .top_mod_from_file_path(db.as_lower_hir_db(), fixture.path()) - .unwrap(); + file.set_text(db).to((*fixture.content()).to_string()); + let top_mod = map_file_to_mod(db.as_lower_hir_db(), ingot, file); let snapshot = make_goto_cursors_snapshot(db, &fixture, top_mod); snap_test!(snapshot, fixture.path()); @@ -331,14 +331,11 @@ mod tests { let db = &mut LanguageServerDatabase::default(); let workspace = &mut Workspace::default(); - workspace + let (ingot, file) = workspace .touch_input_for_file_path(db, fixture.path()) - .unwrap() - .set_text(db) - .to((*fixture.content()).to_string()); - let top_mod = workspace - .top_mod_from_file_path(db.as_lower_hir_db(), fixture.path()) .unwrap(); + file.set_text(db).to((*fixture.content()).to_string()); + let top_mod = map_file_to_mod(db.as_lower_hir_db(), ingot, file); let cursors = extract_multiple_cursor_positions_from_spans(db, top_mod); diff --git a/crates/language-server/src/functionality/handlers.rs b/crates/language-server/src/functionality/handlers.rs index c252e86f6..84ca58d1a 100644 --- a/crates/language-server/src/functionality/handlers.rs +++ b/crates/language-server/src/functionality/handlers.rs @@ -15,7 +15,7 @@ use super::{capabilities::server_capabilities, hover::hover_helper}; use crate::backend::workspace::IngotFileContext; -use tracing::{error, info}; +use tracing::{error, info, warn}; #[derive(Debug)] pub struct FilesNeedDiagnostics(pub Vec); @@ -51,11 +51,11 @@ pub enum ChangeKind { impl Backend { fn update_input_file_text(&mut self, path: &str, contents: String) { - let input = self + let (_ingot, file) = self .workspace .touch_input_for_file_path(&mut self.db, path) .unwrap(); - input.set_text(&mut self.db).to(contents); + file.set_text(&mut self.db).to(contents); } } @@ -243,22 +243,22 @@ pub async fn handle_hover_request( backend: &Backend, message: HoverParams, ) -> Result, ResponseError> { - let file = backend.workspace.get_input_for_file_path( - message - .text_document_position_params - .text_document - .uri - .path(), - ); - info!("handling hover request in file: {:?}", file); + let path = message + .text_document_position_params + .text_document + .uri + .path(); - let response = file.and_then(|file| { - hover_helper(&backend.db, file, message).unwrap_or_else(|e| { - error!("Error handling hover: {:?}", e); - None - }) - }); + let Some((ingot, file)) = backend.workspace.get_input_for_file_path(path) else { + warn!("handle_hover_request failed to get file for path: `{path}`"); + return Ok(None); + }; + info!("handling hover request in file: {:?}", file); + let response = hover_helper(&backend.db, ingot, file, message).unwrap_or_else(|e| { + error!("Error handling hover: {:?}", e); + None + }); info!("sending hover response: {:?}", response); Ok(response) } diff --git a/crates/language-server/src/functionality/hover.rs b/crates/language-server/src/functionality/hover.rs index a1ecd8c0e..7cddb0919 100644 --- a/crates/language-server/src/functionality/hover.rs +++ b/crates/language-server/src/functionality/hover.rs @@ -1,5 +1,5 @@ use anyhow::Error; -use common::InputFile; +use common::{InputFile, InputIngot}; use hir::lower::map_file_to_mod; use async_lsp::lsp_types::Hover; @@ -13,18 +13,19 @@ use super::item_info::{get_item_definition_markdown, get_item_docstring, get_ite pub fn hover_helper( db: &dyn LanguageServerDb, - input: InputFile, + ingot: InputIngot, + file: InputFile, params: async_lsp::lsp_types::HoverParams, ) -> Result, Error> { info!("handling hover"); - let file_text = input.text(db.as_input_db()); + let file_text = file.text(db.as_input_db()); let cursor: Cursor = to_offset_from_position( params.text_document_position_params.position, file_text.as_str(), ); - let top_mod = map_file_to_mod(db.as_lower_hir_db(), input); + let top_mod = map_file_to_mod(db.as_lower_hir_db(), ingot, file); let goto_info = &get_goto_target_scopes_for_cursor(db, top_mod, cursor).unwrap_or_default(); let hir_db = db.as_hir_db(); diff --git a/crates/language-server/src/util.rs b/crates/language-server/src/util.rs index 5ca9473a4..182e61045 100644 --- a/crates/language-server/src/util.rs +++ b/crates/language-server/src/util.rs @@ -1,7 +1,7 @@ use async_lsp::lsp_types::Position; use common::{ diagnostics::{CompleteDiagnostic, Severity, Span}, - InputDb, + InputDb, InputFile, InputIngot, }; use fxhash::FxHashMap; use hir::{hir_def::scope_graph::ScopeId, span::LazySpan, SpannedHirDb}; @@ -53,8 +53,10 @@ pub fn to_lsp_range_from_span( } pub fn to_lsp_location_from_scope( - scope: ScopeId, db: &dyn SpannedHirDb, + ingot: InputIngot, + file: InputFile, + scope: ScopeId, ) -> Result> { let lazy_span = scope .name_span(db.as_hir_db()) @@ -62,7 +64,7 @@ pub fn to_lsp_location_from_scope( let span = lazy_span .resolve(db.as_spanned_hir_db()) .ok_or("Failed to resolve span")?; - let uri = span.file.abs_path(db.as_input_db()); + let uri = file.abs_path(db.as_input_db(), ingot); let range = to_lsp_range_from_span(span, db.as_input_db())?; let uri = async_lsp::lsp_types::Url::from_file_path(uri) .map_err(|()| "Failed to convert path to URL")?; @@ -78,12 +80,16 @@ pub fn severity_to_lsp(severity: Severity) -> async_lsp::lsp_types::DiagnosticSe } pub fn diag_to_lsp( - diag: CompleteDiagnostic, db: &dyn InputDb, + ingot: InputIngot, + diag: CompleteDiagnostic, ) -> FxHashMap> { let mut result = FxHashMap::>::default(); + // TODO: this assumes that all sub_diagnostics point at files in the same ingot, + // which might not be the case + diag.sub_diagnostics.into_iter().for_each(|sub| { let span = match sub.span { Some(span) => span, @@ -93,7 +99,7 @@ pub fn diag_to_lsp( } }; - let uri = span.file.abs_path(db); + let uri = span.file.abs_path(db, ingot); let uri = match Url::from_file_path(uri) { Ok(uri) => uri, Err(()) => { diff --git a/crates/uitest/tests/name_resolution.rs b/crates/uitest/tests/name_resolution.rs index 905712bea..8f0fdd549 100644 --- a/crates/uitest/tests/name_resolution.rs +++ b/crates/uitest/tests/name_resolution.rs @@ -12,8 +12,8 @@ fn run_name_resolution(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -38,8 +38,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } diff --git a/crates/uitest/tests/parser.rs b/crates/uitest/tests/parser.rs index 572defc48..3d77cb94d 100644 --- a/crates/uitest/tests/parser.rs +++ b/crates/uitest/tests/parser.rs @@ -13,8 +13,8 @@ fn run_parser(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_file_with_pass_manager(top_mod, init_parser_pass); let diags = diags.format_diags(&db); @@ -45,8 +45,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } diff --git a/crates/uitest/tests/ty.rs b/crates/uitest/tests/ty.rs index 7e116ef63..f0ddacded 100644 --- a/crates/uitest/tests/ty.rs +++ b/crates/uitest/tests/ty.rs @@ -12,8 +12,8 @@ fn run_ty_def(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -28,8 +28,8 @@ fn run_const_ty(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -44,8 +44,8 @@ fn run_trait_bound(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -60,8 +60,8 @@ fn run_trait_impl(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -90,8 +90,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } @@ -111,8 +111,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } @@ -132,8 +132,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } @@ -153,8 +153,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } } diff --git a/crates/uitest/tests/ty_check.rs b/crates/uitest/tests/ty_check.rs index 35b2f1d61..ac2c0a957 100644 --- a/crates/uitest/tests/ty_check.rs +++ b/crates/uitest/tests/ty_check.rs @@ -12,8 +12,8 @@ fn run_ty_check(fixture: Fixture<&str>) { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); let diags = db.run_on_top_mod(top_mod); let diags = diags.format_diags(&db); @@ -38,8 +38,8 @@ mod wasm { let mut db = DriverDataBase::default(); let path = Path::new(fixture.path()); - let input_file = db.standalone(path, fixture.content()); - let top_mod = db.top_mod(input_file); + let (ingot, file) = db.standalone(path, fixture.content()); + let top_mod = db.top_mod(ingot, file); db.run_on_top_mod(top_mod); } }