Skip to content

Commit 8b77e26

Browse files
committed
Implement a config override for the default #[cfg(test)] in cargo crates
Fixes crates which vanish when the 'test' cfg atom is set. Fix #7243. Fix #9203. Fix #7225.
1 parent 1f6abb7 commit 8b77e26

File tree

7 files changed

+108
-55
lines changed

7 files changed

+108
-55
lines changed

crates/cfg/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl CfgOptions {
5252
}
5353
}
5454

55+
#[derive(Clone, Debug, PartialEq, Eq)]
5556
pub struct CfgDiff {
5657
// Invariants: No duplicates, no atom that's both in `enable` and `disable`.
5758
enable: Vec<CfgAtom>,

crates/project_model/src/cargo_workspace.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! See [`CargoWorkspace`].
22
3+
use std::iter;
34
use std::path::PathBuf;
45
use std::{convert::TryInto, ops, process::Command, sync::Arc};
56

@@ -12,6 +13,7 @@ use rustc_hash::FxHashMap;
1213
use serde::Deserialize;
1314
use serde_json::from_value;
1415

16+
use crate::CfgOverrides;
1517
use crate::{build_data::BuildDataConfig, utf8_stdout};
1618

1719
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
@@ -76,6 +78,21 @@ pub struct CargoConfig {
7678

7779
/// rustc private crate source
7880
pub rustc_source: Option<RustcSource>,
81+
82+
/// crates to disable `#[cfg(test)]` on
83+
pub unset_test_crates: Vec<String>,
84+
}
85+
86+
impl CargoConfig {
87+
pub fn cfg_overrides(&self) -> CfgOverrides {
88+
self.unset_test_crates
89+
.iter()
90+
.cloned()
91+
.zip(iter::repeat_with(|| {
92+
cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())]).unwrap()
93+
}))
94+
.collect()
95+
}
7996
}
8097

8198
pub type Package = Idx<PackageData>;

crates/project_model/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub use crate::{
4141
},
4242
project_json::{ProjectJson, ProjectJsonData},
4343
sysroot::Sysroot,
44-
workspace::{PackageRoot, ProjectWorkspace},
44+
workspace::{CfgOverrides, PackageRoot, ProjectWorkspace},
4545
};
4646

4747
pub use proc_macro_api::ProcMacroClient;

crates/project_model/src/workspace.rs

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
77
use anyhow::{format_err, Context, Result};
88
use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
99
use cargo_workspace::DepKind;
10-
use cfg::{CfgAtom, CfgDiff, CfgOptions};
10+
use cfg::{CfgDiff, CfgOptions};
1111
use paths::{AbsPath, AbsPathBuf};
1212
use proc_macro_api::ProcMacroClient;
1313
use rustc_hash::{FxHashMap, FxHashSet};
@@ -22,6 +22,8 @@ use crate::{
2222
Sysroot, TargetKind,
2323
};
2424

25+
pub type CfgOverrides = FxHashMap<String, CfgDiff>;
26+
2527
/// `PackageRoot` describes a package root folder.
2628
/// Which may be an external dependency, or a member of
2729
/// the current workspace.
@@ -46,6 +48,7 @@ pub enum ProjectWorkspace {
4648
/// FIXME: make this a per-crate map, as, eg, build.rs might have a
4749
/// different target.
4850
rustc_cfg: Vec<CfgFlag>,
51+
cfg_overrides: CfgOverrides,
4952
},
5053
/// Project workspace was manually specified using a `rust-project.json` file.
5154
Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
@@ -67,7 +70,7 @@ impl fmt::Debug for ProjectWorkspace {
6770
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6871
// Make sure this isn't too verbose.
6972
match self {
70-
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
73+
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => f
7174
.debug_struct("Cargo")
7275
.field("root", &cargo.workspace_root().file_name())
7376
.field("n_packages", &cargo.packages().len())
@@ -77,6 +80,7 @@ impl fmt::Debug for ProjectWorkspace {
7780
&rustc.as_ref().map_or(0, |rc| rc.packages().len()),
7881
)
7982
.field("n_rustc_cfg", &rustc_cfg.len())
83+
.field("n_cfg_overrides", &cfg_overrides.len())
8084
.finish(),
8185
ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
8286
let mut debug_struct = f.debug_struct("Json");
@@ -164,7 +168,9 @@ impl ProjectWorkspace {
164168
};
165169

166170
let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
167-
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }
171+
172+
let cfg_overrides = config.cfg_overrides();
173+
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides }
168174
}
169175
};
170176

@@ -213,43 +219,45 @@ impl ProjectWorkspace {
213219
})
214220
}))
215221
.collect::<Vec<_>>(),
216-
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _ } => cargo
217-
.packages()
218-
.map(|pkg| {
219-
let is_member = cargo[pkg].is_member;
220-
let pkg_root = cargo[pkg].root().to_path_buf();
221-
222-
let mut include = vec![pkg_root.clone()];
223-
include.extend(
224-
build_data
225-
.and_then(|it| it.get(cargo.workspace_root()))
226-
.and_then(|map| map.get(&cargo[pkg].id))
227-
.and_then(|it| it.out_dir.clone()),
228-
);
222+
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _, cfg_overrides: _ } => {
223+
cargo
224+
.packages()
225+
.map(|pkg| {
226+
let is_member = cargo[pkg].is_member;
227+
let pkg_root = cargo[pkg].root().to_path_buf();
228+
229+
let mut include = vec![pkg_root.clone()];
230+
include.extend(
231+
build_data
232+
.and_then(|it| it.get(cargo.workspace_root()))
233+
.and_then(|map| map.get(&cargo[pkg].id))
234+
.and_then(|it| it.out_dir.clone()),
235+
);
229236

230-
let mut exclude = vec![pkg_root.join(".git")];
231-
if is_member {
232-
exclude.push(pkg_root.join("target"));
233-
} else {
234-
exclude.push(pkg_root.join("tests"));
235-
exclude.push(pkg_root.join("examples"));
236-
exclude.push(pkg_root.join("benches"));
237-
}
238-
PackageRoot { is_member, include, exclude }
239-
})
240-
.chain(sysroot.crates().map(|krate| PackageRoot {
241-
is_member: false,
242-
include: vec![sysroot[krate].root_dir().to_path_buf()],
243-
exclude: Vec::new(),
244-
}))
245-
.chain(rustc.into_iter().flat_map(|rustc| {
246-
rustc.packages().map(move |krate| PackageRoot {
237+
let mut exclude = vec![pkg_root.join(".git")];
238+
if is_member {
239+
exclude.push(pkg_root.join("target"));
240+
} else {
241+
exclude.push(pkg_root.join("tests"));
242+
exclude.push(pkg_root.join("examples"));
243+
exclude.push(pkg_root.join("benches"));
244+
}
245+
PackageRoot { is_member, include, exclude }
246+
})
247+
.chain(sysroot.crates().map(|krate| PackageRoot {
247248
is_member: false,
248-
include: vec![rustc[krate].root().to_path_buf()],
249+
include: vec![sysroot[krate].root_dir().to_path_buf()],
249250
exclude: Vec::new(),
250-
})
251-
}))
252-
.collect(),
251+
}))
252+
.chain(rustc.into_iter().flat_map(|rustc| {
253+
rustc.packages().map(move |krate| PackageRoot {
254+
is_member: false,
255+
include: vec![rustc[krate].root().to_path_buf()],
256+
exclude: Vec::new(),
257+
})
258+
}))
259+
.collect()
260+
}
253261
ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
254262
.into_iter()
255263
.map(|detached_file| PackageRoot {
@@ -299,16 +307,22 @@ impl ProjectWorkspace {
299307
project,
300308
sysroot,
301309
),
302-
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => cargo_to_crate_graph(
303-
rustc_cfg.clone(),
304-
&proc_macro_loader,
305-
load,
306-
cargo,
307-
build_data.and_then(|it| it.get(cargo.workspace_root())),
308-
sysroot,
309-
rustc,
310-
rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())),
311-
),
310+
ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => {
311+
cargo_to_crate_graph(
312+
rustc_cfg.clone(),
313+
cfg_overrides,
314+
&proc_macro_loader,
315+
load,
316+
cargo,
317+
build_data.and_then(|it| it.get(cargo.workspace_root())),
318+
sysroot,
319+
rustc,
320+
rustc
321+
.as_ref()
322+
.zip(build_data)
323+
.and_then(|(it, map)| map.get(it.workspace_root())),
324+
)
325+
}
312326
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
313327
detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
314328
}
@@ -398,6 +412,7 @@ fn project_json_to_crate_graph(
398412

399413
fn cargo_to_crate_graph(
400414
rustc_cfg: Vec<CfgFlag>,
415+
override_cfg: &CfgOverrides,
401416
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
402417
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
403418
cargo: &CargoWorkspace,
@@ -427,15 +442,16 @@ fn cargo_to_crate_graph(
427442
for pkg in cargo.packages() {
428443
let mut cfg_options = &cfg_options;
429444
let mut replaced_cfg_options;
430-
if cargo[pkg].name == "core" {
431-
// FIXME: in the specific case of libcore in rust-lang/rust (i.e. it is not coming from
432-
// a sysroot), there's a `#![cfg(not(test))]` at the top of it that makes its contents
433-
// get ignored by r-a. We should implement a more general solution for this
445+
if let Some(overrides) = override_cfg.get(&cargo[pkg].name) {
446+
// FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
447+
// in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
448+
// working on rust-lang/rust as that's the only time it appears outside sysroot).
449+
//
450+
// A more ideal solution might be to reanalyze crates based on where the cursor is and
451+
// figure out the set of cfgs that would have to apply to make it active.
434452

435453
replaced_cfg_options = cfg_options.clone();
436-
replaced_cfg_options.apply_diff(
437-
CfgDiff::new(Default::default(), vec![CfgAtom::Flag("test".into())]).unwrap(),
438-
);
454+
replaced_cfg_options.apply_diff(overrides.clone());
439455
cfg_options = &replaced_cfg_options;
440456
};
441457

crates/rust-analyzer/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ config_data! {
5555
cargo_autoreload: bool = "true",
5656
/// Activate all available features (`--all-features`).
5757
cargo_allFeatures: bool = "false",
58+
/// Unsets `#[cfg(test)]` for the specified crates.
59+
cargo_unsetTest: Vec<String> = "[\"core\"]",
5860
/// List of features to activate.
5961
cargo_features: Vec<String> = "[]",
6062
/// Run build scripts (`build.rs`) for more precise code analysis.
@@ -595,8 +597,10 @@ impl Config {
595597
target: self.data.cargo_target.clone(),
596598
rustc_source,
597599
no_sysroot: self.data.cargo_noSysroot,
600+
unset_test_crates: self.data.cargo_unsetTest.clone(),
598601
}
599602
}
603+
600604
pub fn rustfmt(&self) -> RustfmtConfig {
601605
match &self.data.rustfmt_overrideCommand {
602606
Some(args) if !args.is_empty() => {

docs/user/generated_config.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ Automatically refresh project info via `cargo metadata` on
3939
--
4040
Activate all available features (`--all-features`).
4141
--
42+
[[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`)::
43+
+
44+
--
45+
Unsets `#[cfg(test)]` for the specified crates.
46+
--
4247
[[rust-analyzer.cargo.features]]rust-analyzer.cargo.features (default: `[]`)::
4348
+
4449
--

editors/code/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,16 @@
452452
"default": false,
453453
"type": "boolean"
454454
},
455+
"rust-analyzer.cargo.unsetTest": {
456+
"markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.",
457+
"default": [
458+
"core"
459+
],
460+
"type": "array",
461+
"items": {
462+
"type": "string"
463+
}
464+
},
455465
"rust-analyzer.cargo.features": {
456466
"markdownDescription": "List of features to activate.",
457467
"default": [],

0 commit comments

Comments
 (0)