Skip to content

Commit 04006de

Browse files
fix
1 parent b430a08 commit 04006de

2 files changed

Lines changed: 137 additions & 9 deletions

File tree

pyrefly/lib/lsp/non_wasm/server.rs

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,12 @@ use pyrefly_python::module::TextRangeWithModule;
201201
use pyrefly_python::module_name::ModuleName;
202202
use pyrefly_python::module_name::ModuleNameWithKind;
203203
use pyrefly_python::module_path::ModulePath;
204+
use pyrefly_python::module_path::ModulePathDetails;
204205
use pyrefly_python::short_identifier::ShortIdentifier;
205206
use pyrefly_util::absolutize::Absolutize as _;
206207
use pyrefly_util::arc_id::ArcId;
207208
use pyrefly_util::events::CategorizedEvents;
209+
use pyrefly_util::fs_anyhow;
208210
use pyrefly_util::globs::FilteredGlobs;
209211
use pyrefly_util::globs::HiddenDirFilter;
210212
use pyrefly_util::includes::Includes as _;
@@ -313,6 +315,7 @@ use crate::lsp::wasm::provide_type::ProvideTypeParams;
313315
use crate::lsp::wasm::provide_type::ProvideTypeResponse;
314316
use crate::lsp::wasm::provide_type::provide_type;
315317
use crate::module::bundled::BundledStub;
318+
use crate::state::load::FileContents;
316319
use crate::state::load::LspFile;
317320
use crate::state::lsp::DisplayTypeErrors;
318321
use crate::state::lsp::FindDefinitionItemWithDocstring;
@@ -1528,6 +1531,41 @@ impl Server {
15281531
}
15291532
}
15301533

1534+
fn stale_definition_request_disk_contents(
1535+
&self,
1536+
transaction: &Transaction<'_>,
1537+
handle: &Handle,
1538+
path: &Path,
1539+
) -> Option<String> {
1540+
let disk_contents = fs_anyhow::read_to_string(path).ok()?;
1541+
if let Some(open_file) = self.open_files.read().get(path) {
1542+
return match &**open_file {
1543+
LspFile::Source(contents) if contents.as_str() != disk_contents.as_str() => {
1544+
Some(disk_contents)
1545+
}
1546+
LspFile::Source(_) | LspFile::Notebook(_) => None,
1547+
};
1548+
}
1549+
let module_info = transaction.get_module_info(handle)?;
1550+
if module_info.is_notebook() || module_info.contents().as_str() == disk_contents.as_str() {
1551+
None
1552+
} else {
1553+
Some(disk_contents)
1554+
}
1555+
}
1556+
1557+
fn invalidate_all_definition_files(&self, transaction: &mut Transaction<'_>) {
1558+
let files = transaction
1559+
.handles()
1560+
.into_iter()
1561+
.filter_map(|handle| match handle.path().details() {
1562+
ModulePathDetails::FileSystem(path) => Some(path.as_path().to_path_buf()),
1563+
_ => None,
1564+
})
1565+
.collect::<Vec<_>>();
1566+
transaction.invalidate_disk(&files);
1567+
}
1568+
15311569
/// Returns a snapshot of all currently open notebooks, keyed by their filesystem path.
15321570
/// Used to remap file paths to notebook cell URIs in closures that don't have
15331571
/// access to `self`.
@@ -1861,7 +1899,7 @@ impl Server {
18611899
params, &x.id,
18621900
)
18631901
{
1864-
let response = match self.goto_definition(&transaction, params) {
1902+
let response = match self.goto_definition(&mut transaction, params) {
18651903
Ok(response) => response,
18661904
Err(reason) => {
18671905
telemetry_event.set_empty_response_reason(reason);
@@ -3933,19 +3971,18 @@ impl Server {
39333971
.map(|(handle, _)| handle)
39343972
}
39353973

3936-
fn goto_definition(
3974+
fn goto_definition_response(
39373975
&self,
39383976
transaction: &Transaction<'_>,
3939-
params: GotoDefinitionParams,
3977+
handle: &Handle,
3978+
uri: &Url,
3979+
position: Position,
39403980
) -> Result<Option<GotoDefinitionResponse>, EmptyResponseReason> {
3941-
let uri = &params.text_document_position_params.text_document.uri;
3942-
let handle = self.make_handle_if_enabled(uri, Some(GotoDefinition::METHOD))?;
39433981
let info = transaction
3944-
.get_module_info(&handle)
3982+
.get_module_info(handle)
39453983
.ok_or(EmptyResponseReason::ModuleInfoNotFound)?;
3946-
let range =
3947-
self.from_lsp_position(uri, &info, params.text_document_position_params.position);
3948-
let targets = transaction.goto_definition(&handle, range)?;
3984+
let range = self.from_lsp_position(uri, &info, position);
3985+
let targets = transaction.goto_definition(handle, range)?;
39493986
let mut lsp_targets = targets
39503987
.iter()
39513988
.filter_map(|x| self.to_lsp_location(x))
@@ -3967,6 +4004,55 @@ impl Server {
39674004
}
39684005
}
39694006

4007+
fn goto_definition(
4008+
&self,
4009+
transaction: &mut Transaction<'_>,
4010+
params: GotoDefinitionParams,
4011+
) -> Result<Option<GotoDefinitionResponse>, EmptyResponseReason> {
4012+
let uri = &params.text_document_position_params.text_document.uri;
4013+
let handle = self.make_handle_if_enabled(uri, Some(GotoDefinition::METHOD))?;
4014+
let path = self.path_for_uri(uri);
4015+
let stale_disk_contents = path.as_deref().and_then(|path| {
4016+
self.stale_definition_request_disk_contents(transaction, &handle, path)
4017+
});
4018+
if stale_disk_contents.is_some() {
4019+
self.invalidate_all_definition_files(transaction);
4020+
transaction.run(
4021+
std::slice::from_ref(&handle),
4022+
Require::Everything,
4023+
Some(&self.lsp_thread_pool),
4024+
);
4025+
}
4026+
let position = params.text_document_position_params.position;
4027+
let response = self.goto_definition_response(transaction, &handle, uri, position);
4028+
if matches!(response, Ok(Some(_))) {
4029+
return response;
4030+
}
4031+
let Some(path) = path else {
4032+
return response;
4033+
};
4034+
let Some(disk_contents) = stale_disk_contents else {
4035+
return response;
4036+
};
4037+
let should_retry = matches!(
4038+
self.open_files.read().get(&path).map(|file| &**file),
4039+
Some(LspFile::Source(_))
4040+
);
4041+
if !should_retry {
4042+
return response;
4043+
}
4044+
transaction.set_memory(vec![(
4045+
path,
4046+
Some(Arc::new(FileContents::from_source(disk_contents))),
4047+
)]);
4048+
transaction.run(
4049+
std::slice::from_ref(&handle),
4050+
Require::Everything,
4051+
Some(&self.lsp_thread_pool),
4052+
);
4053+
self.goto_definition_response(transaction, &handle, uri, position)
4054+
}
4055+
39704056
fn goto_declaration(
39714057
&self,
39724058
transaction: &Transaction<'_>,

pyrefly/lib/test/lsp/lsp_interaction/definition.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
use std::fs;
89
use std::path::PathBuf;
910
use std::sync::Arc;
1011

@@ -141,6 +142,47 @@ fn test_go_to_def_basic(root: &TempDir, workspace_folders: Option<Vec<(String, U
141142
.unwrap();
142143
}
143144

145+
#[test]
146+
fn goto_definition_recovers_from_stale_disk_source_file() {
147+
let root = TempDir::with_prefix("pyrefly_goto_def_stale_disk").unwrap();
148+
fs::write(
149+
root.path().join("foo.py"),
150+
"from bar import target\n\nvalue = target\n",
151+
)
152+
.unwrap();
153+
fs::write(root.path().join("bar.py"), "target = 1\n").unwrap();
154+
155+
let mut interaction = LspInteraction::new();
156+
interaction.set_root(root.path().to_path_buf());
157+
interaction
158+
.initialize(InitializeSettings {
159+
..Default::default()
160+
})
161+
.unwrap();
162+
interaction.client.did_open("foo.py");
163+
164+
interaction
165+
.client
166+
.definition("foo.py", 2, 10)
167+
.expect_definition_response_from_root("bar.py", 0, 0, 0, 6)
168+
.unwrap();
169+
170+
fs::write(
171+
root.path().join("foo.py"),
172+
"# rebased\nfrom bar import target\n\nvalue = target\n",
173+
)
174+
.unwrap();
175+
fs::write(root.path().join("bar.py"), "# moved\ntarget = 1\n").unwrap();
176+
177+
interaction
178+
.client
179+
.definition("foo.py", 3, 10)
180+
.expect_definition_response_from_root("bar.py", 1, 0, 1, 6)
181+
.unwrap();
182+
183+
interaction.shutdown().unwrap();
184+
}
185+
144186
#[test]
145187
fn test_go_to_def_single_root() {
146188
let root = get_test_files_root();

0 commit comments

Comments
 (0)