diff --git a/Cargo.lock b/Cargo.lock index 988f7ec0bbf..736f1994edf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -922,6 +922,16 @@ dependencies = [ "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ra_cfg" +version = "0.1.0" +dependencies = [ + "ra_mbe 0.1.0", + "ra_syntax 0.1.0", + "ra_tt 0.1.0", + "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ra_cli" version = "0.1.0" @@ -941,6 +951,7 @@ dependencies = [ name = "ra_db" version = "0.1.0" dependencies = [ + "ra_cfg 0.1.0", "ra_prof 0.1.0", "ra_syntax 0.1.0", "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -971,6 +982,7 @@ dependencies = [ "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_arena 0.1.0", + "ra_cfg 0.1.0", "ra_db 0.1.0", "ra_mbe 0.1.0", "ra_prof 0.1.0", @@ -993,6 +1005,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "ra_assists 0.1.0", + "ra_cfg 0.1.0", "ra_db 0.1.0", "ra_fmt 0.1.0", "ra_hir 0.1.0", @@ -1019,6 +1032,7 @@ dependencies = [ "lsp-server 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types 0.61.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ra_cfg 0.1.0", "ra_db 0.1.0", "ra_ide_api 0.1.0", "ra_prof 0.1.0", @@ -1075,6 +1089,7 @@ dependencies = [ "cargo_metadata 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "ra_arena 0.1.0", + "ra_cfg 0.1.0", "ra_db 0.1.0", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs index 939f72037d3..a5fc2a23e16 100644 --- a/crates/ra_batch/src/lib.rs +++ b/crates/ra_batch/src/lib.rs @@ -7,7 +7,7 @@ use rustc_hash::FxHashMap; use crossbeam_channel::{unbounded, Receiver}; use ra_db::{CrateGraph, FileId, SourceRootId}; use ra_ide_api::{AnalysisChange, AnalysisHost, FeatureFlags}; -use ra_project_model::{PackageRoot, ProjectWorkspace}; +use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace}; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use ra_vfs_glob::RustPackageFilterBuilder; @@ -41,11 +41,17 @@ pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap {:?}", path, vfs_file); - vfs_file.map(vfs_file_to_id) - }); + + // FIXME: cfg options? + let default_cfg_options = + get_rustc_cfg_options().atom("test".into()).atom("debug_assertion".into()); + + let (crate_graph, _crate_names) = + ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| { + let vfs_file = vfs.load(path); + log::debug!("vfs file {:?} -> {:?}", path, vfs_file); + vfs_file.map(vfs_file_to_id) + }); log::debug!("crate graph: {:?}", crate_graph); let source_roots = roots diff --git a/crates/ra_cfg/Cargo.toml b/crates/ra_cfg/Cargo.toml new file mode 100644 index 00000000000..b28affc3a3d --- /dev/null +++ b/crates/ra_cfg/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2018" +name = "ra_cfg" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[dependencies] +rustc-hash = "1.0.1" + +ra_syntax = { path = "../ra_syntax" } +tt = { path = "../ra_tt", package = "ra_tt" } + +[dev-dependencies] +mbe = { path = "../ra_mbe", package = "ra_mbe" } diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs new file mode 100644 index 00000000000..39d71851ca7 --- /dev/null +++ b/crates/ra_cfg/src/cfg_expr.rs @@ -0,0 +1,132 @@ +//! The condition expression used in `#[cfg(..)]` attributes. +//! +//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation + +use std::slice::Iter as SliceIter; + +use ra_syntax::SmolStr; +use tt::{Leaf, Subtree, TokenTree}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CfgExpr { + Invalid, + Atom(SmolStr), + KeyValue { key: SmolStr, value: SmolStr }, + All(Vec), + Any(Vec), + Not(Box), +} + +impl CfgExpr { + /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. + pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option { + match self { + CfgExpr::Invalid => None, + CfgExpr::Atom(name) => Some(query(name, None)), + CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))), + CfgExpr::All(preds) => { + preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?)) + } + CfgExpr::Any(preds) => { + preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?)) + } + CfgExpr::Not(pred) => pred.fold(query).map(|s| !s), + } + } +} + +pub fn parse_cfg(tt: &Subtree) -> CfgExpr { + next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) +} + +fn next_cfg_expr(it: &mut SliceIter) -> Option { + let name = match it.next() { + None => return None, + Some(TokenTree::Leaf(Leaf::Ident(ident))) => ident.text.clone(), + Some(_) => return Some(CfgExpr::Invalid), + }; + + // Peek + let ret = match it.as_slice().first() { + Some(TokenTree::Leaf(Leaf::Punct(punct))) if punct.char == '=' => { + match it.as_slice().get(1) { + Some(TokenTree::Leaf(Leaf::Literal(literal))) => { + it.next(); + it.next(); + // FIXME: escape? raw string? + let value = + SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); + CfgExpr::KeyValue { key: name, value } + } + _ => return Some(CfgExpr::Invalid), + } + } + Some(TokenTree::Subtree(subtree)) => { + it.next(); + let mut sub_it = subtree.token_trees.iter(); + let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect(); + match name.as_str() { + "all" => CfgExpr::All(subs), + "any" => CfgExpr::Any(subs), + "not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))), + _ => CfgExpr::Invalid, + } + } + _ => CfgExpr::Atom(name), + }; + + // Eat comma separator + if let Some(TokenTree::Leaf(Leaf::Punct(punct))) = it.as_slice().first() { + if punct.char == ',' { + it.next(); + } + } + Some(ret) +} + +#[cfg(test)] +mod tests { + use super::*; + + use mbe::ast_to_token_tree; + use ra_syntax::ast::{self, AstNode}; + + fn assert_parse_result(input: &str, expected: CfgExpr) { + let source_file = ast::SourceFile::parse(input).ok().unwrap(); + let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let (tt, _) = ast_to_token_tree(&tt).unwrap(); + assert_eq!(parse_cfg(&tt), expected); + } + + #[test] + fn test_cfg_expr_parser() { + assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into())); + assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into())); + assert_parse_result( + "#![cfg(not(foo))]", + CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))), + ); + assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid); + + // Only take the first + assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into())); + + assert_parse_result( + r#"#![cfg(all(foo, bar = "baz"))]"#, + CfgExpr::All(vec![ + CfgExpr::Atom("foo".into()), + CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() }, + ]), + ); + + assert_parse_result( + r#"#![cfg(any(not(), all(), , bar = "baz",))]"#, + CfgExpr::Any(vec![ + CfgExpr::Not(Box::new(CfgExpr::Invalid)), + CfgExpr::All(vec![]), + CfgExpr::Invalid, + CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() }, + ]), + ); + } +} diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs new file mode 100644 index 00000000000..e1c92fbba30 --- /dev/null +++ b/crates/ra_cfg/src/lib.rs @@ -0,0 +1,61 @@ +//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator +use std::iter::IntoIterator; + +use ra_syntax::SmolStr; +use rustc_hash::FxHashSet; + +mod cfg_expr; + +pub use cfg_expr::{parse_cfg, CfgExpr}; + +/// Configuration options used for conditional compilition on items with `cfg` attributes. +/// We have two kind of options in different namespaces: atomic options like `unix`, and +/// key-value options like `target_arch="x86"`. +/// +/// Note that for key-value options, one key can have multiple values (but not none). +/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features +/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple +/// of key and value in `key_values`. +/// +/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct CfgOptions { + atoms: FxHashSet, + key_values: FxHashSet<(SmolStr, SmolStr)>, +} + +impl CfgOptions { + pub fn check(&self, cfg: &CfgExpr) -> Option { + cfg.fold(&|key, value| match value { + None => self.atoms.contains(key), + Some(value) => self.key_values.contains(&(key.clone(), value.clone())), + }) + } + + pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option { + self.check(&parse_cfg(attr)) + } + + pub fn atom(mut self, name: SmolStr) -> CfgOptions { + self.atoms.insert(name); + self + } + + pub fn key_value(mut self, key: SmolStr, value: SmolStr) -> CfgOptions { + self.key_values.insert((key, value)); + self + } + + /// Shortcut to set features + pub fn features(mut self, iter: impl IntoIterator) -> CfgOptions { + for feat in iter { + self = self.key_value("feature".into(), feat); + } + self + } + + pub fn remove_atom(mut self, name: &SmolStr) -> CfgOptions { + self.atoms.remove(name); + self + } +} diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 2fac07bc5ae..c141f1a88dc 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml @@ -10,4 +10,5 @@ relative-path = "0.4.0" rustc-hash = "1.0" ra_syntax = { path = "../ra_syntax" } +ra_cfg = { path = "../ra_cfg" } ra_prof = { path = "../ra_prof" } diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index 52f89289146..23148096cb1 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs @@ -9,6 +9,7 @@ use relative_path::{RelativePath, RelativePathBuf}; use rustc_hash::FxHashMap; +use ra_cfg::CfgOptions; use ra_syntax::SmolStr; use rustc_hash::FxHashSet; @@ -109,11 +110,12 @@ struct CrateData { file_id: FileId, edition: Edition, dependencies: Vec, + cfg_options: CfgOptions, } impl CrateData { - fn new(file_id: FileId, edition: Edition) -> CrateData { - CrateData { file_id, edition, dependencies: Vec::new() } + fn new(file_id: FileId, edition: Edition, cfg_options: CfgOptions) -> CrateData { + CrateData { file_id, edition, dependencies: Vec::new(), cfg_options } } fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) { @@ -134,13 +136,22 @@ impl Dependency { } impl CrateGraph { - pub fn add_crate_root(&mut self, file_id: FileId, edition: Edition) -> CrateId { + pub fn add_crate_root( + &mut self, + file_id: FileId, + edition: Edition, + cfg_options: CfgOptions, + ) -> CrateId { let crate_id = CrateId(self.arena.len() as u32); - let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition)); + let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition, cfg_options)); assert!(prev.is_none()); crate_id } + pub fn cfg_options(&self, crate_id: CrateId) -> &CfgOptions { + &self.arena[&crate_id].cfg_options + } + pub fn add_dep( &mut self, from: CrateId, @@ -221,14 +232,14 @@ impl CrateGraph { #[cfg(test)] mod tests { - use super::{CrateGraph, Edition::Edition2018, FileId, SmolStr}; + use super::{CfgOptions, CrateGraph, Edition::Edition2018, FileId, SmolStr}; #[test] fn it_should_panic_because_of_cycle_dependencies() { let mut graph = CrateGraph::default(); - let crate1 = graph.add_crate_root(FileId(1u32), Edition2018); - let crate2 = graph.add_crate_root(FileId(2u32), Edition2018); - let crate3 = graph.add_crate_root(FileId(3u32), Edition2018); + let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default()); + let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default()); + let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default()); assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok()); assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok()); assert!(graph.add_dep(crate3, SmolStr::new("crate1"), crate1).is_err()); @@ -237,9 +248,9 @@ mod tests { #[test] fn it_works() { let mut graph = CrateGraph::default(); - let crate1 = graph.add_crate_root(FileId(1u32), Edition2018); - let crate2 = graph.add_crate_root(FileId(2u32), Edition2018); - let crate3 = graph.add_crate_root(FileId(3u32), Edition2018); + let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default()); + let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default()); + let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default()); assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok()); assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok()); } diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index d9bed4ddaf2..cc117f84d17 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -15,6 +15,7 @@ once_cell = "1.0.1" ra_syntax = { path = "../ra_syntax" } ra_arena = { path = "../ra_arena" } +ra_cfg = { path = "../ra_cfg" } ra_db = { path = "../ra_db" } mbe = { path = "../ra_mbe", package = "ra_mbe" } tt = { path = "../ra_tt", package = "ra_tt" } diff --git a/crates/ra_hir/src/attr.rs b/crates/ra_hir/src/attr.rs new file mode 100644 index 00000000000..f67e80bfd15 --- /dev/null +++ b/crates/ra_hir/src/attr.rs @@ -0,0 +1,80 @@ +//! A higher level attributes based on TokenTree, with also some shortcuts. + +use std::sync::Arc; + +use mbe::ast_to_token_tree; +use ra_cfg::CfgOptions; +use ra_syntax::{ + ast::{self, AstNode, AttrsOwner}, + SmolStr, +}; +use tt::Subtree; + +use crate::{db::AstDatabase, path::Path, HirFileId, Source}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Attr { + pub(crate) path: Path, + pub(crate) input: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AttrInput { + Literal(SmolStr), + TokenTree(Subtree), +} + +impl Attr { + pub(crate) fn from_src( + Source { file_id, ast }: Source, + db: &impl AstDatabase, + ) -> Option { + let path = Path::from_src(Source { file_id, ast: ast.path()? }, db)?; + let input = match ast.input() { + None => None, + Some(ast::AttrInput::Literal(lit)) => { + // FIXME: escape? raw string? + let value = lit.syntax().first_token()?.text().trim_matches('"').into(); + Some(AttrInput::Literal(value)) + } + Some(ast::AttrInput::TokenTree(tt)) => { + Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) + } + }; + + Some(Attr { path, input }) + } + + pub(crate) fn from_attrs_owner( + file_id: HirFileId, + owner: &dyn AttrsOwner, + db: &impl AstDatabase, + ) -> Option> { + let mut attrs = owner.attrs().peekable(); + if attrs.peek().is_none() { + // Avoid heap allocation + return None; + } + Some(attrs.flat_map(|ast| Attr::from_src(Source { file_id, ast }, db)).collect()) + } + + pub(crate) fn is_simple_atom(&self, name: &str) -> bool { + // FIXME: Avoid cloning + self.path.as_ident().map_or(false, |s| s.to_string() == name) + } + + pub(crate) fn as_cfg(&self) -> Option<&Subtree> { + if self.is_simple_atom("cfg") { + match &self.input { + Some(AttrInput::TokenTree(subtree)) => Some(subtree), + _ => None, + } + } else { + None + } + } + + pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Option { + cfg_options.is_cfg_enabled(self.as_cfg()?) + } +} diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs index 8cf74ddc7f2..55dfc393b40 100644 --- a/crates/ra_hir/src/impl_block.rs +++ b/crates/ra_hir/src/impl_block.rs @@ -4,12 +4,14 @@ use rustc_hash::FxHashMap; use std::sync::Arc; use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId}; +use ra_cfg::CfgOptions; use ra_syntax::{ ast::{self, AstNode}, AstPtr, }; use crate::{ + attr::Attr, code_model::{Module, ModuleSource}, db::{AstDatabase, DefDatabase, HirDatabase}, generics::HasGenericParams, @@ -176,6 +178,7 @@ pub struct ModuleImplBlocks { impl ModuleImplBlocks { fn collect( db: &(impl DefDatabase + AstDatabase), + cfg_options: &CfgOptions, module: Module, source_map: &mut ImplSourceMap, ) -> Self { @@ -188,11 +191,11 @@ impl ModuleImplBlocks { let src = m.module.definition_source(db); match &src.ast { ModuleSource::SourceFile(node) => { - m.collect_from_item_owner(db, source_map, node, src.file_id) + m.collect_from_item_owner(db, cfg_options, source_map, node, src.file_id) } ModuleSource::Module(node) => { let item_list = node.item_list().expect("inline module should have item list"); - m.collect_from_item_owner(db, source_map, &item_list, src.file_id) + m.collect_from_item_owner(db, cfg_options, source_map, &item_list, src.file_id) } }; m @@ -201,6 +204,7 @@ impl ModuleImplBlocks { fn collect_from_item_owner( &mut self, db: &(impl DefDatabase + AstDatabase), + cfg_options: &CfgOptions, source_map: &mut ImplSourceMap, owner: &dyn ast::ModuleItemOwner, file_id: HirFileId, @@ -208,6 +212,13 @@ impl ModuleImplBlocks { for item in owner.items_with_macros() { match item { ast::ItemOrMacro::Item(ast::ModuleItem::ImplBlock(impl_block_ast)) => { + let attrs = Attr::from_attrs_owner(file_id, &impl_block_ast, db); + if attrs.map_or(false, |attrs| { + attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) + }) { + continue; + } + let impl_block = ImplData::from_ast(db, file_id, self.module, &impl_block_ast); let id = self.impls.alloc(impl_block); for &impl_item in &self.impls[id].items { @@ -218,6 +229,13 @@ impl ModuleImplBlocks { } ast::ItemOrMacro::Item(_) => (), ast::ItemOrMacro::Macro(macro_call) => { + let attrs = Attr::from_attrs_owner(file_id, ¯o_call, db); + if attrs.map_or(false, |attrs| { + attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) + }) { + continue; + } + //FIXME: we should really cut down on the boilerplate required to process a macro let ast_id = db.ast_id_map(file_id).ast_id(¯o_call).with_file_id(file_id); if let Some(path) = macro_call @@ -231,7 +249,13 @@ impl ModuleImplBlocks { if let Some(item_list) = db.parse_or_expand(file_id).and_then(ast::MacroItems::cast) { - self.collect_from_item_owner(db, source_map, &item_list, file_id) + self.collect_from_item_owner( + db, + cfg_options, + source_map, + &item_list, + file_id, + ) } } } @@ -246,8 +270,10 @@ pub(crate) fn impls_in_module_with_source_map_query( module: Module, ) -> (Arc, Arc) { let mut source_map = ImplSourceMap::default(); + let crate_graph = db.crate_graph(); + let cfg_options = crate_graph.cfg_options(module.krate.crate_id()); - let result = ModuleImplBlocks::collect(db, module, &mut source_map); + let result = ModuleImplBlocks::collect(db, cfg_options, module, &mut source_map); (Arc::new(result), Arc::new(source_map)) } diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 00031debab6..4340e9d3484 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -44,6 +44,7 @@ mod traits; mod type_alias; mod type_ref; mod ty; +mod attr; mod impl_block; mod expr; mod lang_item; diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index 50feb98fb35..f750986b8b3 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -3,6 +3,7 @@ use std::{panic, sync::Arc}; use parking_lot::Mutex; +use ra_cfg::CfgOptions; use ra_db::{ salsa, CrateGraph, CrateId, Edition, FileId, FilePosition, SourceDatabase, SourceRoot, SourceRootId, @@ -74,13 +75,13 @@ impl MockDatabase { pub fn set_crate_graph_from_fixture(&mut self, graph: CrateGraphFixture) { let mut ids = FxHashMap::default(); let mut crate_graph = CrateGraph::default(); - for (crate_name, (crate_root, edition, _)) in graph.0.iter() { + for (crate_name, (crate_root, edition, cfg_options, _)) in graph.0.iter() { let crate_root = self.file_id_of(&crate_root); - let crate_id = crate_graph.add_crate_root(crate_root, *edition); + let crate_id = crate_graph.add_crate_root(crate_root, *edition, cfg_options.clone()); Arc::make_mut(&mut self.crate_names).insert(crate_id, crate_name.clone()); ids.insert(crate_name, crate_id); } - for (crate_name, (_, _, deps)) in graph.0.iter() { + for (crate_name, (_, _, _, deps)) in graph.0.iter() { let from = ids[crate_name]; for dep in deps { let to = ids[dep]; @@ -184,7 +185,7 @@ impl MockDatabase { if is_crate_root { let mut crate_graph = CrateGraph::default(); - crate_graph.add_crate_root(file_id, Edition::Edition2018); + crate_graph.add_crate_root(file_id, Edition::Edition2018, CfgOptions::default()); self.set_crate_graph(Arc::new(crate_graph)); } file_id @@ -268,19 +269,27 @@ impl MockDatabase { } #[derive(Default)] -pub struct CrateGraphFixture(pub Vec<(String, (String, Edition, Vec))>); +pub struct CrateGraphFixture(pub Vec<(String, (String, Edition, CfgOptions, Vec))>); #[macro_export] macro_rules! crate_graph { - ($($crate_name:literal: ($crate_path:literal, $($edition:literal,)? [$($dep:literal),*]),)*) => {{ + ($( + $crate_name:literal: ( + $crate_path:literal, + $($edition:literal,)? + [$($dep:literal),*] + $(,$cfg:expr)? + ), + )*) => {{ let mut res = $crate::mock::CrateGraphFixture::default(); $( #[allow(unused_mut, unused_assignments)] let mut edition = ra_db::Edition::Edition2018; $(edition = ra_db::Edition::from_string($edition);)? + let cfg_options = { ::ra_cfg::CfgOptions::default() $(; $cfg)? }; res.0.push(( $crate_name.to_string(), - ($crate_path.to_string(), edition, vec![$($dep.to_string()),*]) + ($crate_path.to_string(), edition, cfg_options, vec![$($dep.to_string()),*]) )); )* res diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs index a568fdabdab..cef2dc9d287 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir/src/nameres/collector.rs @@ -1,5 +1,6 @@ //! FIXME: write short doc here +use ra_cfg::CfgOptions; use ra_db::FileId; use ra_syntax::{ast, SmolStr}; use rustc_hash::FxHashMap; @@ -35,6 +36,9 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C } } + let crate_graph = db.crate_graph(); + let cfg_options = crate_graph.cfg_options(def_map.krate().crate_id()); + let mut collector = DefCollector { db, def_map, @@ -42,6 +46,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C unresolved_imports: Vec::new(), unexpanded_macros: Vec::new(), macro_stack_monitor: MacroStackMonitor::default(), + cfg_options, }; collector.collect(); collector.finish() @@ -76,8 +81,8 @@ impl MacroStackMonitor { } /// Walks the tree of module recursively -struct DefCollector { - db: DB, +struct DefCollector<'a, DB> { + db: &'a DB, def_map: CrateDefMap, glob_imports: FxHashMap>, unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>, @@ -86,9 +91,11 @@ struct DefCollector { /// Some macro use `$tt:tt which mean we have to handle the macro perfectly /// To prevent stack overflow, we add a deep counter here for prevent that. macro_stack_monitor: MacroStackMonitor, + + cfg_options: &'a CfgOptions, } -impl<'a, DB> DefCollector<&'a DB> +impl DefCollector<'_, DB> where DB: DefDatabase, { @@ -506,7 +513,7 @@ struct ModCollector<'a, D> { parent_module: Option>, } -impl ModCollector<'_, &'_ mut DefCollector<&'_ DB>> +impl ModCollector<'_, &'_ mut DefCollector<'_, DB>> where DB: DefDatabase, { @@ -523,24 +530,27 @@ where // `#[macro_use] extern crate` is hoisted to imports macros before collecting // any other items. for item in items { - if let raw::RawItem::Import(import_id) = *item { - let import = self.raw_items[import_id].clone(); - if import.is_extern_crate && import.is_macro_use { - self.def_collector.import_macros_from_extern_crate(self.module_id, &import); + if self.is_cfg_enabled(&item.attrs) { + if let raw::RawItemKind::Import(import_id) = item.kind { + let import = self.raw_items[import_id].clone(); + if import.is_extern_crate && import.is_macro_use { + self.def_collector.import_macros_from_extern_crate(self.module_id, &import); + } } } } for item in items { - match *item { - raw::RawItem::Module(m) => self.collect_module(&self.raw_items[m]), - raw::RawItem::Import(import_id) => self.def_collector.unresolved_imports.push(( - self.module_id, - import_id, - self.raw_items[import_id].clone(), - )), - raw::RawItem::Def(def) => self.define_def(&self.raw_items[def]), - raw::RawItem::Macro(mac) => self.collect_macro(&self.raw_items[mac]), + if self.is_cfg_enabled(&item.attrs) { + match item.kind { + raw::RawItemKind::Module(m) => self.collect_module(&self.raw_items[m]), + raw::RawItemKind::Import(import_id) => self + .def_collector + .unresolved_imports + .push((self.module_id, import_id, self.raw_items[import_id].clone())), + raw::RawItemKind::Def(def) => self.define_def(&self.raw_items[def]), + raw::RawItemKind::Macro(mac) => self.collect_macro(&self.raw_items[mac]), + } } } } @@ -703,6 +713,14 @@ where self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_); } } + + fn is_cfg_enabled(&self, attrs: &raw::Attrs) -> bool { + attrs.as_ref().map_or(true, |attrs| { + attrs + .iter() + .all(|attr| attr.is_cfg_enabled(&self.def_collector.cfg_options) != Some(false)) + }) + } } fn is_macro_rules(path: &Path) -> bool { @@ -730,6 +748,7 @@ mod tests { unresolved_imports: Vec::new(), unexpanded_macros: Vec::new(), macro_stack_monitor: monitor, + cfg_options: &CfgOptions::default(), }; collector.collect(); collector.finish() diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs index 606bd1a9586..623b343c4fd 100644 --- a/crates/ra_hir/src/nameres/raw.rs +++ b/crates/ra_hir/src/nameres/raw.rs @@ -10,6 +10,7 @@ use ra_syntax::{ use test_utils::tested_by; use crate::{ + attr::Attr, db::{AstDatabase, DefDatabase}, AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, Source, }; @@ -119,8 +120,17 @@ impl Index for RawItems { } } +// Avoid heap allocation on items without attributes. +pub(super) type Attrs = Option>; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub(super) struct RawItem { + pub(super) attrs: Attrs, + pub(super) kind: RawItemKind, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(super) enum RawItem { +pub(super) enum RawItemKind { Module(Module), Import(ImportId), Def(Def), @@ -215,6 +225,7 @@ impl RawItemsCollector<&DB> { } fn add_item(&mut self, current_module: Option, item: ast::ModuleItem) { + let attrs = self.parse_attrs(&item); let (kind, name) = match item { ast::ModuleItem::Module(module) => { self.add_module(current_module, module); @@ -263,7 +274,7 @@ impl RawItemsCollector<&DB> { if let Some(name) = name { let name = name.as_name(); let def = self.raw_items.defs.alloc(DefData { name, kind }); - self.push_item(current_module, RawItem::Def(def)) + self.push_item(current_module, attrs, RawItemKind::Def(def)); } } @@ -272,8 +283,10 @@ impl RawItemsCollector<&DB> { Some(it) => it.as_name(), None => return, }; + let attrs = self.parse_attrs(&module); let ast_id = self.source_ast_id_map.ast_id(&module); + // FIXME: cfg_attr let is_macro_use = module.has_atom_attr("macro_use"); if module.has_semi() { let attr_path = extract_mod_path_attribute(&module); @@ -283,7 +296,7 @@ impl RawItemsCollector<&DB> { attr_path, is_macro_use, }); - self.push_item(current_module, RawItem::Module(item)); + self.push_item(current_module, attrs, RawItemKind::Module(item)); return; } @@ -297,14 +310,16 @@ impl RawItemsCollector<&DB> { is_macro_use, }); self.process_module(Some(item), item_list); - self.push_item(current_module, RawItem::Module(item)); + self.push_item(current_module, attrs, RawItemKind::Module(item)); return; } tested_by!(name_res_works_for_broken_modules); } fn add_use_item(&mut self, current_module: Option, use_item: ast::UseItem) { + // FIXME: cfg_attr let is_prelude = use_item.has_atom_attr("prelude_import"); + let attrs = self.parse_attrs(&use_item); Path::expand_use_item( Source { ast: use_item, file_id: self.file_id }, @@ -318,7 +333,12 @@ impl RawItemsCollector<&DB> { is_extern_crate: false, is_macro_use: false, }; - self.push_import(current_module, import_data, Either::A(AstPtr::new(use_tree))); + self.push_import( + current_module, + attrs.clone(), + import_data, + Either::A(AstPtr::new(use_tree)), + ); }, ) } @@ -331,6 +351,8 @@ impl RawItemsCollector<&DB> { if let Some(name_ref) = extern_crate.name_ref() { let path = Path::from_name_ref(&name_ref); let alias = extern_crate.alias().and_then(|a| a.name()).map(|it| it.as_name()); + let attrs = self.parse_attrs(&extern_crate); + // FIXME: cfg_attr let is_macro_use = extern_crate.has_atom_attr("macro_use"); let import_data = ImportData { path, @@ -340,11 +362,17 @@ impl RawItemsCollector<&DB> { is_extern_crate: true, is_macro_use, }; - self.push_import(current_module, import_data, Either::B(AstPtr::new(&extern_crate))); + self.push_import( + current_module, + attrs, + import_data, + Either::B(AstPtr::new(&extern_crate)), + ); } } fn add_macro(&mut self, current_module: Option, m: ast::MacroCall) { + let attrs = self.parse_attrs(&m); let path = match m .path() .and_then(|path| Path::from_src(Source { ast: path, file_id: self.file_id }, self.db)) @@ -355,24 +383,26 @@ impl RawItemsCollector<&DB> { let name = m.name().map(|it| it.as_name()); let ast_id = self.source_ast_id_map.ast_id(&m); + // FIXME: cfg_attr let export = m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "macro_export"); let m = self.raw_items.macros.alloc(MacroData { ast_id, path, name, export }); - self.push_item(current_module, RawItem::Macro(m)); + self.push_item(current_module, attrs, RawItemKind::Macro(m)); } fn push_import( &mut self, current_module: Option, + attrs: Attrs, data: ImportData, source: ImportSourcePtr, ) { let import = self.raw_items.imports.alloc(data); self.source_map.insert(import, source); - self.push_item(current_module, RawItem::Import(import)) + self.push_item(current_module, attrs, RawItemKind::Import(import)) } - fn push_item(&mut self, current_module: Option, item: RawItem) { + fn push_item(&mut self, current_module: Option, attrs: Attrs, kind: RawItemKind) { match current_module { Some(module) => match &mut self.raw_items.modules[module] { ModuleData::Definition { items, .. } => items, @@ -380,7 +410,11 @@ impl RawItemsCollector<&DB> { }, None => &mut self.raw_items.items, } - .push(item) + .push(RawItem { attrs, kind }) + } + + fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs { + Attr::from_attrs_owner(self.file_id, item, self.db) } } diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs index bc4b47b704c..34dd7957438 100644 --- a/crates/ra_hir/src/nameres/tests.rs +++ b/crates/ra_hir/src/nameres/tests.rs @@ -7,6 +7,7 @@ mod mod_resolution; use std::sync::Arc; use insta::assert_snapshot; +use ra_cfg::CfgOptions; use ra_db::SourceDatabase; use test_utils::covers; @@ -507,3 +508,72 @@ fn values_dont_shadow_extern_crates() { ⋮foo: v "###); } + +#[test] +fn cfg_not_test() { + let map = def_map_with_crate_graph( + r#" + //- /main.rs + use {Foo, Bar, Baz}; + //- /lib.rs + #[prelude_import] + pub use self::prelude::*; + mod prelude { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; + } + "#, + crate_graph! { + "main": ("/main.rs", ["std"]), + "std": ("/lib.rs", []), + }, + ); + + assert_snapshot!(map, @r###" + ⋮crate + ⋮Bar: t v + ⋮Baz: _ + ⋮Foo: _ + "###); +} + +#[test] +fn cfg_test() { + let map = def_map_with_crate_graph( + r#" + //- /main.rs + use {Foo, Bar, Baz}; + //- /lib.rs + #[prelude_import] + pub use self::prelude::*; + mod prelude { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; + } + "#, + crate_graph! { + "main": ("/main.rs", ["std"]), + "std": ("/lib.rs", [], CfgOptions::default() + .atom("test".into()) + .key_value("feature".into(), "foo".into()) + .key_value("feature".into(), "bar".into()) + .key_value("opt".into(), "42".into()) + ), + }, + ); + + assert_snapshot!(map, @r###" + ⋮crate + ⋮Bar: _ + ⋮Baz: t v + ⋮Foo: t v + "###); +} diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 66f63ca2401..25dad81eb7c 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use insta::assert_snapshot; +use ra_cfg::CfgOptions; use ra_db::{salsa::Database, FilePosition, SourceDatabase}; use ra_syntax::{ algo, @@ -23,6 +24,50 @@ use crate::{ mod never_type; mod coercion; +#[test] +fn cfg_impl_block() { + let (mut db, pos) = MockDatabase::with_position( + r#" +//- /main.rs +use foo::S as T; +struct S; + +#[cfg(test)] +impl S { + fn foo1(&self) -> i32 { 0 } +} + +#[cfg(not(test))] +impl S { + fn foo2(&self) -> i32 { 0 } +} + +fn test() { + let t = (S.foo1(), S.foo2(), T.foo3(), T.foo4()); + t<|>; +} + +//- /foo.rs +struct S; + +#[cfg(not(test))] +impl S { + fn foo3(&self) -> i32 { 0 } +} + +#[cfg(test)] +impl S { + fn foo4(&self) -> i32 { 0 } +} +"#, + ); + db.set_crate_graph_from_fixture(crate_graph! { + "main": ("/main.rs", ["foo"], CfgOptions::default().atom("test".into())), + "foo": ("/foo.rs", []), + }); + assert_eq!("(i32, {unknown}, i32, {unknown})", type_at_pos(&db, pos)); +} + #[test] fn infer_await() { let (mut db, pos) = MockDatabase::with_position( diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml index 6bbf9d5dde0..f919a2d6152 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml @@ -23,6 +23,7 @@ rand = { version = "0.7.0", features = ["small_rng"] } ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } ra_db = { path = "../ra_db" } +ra_cfg = { path = "../ra_cfg" } ra_fmt = { path = "../ra_fmt" } ra_prof = { path = "../ra_prof" } hir = { path = "../ra_hir", package = "ra_hir" } diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 44d1ec77b0c..24f1b91f68c 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -49,6 +49,7 @@ mod test_utils; use std::sync::Arc; +use ra_cfg::CfgOptions; use ra_db::{ salsa::{self, ParallelDatabase}, CheckCanceled, SourceDatabase, @@ -322,7 +323,10 @@ impl Analysis { change.add_root(source_root, true); let mut crate_graph = CrateGraph::default(); let file_id = FileId(0); - crate_graph.add_crate_root(file_id, Edition::Edition2018); + // FIXME: cfg options + // Default to enable test for single file. + let cfg_options = CfgOptions::default().atom("test".into()); + crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options); change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); change.set_crate_graph(crate_graph); host.apply_change(change); diff --git a/crates/ra_ide_api/src/mock_analysis.rs b/crates/ra_ide_api/src/mock_analysis.rs index 16870c7ae49..80b71894cdf 100644 --- a/crates/ra_ide_api/src/mock_analysis.rs +++ b/crates/ra_ide_api/src/mock_analysis.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use ra_cfg::CfgOptions; use relative_path::RelativePathBuf; use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; @@ -93,10 +94,11 @@ impl MockAnalysis { assert!(path.starts_with('/')); let path = RelativePathBuf::from_path(&path[1..]).unwrap(); let file_id = FileId(i as u32 + 1); + let cfg_options = CfgOptions::default(); if path == "/lib.rs" || path == "/main.rs" { - root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018)); + root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018, cfg_options)); } else if path.ends_with("/lib.rs") { - let other_crate = crate_graph.add_crate_root(file_id, Edition2018); + let other_crate = crate_graph.add_crate_root(file_id, Edition2018, cfg_options); let crate_name = path.parent().unwrap().file_name().unwrap(); if let Some(root_crate) = root_crate { crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap(); diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs index c85f1d0d02f..56650984995 100644 --- a/crates/ra_ide_api/src/parent_module.rs +++ b/crates/ra_ide_api/src/parent_module.rs @@ -41,6 +41,7 @@ mod tests { AnalysisChange, CrateGraph, Edition::Edition2018, }; + use ra_cfg::CfgOptions; #[test] fn test_resolve_parent_module() { @@ -88,7 +89,7 @@ mod tests { assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); let mut crate_graph = CrateGraph::default(); - let crate_id = crate_graph.add_crate_root(root_file, Edition2018); + let crate_id = crate_graph.add_crate_root(root_file, Edition2018, CfgOptions::default()); let mut change = AnalysisChange::new(); change.set_crate_graph(crate_graph); host.apply_change(change); diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 677d81835e9..aedc55a95ce 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -19,6 +19,7 @@ jod-thread = "0.1.0" ra_vfs = "0.4.0" ra_syntax = { path = "../ra_syntax" } ra_db = { path = "../ra_db" } +ra_cfg = { path = "../ra_cfg" } ra_text_edit = { path = "../ra_text_edit" } ra_ide_api = { path = "../ra_ide_api" } lsp-server = "0.2.0" diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index b55046ec9a9..27da751ab4a 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -13,7 +13,7 @@ use ra_ide_api::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, SourceRootId, }; -use ra_project_model::ProjectWorkspace; +use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace}; use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; use relative_path::RelativePathBuf; @@ -97,6 +97,10 @@ impl WorldState { change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); } + // FIXME: Read default cfgs from config + let default_cfg_options = + get_rustc_cfg_options().atom("test".into()).atom("debug_assertion".into()); + // Create crate graph from all the workspaces let mut crate_graph = CrateGraph::default(); let mut load = |path: &std::path::Path| { @@ -104,7 +108,7 @@ impl WorldState { vfs_file.map(|f| FileId(f.0)) }; for ws in workspaces.iter() { - let (graph, crate_names) = ws.to_crate_graph(&mut load); + let (graph, crate_names) = ws.to_crate_graph(&default_cfg_options, &mut load); let shift = crate_graph.extend(graph); for (crate_id, name) in crate_names { change.set_debug_crate_name(crate_id.shift(shift), name) diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs index 152681062e5..2ba82ab051f 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs @@ -286,7 +286,13 @@ fn test_missing_module_code_action_in_json_project() { let project = json!({ "roots": [path], - "crates": [ { "root_module": path.join("src/lib.rs"), "deps": [], "edition": "2015" } ] + "crates": [ { + "root_module": path.join("src/lib.rs"), + "deps": [], + "edition": "2015", + "atom_cfgs": [], + "key_value_cfgs": {} + } ] }); let code = format!( diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml index ae6b91aa69b..a651000315b 100644 --- a/crates/ra_project_model/Cargo.toml +++ b/crates/ra_project_model/Cargo.toml @@ -12,6 +12,7 @@ cargo_metadata = "0.8.2" ra_arena = { path = "../ra_arena" } ra_db = { path = "../ra_db" } +ra_cfg = { path = "../ra_cfg" } serde = { version = "1.0.89", features = ["derive"] } serde_json = "1.0.39" diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index 805eaa1780a..28dadea9d7e 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs @@ -39,6 +39,7 @@ struct PackageData { is_member: bool, dependencies: Vec, edition: Edition, + features: Vec, } #[derive(Debug, Clone)] @@ -91,6 +92,9 @@ impl Package { pub fn edition(self, ws: &CargoWorkspace) -> Edition { ws.packages[self].edition } + pub fn features(self, ws: &CargoWorkspace) -> &[String] { + &ws.packages[self].features + } pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator + 'a { ws.packages[self].targets.iter().cloned() } @@ -144,6 +148,7 @@ impl CargoWorkspace { is_member, edition: Edition::from_string(&meta_pkg.edition), dependencies: Vec::new(), + features: Vec::new(), }); let pkg_data = &mut packages[pkg]; pkg_by_id.insert(meta_pkg.id.clone(), pkg); @@ -164,6 +169,7 @@ impl CargoWorkspace { let dep = PackageDependency { name: dep_node.name, pkg: pkg_by_id[&dep_node.pkg] }; packages[source].dependencies.push(dep); } + packages[source].features.extend(node.features); } Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root }) diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs index 54ddca2cb6d..1bacb1d09a1 100644 --- a/crates/ra_project_model/src/json_project.rs +++ b/crates/ra_project_model/src/json_project.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; +use rustc_hash::{FxHashMap, FxHashSet}; use serde::Deserialize; /// A root points to the directory which contains Rust crates. rust-analyzer watches all files in @@ -19,6 +20,8 @@ pub struct Crate { pub(crate) root_module: PathBuf, pub(crate) edition: Edition, pub(crate) deps: Vec, + pub(crate) atom_cfgs: FxHashSet, + pub(crate) key_value_cfgs: FxHashMap, } #[derive(Clone, Copy, Debug, Deserialize)] diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 5d307859805..640a5ebd321 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -9,8 +9,10 @@ use std::{ fs::File, io::BufReader, path::{Path, PathBuf}, + process::Command, }; +use ra_cfg::CfgOptions; use ra_db::{CrateGraph, CrateId, Edition, FileId}; use rustc_hash::FxHashMap; use serde_json::from_reader; @@ -117,6 +119,7 @@ impl ProjectWorkspace { pub fn to_crate_graph( &self, + default_cfg_options: &CfgOptions, load: &mut dyn FnMut(&Path) -> Option, ) -> (CrateGraph, FxHashMap) { let mut crate_graph = CrateGraph::default(); @@ -131,7 +134,17 @@ impl ProjectWorkspace { json_project::Edition::Edition2015 => Edition::Edition2015, json_project::Edition::Edition2018 => Edition::Edition2018, }; - crates.insert(crate_id, crate_graph.add_crate_root(file_id, edition)); + let mut cfg_options = default_cfg_options.clone(); + for name in &krate.atom_cfgs { + cfg_options = cfg_options.atom(name.into()); + } + for (key, value) in &krate.key_value_cfgs { + cfg_options = cfg_options.key_value(key.into(), value.into()); + } + crates.insert( + crate_id, + crate_graph.add_crate_root(file_id, edition, cfg_options), + ); } } @@ -157,7 +170,10 @@ impl ProjectWorkspace { let mut sysroot_crates = FxHashMap::default(); for krate in sysroot.crates() { if let Some(file_id) = load(krate.root(&sysroot)) { - let crate_id = crate_graph.add_crate_root(file_id, Edition::Edition2018); + // Crates from sysroot have `cfg(test)` disabled + let cfg_options = default_cfg_options.clone().remove_atom(&"test".into()); + let crate_id = + crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options); sysroot_crates.insert(krate, crate_id); names.insert(crate_id, krate.name(&sysroot).to_string()); } @@ -186,7 +202,11 @@ impl ProjectWorkspace { let root = tgt.root(&cargo); if let Some(file_id) = load(root) { let edition = pkg.edition(&cargo); - let crate_id = crate_graph.add_crate_root(file_id, edition); + let cfg_options = default_cfg_options + .clone() + .features(pkg.features(&cargo).iter().map(Into::into)); + let crate_id = + crate_graph.add_crate_root(file_id, edition, cfg_options); names.insert(crate_id, pkg.name(&cargo).to_string()); if tgt.kind(&cargo) == TargetKind::Lib { lib_tgt = Some(crate_id); @@ -286,3 +306,32 @@ fn find_cargo_toml(path: &Path) -> Result { } Err(format!("can't find Cargo.toml at {}", path.display()))? } + +pub fn get_rustc_cfg_options() -> CfgOptions { + let mut cfg_options = CfgOptions::default(); + + match (|| -> Result<_> { + // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. + let output = Command::new("rustc").args(&["--print", "cfg", "-O"]).output()?; + if !output.status.success() { + Err("failed to get rustc cfgs")?; + } + Ok(String::from_utf8(output.stdout)?) + })() { + Ok(rustc_cfgs) => { + for line in rustc_cfgs.lines() { + match line.find('=') { + None => cfg_options = cfg_options.atom(line.into()), + Some(pos) => { + let key = &line[..pos]; + let value = line[pos + 1..].trim_matches('"'); + cfg_options = cfg_options.key_value(key.into(), value.into()); + } + } + } + } + Err(e) => log::error!("failed to get rustc cfgs: {}", e), + } + + cfg_options +} diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index 3bb5571ee61..34b22c3e2ac 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -1962,6 +1962,7 @@ impl AstNode for ModuleItem { } } } +impl ast::AttrsOwner for ModuleItem {} impl ModuleItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Name { diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 17477a696db..25e6f64ce0e 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -397,7 +397,8 @@ Grammar( ), "ModuleItem": ( enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", - "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ] + "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ], + traits: ["AttrsOwner"] ), "ImplItem": ( enum: ["FnDef", "TypeAliasDef", "ConstDef"]