From b1ed887d813bf5775a16624694939fdf836f97b1 Mon Sep 17 00:00:00 2001 From: uHOOCCOOHu Date: Mon, 30 Sep 2019 06:52:15 +0800 Subject: 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 988f7ec0b..b95f176fc 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 000000000..b28affc3a --- /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 000000000..efeadf462 --- /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), + Any(Vec), + Not(Box), +} + +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 { + 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 000000000..fa5822d8a --- /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, + features: FxHashSet, + options: FxHashMap, +} + +impl CfgOptions { + pub fn check(&self, cfg: &CfgExpr) -> Option { + 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 { + 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 2fac07bc5..c141f1a88 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 52f892891..5fd6edd78 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, + 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 d9bed4dda..cc117f84d 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 000000000..19be6de32 --- /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, +} + +#[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 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 00031deba..4340e9d34 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 40e56dfe0..f0e790e4c 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, +struct DefCollector<'a, DB> { + db: &'a DB, def_map: CrateDefMap, glob_imports: FxHashMap>, unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>, @@ -86,9 +92,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 +514,7 @@ struct ModCollector<'a, D> { parent_module: Option>, } -impl ModCollector<'_, &'_ mut DefCollector<&'_ DB>> +impl 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 cacbcb517..ff079bcf1 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, } -type Attrs = Arc<[tt::Subtree]>; - #[derive(Debug, Default, PartialEq, Eq)] pub struct ImportSourceMap { map: ArenaMap, @@ -124,7 +122,7 @@ impl Index 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 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 RawItemsCollector<&DB> { } 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); @@ -349,6 +349,7 @@ impl 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 RawItemsCollector<&DB> { } 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)) @@ -378,6 +380,7 @@ 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 }); @@ -387,7 +390,7 @@ impl RawItemsCollector<&DB> { fn push_import( &mut self, current_module: Option, - attrs: Attrs, + attrs: Arc<[Attr]>, data: ImportData, source: ImportSourcePtr, ) { @@ -396,7 +399,7 @@ impl RawItemsCollector<&DB> { self.push_item(current_module, attrs, RawItemKind::Import(import)) } - fn push_item(&mut self, current_module: Option, attrs: Attrs, kind: RawItemKind) { + fn push_item(&mut self, current_module: Option, 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 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() } } -- cgit v1.2.3