From ffe179a73663b111e4b3ee8a3f525fb3e461c78e Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Mon, 30 Sep 2019 02:50:56 +0800
Subject: [PATCH 1/9] Pass attributes as token tree to DefCollector

---
 crates/ra_hir/src/nameres/collector.rs | 19 +++++-----
 crates/ra_hir/src/nameres/raw.rs       | 52 +++++++++++++++++++++-----
 crates/ra_syntax/src/ast/generated.rs  |  1 +
 crates/ra_syntax/src/grammar.ron       |  3 +-
 4 files changed, 54 insertions(+), 21 deletions(-)

diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index a568fdabdab..40e56dfe044 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -523,7 +523,7 @@ 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 {
+            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);
@@ -532,15 +532,14 @@ where
         }
 
         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]),
+            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]),
             }
         }
     }
diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs
index 606bd1a9586..cacbcb5177f 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -2,6 +2,7 @@
 
 use std::{ops::Index, sync::Arc};
 
+use mbe::ast_to_token_tree;
 use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
 use ra_syntax::{
     ast::{self, AttrsOwner, NameOwner},
@@ -28,6 +29,8 @@ pub struct RawItems {
     items: Vec<RawItem>,
 }
 
+type Attrs = Arc<[tt::Subtree]>;
+
 #[derive(Debug, Default, PartialEq, Eq)]
 pub struct ImportSourceMap {
     map: ArenaMap<ImportId, ImportSourcePtr>,
@@ -119,8 +122,14 @@ impl Index<Macro> for RawItems {
     }
 }
 
+#[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 +224,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     }
 
     fn add_item(&mut self, current_module: Option<Module>, 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 +273,7 @@ impl<DB: AstDatabase> 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,6 +282,7 @@ impl<DB: AstDatabase> 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);
         let is_macro_use = module.has_atom_attr("macro_use");
@@ -283,7 +294,7 @@ impl<DB: AstDatabase> 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,7 +308,7 @@ impl<DB: AstDatabase> 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);
@@ -305,6 +316,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
 
     fn add_use_item(&mut self, current_module: Option<Module>, use_item: ast::UseItem) {
         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 +330,12 @@ impl<DB: AstDatabase> 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 +348,7 @@ impl<DB: AstDatabase> 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);
             let is_macro_use = extern_crate.has_atom_attr("macro_use");
             let import_data = ImportData {
                 path,
@@ -340,7 +358,12 @@ impl<DB: AstDatabase> 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)),
+            );
         }
     }
 
@@ -358,21 +381,22 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         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<Module>,
+        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<Module>, item: RawItem) {
+    fn push_item(&mut self, current_module: Option<Module>, attrs: Attrs, kind: RawItemKind) {
         match current_module {
             Some(module) => match &mut self.raw_items.modules[module] {
                 ModuleData::Definition { items, .. } => items,
@@ -380,7 +404,15 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
             },
             None => &mut self.raw_items.items,
         }
-        .push(item)
+        .push(RawItem { attrs, kind })
+    }
+
+    fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs {
+        item.attrs()
+            .flat_map(|attr| attr.value())
+            .flat_map(|tt| ast_to_token_tree(&tt))
+            .map(|(tt, _)| tt)
+            .collect()
     }
 }
 
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 30328f59faf..5f7d8c3bfc3 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"]

From b1ed887d813bf5775a16624694939fdf836f97b1 Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Mon, 30 Sep 2019 06:52:15 +0800
Subject: [PATCH 2/9] Introduce ra_cfg to parse and evaluate CfgExpr

---
 Cargo.lock                             |  14 +++
 crates/ra_cfg/Cargo.toml               |  14 +++
 crates/ra_cfg/src/cfg_expr.rs          | 128 +++++++++++++++++++++++++
 crates/ra_cfg/src/lib.rs               |  43 +++++++++
 crates/ra_db/Cargo.toml                |   1 +
 crates/ra_db/src/input.rs              |   9 +-
 crates/ra_hir/Cargo.toml               |   1 +
 crates/ra_hir/src/attr.rs              |  58 +++++++++++
 crates/ra_hir/src/lib.rs               |   1 +
 crates/ra_hir/src/nameres/collector.rs |  52 ++++++----
 crates/ra_hir/src/nameres/raw.rs       |  21 ++--
 11 files changed, 315 insertions(+), 27 deletions(-)
 create mode 100644 crates/ra_cfg/Cargo.toml
 create mode 100644 crates/ra_cfg/src/cfg_expr.rs
 create mode 100644 crates/ra_cfg/src/lib.rs
 create mode 100644 crates/ra_hir/src/attr.rs

diff --git a/Cargo.lock b/Cargo.lock
index 988f7ec0bbf..b95f176fc69 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",
@@ -1075,6 +1088,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_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..efeadf46265
--- /dev/null
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -0,0 +1,128 @@
+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<CfgExpr>),
+    Any(Vec<CfgExpr>),
+    Not(Box<CfgExpr>),
+}
+
+impl CfgExpr {
+    /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
+    pub fn fold(&self, query: &impl Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
+        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<tt::TokenTree>) -> Option<CfgExpr> {
+    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..fa5822d8a6b
--- /dev/null
+++ b/crates/ra_cfg/src/lib.rs
@@ -0,0 +1,43 @@
+//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
+use ra_syntax::SmolStr;
+use rustc_hash::{FxHashMap, FxHashSet};
+
+mod cfg_expr;
+
+pub use cfg_expr::{parse_cfg, CfgExpr};
+
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct CfgOptions {
+    atoms: FxHashSet<SmolStr>,
+    features: FxHashSet<SmolStr>,
+    options: FxHashMap<SmolStr, SmolStr>,
+}
+
+impl CfgOptions {
+    pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
+        cfg.fold(&|key, value| match value {
+            None => self.atoms.contains(key),
+            Some(value) if key == "feature" => self.features.contains(value),
+            Some(value) => self.options.get(key).map_or(false, |v| v == value),
+        })
+    }
+
+    pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option<bool> {
+        self.check(&parse_cfg(attr))
+    }
+
+    pub fn atom(mut self, name: SmolStr) -> CfgOptions {
+        self.atoms.insert(name);
+        self
+    }
+
+    pub fn feature(mut self, name: SmolStr) -> CfgOptions {
+        self.features.insert(name);
+        self
+    }
+
+    pub fn option(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
+        self.options.insert(key, value);
+        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..5fd6edd78d1 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,13 @@ struct CrateData {
     file_id: FileId,
     edition: Edition,
     dependencies: Vec<Dependency>,
+    cfg_options: CfgOptions,
 }
 
 impl CrateData {
     fn new(file_id: FileId, edition: Edition) -> CrateData {
-        CrateData { file_id, edition, dependencies: Vec::new() }
+        // FIXME: cfg options
+        CrateData { file_id, edition, dependencies: Vec::new(), cfg_options: CfgOptions::default() }
     }
 
     fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) {
@@ -141,6 +144,10 @@ impl CrateGraph {
         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,
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..19be6de32fd
--- /dev/null
+++ b/crates/ra_hir/src/attr.rs
@@ -0,0 +1,58 @@
+use mbe::ast_to_token_tree;
+use ra_syntax::{
+    ast::{self, AstNode},
+    SmolStr,
+};
+use tt::Subtree;
+
+use crate::{db::AstDatabase, path::Path, Source};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Attr {
+    pub(crate) path: Path,
+    pub(crate) input: Option<AttrInput>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum AttrInput {
+    Literal(SmolStr),
+    TokenTree(Subtree),
+}
+
+impl Attr {
+    pub(crate) fn from_src(
+        Source { file_id, ast }: Source<ast::Attr>,
+        db: &impl AstDatabase,
+    ) -> Option<Attr> {
+        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 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
+        }
+    }
+}
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/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 40e56dfe044..f0e790e4c45 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -1,11 +1,13 @@
 //! FIXME: write short doc here
 
+use ra_cfg::CfgOptions;
 use ra_db::FileId;
 use ra_syntax::{ast, SmolStr};
 use rustc_hash::FxHashMap;
 use test_utils::tested_by;
 
 use crate::{
+    attr::Attr,
     db::DefDatabase,
     ids::{AstItemDef, LocationCtx, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind},
     name::MACRO_RULES,
@@ -35,6 +37,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 +47,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 +82,8 @@ impl MacroStackMonitor {
 }
 
 /// Walks the tree of module recursively
-struct DefCollector<DB> {
-    db: DB,
+struct DefCollector<'a, DB> {
+    db: &'a DB,
     def_map: CrateDefMap,
     glob_imports: FxHashMap<CrateModuleId, Vec<(CrateModuleId, raw::ImportId)>>,
     unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>,
@@ -86,9 +92,11 @@ struct DefCollector<DB> {
     /// 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<DB> DefCollector<'_, DB>
 where
     DB: DefDatabase,
 {
@@ -506,7 +514,7 @@ struct ModCollector<'a, D> {
     parent_module: Option<ParentModule<'a>>,
 }
 
-impl<DB> ModCollector<'_, &'_ mut DefCollector<&'_ DB>>
+impl<DB> ModCollector<'_, &'_ mut DefCollector<'_, DB>>
 where
     DB: DefDatabase,
 {
@@ -523,23 +531,27 @@ where
         // `#[macro_use] extern crate` is hoisted to imports macros before collecting
         // any other items.
         for item in items {
-            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);
+            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.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]),
+            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]),
+                }
             }
         }
     }
@@ -702,6 +714,13 @@ where
             self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_);
         }
     }
+
+    fn is_cfg_enabled(&self, attrs: &[Attr]) -> bool {
+        attrs
+            .iter()
+            .flat_map(|attr| attr.as_cfg())
+            .all(|cfg| self.def_collector.cfg_options.is_cfg_enabled(cfg).unwrap_or(true))
+    }
 }
 
 fn is_macro_rules(path: &Path) -> bool {
@@ -729,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 cacbcb5177f..ff079bcf120 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -2,7 +2,6 @@
 
 use std::{ops::Index, sync::Arc};
 
-use mbe::ast_to_token_tree;
 use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
 use ra_syntax::{
     ast::{self, AttrsOwner, NameOwner},
@@ -11,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,
 };
@@ -29,8 +29,6 @@ pub struct RawItems {
     items: Vec<RawItem>,
 }
 
-type Attrs = Arc<[tt::Subtree]>;
-
 #[derive(Debug, Default, PartialEq, Eq)]
 pub struct ImportSourceMap {
     map: ArenaMap<ImportId, ImportSourcePtr>,
@@ -124,7 +122,7 @@ impl Index<Macro> for RawItems {
 
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub(super) struct RawItem {
-    pub(super) attrs: Attrs,
+    pub(super) attrs: Arc<[Attr]>,
     pub(super) kind: RawItemKind,
 }
 
@@ -285,6 +283,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         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);
@@ -315,6 +314,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     }
 
     fn add_use_item(&mut self, current_module: Option<Module>, use_item: ast::UseItem) {
+        // FIXME: cfg_attr
         let is_prelude = use_item.has_atom_attr("prelude_import");
         let attrs = self.parse_attrs(&use_item);
 
@@ -349,6 +349,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
             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,
@@ -368,6 +369,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     }
 
     fn add_macro(&mut self, current_module: Option<Module>, 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))
@@ -378,6 +380,7 @@ impl<DB: AstDatabase> 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 });
@@ -387,7 +390,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     fn push_import(
         &mut self,
         current_module: Option<Module>,
-        attrs: Attrs,
+        attrs: Arc<[Attr]>,
         data: ImportData,
         source: ImportSourcePtr,
     ) {
@@ -396,7 +399,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         self.push_item(current_module, attrs, RawItemKind::Import(import))
     }
 
-    fn push_item(&mut self, current_module: Option<Module>, attrs: Attrs, kind: RawItemKind) {
+    fn push_item(&mut self, current_module: Option<Module>, attrs: Arc<[Attr]>, kind: RawItemKind) {
         match current_module {
             Some(module) => match &mut self.raw_items.modules[module] {
                 ModuleData::Definition { items, .. } => items,
@@ -407,11 +410,9 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         .push(RawItem { attrs, kind })
     }
 
-    fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs {
+    fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Arc<[Attr]> {
         item.attrs()
-            .flat_map(|attr| attr.value())
-            .flat_map(|tt| ast_to_token_tree(&tt))
-            .map(|(tt, _)| tt)
+            .flat_map(|ast| Attr::from_src(Source { ast, file_id: self.file_id }, self.db))
             .collect()
     }
 }

From d2ea776b8fbb5286a04dde75a9a8f8b14f12bfe9 Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Mon, 30 Sep 2019 07:38:16 +0800
Subject: [PATCH 3/9] Enable CfgOptions `test` for workspace crates

---
 crates/ra_db/src/input.rs              | 28 ++++++-----
 crates/ra_hir/src/mock.rs              | 23 ++++++---
 crates/ra_hir/src/nameres/tests.rs     | 70 ++++++++++++++++++++++++++
 crates/ra_ide_api/Cargo.toml           |  1 +
 crates/ra_ide_api/src/lib.rs           |  6 ++-
 crates/ra_ide_api/src/mock_analysis.rs |  7 ++-
 crates/ra_ide_api/src/parent_module.rs |  3 +-
 crates/ra_project_model/Cargo.toml     |  1 +
 crates/ra_project_model/src/lib.rs     | 21 ++++++--
 9 files changed, 134 insertions(+), 26 deletions(-)

diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index 5fd6edd78d1..23148096cb1 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -114,9 +114,8 @@ struct CrateData {
 }
 
 impl CrateData {
-    fn new(file_id: FileId, edition: Edition) -> CrateData {
-        // FIXME: cfg options
-        CrateData { file_id, edition, dependencies: Vec::new(), cfg_options: CfgOptions::default() }
+    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) {
@@ -137,9 +136,14 @@ 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
     }
@@ -228,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());
@@ -244,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/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<String>))>);
+pub struct CrateGraphFixture(pub Vec<(String, (String, Edition, CfgOptions, Vec<String>))>);
 
 #[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/tests.rs b/crates/ra_hir/src/nameres/tests.rs
index bc4b47b704c..f43767e5958 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())
+                .feature("foo".into())
+                .feature("bar".into())
+                .option("opt".into(), "42".into())
+            ),
+        },
+    );
+
+    assert_snapshot!(map, @r###"
+        ⋮crate
+        ⋮Bar: _
+        ⋮Baz: t v
+        ⋮Foo: t v
+    "###);
+}
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..13258b63dd9 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,12 @@ impl MockAnalysis {
             assert!(path.starts_with('/'));
             let path = RelativePathBuf::from_path(&path[1..]).unwrap();
             let file_id = FileId(i as u32 + 1);
+            // FIXME: cfg options
+            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_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/lib.rs b/crates/ra_project_model/src/lib.rs
index 5d307859805..5ff3971e064 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -11,6 +11,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
+use ra_cfg::CfgOptions;
 use ra_db::{CrateGraph, CrateId, Edition, FileId};
 use rustc_hash::FxHashMap;
 use serde_json::from_reader;
@@ -131,7 +132,13 @@ 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));
+                        // FIXME: cfg options
+                        // Default to enable test for workspace crates.
+                        let cfg_options = CfgOptions::default().atom("test".into());
+                        crates.insert(
+                            crate_id,
+                            crate_graph.add_crate_root(file_id, edition, cfg_options),
+                        );
                     }
                 }
 
@@ -157,7 +164,11 @@ 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);
+                        // FIXME: cfg options
+                        // Crates from sysroot have `cfg(test)` disabled
+                        let cfg_options = CfgOptions::default();
+                        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 +197,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);
+                            // FIXME: cfg options
+                            // Default to enable test for workspace crates.
+                            let cfg_options = CfgOptions::default().atom("test".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);

From a49ad47e5afa5950f92b77badc6679295101328a Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Mon, 30 Sep 2019 17:47:17 +0800
Subject: [PATCH 4/9] Support cfg attribute on impl blocks

---
 crates/ra_hir/src/attr.rs              | 19 +++++++++--
 crates/ra_hir/src/impl_block.rs        | 30 ++++++++++++++---
 crates/ra_hir/src/nameres/collector.rs |  5 +--
 crates/ra_hir/src/nameres/raw.rs       |  4 +--
 crates/ra_hir/src/ty/tests.rs          | 45 ++++++++++++++++++++++++++
 5 files changed, 90 insertions(+), 13 deletions(-)

diff --git a/crates/ra_hir/src/attr.rs b/crates/ra_hir/src/attr.rs
index 19be6de32fd..84c36b8daa0 100644
--- a/crates/ra_hir/src/attr.rs
+++ b/crates/ra_hir/src/attr.rs
@@ -1,11 +1,14 @@
+use std::sync::Arc;
+
 use mbe::ast_to_token_tree;
+use ra_cfg::CfgOptions;
 use ra_syntax::{
-    ast::{self, AstNode},
+    ast::{self, AstNode, AttrsOwner},
     SmolStr,
 };
 use tt::Subtree;
 
-use crate::{db::AstDatabase, path::Path, Source};
+use crate::{db::AstDatabase, path::Path, HirFileId, Source};
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub(crate) struct Attr {
@@ -40,6 +43,14 @@ impl Attr {
         Some(Attr { path, input })
     }
 
+    pub(crate) fn from_attrs_owner(
+        file_id: HirFileId,
+        owner: &impl AttrsOwner,
+        db: &impl AstDatabase,
+    ) -> Arc<[Attr]> {
+        owner.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)
@@ -55,4 +66,8 @@ impl Attr {
             None
         }
     }
+
+    pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Option<bool> {
+        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..7877c317165 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,11 @@ 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.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 +227,11 @@ impl ModuleImplBlocks {
                 }
                 ast::ItemOrMacro::Item(_) => (),
                 ast::ItemOrMacro::Macro(macro_call) => {
+                    let attrs = Attr::from_attrs_owner(file_id, &macro_call, db);
+                    if 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(&macro_call).with_file_id(file_id);
                     if let Some(path) = macro_call
@@ -231,7 +245,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 +266,10 @@ pub(crate) fn impls_in_module_with_source_map_query(
     module: Module,
 ) -> (Arc<ModuleImplBlocks>, Arc<ImplSourceMap>) {
     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/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index f0e790e4c45..1d79cbd8c8a 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -716,10 +716,7 @@ where
     }
 
     fn is_cfg_enabled(&self, attrs: &[Attr]) -> bool {
-        attrs
-            .iter()
-            .flat_map(|attr| attr.as_cfg())
-            .all(|cfg| self.def_collector.cfg_options.is_cfg_enabled(cfg).unwrap_or(true))
+        attrs.iter().all(|attr| attr.is_cfg_enabled(&self.def_collector.cfg_options) != Some(false))
     }
 }
 
diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs
index ff079bcf120..f02d4eb7a8a 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -411,9 +411,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     }
 
     fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Arc<[Attr]> {
-        item.attrs()
-            .flat_map(|ast| Attr::from_src(Source { ast, file_id: self.file_id }, self.db))
-            .collect()
+        Attr::from_attrs_owner(self.file_id, item, self.db)
     }
 }
 
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 4df39c1915f..171aead1841 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(

From 43f09ad36ccc1c53c78a66274693e53161c9b2fa Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Thu, 3 Oct 2019 01:20:08 +0800
Subject: [PATCH 5/9] Refactor CfgOptions inside

---
 crates/ra_cfg/src/lib.rs               | 26 +++++++++++++++++---------
 crates/ra_hir/src/nameres/tests.rs     |  6 +++---
 crates/ra_ide_api/src/mock_analysis.rs |  1 -
 3 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs
index fa5822d8a6b..dd81a73f483 100644
--- a/crates/ra_cfg/src/lib.rs
+++ b/crates/ra_cfg/src/lib.rs
@@ -1,24 +1,32 @@
 //! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
 use ra_syntax::SmolStr;
-use rustc_hash::{FxHashMap, FxHashSet};
+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<SmolStr>,
-    features: FxHashSet<SmolStr>,
-    options: FxHashMap<SmolStr, SmolStr>,
+    key_values: FxHashSet<(SmolStr, SmolStr)>,
 }
 
 impl CfgOptions {
     pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
         cfg.fold(&|key, value| match value {
             None => self.atoms.contains(key),
-            Some(value) if key == "feature" => self.features.contains(value),
-            Some(value) => self.options.get(key).map_or(false, |v| v == value),
+            Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
         })
     }
 
@@ -31,13 +39,13 @@ impl CfgOptions {
         self
     }
 
-    pub fn feature(mut self, name: SmolStr) -> CfgOptions {
-        self.features.insert(name);
+    pub fn key_value(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
+        self.key_values.insert((key, value));
         self
     }
 
-    pub fn option(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
-        self.options.insert(key, value);
+    pub fn remove_atom(mut self, name: &SmolStr) -> CfgOptions {
+        self.atoms.remove(name);
         self
     }
 }
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs
index f43767e5958..34dd7957438 100644
--- a/crates/ra_hir/src/nameres/tests.rs
+++ b/crates/ra_hir/src/nameres/tests.rs
@@ -563,9 +563,9 @@ fn cfg_test() {
             "main": ("/main.rs", ["std"]),
             "std": ("/lib.rs", [], CfgOptions::default()
                 .atom("test".into())
-                .feature("foo".into())
-                .feature("bar".into())
-                .option("opt".into(), "42".into())
+                .key_value("feature".into(), "foo".into())
+                .key_value("feature".into(), "bar".into())
+                .key_value("opt".into(), "42".into())
             ),
         },
     );
diff --git a/crates/ra_ide_api/src/mock_analysis.rs b/crates/ra_ide_api/src/mock_analysis.rs
index 13258b63dd9..80b71894cdf 100644
--- a/crates/ra_ide_api/src/mock_analysis.rs
+++ b/crates/ra_ide_api/src/mock_analysis.rs
@@ -94,7 +94,6 @@ impl MockAnalysis {
             assert!(path.starts_with('/'));
             let path = RelativePathBuf::from_path(&path[1..]).unwrap();
             let file_id = FileId(i as u32 + 1);
-            // FIXME: cfg options
             let cfg_options = CfgOptions::default();
             if path == "/lib.rs" || path == "/main.rs" {
                 root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018, cfg_options));

From e0100e63ae2e873f119b905ac77c3355ffb351b0 Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Thu, 3 Oct 2019 01:38:56 +0800
Subject: [PATCH 6/9] Optimize

---
 crates/ra_cfg/src/cfg_expr.rs          |  2 +-
 crates/ra_hir/src/attr.rs              | 11 ++++++++---
 crates/ra_hir/src/impl_block.rs        |  8 ++++++--
 crates/ra_hir/src/nameres/collector.rs |  9 ++++++---
 crates/ra_hir/src/nameres/raw.rs       | 11 +++++++----
 5 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
index efeadf46265..811f8304888 100644
--- a/crates/ra_cfg/src/cfg_expr.rs
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -15,7 +15,7 @@ pub enum CfgExpr {
 
 impl CfgExpr {
     /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
-    pub fn fold(&self, query: &impl Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
+    pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
         match self {
             CfgExpr::Invalid => None,
             CfgExpr::Atom(name) => Some(query(name, None)),
diff --git a/crates/ra_hir/src/attr.rs b/crates/ra_hir/src/attr.rs
index 84c36b8daa0..a8a7e900612 100644
--- a/crates/ra_hir/src/attr.rs
+++ b/crates/ra_hir/src/attr.rs
@@ -45,10 +45,15 @@ impl Attr {
 
     pub(crate) fn from_attrs_owner(
         file_id: HirFileId,
-        owner: &impl AttrsOwner,
+        owner: &dyn AttrsOwner,
         db: &impl AstDatabase,
-    ) -> Arc<[Attr]> {
-        owner.attrs().flat_map(|ast| Attr::from_src(Source { file_id, ast }, db)).collect()
+    ) -> Option<Arc<[Attr]>> {
+        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 {
diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs
index 7877c317165..55dfc393b40 100644
--- a/crates/ra_hir/src/impl_block.rs
+++ b/crates/ra_hir/src/impl_block.rs
@@ -213,7 +213,9 @@ impl ModuleImplBlocks {
             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.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) {
+                    if attrs.map_or(false, |attrs| {
+                        attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
+                    }) {
                         continue;
                     }
 
@@ -228,7 +230,9 @@ impl ModuleImplBlocks {
                 ast::ItemOrMacro::Item(_) => (),
                 ast::ItemOrMacro::Macro(macro_call) => {
                     let attrs = Attr::from_attrs_owner(file_id, &macro_call, db);
-                    if attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) {
+                    if attrs.map_or(false, |attrs| {
+                        attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
+                    }) {
                         continue;
                     }
 
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 1d79cbd8c8a..cef2dc9d287 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -7,7 +7,6 @@ use rustc_hash::FxHashMap;
 use test_utils::tested_by;
 
 use crate::{
-    attr::Attr,
     db::DefDatabase,
     ids::{AstItemDef, LocationCtx, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind},
     name::MACRO_RULES,
@@ -715,8 +714,12 @@ where
         }
     }
 
-    fn is_cfg_enabled(&self, attrs: &[Attr]) -> bool {
-        attrs.iter().all(|attr| attr.is_cfg_enabled(&self.def_collector.cfg_options) != Some(false))
+    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))
+        })
     }
 }
 
diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs
index f02d4eb7a8a..623b343c4fd 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -120,9 +120,12 @@ impl Index<Macro> for RawItems {
     }
 }
 
+// Avoid heap allocation on items without attributes.
+pub(super) type Attrs = Option<Arc<[Attr]>>;
+
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub(super) struct RawItem {
-    pub(super) attrs: Arc<[Attr]>,
+    pub(super) attrs: Attrs,
     pub(super) kind: RawItemKind,
 }
 
@@ -390,7 +393,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     fn push_import(
         &mut self,
         current_module: Option<Module>,
-        attrs: Arc<[Attr]>,
+        attrs: Attrs,
         data: ImportData,
         source: ImportSourcePtr,
     ) {
@@ -399,7 +402,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         self.push_item(current_module, attrs, RawItemKind::Import(import))
     }
 
-    fn push_item(&mut self, current_module: Option<Module>, attrs: Arc<[Attr]>, kind: RawItemKind) {
+    fn push_item(&mut self, current_module: Option<Module>, attrs: Attrs, kind: RawItemKind) {
         match current_module {
             Some(module) => match &mut self.raw_items.modules[module] {
                 ModuleData::Definition { items, .. } => items,
@@ -410,7 +413,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         .push(RawItem { attrs, kind })
     }
 
-    fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Arc<[Attr]> {
+    fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs {
         Attr::from_attrs_owner(self.file_id, item, self.db)
     }
 }

From 1067a1c5f649cc206e35b427eaa8d6553280cc96 Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Thu, 3 Oct 2019 02:02:53 +0800
Subject: [PATCH 7/9] Read default cfgs from rustc

---
 Cargo.lock                                    |  1 +
 crates/ra_batch/src/lib.rs                    | 18 +++++---
 crates/ra_cfg/src/lib.rs                      | 10 +++++
 crates/ra_lsp_server/Cargo.toml               |  1 +
 crates/ra_lsp_server/src/world.rs             |  8 +++-
 .../ra_project_model/src/cargo_workspace.rs   |  6 +++
 crates/ra_project_model/src/json_project.rs   |  2 +
 crates/ra_project_model/src/lib.rs            | 44 ++++++++++++++++---
 8 files changed, 76 insertions(+), 14 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b95f176fc69..736f1994edf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1032,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",
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<SourceRootId,
         sender,
         Watch(false),
     );
-    let (crate_graph, _crate_names) = ws.to_crate_graph(&mut |path: &Path| {
-        let vfs_file = vfs.load(path);
-        log::debug!("vfs file {:?} -> {:?}", 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/src/lib.rs b/crates/ra_cfg/src/lib.rs
index dd81a73f483..e1c92fbba30 100644
--- a/crates/ra_cfg/src/lib.rs
+++ b/crates/ra_cfg/src/lib.rs
@@ -1,4 +1,6 @@
 //! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
+use std::iter::IntoIterator;
+
 use ra_syntax::SmolStr;
 use rustc_hash::FxHashSet;
 
@@ -44,6 +46,14 @@ impl CfgOptions {
         self
     }
 
+    /// Shortcut to set features
+    pub fn features(mut self, iter: impl IntoIterator<Item = SmolStr>) -> 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_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_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<PackageDependency>,
     edition: Edition,
+    features: Vec<String>,
 }
 
 #[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<Item = Target> + '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..b0d339f3839 100644
--- a/crates/ra_project_model/src/json_project.rs
+++ b/crates/ra_project_model/src/json_project.rs
@@ -19,6 +19,8 @@ pub struct Crate {
     pub(crate) root_module: PathBuf,
     pub(crate) edition: Edition,
     pub(crate) deps: Vec<Dep>,
+    #[serde(default)]
+    pub(crate) features: Vec<String>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize)]
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 5ff3971e064..05e49f5ce6b 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -9,6 +9,7 @@ use std::{
     fs::File,
     io::BufReader,
     path::{Path, PathBuf},
+    process::Command,
 };
 
 use ra_cfg::CfgOptions;
@@ -118,6 +119,7 @@ impl ProjectWorkspace {
 
     pub fn to_crate_graph(
         &self,
+        default_cfg_options: &CfgOptions,
         load: &mut dyn FnMut(&Path) -> Option<FileId>,
     ) -> (CrateGraph, FxHashMap<CrateId, String>) {
         let mut crate_graph = CrateGraph::default();
@@ -134,7 +136,9 @@ impl ProjectWorkspace {
                         };
                         // FIXME: cfg options
                         // Default to enable test for workspace crates.
-                        let cfg_options = CfgOptions::default().atom("test".into());
+                        let cfg_options = default_cfg_options
+                            .clone()
+                            .features(krate.features.iter().map(Into::into));
                         crates.insert(
                             crate_id,
                             crate_graph.add_crate_root(file_id, edition, cfg_options),
@@ -164,9 +168,8 @@ impl ProjectWorkspace {
                 let mut sysroot_crates = FxHashMap::default();
                 for krate in sysroot.crates() {
                     if let Some(file_id) = load(krate.root(&sysroot)) {
-                        // FIXME: cfg options
                         // Crates from sysroot have `cfg(test)` disabled
-                        let cfg_options = CfgOptions::default();
+                        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);
@@ -197,9 +200,9 @@ impl ProjectWorkspace {
                         let root = tgt.root(&cargo);
                         if let Some(file_id) = load(root) {
                             let edition = pkg.edition(&cargo);
-                            // FIXME: cfg options
-                            // Default to enable test for workspace crates.
-                            let cfg_options = CfgOptions::default().atom("test".into());
+                            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());
@@ -301,3 +304,32 @@ fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
     }
     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)` ans `cfg(debug_assertion)` is 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
+}

From b271cb18d5ab19624e751867df6705cd1e94edbc Mon Sep 17 00:00:00 2001
From: uHOOCCOOHu <hooccooh1896@gmail.com>
Date: Thu, 3 Oct 2019 02:50:22 +0800
Subject: [PATCH 8/9] Add docs

---
 crates/ra_cfg/src/cfg_expr.rs | 4 ++++
 crates/ra_hir/src/attr.rs     | 2 ++
 2 files changed, 6 insertions(+)

diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
index 811f8304888..39d71851ca7 100644
--- a/crates/ra_cfg/src/cfg_expr.rs
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -1,3 +1,7 @@
+//! 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;
diff --git a/crates/ra_hir/src/attr.rs b/crates/ra_hir/src/attr.rs
index a8a7e900612..f67e80bfd15 100644
--- a/crates/ra_hir/src/attr.rs
+++ b/crates/ra_hir/src/attr.rs
@@ -1,3 +1,5 @@
+//! A higher level attributes based on TokenTree, with also some shortcuts.
+
 use std::sync::Arc;
 
 use mbe::ast_to_token_tree;

From c6303d9fee98232ac83a77f943c39d65c9c6b6db Mon Sep 17 00:00:00 2001
From: oxalica <oxalicc@pm.me>
Date: Sat, 5 Oct 2019 20:55:27 +0800
Subject: [PATCH 9/9] Use raw cfgs in json project and fix typo

---
 crates/ra_lsp_server/tests/heavy_tests/main.rs |  8 +++++++-
 crates/ra_project_model/src/json_project.rs    |  5 +++--
 crates/ra_project_model/src/lib.rs             | 14 ++++++++------
 3 files changed, 18 insertions(+), 9 deletions(-)

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/src/json_project.rs b/crates/ra_project_model/src/json_project.rs
index b0d339f3839..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,8 +20,8 @@ pub struct Crate {
     pub(crate) root_module: PathBuf,
     pub(crate) edition: Edition,
     pub(crate) deps: Vec<Dep>,
-    #[serde(default)]
-    pub(crate) features: Vec<String>,
+    pub(crate) atom_cfgs: FxHashSet<String>,
+    pub(crate) key_value_cfgs: FxHashMap<String, String>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize)]
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 05e49f5ce6b..640a5ebd321 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -134,11 +134,13 @@ impl ProjectWorkspace {
                             json_project::Edition::Edition2015 => Edition::Edition2015,
                             json_project::Edition::Edition2018 => Edition::Edition2018,
                         };
-                        // FIXME: cfg options
-                        // Default to enable test for workspace crates.
-                        let cfg_options = default_cfg_options
-                            .clone()
-                            .features(krate.features.iter().map(Into::into));
+                        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),
@@ -309,7 +311,7 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
     let mut cfg_options = CfgOptions::default();
 
     match (|| -> Result<_> {
-        // `cfg(test)` ans `cfg(debug_assertion)` is handled outside, so we suppress them here.
+        // `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")?;