Skip to content

Commit b3d6a83

Browse files
committed
fix: Updating settings should not clobber discovered projects
`linkedProjects` is owned by the user's configuration, so when users update this setting, `linkedProjects` is reset. This is problematic when `linkedProjects` also contains projects discovered with `discoverCommand`. The buggy behaviour occurred when: (1) The user configures `discoverCommand` and loads a Rust project. (2) The user changes any setting in VS Code, so rust-analyzer receives `workspace/didChangeConfiguration`. (3) `handle_did_change_configuration` ultimately calls `Client::apply_change_with_sink()`, which updates `config.user_config` and discards any items we added in `linkedProjects`. Instead, separate out `discovered_projects_from_filesystem` and `discovered_projects_from_command` from user configuration, so user settings cannot affect any type of discovered project. This fixes the subtle issue mentioned here: #17246 (comment)
1 parent 124c748 commit b3d6a83

File tree

4 files changed

+85
-61
lines changed

4 files changed

+85
-61
lines changed

crates/project-model/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ pub use crate::{
5252
};
5353
pub use cargo_metadata::Metadata;
5454

55+
#[derive(Debug, Clone, PartialEq, Eq)]
56+
pub struct ProjectJsonFromCommand {
57+
/// The data describing this project, such as its dependencies.
58+
pub data: ProjectJsonData,
59+
/// The build system specific file that describes this project,
60+
/// such as a `my-project/BUCK` file.
61+
pub buildfile: AbsPathBuf,
62+
}
63+
5564
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
5665
pub enum ProjectManifest {
5766
ProjectJson(ManifestPath),

crates/project-model/src/project_json.rs

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ pub struct ProjectJson {
6666
/// e.g. `path/to/sysroot/lib/rustlib/src/rust`
6767
pub(crate) sysroot_src: Option<AbsPathBuf>,
6868
project_root: AbsPathBuf,
69+
/// The path to the rust-project.json file. May be None if this
70+
/// data was generated by the discoverConfig command.
6971
manifest: Option<ManifestPath>,
7072
crates: Vec<Crate>,
7173
/// Configuration for CLI commands.

crates/rust-analyzer/src/config.rs

+73-60
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use ide_db::{
2424
use itertools::Itertools;
2525
use paths::{Utf8Path, Utf8PathBuf};
2626
use project_model::{
27-
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
27+
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand, ProjectManifest,
28+
RustLibSource,
2829
};
2930
use rustc_hash::{FxHashMap, FxHashSet};
3031
use semver::Version;
@@ -761,7 +762,13 @@ enum RatomlFile {
761762

762763
#[derive(Debug, Clone)]
763764
pub struct Config {
764-
discovered_projects: Vec<ProjectManifest>,
765+
/// Projects that have a Cargo.toml or a rust-project.json in a
766+
/// parent directory, so we can discover them by walking the
767+
/// file system.
768+
discovered_projects_from_filesystem: Vec<ProjectManifest>,
769+
/// Projects whose configuration was generated by a command
770+
/// configured in discoverConfig.
771+
discovered_projects_from_command: Vec<ProjectJsonFromCommand>,
765772
/// The workspace roots as registered by the LSP client
766773
workspace_roots: Vec<AbsPathBuf>,
767774
caps: ClientCapabilities,
@@ -1054,19 +1061,19 @@ impl Config {
10541061
(config, e, should_update)
10551062
}
10561063

1057-
pub fn add_linked_projects(&mut self, data: ProjectJsonData, buildfile: AbsPathBuf) {
1058-
let linked_projects = &mut self.client_config.0.global.linkedProjects;
1059-
1060-
let new_project = ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile };
1061-
match linked_projects {
1062-
Some(projects) => {
1063-
match projects.iter_mut().find(|p| p.manifest() == new_project.manifest()) {
1064-
Some(p) => *p = new_project,
1065-
None => projects.push(new_project),
1066-
}
1064+
pub fn add_discovered_project_from_command(
1065+
&mut self,
1066+
data: ProjectJsonData,
1067+
buildfile: AbsPathBuf,
1068+
) {
1069+
for proj in self.discovered_projects_from_command.iter_mut() {
1070+
if proj.buildfile == buildfile {
1071+
proj.data = data;
1072+
return;
10671073
}
1068-
None => *linked_projects = Some(vec![new_project]),
10691074
}
1075+
1076+
self.discovered_projects_from_command.push(ProjectJsonFromCommand { data, buildfile });
10701077
}
10711078
}
10721079

@@ -1344,7 +1351,8 @@ impl Config {
13441351

13451352
Config {
13461353
caps: ClientCapabilities::new(caps),
1347-
discovered_projects: Vec::new(),
1354+
discovered_projects_from_filesystem: Vec::new(),
1355+
discovered_projects_from_command: Vec::new(),
13481356
root_path,
13491357
snippets: Default::default(),
13501358
workspace_roots,
@@ -1365,7 +1373,7 @@ impl Config {
13651373
if discovered.is_empty() {
13661374
tracing::error!("failed to find any projects in {:?}", &self.workspace_roots);
13671375
}
1368-
self.discovered_projects = discovered;
1376+
self.discovered_projects_from_filesystem = discovered;
13691377
}
13701378

13711379
pub fn remove_workspace(&mut self, path: &AbsPath) {
@@ -1687,42 +1695,59 @@ impl Config {
16871695
self.workspace_discoverConfig().as_ref()
16881696
}
16891697

1690-
pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
1691-
match self.linkedProjects().as_slice() {
1692-
[] => {
1693-
let exclude_dirs: Vec<_> =
1694-
self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
1695-
self.discovered_projects
1696-
.iter()
1697-
.filter(|project| {
1698-
!exclude_dirs.iter().any(|p| project.manifest_path().starts_with(p))
1699-
})
1700-
.cloned()
1701-
.map(LinkedProject::from)
1702-
.collect()
1698+
fn discovered_projects(&self) -> Vec<ManifestOrProjectJson> {
1699+
let exclude_dirs: Vec<_> =
1700+
self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
1701+
1702+
let mut projects = vec![];
1703+
for fs_proj in &self.discovered_projects_from_filesystem {
1704+
let manifest_path = fs_proj.manifest_path();
1705+
if exclude_dirs.iter().any(|p| manifest_path.starts_with(p)) {
1706+
continue;
17031707
}
1704-
linked_projects => linked_projects
1705-
.iter()
1706-
.filter_map(|linked_project| match linked_project {
1707-
ManifestOrProjectJson::Manifest(it) => {
1708-
let path = self.root_path.join(it);
1709-
ProjectManifest::from_manifest_file(path)
1710-
.map_err(|e| tracing::error!("failed to load linked project: {}", e))
1711-
.ok()
1712-
.map(Into::into)
1713-
}
1714-
ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
1715-
let root_path =
1716-
buildfile.parent().expect("Unable to get parent of buildfile");
17171708

1718-
Some(ProjectJson::new(None, root_path, data.clone()).into())
1719-
}
1720-
ManifestOrProjectJson::ProjectJson(it) => {
1721-
Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
1722-
}
1723-
})
1724-
.collect(),
1709+
let buf: Utf8PathBuf = manifest_path.to_path_buf().into();
1710+
projects.push(ManifestOrProjectJson::Manifest(buf));
1711+
}
1712+
1713+
for dis_proj in &self.discovered_projects_from_command {
1714+
projects.push(ManifestOrProjectJson::DiscoveredProjectJson {
1715+
data: dis_proj.data.clone(),
1716+
buildfile: dis_proj.buildfile.clone(),
1717+
});
17251718
}
1719+
1720+
projects
1721+
}
1722+
1723+
pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
1724+
let linked_projects = self.linkedProjects();
1725+
let projects = if linked_projects.is_empty() {
1726+
self.discovered_projects()
1727+
} else {
1728+
linked_projects.clone()
1729+
};
1730+
1731+
projects
1732+
.iter()
1733+
.filter_map(|linked_project| match linked_project {
1734+
ManifestOrProjectJson::Manifest(it) => {
1735+
let path = self.root_path.join(it);
1736+
ProjectManifest::from_manifest_file(path)
1737+
.map_err(|e| tracing::error!("failed to load linked project: {}", e))
1738+
.ok()
1739+
.map(Into::into)
1740+
}
1741+
ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
1742+
let root_path = buildfile.parent().expect("Unable to get parent of buildfile");
1743+
1744+
Some(ProjectJson::new(None, root_path, data.clone()).into())
1745+
}
1746+
ManifestOrProjectJson::ProjectJson(it) => {
1747+
Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
1748+
}
1749+
})
1750+
.collect()
17261751
}
17271752

17281753
pub fn prefill_caches(&self) -> bool {
@@ -2282,18 +2307,6 @@ where
22822307
se.serialize_str(path.as_str())
22832308
}
22842309

2285-
impl ManifestOrProjectJson {
2286-
fn manifest(&self) -> Option<&Utf8Path> {
2287-
match self {
2288-
ManifestOrProjectJson::Manifest(manifest) => Some(manifest),
2289-
ManifestOrProjectJson::DiscoveredProjectJson { buildfile, .. } => {
2290-
Some(buildfile.as_ref())
2291-
}
2292-
ManifestOrProjectJson::ProjectJson(_) => None,
2293-
}
2294-
}
2295-
}
2296-
22972310
#[derive(Serialize, Deserialize, Debug, Clone)]
22982311
#[serde(rename_all = "snake_case")]
22992312
enum ExprFillDefaultDef {

crates/rust-analyzer/src/main_loop.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ impl GlobalState {
875875
self.discover_workspace_queue.op_completed(());
876876

877877
let mut config = Config::clone(&*self.config);
878-
config.add_linked_projects(project, buildfile);
878+
config.add_discovered_project_from_command(project, buildfile);
879879
self.update_configuration(config);
880880
}
881881
DiscoverProjectMessage::Progress { message } => {

0 commit comments

Comments
 (0)