From b57c62e6b3d96b38fd6090896451a78e8d57b50d Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 1 Apr 2025 09:36:00 +0200 Subject: [PATCH] [red-knot] IDE crate (#17045) ## Summary This PR adds a new but so far empty and unused `red_knot_ide` crate. This new crate's purpose is to implement IDE-specific functionality, such as go to definition, hover, completion, etc., which are used by both the LSP and the playground. The crate itself doesn't depend on `lsptypes`. The idea is that the facade crates (e.g., `red_knot_server`) convert external to internal types. Not only allows this to share the logic between server and playground, it also ensures that the core functionality is easier to test because it can be tested without needing a full LSP. ## Test Plan `cargo build` --- Cargo.lock | 12 +++ Cargo.toml | 3 +- crates/red_knot_ide/Cargo.toml | 24 ++++++ crates/red_knot_ide/src/db.rs | 124 +++++++++++++++++++++++++++++ crates/red_knot_ide/src/lib.rs | 3 + crates/red_knot_project/Cargo.toml | 1 + crates/red_knot_project/src/db.rs | 14 ++++ crates/red_knot_server/Cargo.toml | 1 + 8 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 crates/red_knot_ide/Cargo.toml create mode 100644 crates/red_knot_ide/src/db.rs create mode 100644 crates/red_knot_ide/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ccc986e17a..f0e54ece63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2503,6 +2503,17 @@ dependencies = [ "wild", ] +[[package]] +name = "red_knot_ide" +version = "0.0.0" +dependencies = [ + "red_knot_python_semantic", + "red_knot_vendored", + "ruff_db", + "salsa", + "tracing", +] + [[package]] name = "red_knot_project" version = "0.0.0" @@ -2514,6 +2525,7 @@ dependencies = [ "notify", "pep440_rs", "rayon", + "red_knot_ide", "red_knot_python_semantic", "red_knot_vendored", "ruff_cache", diff --git a/Cargo.toml b/Cargo.toml index 0efc208c99..3bfcef5067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,11 @@ ruff_text_size = { path = "crates/ruff_text_size" } red_knot_vendored = { path = "crates/red_knot_vendored" } ruff_workspace = { path = "crates/ruff_workspace" } +red_knot_ide = { path = "crates/red_knot_ide" } +red_knot_project = { path = "crates/red_knot_project", default-features = false } red_knot_python_semantic = { path = "crates/red_knot_python_semantic" } red_knot_server = { path = "crates/red_knot_server" } red_knot_test = { path = "crates/red_knot_test" } -red_knot_project = { path = "crates/red_knot_project", default-features = false } aho-corasick = { version = "1.1.3" } anstream = { version = "0.6.18" } diff --git a/crates/red_knot_ide/Cargo.toml b/crates/red_knot_ide/Cargo.toml new file mode 100644 index 0000000000..184154a9b1 --- /dev/null +++ b/crates/red_knot_ide/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "red_knot_ide" +version = "0.0.0" +publish = false +authors = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +license = { workspace = true } + +[dependencies] +ruff_db = { workspace = true } +red_knot_python_semantic = { workspace = true } + +salsa = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +red_knot_vendored = { workspace = true } + +[lints] +workspace = true diff --git a/crates/red_knot_ide/src/db.rs b/crates/red_knot_ide/src/db.rs new file mode 100644 index 0000000000..fff5b7d7dc --- /dev/null +++ b/crates/red_knot_ide/src/db.rs @@ -0,0 +1,124 @@ +use red_knot_python_semantic::Db as SemanticDb; +use ruff_db::{Db as SourceDb, Upcast}; + +#[salsa::db] +pub trait Db: SemanticDb + Upcast {} + +#[cfg(test)] +pub(crate) mod tests { + use std::sync::Arc; + + use super::Db; + use red_knot_python_semantic::lint::{LintRegistry, RuleSelection}; + use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb}; + use ruff_db::files::{File, Files}; + use ruff_db::system::{DbWithTestSystem, System, TestSystem}; + use ruff_db::vendored::VendoredFileSystem; + use ruff_db::{Db as SourceDb, Upcast}; + + #[salsa::db] + #[derive(Clone)] + pub(crate) struct TestDb { + storage: salsa::Storage, + files: Files, + system: TestSystem, + vendored: VendoredFileSystem, + events: Arc>>, + rule_selection: Arc, + } + + #[allow(dead_code)] + impl TestDb { + pub(crate) fn new() -> Self { + Self { + storage: salsa::Storage::default(), + system: TestSystem::default(), + vendored: red_knot_vendored::file_system().clone(), + events: Arc::default(), + files: Files::default(), + rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())), + } + } + + /// Takes the salsa events. + /// + /// ## Panics + /// If there are any pending salsa snapshots. + pub(crate) fn take_salsa_events(&mut self) -> Vec { + let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots"); + + let events = inner.get_mut().unwrap(); + std::mem::take(&mut *events) + } + + /// Clears the salsa events. + /// + /// ## Panics + /// If there are any pending salsa snapshots. + pub(crate) fn clear_salsa_events(&mut self) { + self.take_salsa_events(); + } + } + + impl DbWithTestSystem for TestDb { + fn test_system(&self) -> &TestSystem { + &self.system + } + + fn test_system_mut(&mut self) -> &mut TestSystem { + &mut self.system + } + } + + #[salsa::db] + impl SourceDb for TestDb { + fn vendored(&self) -> &VendoredFileSystem { + &self.vendored + } + + fn system(&self) -> &dyn System { + &self.system + } + + fn files(&self) -> &Files { + &self.files + } + } + + impl Upcast for TestDb { + fn upcast(&self) -> &(dyn SourceDb + 'static) { + self + } + fn upcast_mut(&mut self) -> &mut (dyn SourceDb + 'static) { + self + } + } + + #[salsa::db] + impl SemanticDb for TestDb { + fn is_file_open(&self, file: File) -> bool { + !file.path(self).is_vendored_path() + } + + fn rule_selection(&self) -> Arc { + self.rule_selection.clone() + } + + fn lint_registry(&self) -> &LintRegistry { + default_lint_registry() + } + } + + #[salsa::db] + impl Db for TestDb {} + + #[salsa::db] + impl salsa::Database for TestDb { + fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) { + let event = event(); + tracing::trace!("event: {event:?}"); + let mut events = self.events.lock().unwrap(); + events.push(event); + } + } +} diff --git a/crates/red_knot_ide/src/lib.rs b/crates/red_knot_ide/src/lib.rs new file mode 100644 index 0000000000..6f53e6094d --- /dev/null +++ b/crates/red_knot_ide/src/lib.rs @@ -0,0 +1,3 @@ +mod db; + +pub use db::Db; diff --git a/crates/red_knot_project/Cargo.toml b/crates/red_knot_project/Cargo.toml index fc2752a1e6..3fef70dc8d 100644 --- a/crates/red_knot_project/Cargo.toml +++ b/crates/red_knot_project/Cargo.toml @@ -17,6 +17,7 @@ ruff_db = { workspace = true, features = ["cache", "serde"] } ruff_macros = { workspace = true } ruff_python_ast = { workspace = true, features = ["serde"] } ruff_text_size = { workspace = true } +red_knot_ide = { workspace = true } red_knot_python_semantic = { workspace = true, features = ["serde"] } red_knot_vendored = { workspace = true } diff --git a/crates/red_knot_project/src/db.rs b/crates/red_knot_project/src/db.rs index 6b941fe71d..1acbee6504 100644 --- a/crates/red_knot_project/src/db.rs +++ b/crates/red_knot_project/src/db.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::DEFAULT_LINT_REGISTRY; use crate::{Project, ProjectMetadata}; +use red_knot_ide::Db as IdeDb; use red_knot_python_semantic::lint::{LintRegistry, RuleSelection}; use red_knot_python_semantic::{Db as SemanticDb, Program}; use ruff_db::diagnostic::OldDiagnosticTrait; @@ -103,6 +104,19 @@ impl Upcast for ProjectDatabase { } } +impl Upcast for ProjectDatabase { + fn upcast(&self) -> &(dyn IdeDb + 'static) { + self + } + + fn upcast_mut(&mut self) -> &mut (dyn IdeDb + 'static) { + self + } +} + +#[salsa::db] +impl IdeDb for ProjectDatabase {} + #[salsa::db] impl SemanticDb for ProjectDatabase { fn is_file_open(&self, file: File) -> bool { diff --git a/crates/red_knot_server/Cargo.toml b/crates/red_knot_server/Cargo.toml index f14aa19350..d38ca95e3d 100644 --- a/crates/red_knot_server/Cargo.toml +++ b/crates/red_knot_server/Cargo.toml @@ -12,6 +12,7 @@ license = { workspace = true } [dependencies] red_knot_project = { workspace = true } + ruff_db = { workspace = true, features = ["os"] } ruff_notebook = { workspace = true } ruff_python_ast = { workspace = true }