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

15
Cargo.lock generated
View file

@ -922,6 +922,16 @@ dependencies = [
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ra_cfg"
version = "0.1.0"
dependencies = [
"ra_mbe 0.1.0",
"ra_syntax 0.1.0",
"ra_tt 0.1.0",
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ra_cli"
version = "0.1.0"
@ -941,6 +951,7 @@ dependencies = [
name = "ra_db"
version = "0.1.0"
dependencies = [
"ra_cfg 0.1.0",
"ra_prof 0.1.0",
"ra_syntax 0.1.0",
"relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -971,6 +982,7 @@ dependencies = [
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_arena 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"ra_mbe 0.1.0",
"ra_prof 0.1.0",
@ -993,6 +1005,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_assists 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"ra_fmt 0.1.0",
"ra_hir 0.1.0",
@ -1019,6 +1032,7 @@ dependencies = [
"lsp-server 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lsp-types 0.61.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"ra_ide_api 0.1.0",
"ra_prof 0.1.0",
@ -1075,6 +1089,7 @@ dependencies = [
"cargo_metadata 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_arena 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",

View file

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

14
crates/ra_cfg/Cargo.toml Normal file
View file

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

View file

@ -0,0 +1,132 @@
//! The condition expression used in `#[cfg(..)]` attributes.
//!
//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
use std::slice::Iter as SliceIter;
use ra_syntax::SmolStr;
use tt::{Leaf, Subtree, TokenTree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfgExpr {
Invalid,
Atom(SmolStr),
KeyValue { key: SmolStr, value: SmolStr },
All(Vec<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: &dyn 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() },
]),
);
}
}

61
crates/ra_cfg/src/lib.rs Normal file
View file

@ -0,0 +1,61 @@
//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
use std::iter::IntoIterator;
use ra_syntax::SmolStr;
use rustc_hash::FxHashSet;
mod cfg_expr;
pub use cfg_expr::{parse_cfg, CfgExpr};
/// Configuration options used for conditional compilition on items with `cfg` attributes.
/// We have two kind of options in different namespaces: atomic options like `unix`, and
/// key-value options like `target_arch="x86"`.
///
/// Note that for key-value options, one key can have multiple values (but not none).
/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features
/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple
/// of key and value in `key_values`.
///
/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CfgOptions {
atoms: FxHashSet<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) => self.key_values.contains(&(key.clone(), value.clone())),
})
}
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 key_value(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
self.key_values.insert((key, value));
self
}
/// Shortcut to set features
pub fn features(mut self, iter: impl IntoIterator<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
}
}

View file

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

View file

@ -9,6 +9,7 @@
use relative_path::{RelativePath, RelativePathBuf};
use rustc_hash::FxHashMap;
use ra_cfg::CfgOptions;
use ra_syntax::SmolStr;
use rustc_hash::FxHashSet;
@ -109,11 +110,12 @@ struct CrateData {
file_id: FileId,
edition: Edition,
dependencies: Vec<Dependency>,
cfg_options: CfgOptions,
}
impl CrateData {
fn new(file_id: FileId, edition: Edition) -> CrateData {
CrateData { file_id, edition, dependencies: Vec::new() }
fn new(file_id: FileId, edition: Edition, cfg_options: CfgOptions) -> CrateData {
CrateData { file_id, edition, dependencies: Vec::new(), cfg_options }
}
fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) {
@ -134,13 +136,22 @@ impl Dependency {
}
impl CrateGraph {
pub fn add_crate_root(&mut self, file_id: FileId, edition: Edition) -> CrateId {
pub fn add_crate_root(
&mut self,
file_id: FileId,
edition: Edition,
cfg_options: CfgOptions,
) -> CrateId {
let crate_id = CrateId(self.arena.len() as u32);
let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition));
let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition, cfg_options));
assert!(prev.is_none());
crate_id
}
pub fn cfg_options(&self, crate_id: CrateId) -> &CfgOptions {
&self.arena[&crate_id].cfg_options
}
pub fn add_dep(
&mut self,
from: CrateId,
@ -221,14 +232,14 @@ impl CrateGraph {
#[cfg(test)]
mod tests {
use super::{CrateGraph, Edition::Edition2018, FileId, SmolStr};
use super::{CfgOptions, CrateGraph, Edition::Edition2018, FileId, SmolStr};
#[test]
fn it_should_panic_because_of_cycle_dependencies() {
let mut graph = CrateGraph::default();
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default());
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default());
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default());
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
assert!(graph.add_dep(crate3, SmolStr::new("crate1"), crate1).is_err());
@ -237,9 +248,9 @@ mod tests {
#[test]
fn it_works() {
let mut graph = CrateGraph::default();
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default());
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default());
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default());
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
}

View file

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

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(

View file

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

View file

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

View file

@ -2,6 +2,7 @@
use std::sync::Arc;
use ra_cfg::CfgOptions;
use relative_path::RelativePathBuf;
use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
@ -93,10 +94,11 @@ impl MockAnalysis {
assert!(path.starts_with('/'));
let path = RelativePathBuf::from_path(&path[1..]).unwrap();
let file_id = FileId(i as u32 + 1);
let cfg_options = CfgOptions::default();
if path == "/lib.rs" || path == "/main.rs" {
root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018));
root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018, cfg_options));
} else if path.ends_with("/lib.rs") {
let other_crate = crate_graph.add_crate_root(file_id, Edition2018);
let other_crate = crate_graph.add_crate_root(file_id, Edition2018, cfg_options);
let crate_name = path.parent().unwrap().file_name().unwrap();
if let Some(root_crate) = root_crate {
crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
use std::path::PathBuf;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize;
/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in
@ -19,6 +20,8 @@ pub struct Crate {
pub(crate) root_module: PathBuf,
pub(crate) edition: Edition,
pub(crate) deps: Vec<Dep>,
pub(crate) atom_cfgs: FxHashSet<String>,
pub(crate) key_value_cfgs: FxHashMap<String, String>,
}
#[derive(Clone, Copy, Debug, Deserialize)]

View file

@ -9,8 +9,10 @@ use std::{
fs::File,
io::BufReader,
path::{Path, PathBuf},
process::Command,
};
use ra_cfg::CfgOptions;
use ra_db::{CrateGraph, CrateId, Edition, FileId};
use rustc_hash::FxHashMap;
use serde_json::from_reader;
@ -117,6 +119,7 @@ impl ProjectWorkspace {
pub fn to_crate_graph(
&self,
default_cfg_options: &CfgOptions,
load: &mut dyn FnMut(&Path) -> Option<FileId>,
) -> (CrateGraph, FxHashMap<CrateId, String>) {
let mut crate_graph = CrateGraph::default();
@ -131,7 +134,17 @@ impl ProjectWorkspace {
json_project::Edition::Edition2015 => Edition::Edition2015,
json_project::Edition::Edition2018 => Edition::Edition2018,
};
crates.insert(crate_id, crate_graph.add_crate_root(file_id, edition));
let mut cfg_options = default_cfg_options.clone();
for name in &krate.atom_cfgs {
cfg_options = cfg_options.atom(name.into());
}
for (key, value) in &krate.key_value_cfgs {
cfg_options = cfg_options.key_value(key.into(), value.into());
}
crates.insert(
crate_id,
crate_graph.add_crate_root(file_id, edition, cfg_options),
);
}
}
@ -157,7 +170,10 @@ impl ProjectWorkspace {
let mut sysroot_crates = FxHashMap::default();
for krate in sysroot.crates() {
if let Some(file_id) = load(krate.root(&sysroot)) {
let crate_id = crate_graph.add_crate_root(file_id, Edition::Edition2018);
// Crates from sysroot have `cfg(test)` disabled
let cfg_options = default_cfg_options.clone().remove_atom(&"test".into());
let crate_id =
crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options);
sysroot_crates.insert(krate, crate_id);
names.insert(crate_id, krate.name(&sysroot).to_string());
}
@ -186,7 +202,11 @@ impl ProjectWorkspace {
let root = tgt.root(&cargo);
if let Some(file_id) = load(root) {
let edition = pkg.edition(&cargo);
let crate_id = crate_graph.add_crate_root(file_id, edition);
let cfg_options = default_cfg_options
.clone()
.features(pkg.features(&cargo).iter().map(Into::into));
let crate_id =
crate_graph.add_crate_root(file_id, edition, cfg_options);
names.insert(crate_id, pkg.name(&cargo).to_string());
if tgt.kind(&cargo) == TargetKind::Lib {
lib_tgt = Some(crate_id);
@ -286,3 +306,32 @@ fn find_cargo_toml(path: &Path) -> Result<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)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
let output = Command::new("rustc").args(&["--print", "cfg", "-O"]).output()?;
if !output.status.success() {
Err("failed to get rustc cfgs")?;
}
Ok(String::from_utf8(output.stdout)?)
})() {
Ok(rustc_cfgs) => {
for line in rustc_cfgs.lines() {
match line.find('=') {
None => cfg_options = cfg_options.atom(line.into()),
Some(pos) => {
let key = &line[..pos];
let value = line[pos + 1..].trim_matches('"');
cfg_options = cfg_options.key_value(key.into(), value.into());
}
}
}
}
Err(e) => log::error!("failed to get rustc cfgs: {}", e),
}
cfg_options
}

View file

@ -1962,6 +1962,7 @@ impl AstNode for ModuleItem {
}
}
}
impl ast::AttrsOwner for ModuleItem {}
impl ModuleItem {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Name {

View file

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