diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs index 03b8dd767f..916d39a0b4 100644 --- a/crates/cfg/src/lib.rs +++ b/crates/cfg/src/lib.rs @@ -1,4 +1,4 @@ -//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator +//! cfg defines conditional compiling options, `cfg` attribute parser and evaluator mod cfg_expr; mod dnf; @@ -52,6 +52,7 @@ impl CfgOptions { } } +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. enable: Vec, @@ -59,6 +60,20 @@ pub struct CfgDiff { } impl CfgDiff { + /// Create a new CfgDiff. Will return None if the same item appears more than once in the set + /// of both. + pub fn new(enable: Vec, disable: Vec) -> Option { + let mut occupied = FxHashSet::default(); + for item in enable.iter().chain(disable.iter()) { + if !occupied.insert(item) { + // was present + return None; + } + } + + Some(CfgDiff { enable, disable }) + } + /// Returns the total number of atoms changed by this diff. pub fn len(&self) -> usize { self.enable.len() + self.disable.len() diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index ac079f83e6..0935ea9676 100644 --- a/crates/project_model/src/cargo_workspace.rs +++ b/crates/project_model/src/cargo_workspace.rs @@ -1,5 +1,6 @@ //! See [`CargoWorkspace`]. +use std::iter; use std::path::PathBuf; use std::{convert::TryInto, ops, process::Command, sync::Arc}; @@ -12,6 +13,7 @@ use rustc_hash::FxHashMap; use serde::Deserialize; use serde_json::from_value; +use crate::CfgOverrides; use crate::{build_data::BuildDataConfig, utf8_stdout}; /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo @@ -76,6 +78,21 @@ pub struct CargoConfig { /// rustc private crate source pub rustc_source: Option, + + /// crates to disable `#[cfg(test)]` on + pub unset_test_crates: Vec, +} + +impl CargoConfig { + pub fn cfg_overrides(&self) -> CfgOverrides { + self.unset_test_crates + .iter() + .cloned() + .zip(iter::repeat_with(|| { + cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())]).unwrap() + })) + .collect() + } } pub type Package = Idx; diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 8c6cf94c24..1d408dff2e 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs @@ -41,7 +41,7 @@ pub use crate::{ }, project_json::{ProjectJson, ProjectJsonData}, sysroot::Sysroot, - workspace::{PackageRoot, ProjectWorkspace}, + workspace::{CfgOverrides, PackageRoot, ProjectWorkspace}, }; pub use proc_macro_api::ProcMacroClient; diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index ef0f3c9e42..d8217f714e 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -7,7 +7,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command}; use anyhow::{format_err, Context, Result}; use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; use cargo_workspace::DepKind; -use cfg::CfgOptions; +use cfg::{CfgDiff, CfgOptions}; use paths::{AbsPath, AbsPathBuf}; use proc_macro_api::ProcMacroClient; use rustc_hash::{FxHashMap, FxHashSet}; @@ -22,6 +22,8 @@ use crate::{ Sysroot, TargetKind, }; +pub type CfgOverrides = FxHashMap; + /// `PackageRoot` describes a package root folder. /// Which may be an external dependency, or a member of /// the current workspace. @@ -46,6 +48,7 @@ pub enum ProjectWorkspace { /// FIXME: make this a per-crate map, as, eg, build.rs might have a /// different target. rustc_cfg: Vec, + cfg_overrides: CfgOverrides, }, /// Project workspace was manually specified using a `rust-project.json` file. Json { project: ProjectJson, sysroot: Option, rustc_cfg: Vec }, @@ -67,7 +70,7 @@ impl fmt::Debug for ProjectWorkspace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Make sure this isn't too verbose. match self { - ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f + ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => f .debug_struct("Cargo") .field("root", &cargo.workspace_root().file_name()) .field("n_packages", &cargo.packages().len()) @@ -77,6 +80,7 @@ impl fmt::Debug for ProjectWorkspace { &rustc.as_ref().map_or(0, |rc| rc.packages().len()), ) .field("n_rustc_cfg", &rustc_cfg.len()) + .field("n_cfg_overrides", &cfg_overrides.len()) .finish(), ProjectWorkspace::Json { project, sysroot, rustc_cfg } => { let mut debug_struct = f.debug_struct("Json"); @@ -164,7 +168,9 @@ impl ProjectWorkspace { }; let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref()); - ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } + + let cfg_overrides = config.cfg_overrides(); + ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } } }; @@ -213,43 +219,45 @@ impl ProjectWorkspace { }) })) .collect::>(), - ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _ } => cargo - .packages() - .map(|pkg| { - let is_member = cargo[pkg].is_member; - let pkg_root = cargo[pkg].root().to_path_buf(); + ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _, cfg_overrides: _ } => { + cargo + .packages() + .map(|pkg| { + let is_member = cargo[pkg].is_member; + let pkg_root = cargo[pkg].root().to_path_buf(); - let mut include = vec![pkg_root.clone()]; - include.extend( - build_data - .and_then(|it| it.get(cargo.workspace_root())) - .and_then(|map| map.get(&cargo[pkg].id)) - .and_then(|it| it.out_dir.clone()), - ); + let mut include = vec![pkg_root.clone()]; + include.extend( + build_data + .and_then(|it| it.get(cargo.workspace_root())) + .and_then(|map| map.get(&cargo[pkg].id)) + .and_then(|it| it.out_dir.clone()), + ); - let mut exclude = vec![pkg_root.join(".git")]; - if is_member { - exclude.push(pkg_root.join("target")); - } else { - exclude.push(pkg_root.join("tests")); - exclude.push(pkg_root.join("examples")); - exclude.push(pkg_root.join("benches")); - } - PackageRoot { is_member, include, exclude } - }) - .chain(sysroot.crates().map(|krate| PackageRoot { - is_member: false, - include: vec![sysroot[krate].root_dir().to_path_buf()], - exclude: Vec::new(), - })) - .chain(rustc.into_iter().flat_map(|rustc| { - rustc.packages().map(move |krate| PackageRoot { - is_member: false, - include: vec![rustc[krate].root().to_path_buf()], - exclude: Vec::new(), + let mut exclude = vec![pkg_root.join(".git")]; + if is_member { + exclude.push(pkg_root.join("target")); + } else { + exclude.push(pkg_root.join("tests")); + exclude.push(pkg_root.join("examples")); + exclude.push(pkg_root.join("benches")); + } + PackageRoot { is_member, include, exclude } }) - })) - .collect(), + .chain(sysroot.crates().map(|krate| PackageRoot { + is_member: false, + include: vec![sysroot[krate].root_dir().to_path_buf()], + exclude: Vec::new(), + })) + .chain(rustc.into_iter().flat_map(|rustc| { + rustc.packages().map(move |krate| PackageRoot { + is_member: false, + include: vec![rustc[krate].root().to_path_buf()], + exclude: Vec::new(), + }) + })) + .collect() + } ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files .into_iter() .map(|detached_file| PackageRoot { @@ -299,16 +307,22 @@ impl ProjectWorkspace { project, sysroot, ), - ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => cargo_to_crate_graph( - rustc_cfg.clone(), - &proc_macro_loader, - load, - cargo, - build_data.and_then(|it| it.get(cargo.workspace_root())), - sysroot, - rustc, - rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())), - ), + ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => { + cargo_to_crate_graph( + rustc_cfg.clone(), + cfg_overrides, + &proc_macro_loader, + load, + cargo, + build_data.and_then(|it| it.get(cargo.workspace_root())), + sysroot, + rustc, + rustc + .as_ref() + .zip(build_data) + .and_then(|(it, map)| map.get(it.workspace_root())), + ) + } ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => { detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot) } @@ -398,6 +412,7 @@ fn project_json_to_crate_graph( fn cargo_to_crate_graph( rustc_cfg: Vec, + override_cfg: &CfgOverrides, proc_macro_loader: &dyn Fn(&Path) -> Vec, load: &mut dyn FnMut(&AbsPath) -> Option, cargo: &CargoWorkspace, @@ -425,6 +440,21 @@ fn cargo_to_crate_graph( let mut has_private = false; // Next, create crates for each package, target pair for pkg in cargo.packages() { + let mut cfg_options = &cfg_options; + let mut replaced_cfg_options; + if let Some(overrides) = override_cfg.get(&cargo[pkg].name) { + // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen + // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while + // working on rust-lang/rust as that's the only time it appears outside sysroot). + // + // A more ideal solution might be to reanalyze crates based on where the cursor is and + // figure out the set of cfgs that would have to apply to make it active. + + replaced_cfg_options = cfg_options.clone(); + replaced_cfg_options.apply_diff(overrides.clone()); + cfg_options = &replaced_cfg_options; + }; + has_private |= cargo[pkg].metadata.rustc_private; let mut lib_tgt = None; for &tgt in cargo[pkg].targets.iter() { diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 16c295639e..7e0276c10e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -55,6 +55,8 @@ config_data! { cargo_autoreload: bool = "true", /// Activate all available features (`--all-features`). cargo_allFeatures: bool = "false", + /// Unsets `#[cfg(test)]` for the specified crates. + cargo_unsetTest: Vec = "[\"core\"]", /// List of features to activate. cargo_features: Vec = "[]", /// Run build scripts (`build.rs`) for more precise code analysis. @@ -595,8 +597,10 @@ impl Config { target: self.data.cargo_target.clone(), rustc_source, no_sysroot: self.data.cargo_noSysroot, + unset_test_crates: self.data.cargo_unsetTest.clone(), } } + pub fn rustfmt(&self) -> RustfmtConfig { match &self.data.rustfmt_overrideCommand { Some(args) if !args.is_empty() => { diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 18ea772669..58cb469740 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -39,6 +39,11 @@ Automatically refresh project info via `cargo metadata` on -- Activate all available features (`--all-features`). -- +[[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`):: ++ +-- +Unsets `#[cfg(test)]` for the specified crates. +-- [[rust-analyzer.cargo.features]]rust-analyzer.cargo.features (default: `[]`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index c077bd2c0a..b20a39a956 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -452,6 +452,16 @@ "default": false, "type": "boolean" }, + "rust-analyzer.cargo.unsetTest": { + "markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.", + "default": [ + "core" + ], + "type": "array", + "items": { + "type": "string" + } + }, "rust-analyzer.cargo.features": { "markdownDescription": "List of features to activate.", "default": [],