Skip to content

Commit f1cff1e

Browse files
committed
Add a test that generates a tree from running path.ancestors() on the roots
Also renames a bunch of functions and hides PointerCmp from public API
1 parent 359bf1d commit f1cff1e

File tree

1 file changed

+122
-22
lines changed
  • crates/rust-analyzer/src/config

1 file changed

+122
-22
lines changed

crates/rust-analyzer/src/config/tree.rs

Lines changed: 122 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,23 @@ trait ConfigTreeQueries {
8888
fn client_config(&self) -> Option<PointerCmp<ConfigInput>>;
8989

9090
#[salsa::input]
91-
fn config_parent(&self, file_id: FileId) -> Option<FileId>;
91+
fn parent(&self, file_id: FileId) -> Option<FileId>;
9292

9393
#[salsa::input]
9494
fn config_input(&self, file_id: FileId) -> Option<PointerCmp<ConfigInput>>;
9595

96-
fn compute_recursive(&self, file_id: FileId) -> PointerCmp<LocalConfigData>;
96+
fn recursive_local(&self, file_id: FileId) -> PointerCmp<LocalConfigData>;
9797

98-
fn local_config(&self, file_id: FileId) -> PointerCmp<LocalConfigData>;
98+
/// The output
99+
fn computed_local_config(&self, file_id: FileId) -> PointerCmp<LocalConfigData>;
99100
}
100101

101-
fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp<LocalConfigData> {
102+
fn recursive_local(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp<LocalConfigData> {
102103
let self_input = db.config_input(file_id);
103104
tracing::trace!(?self_input, ?file_id);
104-
match db.config_parent(file_id) {
105+
match db.parent(file_id) {
105106
Some(parent) if parent != file_id => {
106-
let parent_computed = db.compute_recursive(parent);
107+
let parent_computed = db.recursive_local(parent);
107108
if let Some(input) = self_input.as_deref() {
108109
PointerCmp::new(parent_computed.clone_with_overrides(input.local.clone()))
109110
} else {
@@ -122,8 +123,11 @@ fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp<
122123
}
123124
}
124125

125-
fn local_config(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp<LocalConfigData> {
126-
let computed = db.compute_recursive(file_id);
126+
fn computed_local_config(
127+
db: &dyn ConfigTreeQueries,
128+
file_id: FileId,
129+
) -> PointerCmp<LocalConfigData> {
130+
let computed = db.recursive_local(file_id);
127131
if let Some(client) = db.client_config() {
128132
PointerCmp::new(computed.clone_with_overrides(client.local.clone()))
129133
} else {
@@ -149,14 +153,35 @@ impl ConfigDb {
149153
};
150154
this.set_client_config(None);
151155
this.ensure_node(xdg_config_file_id);
152-
this.set_config_parent(xdg_config_file_id, None);
153156
this
154157
}
155158

159+
/// Gets the value of LocalConfigData for a given `rust-analyzer.toml` FileId.
160+
///
161+
/// The rust-analyzer.toml does not need to exist on disk. All values are the expression of
162+
/// overriding the parent `rust-analyzer.toml`, set by adding an entry in
163+
/// `ConfigChanges.parent_changes`.
164+
///
165+
/// If the db is not aware of the given `rust-analyzer.toml` FileId, then the config is read
166+
/// from the user's system-wide default config.
167+
///
168+
/// Note that the client config overrides all configs.
169+
pub fn local_config(&self, ra_toml_file_id: FileId) -> Arc<LocalConfigData> {
170+
if self.known_file_ids.contains(&ra_toml_file_id) {
171+
self.computed_local_config(ra_toml_file_id).0
172+
} else {
173+
tracing::warn!(?ra_toml_file_id, "called local_config with unknown file id");
174+
self.computed_local_config(self.xdg_config_file_id).0
175+
}
176+
}
177+
178+
/// Applies a bunch of [`ConfigChanges`]. The FileIds referred to in `ConfigChanges` do not
179+
/// need to exist. You can generate the `parent_changes` hashmap by iterating ancestors of all
180+
/// of the [`ide::SourceRoot`]s, slapping `.map(|path| path.join("rust-analyzer.toml"))`.
156181
pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &Vfs) -> Vec<ConfigTreeError> {
157182
let mut scratch_errors = Vec::new();
158183
let mut errors = Vec::new();
159-
let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes;
184+
let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes;
160185

161186
if let Some(change) = client_change {
162187
let current = self.client_config();
@@ -174,9 +199,6 @@ impl ConfigDb {
174199
// turn and face the strain
175200
match change.change_kind {
176201
vfs::ChangeKind::Create | vfs::ChangeKind::Modify => {
177-
if change.change_kind == vfs::ChangeKind::Create {
178-
parent_changes.entry(change.file_id).or_insert(ConfigParent::UserDefault);
179-
}
180202
let input = parse_toml(change.file_id, vfs, &mut scratch_errors, &mut errors)
181203
.map(PointerCmp);
182204
tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input);
@@ -203,15 +225,25 @@ impl ConfigDb {
203225
};
204226
// order of children within the parent node does not matter
205227
tracing::trace!("appending child {file_id:?} to {parent_node_id:?}");
206-
self.set_config_parent(file_id, Some(parent_node_id))
228+
self.set_parent(file_id, Some(parent_node_id))
207229
}
208230

209231
errors
210232
}
211233

234+
/// Inserts default values into the salsa inputs for the given file_id
235+
/// if it's never been seen before
212236
fn ensure_node(&mut self, file_id: FileId) {
213237
if self.known_file_ids.insert(file_id) {
214238
self.set_config_input(file_id, None);
239+
self.set_parent(
240+
file_id,
241+
if file_id == self.xdg_config_file_id {
242+
None
243+
} else {
244+
Some(self.xdg_config_file_id)
245+
},
246+
);
215247
}
216248
}
217249
}
@@ -250,17 +282,15 @@ fn parse_toml(
250282

251283
#[cfg(test)]
252284
mod tests {
253-
use std::path::PathBuf;
285+
use std::path::{Path, PathBuf};
254286

287+
use itertools::Itertools;
255288
use vfs::{AbsPathBuf, VfsPath};
256289

257290
fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId {
258-
tracing_subscriber::fmt().init();
259291
let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap();
260292

261293
let vfs_path = VfsPath::from(abs_path);
262-
// FIXME: the vfs should expose this functionality more simply.
263-
// We shouldn't have to clone the vfs path just to get a FileId.
264294
let file_id = vfs.alloc_file_id(vfs_path);
265295
vfs.set_file_id_contents(file_id, None);
266296
file_id
@@ -270,8 +300,6 @@ mod tests {
270300
let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap();
271301

272302
let vfs_path = VfsPath::from(abs_path);
273-
// FIXME: the vfs should expose this functionality more simply.
274-
// We shouldn't have to clone the vfs path just to get a FileId.
275303
let file_id = vfs.alloc_file_id(vfs_path);
276304
vfs.set_file_id_contents(file_id, Some(config.to_string().into_bytes()));
277305
file_id
@@ -280,6 +308,7 @@ mod tests {
280308
use super::*;
281309
#[test]
282310
fn basic() {
311+
tracing_subscriber::fmt().try_init().ok();
283312
let mut vfs = Vfs::default();
284313
let xdg_config_file_id =
285314
alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml");
@@ -364,9 +393,9 @@ mod tests {
364393
let prev = local;
365394
let local = config_tree.local_config(crate_a);
366395
// Should have been recomputed
367-
assert_ne!(prev, local);
396+
assert!(!Arc::ptr_eq(&prev, &local));
368397
// But without changes in between, should give the same Arc back
369-
assert_eq!(local, config_tree.local_config(crate_a));
398+
assert!(Arc::ptr_eq(&local, &config_tree.local_config(crate_a)));
370399

371400
// The newly added xdg_config_file_id should affect the output if nothing else touches
372401
// this key
@@ -379,4 +408,75 @@ mod tests {
379408
assert_eq!(local.completion_autoimport_enable, false);
380409
assert_eq!(local.semanticHighlighting_strings_enable, false);
381410
}
411+
412+
#[test]
413+
fn generated_parent_changes() {
414+
tracing_subscriber::fmt().try_init().ok();
415+
let mut vfs = Vfs::default();
416+
417+
let xdg =
418+
alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml");
419+
let mut config_tree = ConfigDb::new(xdg);
420+
421+
let project_root = Path::new("/root");
422+
let sourceroots =
423+
[PathBuf::new().join("/root/crate_a"), PathBuf::new().join("/root/crate_a/crate_b")];
424+
let sourceroot_tomls = sourceroots
425+
.iter()
426+
.map(|dir| dir.join("rust-analyzer.toml"))
427+
.map(|path| AbsPathBuf::try_from(path).unwrap())
428+
.map(|path| vfs.alloc_file_id(path.into()))
429+
.collect_vec();
430+
let &[crate_a, crate_b] = &sourceroot_tomls[..] else {
431+
panic!();
432+
};
433+
434+
let parent_changes = sourceroots
435+
.iter()
436+
.flat_map(|path| {
437+
path.ancestors()
438+
.take_while(|x| x.starts_with(project_root))
439+
.map(|dir| dir.join("rust-analyzer.toml"))
440+
.map(|path| AbsPathBuf::try_from(path).unwrap())
441+
.map(|path| vfs.alloc_file_id(path.into()))
442+
.collect_vec()
443+
.into_iter()
444+
.tuple_windows()
445+
.map(|(a, b)| (a, ConfigParent::Parent(b)))
446+
})
447+
.collect::<FxHashMap<_, _>>();
448+
449+
for (&a, parent) in &parent_changes {
450+
eprintln!(
451+
"{a:?} ({:?}): parent = {parent:?} ({:?})",
452+
vfs.file_path(a),
453+
match parent {
454+
ConfigParent::Parent(p) => vfs.file_path(*p).to_string(),
455+
ConfigParent::UserDefault => "xdg".to_string(),
456+
}
457+
);
458+
}
459+
460+
vfs.set_file_id_contents(
461+
xdg,
462+
Some(b"[inlayHints.discriminantHints]\nenable = \"always\"".to_vec()),
463+
);
464+
vfs.set_file_id_contents(crate_a, Some(b"[completion.autoself]\nenable = false".to_vec()));
465+
// note that crate_b's rust-analyzer.toml doesn't exist
466+
467+
let changes = ConfigChanges {
468+
ra_toml_changes: dbg!(vfs.take_changes()),
469+
parent_changes,
470+
client_change: None,
471+
};
472+
473+
dbg!(config_tree.apply_changes(changes, &vfs));
474+
let local = config_tree.local_config(crate_b);
475+
476+
assert_eq!(
477+
local.inlayHints_discriminantHints_enable,
478+
crate::config::DiscriminantHintsDef::Always
479+
);
480+
assert_eq!(local.completion_autoself_enable, false);
481+
}
382482
}

0 commit comments

Comments
 (0)