diff --git a/crates/ruff_db/src/files.rs b/crates/ruff_db/src/files.rs index 1c322419e0..b58b802faf 100644 --- a/crates/ruff_db/src/files.rs +++ b/crates/ruff_db/src/files.rs @@ -526,7 +526,7 @@ impl VirtualFile { } /// Increments the revision of the underlying [`File`]. - fn sync(&self, db: &mut dyn Db) { + pub fn sync(&self, db: &mut dyn Db) { let file = self.0; tracing::debug!("Updating the revision of `{}`", file.path(db)); let current_revision = file.revision(db); diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index a5139b714a..bf27411947 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -1575,6 +1575,9 @@ impl DocumentHandle { { db.project().remove_file(db, file); } + + // Bump the file's revision back to using the file system's revision. + file.sync(db); } else { // This can only fail when the path is a directory or it doesn't exists but the // file should exists for this handler in this branch. This is because every @@ -1598,6 +1601,8 @@ impl DocumentHandle { if let Some(virtual_file) = db.files().try_virtual_file(virtual_path) { db.project().close_file(db, virtual_file.file()); virtual_file.close(db); + // Bump the file's revision back to using the file system's revision. + virtual_file.sync(db); } else { tracing::warn!("Salsa virtual file does not exists for {}", virtual_path); } diff --git a/crates/ty_server/tests/e2e/semantic_tokens.rs b/crates/ty_server/tests/e2e/semantic_tokens.rs index 5eb5957666..090a1213b8 100644 --- a/crates/ty_server/tests/e2e/semantic_tokens.rs +++ b/crates/ty_server/tests/e2e/semantic_tokens.rs @@ -70,3 +70,40 @@ fn multiline_token_client_supporting_multiline_tokens() -> Result<()> { Ok(()) } + +// Regression test for https://github.com/astral-sh/ty/issues/2346 +#[test] +fn no_stale_tokens_after_opening_the_same_file_with_new_content() -> Result<()> { + let file_name = "src/foo"; + let initial_content = + "def calculate_sum(a):\n # Version A: Basic math\n return a\n\nresult = calculate_sum(5)\n"; + let mut server = TestServerBuilder::new()? + .enable_pull_diagnostics(true) + .enable_multiline_token_support(true) + .with_workspace(SystemPath::new("src"), None)? + .with_file(file_name, initial_content)? + .build() + .wait_until_workspaces_are_initialized(); + + server.open_text_document(file_name, initial_content, 0); + + let initial_tokens = server + .semantic_tokens_full_request(&server.file_uri(file_name)) + .unwrap(); + + server.close_text_document(file_name); + + server.open_text_document( + file_name, + "# Version B: Basic greeting\ndef say_hello():\n print(\"Hello, World!\")\n\nsay_hello()\n", + 0, + ); + + let new_tokens = server + .semantic_tokens_full_request(&server.file_uri(file_name)) + .unwrap(); + + assert_ne!(initial_tokens, new_tokens); + + Ok(()) +}