[ty] Fix goto definition for relative imports in third-party files (#22457)

This commit is contained in:
Micha Reiser
2026-01-09 09:30:31 +01:00
committed by GitHub
parent b3cde98cd1
commit c5f6a74da5

View File

@@ -69,13 +69,6 @@ pub(crate) struct Session {
/// The projects across all workspaces.
projects: BTreeMap<SystemPathBuf, ProjectState>,
/// The project to use for files outside any workspace. For example, if the user
/// opens the project `<home>/my_project` in VS code but they then opens a Python file from their Desktop.
/// This file isn't part of the active workspace, nor is it part of any project. But we still want
/// to provide some basic functionality like navigation, completions, syntax highlighting, etc.
/// That's what we use the default project for.
default_project: DefaultProject,
/// Initialization options that were provided by the client during server initialization.
initialization_options: InitializationOptions,
@@ -165,7 +158,6 @@ impl Session {
workspaces,
deferred_messages: VecDeque::new(),
index: Some(index),
default_project: DefaultProject::new(),
initialization_options,
global_settings: Arc::new(GlobalSettings::default()),
projects: BTreeMap::new(),
@@ -304,7 +296,7 @@ impl Session {
/// Returns a reference to the project's [`ProjectDatabase`] in which the given `path` belongs.
///
/// If the path is a system path, it will return the project database that is closest to the
/// given path, or the default project if no project is found for the path.
/// given path, or the first project if no project is found for the path.
///
/// If the path is a virtual path, it will return the first project database in the session.
pub(crate) fn project_db(&self, path: &AnySystemPath) -> &ProjectDatabase {
@@ -341,15 +333,17 @@ impl Session {
/// Returns a reference to the project's [`ProjectState`] in which the given `path` belongs.
///
/// If the path is a system path, it will return the project database that is closest to the
/// given path, or the default project if no project is found for the path.
/// given path, or the first project if no project is found for the path.
///
/// If the path is a virtual path, it will return the first project database in the session.
pub(crate) fn project_state(&self, path: &AnySystemPath) -> &ProjectState {
match path {
AnySystemPath::System(system_path) => {
self.project_state_for_path(system_path).unwrap_or_else(|| {
self.default_project
.get(self.index.as_ref(), &self.native_system)
self.projects
.values()
.next()
.expect("To always have at least one project")
})
}
AnySystemPath::SystemVirtual(_virtual_path) => {
@@ -373,15 +367,22 @@ impl Session {
/// [`project_db`]: Session::project_db
pub(crate) fn project_state_mut(&mut self, path: &AnySystemPath) -> &mut ProjectState {
match path {
AnySystemPath::System(system_path) => self
.projects
.range_mut(..=system_path.to_path_buf())
.next_back()
.map(|(_, project)| project)
.unwrap_or_else(|| {
self.default_project
.get_mut(self.index.as_ref(), &self.native_system)
}),
AnySystemPath::System(system_path) => {
let range = ..=system_path.to_path_buf();
// Using `range` here to work around a borrow checker limitation
// where it can't prove that the `range_mut` call and the `self.projects.values_mut`
// never borrow `self.projects` mutably at the same time.
// https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions
if self.projects.range(range.clone()).next_back().is_some() {
return self.projects.range_mut(range).next_back().unwrap().1;
}
// TODO: Currently, ty only supports single workspaces but we need to figure out
// which project to use when we support multiple projects (e.g. look for the first project
// with an overlapping search path?)
self.projects.values_mut().next().unwrap()
}
AnySystemPath::SystemVirtual(_virtual_path) => {
// TODO: Currently, ty only supports single workspace but we need to figure out
// which project should this virtual path belong to when there are multiple
@@ -426,19 +427,14 @@ impl Session {
.apply_changes(changes, overrides.as_ref())
}
/// Returns a mutable iterator over all project databases that have been initialized to this point.
///
/// This iterator will only yield the default project database if it has been used.
/// Returns a mutable iterator over all project databases.
pub(crate) fn projects_mut(&mut self) -> impl Iterator<Item = &'_ mut ProjectDatabase> + '_ {
self.project_states_mut().map(|project| &mut project.db)
}
/// Returns a mutable iterator over all projects that have been initialized to this point.
///
/// This iterator will only yield the default project if it has been used.
/// Returns a mutable iterator over all projects.
pub(crate) fn project_states_mut(&mut self) -> impl Iterator<Item = &'_ mut ProjectState> + '_ {
let default_project = self.default_project.try_get_mut();
self.projects.values_mut().chain(default_project)
self.projects.values_mut()
}
pub(crate) fn initialize_workspaces(
@@ -1245,64 +1241,6 @@ impl Workspace {
}
}
/// Thin wrapper around the default project database that ensures it only gets initialized
/// when it's first accessed.
///
/// There are a few advantages to this:
///
/// 1. Salsa has a fast-path for query lookups for the first created database.
/// We really want that to be the actual project database and not our fallback database.
/// 2. The logs when the server starts can be confusing if it once shows it uses Python X (for the default db)
/// but then has another log that it uses Python Y (for the actual project db).
struct DefaultProject(std::sync::OnceLock<ProjectState>);
impl DefaultProject {
pub(crate) fn new() -> Self {
DefaultProject(std::sync::OnceLock::new())
}
pub(crate) fn get(
&self,
index: Option<&Arc<Index>>,
fallback_system: &Arc<dyn System + 'static + Send + Sync + RefUnwindSafe>,
) -> &ProjectState {
self.0.get_or_init(|| {
tracing::info!("Initializing the default project");
let index = index.unwrap();
let system = LSPSystem::new(index.clone(), fallback_system.clone());
let metadata = ProjectMetadata::from_options(
Options::default(),
system.current_directory().to_path_buf(),
None,
MisconfigurationMode::UseDefault,
)
.unwrap();
ProjectState {
db: ProjectDatabase::new(metadata, system).unwrap(),
untracked_files_with_pushed_diagnostics: Vec::new(),
}
})
}
pub(crate) fn get_mut(
&mut self,
index: Option<&Arc<Index>>,
fallback_system: &Arc<dyn System + 'static + Send + Sync + RefUnwindSafe>,
) -> &mut ProjectState {
let _ = self.get(index, fallback_system);
// SAFETY: The `OnceLock` is guaranteed to be initialized at this point because
// we called `get` above, which initializes it if it wasn't already.
self.0.get_mut().unwrap()
}
pub(crate) fn try_get_mut(&mut self) -> Option<&mut ProjectState> {
self.0.get_mut()
}
}
/// A workspace diagnostic request that didn't yield any changes or diagnostic
/// when it ran the last time.
#[derive(Debug)]