Merge #1928
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:
commit
ae6305b90c
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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
14
crates/ra_cfg/Cargo.toml
Normal 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" }
|
132
crates/ra_cfg/src/cfg_expr.rs
Normal file
132
crates/ra_cfg/src/cfg_expr.rs
Normal 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
61
crates/ra_cfg/src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
80
crates/ra_hir/src/attr.rs
Normal 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()?)
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@ use rustc_hash::FxHashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_syntax::{
|
||||
ast::{self, AstNode},
|
||||
AstPtr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
attr::Attr,
|
||||
code_model::{Module, ModuleSource},
|
||||
db::{AstDatabase, DefDatabase, HirDatabase},
|
||||
generics::HasGenericParams,
|
||||
@ -176,6 +178,7 @@ pub struct ModuleImplBlocks {
|
||||
impl ModuleImplBlocks {
|
||||
fn collect(
|
||||
db: &(impl DefDatabase + AstDatabase),
|
||||
cfg_options: &CfgOptions,
|
||||
module: Module,
|
||||
source_map: &mut ImplSourceMap,
|
||||
) -> Self {
|
||||
@ -188,11 +191,11 @@ impl ModuleImplBlocks {
|
||||
let src = m.module.definition_source(db);
|
||||
match &src.ast {
|
||||
ModuleSource::SourceFile(node) => {
|
||||
m.collect_from_item_owner(db, source_map, node, src.file_id)
|
||||
m.collect_from_item_owner(db, cfg_options, source_map, node, src.file_id)
|
||||
}
|
||||
ModuleSource::Module(node) => {
|
||||
let item_list = node.item_list().expect("inline module should have item list");
|
||||
m.collect_from_item_owner(db, source_map, &item_list, src.file_id)
|
||||
m.collect_from_item_owner(db, cfg_options, source_map, &item_list, src.file_id)
|
||||
}
|
||||
};
|
||||
m
|
||||
@ -201,6 +204,7 @@ impl ModuleImplBlocks {
|
||||
fn collect_from_item_owner(
|
||||
&mut self,
|
||||
db: &(impl DefDatabase + AstDatabase),
|
||||
cfg_options: &CfgOptions,
|
||||
source_map: &mut ImplSourceMap,
|
||||
owner: &dyn ast::ModuleItemOwner,
|
||||
file_id: HirFileId,
|
||||
@ -208,6 +212,13 @@ impl ModuleImplBlocks {
|
||||
for item in owner.items_with_macros() {
|
||||
match item {
|
||||
ast::ItemOrMacro::Item(ast::ModuleItem::ImplBlock(impl_block_ast)) => {
|
||||
let attrs = Attr::from_attrs_owner(file_id, &impl_block_ast, db);
|
||||
if attrs.map_or(false, |attrs| {
|
||||
attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let impl_block = ImplData::from_ast(db, file_id, self.module, &impl_block_ast);
|
||||
let id = self.impls.alloc(impl_block);
|
||||
for &impl_item in &self.impls[id].items {
|
||||
@ -218,6 +229,13 @@ impl ModuleImplBlocks {
|
||||
}
|
||||
ast::ItemOrMacro::Item(_) => (),
|
||||
ast::ItemOrMacro::Macro(macro_call) => {
|
||||
let attrs = Attr::from_attrs_owner(file_id, ¯o_call, db);
|
||||
if attrs.map_or(false, |attrs| {
|
||||
attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//FIXME: we should really cut down on the boilerplate required to process a macro
|
||||
let ast_id = db.ast_id_map(file_id).ast_id(¯o_call).with_file_id(file_id);
|
||||
if let Some(path) = macro_call
|
||||
@ -231,7 +249,13 @@ impl ModuleImplBlocks {
|
||||
if let Some(item_list) =
|
||||
db.parse_or_expand(file_id).and_then(ast::MacroItems::cast)
|
||||
{
|
||||
self.collect_from_item_owner(db, source_map, &item_list, file_id)
|
||||
self.collect_from_item_owner(
|
||||
db,
|
||||
cfg_options,
|
||||
source_map,
|
||||
&item_list,
|
||||
file_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,8 +270,10 @@ pub(crate) fn impls_in_module_with_source_map_query(
|
||||
module: Module,
|
||||
) -> (Arc<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))
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ mod traits;
|
||||
mod type_alias;
|
||||
mod type_ref;
|
||||
mod ty;
|
||||
mod attr;
|
||||
mod impl_block;
|
||||
mod expr;
|
||||
mod lang_item;
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
"###);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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" }
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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!(
|
||||
|
@ -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"
|
||||
|
@ -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 })
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1962,6 +1962,7 @@ impl AstNode for ModuleItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ast::AttrsOwner for ModuleItem {}
|
||||
impl ModuleItem {}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Name {
|
||||
|
@ -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"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user