1928: Support `#[cfg(..)]` r=matklad a=oxalica

This PR implement `#[cfg(..)]` conditional compilation. It read default cfg options from `rustc --print cfg` with also hard-coded `test` and `debug_assertion` enabled.
Front-end settings are **not** included in this PR.

There is also a known issue that inner control attributes are totally ignored. I think it is **not** a part of `cfg` and create a separated issue for it. #1949

Fixes #1920 

Related: #1073 


Co-authored-by: uHOOCCOOHu <hooccooh1896@gmail.com>
Co-authored-by: oxalica <oxalicc@pm.me>
This commit is contained in:
bors[bot] 2019-10-05 14:25:59 +00:00 committed by GitHub
commit ae6305b90c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 671 additions and 66 deletions

80
crates/ra_hir/src/attr.rs Normal file
View file

@ -0,0 +1,80 @@
//! A higher level attributes based on TokenTree, with also some shortcuts.
use std::sync::Arc;
use mbe::ast_to_token_tree;
use ra_cfg::CfgOptions;
use ra_syntax::{
ast::{self, AstNode, AttrsOwner},
SmolStr,
};
use tt::Subtree;
use crate::{db::AstDatabase, path::Path, HirFileId, Source};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Attr {
pub(crate) path: Path,
pub(crate) input: Option<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 from_attrs_owner(
file_id: HirFileId,
owner: &dyn AttrsOwner,
db: &impl AstDatabase,
) -> 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 {
// FIXME: Avoid cloning
self.path.as_ident().map_or(false, |s| s.to_string() == name)
}
pub(crate) fn as_cfg(&self) -> Option<&Subtree> {
if self.is_simple_atom("cfg") {
match &self.input {
Some(AttrInput::TokenTree(subtree)) => Some(subtree),
_ => None,
}
} else {
None
}
}
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Option<bool> {
cfg_options.is_cfg_enabled(self.as_cfg()?)
}
}

View file

@ -4,12 +4,14 @@ use rustc_hash::FxHashMap;
use std::sync::Arc;
use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
use ra_cfg::CfgOptions;
use ra_syntax::{
ast::{self, AstNode},
AstPtr,
};
use crate::{
attr::Attr,
code_model::{Module, ModuleSource},
db::{AstDatabase, DefDatabase, HirDatabase},
generics::HasGenericParams,
@ -176,6 +178,7 @@ pub struct ModuleImplBlocks {
impl ModuleImplBlocks {
fn collect(
db: &(impl DefDatabase + AstDatabase),
cfg_options: &CfgOptions,
module: Module,
source_map: &mut ImplSourceMap,
) -> Self {
@ -188,11 +191,11 @@ impl ModuleImplBlocks {
let src = m.module.definition_source(db);
match &src.ast {
ModuleSource::SourceFile(node) => {
m.collect_from_item_owner(db, source_map, node, src.file_id)
m.collect_from_item_owner(db, cfg_options, source_map, node, src.file_id)
}
ModuleSource::Module(node) => {
let item_list = node.item_list().expect("inline module should have item list");
m.collect_from_item_owner(db, source_map, &item_list, src.file_id)
m.collect_from_item_owner(db, cfg_options, source_map, &item_list, src.file_id)
}
};
m
@ -201,6 +204,7 @@ impl ModuleImplBlocks {
fn collect_from_item_owner(
&mut self,
db: &(impl DefDatabase + AstDatabase),
cfg_options: &CfgOptions,
source_map: &mut ImplSourceMap,
owner: &dyn ast::ModuleItemOwner,
file_id: HirFileId,
@ -208,6 +212,13 @@ impl ModuleImplBlocks {
for item in owner.items_with_macros() {
match item {
ast::ItemOrMacro::Item(ast::ModuleItem::ImplBlock(impl_block_ast)) => {
let attrs = Attr::from_attrs_owner(file_id, &impl_block_ast, db);
if attrs.map_or(false, |attrs| {
attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
}) {
continue;
}
let impl_block = ImplData::from_ast(db, file_id, self.module, &impl_block_ast);
let id = self.impls.alloc(impl_block);
for &impl_item in &self.impls[id].items {
@ -218,6 +229,13 @@ impl ModuleImplBlocks {
}
ast::ItemOrMacro::Item(_) => (),
ast::ItemOrMacro::Macro(macro_call) => {
let attrs = Attr::from_attrs_owner(file_id, &macro_call, db);
if attrs.map_or(false, |attrs| {
attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
}) {
continue;
}
//FIXME: we should really cut down on the boilerplate required to process a macro
let ast_id = db.ast_id_map(file_id).ast_id(&macro_call).with_file_id(file_id);
if let Some(path) = macro_call
@ -231,7 +249,13 @@ impl ModuleImplBlocks {
if let Some(item_list) =
db.parse_or_expand(file_id).and_then(ast::MacroItems::cast)
{
self.collect_from_item_owner(db, source_map, &item_list, file_id)
self.collect_from_item_owner(
db,
cfg_options,
source_map,
&item_list,
file_id,
)
}
}
}
@ -246,8 +270,10 @@ pub(crate) fn impls_in_module_with_source_map_query(
module: Module,
) -> (Arc<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))
}

View file

@ -44,6 +44,7 @@ mod traits;
mod type_alias;
mod type_ref;
mod ty;
mod attr;
mod impl_block;
mod expr;
mod lang_item;

View file

@ -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

View file

@ -1,5 +1,6 @@
//! FIXME: write short doc here
use ra_cfg::CfgOptions;
use ra_db::FileId;
use ra_syntax::{ast, SmolStr};
use rustc_hash::FxHashMap;
@ -35,6 +36,9 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
}
}
let crate_graph = db.crate_graph();
let cfg_options = crate_graph.cfg_options(def_map.krate().crate_id());
let mut collector = DefCollector {
db,
def_map,
@ -42,6 +46,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
unresolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
macro_stack_monitor: MacroStackMonitor::default(),
cfg_options,
};
collector.collect();
collector.finish()
@ -76,8 +81,8 @@ impl MacroStackMonitor {
}
/// Walks the tree of module recursively
struct DefCollector<DB> {
db: 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 +91,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 +513,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,24 +530,27 @@ where
// `#[macro_use] extern crate` is hoisted to imports macros before collecting
// any other items.
for item in items {
if let raw::RawItem::Import(import_id) = *item {
let import = self.raw_items[import_id].clone();
if import.is_extern_crate && import.is_macro_use {
self.def_collector.import_macros_from_extern_crate(self.module_id, &import);
if self.is_cfg_enabled(&item.attrs) {
if let raw::RawItemKind::Import(import_id) = item.kind {
let import = self.raw_items[import_id].clone();
if import.is_extern_crate && import.is_macro_use {
self.def_collector.import_macros_from_extern_crate(self.module_id, &import);
}
}
}
}
for item in items {
match *item {
raw::RawItem::Module(m) => self.collect_module(&self.raw_items[m]),
raw::RawItem::Import(import_id) => self.def_collector.unresolved_imports.push((
self.module_id,
import_id,
self.raw_items[import_id].clone(),
)),
raw::RawItem::Def(def) => self.define_def(&self.raw_items[def]),
raw::RawItem::Macro(mac) => self.collect_macro(&self.raw_items[mac]),
if self.is_cfg_enabled(&item.attrs) {
match item.kind {
raw::RawItemKind::Module(m) => self.collect_module(&self.raw_items[m]),
raw::RawItemKind::Import(import_id) => self
.def_collector
.unresolved_imports
.push((self.module_id, import_id, self.raw_items[import_id].clone())),
raw::RawItemKind::Def(def) => self.define_def(&self.raw_items[def]),
raw::RawItemKind::Macro(mac) => self.collect_macro(&self.raw_items[mac]),
}
}
}
}
@ -703,6 +713,14 @@ where
self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_);
}
}
fn is_cfg_enabled(&self, attrs: &raw::Attrs) -> bool {
attrs.as_ref().map_or(true, |attrs| {
attrs
.iter()
.all(|attr| attr.is_cfg_enabled(&self.def_collector.cfg_options) != Some(false))
})
}
}
fn is_macro_rules(path: &Path) -> bool {
@ -730,6 +748,7 @@ mod tests {
unresolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
macro_stack_monitor: monitor,
cfg_options: &CfgOptions::default(),
};
collector.collect();
collector.finish()

View file

@ -10,6 +10,7 @@ use ra_syntax::{
use test_utils::tested_by;
use crate::{
attr::Attr,
db::{AstDatabase, DefDatabase},
AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, Source,
};
@ -119,8 +120,17 @@ impl Index<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: Attrs,
pub(super) kind: RawItemKind,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum RawItem {
pub(super) enum RawItemKind {
Module(Module),
Import(ImportId),
Def(Def),
@ -215,6 +225,7 @@ impl<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 +274,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,8 +283,10 @@ 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);
// FIXME: cfg_attr
let is_macro_use = module.has_atom_attr("macro_use");
if module.has_semi() {
let attr_path = extract_mod_path_attribute(&module);
@ -283,7 +296,7 @@ impl<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,14 +310,16 @@ 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);
}
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);
Path::expand_use_item(
Source { ast: use_item, file_id: self.file_id },
@ -318,7 +333,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 +351,8 @@ 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);
// FIXME: cfg_attr
let is_macro_use = extern_crate.has_atom_attr("macro_use");
let import_data = ImportData {
path,
@ -340,11 +362,17 @@ 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)),
);
}
}
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))
@ -355,24 +383,26 @@ 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 });
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 +410,11 @@ 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 {
Attr::from_attrs_owner(self.file_id, item, self.db)
}
}

View file

@ -7,6 +7,7 @@ mod mod_resolution;
use std::sync::Arc;
use insta::assert_snapshot;
use ra_cfg::CfgOptions;
use ra_db::SourceDatabase;
use test_utils::covers;
@ -507,3 +508,72 @@ fn values_dont_shadow_extern_crates() {
foo: v
"###);
}
#[test]
fn cfg_not_test() {
let map = def_map_with_crate_graph(
r#"
//- /main.rs
use {Foo, Bar, Baz};
//- /lib.rs
#[prelude_import]
pub use self::prelude::*;
mod prelude {
#[cfg(test)]
pub struct Foo;
#[cfg(not(test))]
pub struct Bar;
#[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))]
pub struct Baz;
}
"#,
crate_graph! {
"main": ("/main.rs", ["std"]),
"std": ("/lib.rs", []),
},
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: _
Foo: _
"###);
}
#[test]
fn cfg_test() {
let map = def_map_with_crate_graph(
r#"
//- /main.rs
use {Foo, Bar, Baz};
//- /lib.rs
#[prelude_import]
pub use self::prelude::*;
mod prelude {
#[cfg(test)]
pub struct Foo;
#[cfg(not(test))]
pub struct Bar;
#[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))]
pub struct Baz;
}
"#,
crate_graph! {
"main": ("/main.rs", ["std"]),
"std": ("/lib.rs", [], CfgOptions::default()
.atom("test".into())
.key_value("feature".into(), "foo".into())
.key_value("feature".into(), "bar".into())
.key_value("opt".into(), "42".into())
),
},
);
assert_snapshot!(map, @r###"
crate
Bar: _
Baz: t v
Foo: t v
"###);
}

View file

@ -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(