Skip to content

Commit ae6305b

Browse files
bors[bot]oxalica
andauthored
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 <[email protected]> Co-authored-by: oxalica <[email protected]>
2 parents dbf869b + c6303d9 commit ae6305b

File tree

29 files changed

+671
-66
lines changed

29 files changed

+671
-66
lines changed

Cargo.lock

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_batch/src/lib.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hash::FxHashMap;
77
use crossbeam_channel::{unbounded, Receiver};
88
use ra_db::{CrateGraph, FileId, SourceRootId};
99
use ra_ide_api::{AnalysisChange, AnalysisHost, FeatureFlags};
10-
use ra_project_model::{PackageRoot, ProjectWorkspace};
10+
use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace};
1111
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
1212
use ra_vfs_glob::RustPackageFilterBuilder;
1313

@@ -41,11 +41,17 @@ pub fn load_cargo(root: &Path) -> Result<(AnalysisHost, FxHashMap<SourceRootId,
4141
sender,
4242
Watch(false),
4343
);
44-
let (crate_graph, _crate_names) = ws.to_crate_graph(&mut |path: &Path| {
45-
let vfs_file = vfs.load(path);
46-
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
47-
vfs_file.map(vfs_file_to_id)
48-
});
44+
45+
// FIXME: cfg options?
46+
let default_cfg_options =
47+
get_rustc_cfg_options().atom("test".into()).atom("debug_assertion".into());
48+
49+
let (crate_graph, _crate_names) =
50+
ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| {
51+
let vfs_file = vfs.load(path);
52+
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
53+
vfs_file.map(vfs_file_to_id)
54+
});
4955
log::debug!("crate graph: {:?}", crate_graph);
5056

5157
let source_roots = roots

crates/ra_cfg/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
edition = "2018"
3+
name = "ra_cfg"
4+
version = "0.1.0"
5+
authors = ["rust-analyzer developers"]
6+
7+
[dependencies]
8+
rustc-hash = "1.0.1"
9+
10+
ra_syntax = { path = "../ra_syntax" }
11+
tt = { path = "../ra_tt", package = "ra_tt" }
12+
13+
[dev-dependencies]
14+
mbe = { path = "../ra_mbe", package = "ra_mbe" }

crates/ra_cfg/src/cfg_expr.rs

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! The condition expression used in `#[cfg(..)]` attributes.
2+
//!
3+
//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
4+
5+
use std::slice::Iter as SliceIter;
6+
7+
use ra_syntax::SmolStr;
8+
use tt::{Leaf, Subtree, TokenTree};
9+
10+
#[derive(Debug, Clone, PartialEq, Eq)]
11+
pub enum CfgExpr {
12+
Invalid,
13+
Atom(SmolStr),
14+
KeyValue { key: SmolStr, value: SmolStr },
15+
All(Vec<CfgExpr>),
16+
Any(Vec<CfgExpr>),
17+
Not(Box<CfgExpr>),
18+
}
19+
20+
impl CfgExpr {
21+
/// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
22+
pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
23+
match self {
24+
CfgExpr::Invalid => None,
25+
CfgExpr::Atom(name) => Some(query(name, None)),
26+
CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
27+
CfgExpr::All(preds) => {
28+
preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
29+
}
30+
CfgExpr::Any(preds) => {
31+
preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
32+
}
33+
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
34+
}
35+
}
36+
}
37+
38+
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
39+
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
40+
}
41+
42+
fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
43+
let name = match it.next() {
44+
None => return None,
45+
Some(TokenTree::Leaf(Leaf::Ident(ident))) => ident.text.clone(),
46+
Some(_) => return Some(CfgExpr::Invalid),
47+
};
48+
49+
// Peek
50+
let ret = match it.as_slice().first() {
51+
Some(TokenTree::Leaf(Leaf::Punct(punct))) if punct.char == '=' => {
52+
match it.as_slice().get(1) {
53+
Some(TokenTree::Leaf(Leaf::Literal(literal))) => {
54+
it.next();
55+
it.next();
56+
// FIXME: escape? raw string?
57+
let value =
58+
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
59+
CfgExpr::KeyValue { key: name, value }
60+
}
61+
_ => return Some(CfgExpr::Invalid),
62+
}
63+
}
64+
Some(TokenTree::Subtree(subtree)) => {
65+
it.next();
66+
let mut sub_it = subtree.token_trees.iter();
67+
let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect();
68+
match name.as_str() {
69+
"all" => CfgExpr::All(subs),
70+
"any" => CfgExpr::Any(subs),
71+
"not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))),
72+
_ => CfgExpr::Invalid,
73+
}
74+
}
75+
_ => CfgExpr::Atom(name),
76+
};
77+
78+
// Eat comma separator
79+
if let Some(TokenTree::Leaf(Leaf::Punct(punct))) = it.as_slice().first() {
80+
if punct.char == ',' {
81+
it.next();
82+
}
83+
}
84+
Some(ret)
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use super::*;
90+
91+
use mbe::ast_to_token_tree;
92+
use ra_syntax::ast::{self, AstNode};
93+
94+
fn assert_parse_result(input: &str, expected: CfgExpr) {
95+
let source_file = ast::SourceFile::parse(input).ok().unwrap();
96+
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
97+
let (tt, _) = ast_to_token_tree(&tt).unwrap();
98+
assert_eq!(parse_cfg(&tt), expected);
99+
}
100+
101+
#[test]
102+
fn test_cfg_expr_parser() {
103+
assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
104+
assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
105+
assert_parse_result(
106+
"#![cfg(not(foo))]",
107+
CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
108+
);
109+
assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
110+
111+
// Only take the first
112+
assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
113+
114+
assert_parse_result(
115+
r#"#![cfg(all(foo, bar = "baz"))]"#,
116+
CfgExpr::All(vec![
117+
CfgExpr::Atom("foo".into()),
118+
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
119+
]),
120+
);
121+
122+
assert_parse_result(
123+
r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
124+
CfgExpr::Any(vec![
125+
CfgExpr::Not(Box::new(CfgExpr::Invalid)),
126+
CfgExpr::All(vec![]),
127+
CfgExpr::Invalid,
128+
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
129+
]),
130+
);
131+
}
132+
}

crates/ra_cfg/src/lib.rs

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2+
use std::iter::IntoIterator;
3+
4+
use ra_syntax::SmolStr;
5+
use rustc_hash::FxHashSet;
6+
7+
mod cfg_expr;
8+
9+
pub use cfg_expr::{parse_cfg, CfgExpr};
10+
11+
/// Configuration options used for conditional compilition on items with `cfg` attributes.
12+
/// We have two kind of options in different namespaces: atomic options like `unix`, and
13+
/// key-value options like `target_arch="x86"`.
14+
///
15+
/// Note that for key-value options, one key can have multiple values (but not none).
16+
/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features
17+
/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple
18+
/// of key and value in `key_values`.
19+
///
20+
/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
21+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
22+
pub struct CfgOptions {
23+
atoms: FxHashSet<SmolStr>,
24+
key_values: FxHashSet<(SmolStr, SmolStr)>,
25+
}
26+
27+
impl CfgOptions {
28+
pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
29+
cfg.fold(&|key, value| match value {
30+
None => self.atoms.contains(key),
31+
Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
32+
})
33+
}
34+
35+
pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option<bool> {
36+
self.check(&parse_cfg(attr))
37+
}
38+
39+
pub fn atom(mut self, name: SmolStr) -> CfgOptions {
40+
self.atoms.insert(name);
41+
self
42+
}
43+
44+
pub fn key_value(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
45+
self.key_values.insert((key, value));
46+
self
47+
}
48+
49+
/// Shortcut to set features
50+
pub fn features(mut self, iter: impl IntoIterator<Item = SmolStr>) -> CfgOptions {
51+
for feat in iter {
52+
self = self.key_value("feature".into(), feat);
53+
}
54+
self
55+
}
56+
57+
pub fn remove_atom(mut self, name: &SmolStr) -> CfgOptions {
58+
self.atoms.remove(name);
59+
self
60+
}
61+
}

crates/ra_db/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ relative-path = "0.4.0"
1010
rustc-hash = "1.0"
1111

1212
ra_syntax = { path = "../ra_syntax" }
13+
ra_cfg = { path = "../ra_cfg" }
1314
ra_prof = { path = "../ra_prof" }

crates/ra_db/src/input.rs

+22-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use relative_path::{RelativePath, RelativePathBuf};
1010
use rustc_hash::FxHashMap;
1111

12+
use ra_cfg::CfgOptions;
1213
use ra_syntax::SmolStr;
1314
use rustc_hash::FxHashSet;
1415

@@ -109,11 +110,12 @@ struct CrateData {
109110
file_id: FileId,
110111
edition: Edition,
111112
dependencies: Vec<Dependency>,
113+
cfg_options: CfgOptions,
112114
}
113115

114116
impl CrateData {
115-
fn new(file_id: FileId, edition: Edition) -> CrateData {
116-
CrateData { file_id, edition, dependencies: Vec::new() }
117+
fn new(file_id: FileId, edition: Edition, cfg_options: CfgOptions) -> CrateData {
118+
CrateData { file_id, edition, dependencies: Vec::new(), cfg_options }
117119
}
118120

119121
fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) {
@@ -134,13 +136,22 @@ impl Dependency {
134136
}
135137

136138
impl CrateGraph {
137-
pub fn add_crate_root(&mut self, file_id: FileId, edition: Edition) -> CrateId {
139+
pub fn add_crate_root(
140+
&mut self,
141+
file_id: FileId,
142+
edition: Edition,
143+
cfg_options: CfgOptions,
144+
) -> CrateId {
138145
let crate_id = CrateId(self.arena.len() as u32);
139-
let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition));
146+
let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition, cfg_options));
140147
assert!(prev.is_none());
141148
crate_id
142149
}
143150

151+
pub fn cfg_options(&self, crate_id: CrateId) -> &CfgOptions {
152+
&self.arena[&crate_id].cfg_options
153+
}
154+
144155
pub fn add_dep(
145156
&mut self,
146157
from: CrateId,
@@ -221,14 +232,14 @@ impl CrateGraph {
221232

222233
#[cfg(test)]
223234
mod tests {
224-
use super::{CrateGraph, Edition::Edition2018, FileId, SmolStr};
235+
use super::{CfgOptions, CrateGraph, Edition::Edition2018, FileId, SmolStr};
225236

226237
#[test]
227238
fn it_should_panic_because_of_cycle_dependencies() {
228239
let mut graph = CrateGraph::default();
229-
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
230-
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
231-
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
240+
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default());
241+
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default());
242+
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default());
232243
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
233244
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
234245
assert!(graph.add_dep(crate3, SmolStr::new("crate1"), crate1).is_err());
@@ -237,9 +248,9 @@ mod tests {
237248
#[test]
238249
fn it_works() {
239250
let mut graph = CrateGraph::default();
240-
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
241-
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
242-
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
251+
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default());
252+
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default());
253+
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default());
243254
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
244255
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
245256
}

crates/ra_hir/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ once_cell = "1.0.1"
1515

1616
ra_syntax = { path = "../ra_syntax" }
1717
ra_arena = { path = "../ra_arena" }
18+
ra_cfg = { path = "../ra_cfg" }
1819
ra_db = { path = "../ra_db" }
1920
mbe = { path = "../ra_mbe", package = "ra_mbe" }
2021
tt = { path = "../ra_tt", package = "ra_tt" }

0 commit comments

Comments
 (0)