aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_cfg
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-10-05 15:25:59 +0100
committerGitHub <[email protected]>2019-10-05 15:25:59 +0100
commitae6305b90c80eb919cfde985cba66975b6222ed2 (patch)
treede601daf3714c4bda937e7cad05d048b69d16e71 /crates/ra_cfg
parentdbf869b4d2dac09df17609edf6e67c1611b71dc5 (diff)
parentc6303d9fee98232ac83a77f943c39d65c9c6b6db (diff)
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]>
Diffstat (limited to 'crates/ra_cfg')
-rw-r--r--crates/ra_cfg/Cargo.toml14
-rw-r--r--crates/ra_cfg/src/cfg_expr.rs132
-rw-r--r--crates/ra_cfg/src/lib.rs61
3 files changed, 207 insertions, 0 deletions
diff --git a/crates/ra_cfg/Cargo.toml b/crates/ra_cfg/Cargo.toml
new file mode 100644
index 000000000..b28affc3a
--- /dev/null
+++ b/crates/ra_cfg/Cargo.toml
@@ -0,0 +1,14 @@
1[package]
2edition = "2018"
3name = "ra_cfg"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6
7[dependencies]
8rustc-hash = "1.0.1"
9
10ra_syntax = { path = "../ra_syntax" }
11tt = { path = "../ra_tt", package = "ra_tt" }
12
13[dev-dependencies]
14mbe = { path = "../ra_mbe", package = "ra_mbe" }
diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
new file mode 100644
index 000000000..39d71851c
--- /dev/null
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -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
5use std::slice::Iter as SliceIter;
6
7use ra_syntax::SmolStr;
8use tt::{Leaf, Subtree, TokenTree};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub 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
20impl 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
38pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
39 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
40}
41
42fn 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)]
88mod 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}
diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs
new file mode 100644
index 000000000..e1c92fbba
--- /dev/null
+++ b/crates/ra_cfg/src/lib.rs
@@ -0,0 +1,61 @@
1//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2use std::iter::IntoIterator;
3
4use ra_syntax::SmolStr;
5use rustc_hash::FxHashSet;
6
7mod cfg_expr;
8
9pub 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)]
22pub struct CfgOptions {
23 atoms: FxHashSet<SmolStr>,
24 key_values: FxHashSet<(SmolStr, SmolStr)>,
25}
26
27impl 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}