Skip to content

Use salsa tracking to link InputFile to InputIngot #1070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions crates/common/src/core_ingot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use camino::Utf8PathBuf;
use rust_embed::Embed;

use crate::{
indexmap::IndexSet,
input::{IngotKind, Version},
InputDb, InputFile, InputIngot,
};
#[derive(Embed)]
#[folder = "../../library/core"]
struct Core;

#[salsa::tracked]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling salsa tracking on the core ingot gets rid of the errors in CI. I can't see why these particular tests should fail as a result of reusing the core ingot:

running 17 tests
test run_name_resolution__import_alias_cycle ... ok
test run_name_resolution__conflict_variant ... ok
test run_name_resolution__import_cycle ... ok
test run_name_resolution__conflict_field ... ok
test run_name_resolution__import_missing ... ok
test run_name_resolution__import_invisible ... ok
test run_name_resolution__import_conflict ... ok
test run_name_resolution__conflict_generics ... ok
test run_name_resolution__path_shadow ... ok
test run_name_resolution__conflict ... ok
test run_name_resolution__import_unimpotable ... ok
test run_name_resolution__record_field_visibility ... ok
test run_name_resolution__path_invalid_domain ... ok
test run_name_resolution__path_missing_generics ... ok
test run_name_resolution__trait_visibility ... ok
test run_name_resolution__import_ambiguous ... FAILED
test run_name_resolution__import_ambiguous_builtin ... FAILED

failures:

---- run_name_resolution__import_ambiguous stdout ----
━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Snapshot file: fixtures/name_resolution/import_ambiguous.snap
Snapshot: import_ambiguous
Source: /home/micah/hacker-stuff-2023/fe-stuff/fe/crates/uitest:21
Input file: fixtures/name_resolution/import_ambiguous.fe
──────────────────────────────────────────────────────────────────────────
Expression: diags
──────────────────────────────────────────────────────────────────────────
-old snapshot
+new results
────────────┬─────────────────────────────────────────────────────────────
    0       │-error[2-0004]: `S` is ambiguous
    1       │-   ┌─ import_ambiguous.fe:2:9
    2       │-   │
    3       │- 2 │ pub use S
    4       │-   │         ^ `S` is ambiguous
    5       │-   ·
    6       │-11 │         pub struct S {}
    7       │-   │                    - candidate 1
    8       │-   ·
    9       │-14 │         pub struct S {}
   10       │-   │                    - candidate 2
   11       │-
   12       │-error[2-0004]: `S` is ambiguous
   13       │-   ┌─ import_ambiguous.fe:7:13
   14       │-   │
   15       │- 7 │     pub use S
   16       │-   │             ^ `S` is ambiguous
   17       │-   ·
   18       │-11 │         pub struct S {}
   19       │-   │                    - candidate 1
   20       │-   ·
   21       │-14 │         pub struct S {}
   22       │-   │                    - candidate 2
────────────┴─────────────────────────────────────────────────────────────
To update snapshots run `cargo insta review`
Stopped on the first failure. Run `cargo insta test` to run all snapshots.
thread 'run_name_resolution__import_ambiguous' panicked at /home/micah/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.42.2/src/runtime.rs:679:13:
snapshot assertion for 'import_ambiguous' failed in line 21
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- run_name_resolution__import_ambiguous_builtin stdout ----
━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Snapshot file: fixtures/name_resolution/import_ambiguous_builtin.snap
Snapshot: import_ambiguous_builtin
Source: /home/micah/hacker-stuff-2023/fe-stuff/fe/crates/uitest:21
Input file: fixtures/name_resolution/import_ambiguous_builtin.fe
──────────────────────────────────────────────────────────────────────────
Expression: diags
──────────────────────────────────────────────────────────────────────────
-old snapshot
+new results
────────────┬─────────────────────────────────────────────────────────────
    0       │-error[2-0004]: `i32` is ambiguous
          0 │+error[2-0005]: `i32` can't be used as a middle segment of a path
    1     1 │   ┌─ import_ambiguous_builtin.fe:3:5
    2     2 │   │
    3     3 │ 3 │ use i32::*
    4       │-  │     ^^^ `i32` is ambiguous
          4 │+  │     ^^^ `i32` can't be used as a middle segment of a path
────────────┴─────────────────────────────────────────────────────────────
To update snapshots run `cargo insta review`
Stopped on the first failure. Run `cargo insta test` to run all snapshots.
thread 'run_name_resolution__import_ambiguous_builtin' panicked at /home/micah/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.42.2/src/runtime.rs:679:13:
snapshot assertion for 'import_ambiguous_builtin' failed in line 21


failures:
    run_name_resolution__import_ambiguous
    run_name_resolution__import_ambiguous_builtin

test result: FAILED. 15 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

c.c. @sbillig @g-r-a-n-t this is pretty weird

pub fn core(db: &dyn InputDb) -> InputIngot {
let mut files = IndexSet::new();
let mut root_file = None;
let ingot_path = Utf8PathBuf::from("core");

for file in Core::iter() {
if file.ends_with(".fe") {
let path = ingot_path.join(Utf8PathBuf::from(&file));
if let Some(content) = Core::get(&file) {
let is_root = path == "core/src/lib.fe";
let input_file = InputFile::__new_impl(
db,
path,
String::from_utf8(content.data.into_owned()).unwrap(),
);
if is_root {
root_file = Some(input_file);
}
files.insert(input_file);
}
}
}

if root_file.is_none() {
panic!("root file missing from core")
}

InputIngot::__new_impl(
db,
ingot_path,
IngotKind::Core,
Version::new(0, 0, 0),
IndexSet::default(),
files,
root_file,
)
}

#[cfg(test)]
mod tests {
use crate::impl_db_traits;

use super::*;

#[derive(Clone, Default)]
#[salsa::db]
pub(crate) struct TestDb {
storage: salsa::Storage<Self>,
}
impl_db_traits!(TestDb, InputDb);

#[test]
fn is_core_deduplicated() {
// this is a sanity check
let mut db = TestDb::default();
let core_1 = core(&db);
let core_2 = core(&db);

let foo = InputFile::new(&db, "src/mod1/foo.fe".into(), core_1);

core_2.set_root_file(&mut db, foo);

assert!(core_1.eq(&core_2));
assert!(core_1.root_file(&db).eq(&core_2.root_file(&db)));
}
}
109 changes: 65 additions & 44 deletions crates/common/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
use core::panic;

use camino::Utf8PathBuf;
use rust_embed::Embed;
use salsa::Setter;
use smol_str::SmolStr;

use crate::{indexmap::IndexSet, InputDb};

#[derive(Embed)]
#[folder = "../../library/core"]
struct Core;
use crate::{core_ingot, indexmap::IndexSet, InputDb};

/// An ingot is a collection of files which are compiled together.
/// Ingot can depend on other ingots.
@@ -40,6 +33,7 @@ pub struct InputIngot {
#[get(__get_root_file_impl)]
root_file: Option<InputFile>,
}

impl InputIngot {
pub fn new(
db: &dyn InputDb,
@@ -62,41 +56,7 @@ impl InputIngot {
}

pub fn core(db: &dyn InputDb) -> InputIngot {
let mut files = IndexSet::new();
let mut root_file = None;
let ingot_path = Utf8PathBuf::from("core");

for file in Core::iter() {
if file.ends_with(".fe") {
let path = ingot_path.join(Utf8PathBuf::from(&file));
if let Some(content) = Core::get(&file) {
let is_root = path == "core/src/lib.fe";
let input_file = InputFile::new(
db,
path,
String::from_utf8(content.data.into_owned()).unwrap(),
);
if is_root {
root_file = Some(input_file);
}
files.insert(input_file);
}
}
}

if root_file.is_none() {
panic!("root file missing from core")
}

Self::__new_impl(
db,
ingot_path,
IngotKind::Core,
Version::new(0, 0, 0),
IndexSet::default(),
files,
root_file,
)
core_ingot::core(db)
}

/// Set the root file of the ingot.
@@ -118,10 +78,22 @@ impl InputIngot {
}
}

#[salsa::input]
#[salsa::interned]
pub struct FilePath<'db> {
path: Utf8PathBuf,
}

impl<'db> FilePath<'db> {
pub fn from(db: &'db dyn InputDb, path: impl Into<Utf8PathBuf>) -> Self {
FilePath::new(db, path.into())
}
}

#[salsa::input(constructor = __new_impl)]
pub struct InputFile {
/// A path to the file from the ingot root directory.
#[return_ref]
#[set(__set_path_impl)]
pub path: Utf8PathBuf,
Copy link
Collaborator Author

@micahscopes micahscopes Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If InputFile instances are loosely tied to InputIngot instances, we need to discourage people from modifying InputFile.path directly.

This could be handled by some file_rename method on InputIngot. It could also be real nice to replace InputIngot::files with a map or patricia trie to get nice cheap getters. If we're already going funnel InputFile::path modification through an InputIngot::file_rename method, maintaining that index would be better justified.


#[return_ref]
@@ -132,6 +104,27 @@ impl InputFile {
pub fn abs_path(&self, db: &dyn InputDb, ingot: InputIngot) -> Utf8PathBuf {
ingot.path(db).join(self.path(db))
}

pub fn new(db: &dyn InputDb, path: Utf8PathBuf, ingot: InputIngot) -> Self {
input_for_file_path(db, FilePath::from(db, path), ingot)
}
}

#[salsa::tracked]
pub fn input_for_file_path<'db>(
db: &'db dyn InputDb,
path: FilePath<'db>,
ingot: InputIngot,
) -> InputFile {
// Check if the ingot already has a file with the same path
for file in ingot.files(db).iter() {
if file.path(db).as_path() == path.path(db) {
return *file;
}
}

Comment on lines +119 to +124
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary so that ingots with manually created/added InputFiles (right now just the core ingot) will have their preset InputFiles returned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// If no existing file is found, create a new one
InputFile::__new_impl(db, path.path(db), String::new())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -178,3 +171,31 @@ impl Ord for IngotDependency {
}

pub type Version = semver::Version;

#[cfg(test)]
mod tests {
use crate::{impl_db_traits, input::*};

#[derive(Default, Clone)]
#[salsa::db]
pub struct TestDatabase {
storage: salsa::Storage<Self>,
}

impl_db_traits!(TestDatabase, InputDb);

#[test]
fn test_input_file_equals() {
let path = Utf8PathBuf::from("test.foo");

let mut db = TestDatabase::default();
let ingot = InputIngot::core(&db);

let file1 = InputFile::new(&db, path.clone(), ingot);
let file2 = InputFile::new(&db, path, ingot);
assert_eq!(file1, file2);

file2.set_text(&mut db).to("Hello, world!".into());
assert_eq!(file1.text(&db), file2.text(&db));
}
}
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod core_ingot;
pub mod diagnostics;
pub mod indexmap;
pub mod input;
18 changes: 14 additions & 4 deletions crates/driver/src/db.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ use common::{
diagnostics::CompleteDiagnostic,
impl_db_traits,
indexmap::IndexSet,
input::{IngotDependency, IngotKind, Version},
input::{input_for_file_path, FilePath, IngotDependency, IngotKind, Version},
InputDb, InputFile, InputIngot,
};
use hir::{
@@ -26,6 +26,9 @@ use hir_analysis::{
},
HirAnalysisDb,
};
use salsa::Setter;
// use include_dir::{include_dir, Dir};
// use salsa::Setter;

use crate::diagnostics::ToCsDiag;

@@ -111,7 +114,9 @@ impl DriverDataBase {
);

let file_name = root_file.file_name().unwrap();
let input_file = InputFile::new(self, file_name.into(), source.to_string());
let input_file =
input_for_file_path(self.as_input_db(), FilePath::from(self, file_name), ingot);
input_file.set_text(self).to(source.to_string());
ingot.set_root_file(self, input_file);
ingot.set_files(self, [input_file].into_iter().collect());
(ingot, input_file)
@@ -137,7 +142,8 @@ impl DriverDataBase {
);

let file_name = root_file.file_name().unwrap();
let input_file = InputFile::new(self, file_name.into(), source.to_string());
let input_file = InputFile::new(self, file_name.into(), ingot);
input_file.set_text(self).to(source.to_string());
ingot.set_root_file(self, input_file);
ingot.set_files(self, [input_file].into_iter().collect());
(ingot, input_file)
@@ -193,7 +199,11 @@ impl DriverDataBase {
) -> IndexSet<InputFile> {
let input_files = files
.into_iter()
.map(|(path, content)| InputFile::new(self, path, content))
.map(|(path, content)| {
let input_file = InputFile::new(self, path, ingot);
input_file.set_text(self).to(content);
input_file
})
.collect::<IndexSet<_>>();

let root_file = *input_files
4 changes: 3 additions & 1 deletion crates/hir-analysis/tests/test_db.rs
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ use hir::{
HirDb, LowerHirDb, SpannedHirDb,
};
use rustc_hash::FxHashMap;
use salsa::Setter;

type CodeSpanFileId = usize;

@@ -62,7 +63,8 @@ impl HirAnalysisTestDb {
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, file_name.into(), text.to_string());
let root = InputFile::new(self, file_name.into(), ingot);
root.set_text(self).to(text.to_string());
ingot.set_root_file(self, root);
ingot.set_files(self, [root].into_iter().collect());
(ingot, root)
14 changes: 7 additions & 7 deletions crates/hir/src/hir_def/module_tree.rs
Original file line number Diff line number Diff line change
@@ -295,13 +295,13 @@ mod tests {
Version::new(0, 0, 1),
Default::default(),
);
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());
let local_root = InputFile::new(&db, "src/lib.fe".into(), local_ingot);
let mod1 = InputFile::new(&db, "src/mod1.fe".into(), local_ingot);
let mod2 = InputFile::new(&db, "src/mod2.fe".into(), local_ingot);
let foo = InputFile::new(&db, "src/mod1/foo.fe".into(), local_ingot);
let bar = InputFile::new(&db, "src/mod2/bar.fe".into(), local_ingot);
let baz = InputFile::new(&db, "src/mod2/baz.fe".into(), local_ingot);
let floating = InputFile::new(&db, "src/mod3/floating.fe".into(), local_ingot);
local_ingot.set_root_file(&mut db, local_root);
local_ingot.set_files(
&mut db,
4 changes: 3 additions & 1 deletion crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@ mod test_db {
InputDb, InputFile, InputIngot,
};
use derive_more::TryIntoError;
use salsa::Setter;

use super::HirDb;
use crate::{
@@ -102,7 +103,8 @@ mod test_db {
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, "test_file.fe".into(), text.to_string());
let file = InputFile::new(self, "test_file.fe".into(), ingot);
file.set_text(self).to(text.into());
ingot.set_root_file(self, file);
ingot.set_files(self, [file].into_iter().collect());
(ingot, file)
4 changes: 2 additions & 2 deletions crates/language-server/src/backend/workspace.rs
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ impl IngotFileContext for LocalIngotContext {
.files
.get(path)
.copied()
.unwrap_or_else(|| InputFile::new(db, path.into(), String::new()));
.unwrap_or_else(|| InputFile::new(db, path.into(), ingot));
self.files.insert(path, input);
ingot.set_files(db, self.files.values().copied().collect());
Some((ingot, input))
@@ -181,7 +181,7 @@ impl IngotFileContext for StandaloneIngotContext {
.files
.get(path)
.copied()
.unwrap_or_else(|| InputFile::new(db, path.into(), String::new()));
.unwrap_or_else(|| InputFile::new(db, path.into(), ingot));
let mut files = IndexSet::new();
files.insert(input_file);
ingot.set_files(db, files);
Loading