diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-10-05 15:25:59 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-10-05 15:25:59 +0100 |
commit | ae6305b90c80eb919cfde985cba66975b6222ed2 (patch) | |
tree | de601daf3714c4bda937e7cad05d048b69d16e71 /crates/ra_cfg | |
parent | dbf869b4d2dac09df17609edf6e67c1611b71dc5 (diff) | |
parent | c6303d9fee98232ac83a77f943c39d65c9c6b6db (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.toml | 14 | ||||
-rw-r--r-- | crates/ra_cfg/src/cfg_expr.rs | 132 | ||||
-rw-r--r-- | crates/ra_cfg/src/lib.rs | 61 |
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] | ||
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" } | ||
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 | |||
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 | } | ||
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 | ||
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 | } | ||