From 8b77e2692cd97552b1b8d66eb51cec69695b3a5b Mon Sep 17 00:00:00 2001 From: Jade Date: Sun, 13 Jun 2021 21:41:46 -0700 Subject: [PATCH] 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. --- crates/cfg/src/lib.rs | 1 + crates/project_model/src/cargo_workspace.rs | 17 +++ crates/project_model/src/lib.rs | 2 +- crates/project_model/src/workspace.rs | 124 +++++++++++--------- crates/rust-analyzer/src/config.rs | 4 + docs/user/generated_config.adoc | 5 + editors/code/package.json | 10 ++ 7 files changed, 108 insertions(+), 55 deletions(-) diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs index 87bf0c925d41..916d39a0b4a2 100644 --- a/crates/cfg/src/lib.rs +++ b/crates/cfg/src/lib.rs @@ -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, diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index ac079f83e6d6..0935ea967623 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 8c6cf94c249f..1d408dff2eaf 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 c2edb332748c..d8217f714ebb 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::{CfgAtom, CfgDiff, 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(); - - 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()), - ); + 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 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 { + 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![rustc[krate].root().to_path_buf()], + include: vec![sysroot[krate].root_dir().to_path_buf()], exclude: Vec::new(), - }) - })) - .collect(), + })) + .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, @@ -427,15 +442,16 @@ fn cargo_to_crate_graph( for pkg in cargo.packages() { let mut cfg_options = &cfg_options; let mut replaced_cfg_options; - if cargo[pkg].name == "core" { - // FIXME: in the specific case of libcore in rust-lang/rust (i.e. it is not coming from - // a sysroot), there's a `#![cfg(not(test))]` at the top of it that makes its contents - // get ignored by r-a. We should implement a more general solution for this + 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( - CfgDiff::new(Default::default(), vec![CfgAtom::Flag("test".into())]).unwrap(), - ); + replaced_cfg_options.apply_diff(overrides.clone()); cfg_options = &replaced_cfg_options; }; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 16c295639e2e..7e0276c10e62 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 18ea772669ad..58cb469740fe 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 c077bd2c0ad3..b20a39a956fe 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": [],