diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index 8c578f24a2..6801e67e73 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -69,13 +69,6 @@ pub(crate) struct Session { /// The projects across all workspaces. projects: BTreeMap, - /// The project to use for files outside any workspace. For example, if the user - /// opens the project `/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 + '_ { 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 + '_ { - 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); - -impl DefaultProject { - pub(crate) fn new() -> Self { - DefaultProject(std::sync::OnceLock::new()) - } - - pub(crate) fn get( - &self, - index: Option<&Arc>, - fallback_system: &Arc, - ) -> &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>, - fallback_system: &Arc, - ) -> &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)]