Skip to content

Commit c614374

Browse files
committed
add support of feature flag for runnables #4464
Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
1 parent ebaa05a commit c614374

File tree

7 files changed

+212
-19
lines changed

7 files changed

+212
-19
lines changed

crates/ra_cfg/src/cfg_expr.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,36 @@ impl CfgExpr {
3333
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
3434
}
3535
}
36+
37+
/// Return minimal features needed
38+
pub fn minimal_features_needed(&self) -> Option<Vec<SmolStr>> {
39+
let mut features = vec![];
40+
self.collect_minimal_features_needed(&mut features);
41+
if features.is_empty() {
42+
None
43+
} else {
44+
Some(features)
45+
}
46+
}
47+
48+
fn collect_minimal_features_needed(&self, features: &mut Vec<SmolStr>) {
49+
match self {
50+
CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
51+
CfgExpr::All(preds) => {
52+
preds.iter().for_each(|cfg| cfg.collect_minimal_features_needed(features));
53+
}
54+
CfgExpr::Any(preds) => {
55+
for cfg in preds {
56+
let len_features = features.len();
57+
cfg.collect_minimal_features_needed(features);
58+
if len_features != features.len() {
59+
break;
60+
}
61+
}
62+
}
63+
_ => {}
64+
}
65+
}
3666
}
3767

3868
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
@@ -88,13 +118,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
88118
mod tests {
89119
use super::*;
90120

91-
use mbe::ast_to_token_tree;
121+
use mbe::{ast_to_token_tree, TokenMap};
92122
use ra_syntax::ast::{self, AstNode};
93123

94-
fn assert_parse_result(input: &str, expected: CfgExpr) {
124+
fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
95125
let source_file = ast::SourceFile::parse(input).ok().unwrap();
96126
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
97-
let (tt, _) = ast_to_token_tree(&tt).unwrap();
127+
ast_to_token_tree(&tt).unwrap()
128+
}
129+
130+
fn assert_parse_result(input: &str, expected: CfgExpr) {
131+
let (tt, _) = get_token_tree_generated(input);
98132
assert_eq!(parse_cfg(&tt), expected);
99133
}
100134

@@ -129,4 +163,32 @@ mod tests {
129163
]),
130164
);
131165
}
166+
167+
#[test]
168+
fn test_cfg_expr_minimal_features_needed() {
169+
let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
170+
let cfg_expr = parse_cfg(&subtree);
171+
172+
assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]);
173+
174+
let (subtree, _) =
175+
get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
176+
let cfg_expr = parse_cfg(&subtree);
177+
178+
assert_eq!(
179+
cfg_expr.minimal_features_needed().unwrap(),
180+
vec![SmolStr::new("baz"), SmolStr::new("foo")]
181+
);
182+
183+
let (subtree, _) =
184+
get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
185+
let cfg_expr = parse_cfg(&subtree);
186+
187+
assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]);
188+
189+
let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
190+
let cfg_expr = parse_cfg(&subtree);
191+
192+
assert!(cfg_expr.minimal_features_needed().is_none());
193+
}
132194
}

crates/ra_hir/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub use crate::{
6262

6363
pub use hir_def::{
6464
adt::StructKind,
65+
attr::Attrs,
6566
body::scope::ExprScopes,
6667
builtin_type::BuiltinType,
6768
docs::Documentation,

crates/ra_hir_def/src/attr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl Attrs {
8181
}
8282
}
8383

84-
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
84+
pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
8585
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
8686
Attrs::new(owner.value, &hygiene)
8787
}

crates/ra_hir_def/src/body.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::{
2929
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
3030
};
3131

32-
/// A subset of Exander that only deals with cfg attributes. We only need it to
32+
/// A subset of Expander that only deals with cfg attributes. We only need it to
3333
/// avoid cyclic queries in crate def map during enum processing.
3434
pub(crate) struct CfgExpander {
3535
cfg_options: CfgOptions,

crates/ra_ide/src/runnables.rs

Lines changed: 133 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! FIXME: write short doc here
22
3-
use hir::Semantics;
3+
use hir::{Attrs, HirFileId, InFile, Semantics};
44
use itertools::Itertools;
55
use ra_ide_db::RootDatabase;
66
use ra_syntax::{
77
ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
8-
match_ast, SyntaxNode, TextRange,
8+
match_ast, SmolStr, SyntaxNode, TextRange,
99
};
1010

1111
use crate::FileId;
@@ -16,6 +16,7 @@ use std::fmt::Display;
1616
pub struct Runnable {
1717
pub range: TextRange,
1818
pub kind: RunnableKind,
19+
pub features_needed: Option<Vec<SmolStr>>,
1920
}
2021

2122
#[derive(Debug)]
@@ -45,20 +46,24 @@ pub enum RunnableKind {
4546
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
4647
let sema = Semantics::new(db);
4748
let source_file = sema.parse(file_id);
48-
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect()
49+
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
4950
}
5051

51-
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
52+
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
5253
match_ast! {
5354
match item {
54-
ast::FnDef(it) => runnable_fn(sema, it),
55-
ast::Module(it) => runnable_mod(sema, it),
55+
ast::FnDef(it) => runnable_fn(sema, it, file_id),
56+
ast::Module(it) => runnable_mod(sema, it, file_id),
5657
_ => None,
5758
}
5859
}
5960
}
6061

61-
fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> {
62+
fn runnable_fn(
63+
sema: &Semantics<RootDatabase>,
64+
fn_def: ast::FnDef,
65+
file_id: FileId,
66+
) -> Option<Runnable> {
6267
let name_string = fn_def.name()?.text().to_string();
6368

6469
let kind = if name_string == "main" {
@@ -89,7 +94,11 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
8994
return None;
9095
}
9196
};
92-
Some(Runnable { range: fn_def.syntax().text_range(), kind })
97+
98+
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
99+
let features_needed = get_features_needed(attrs);
100+
101+
Some(Runnable { range: fn_def.syntax().text_range(), kind, features_needed })
93102
}
94103

95104
#[derive(Debug)]
@@ -125,7 +134,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
125134
fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
126135
}
127136

128-
fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
137+
fn runnable_mod(
138+
sema: &Semantics<RootDatabase>,
139+
module: ast::Module,
140+
file_id: FileId,
141+
) -> Option<Runnable> {
129142
let has_test_function = module
130143
.item_list()?
131144
.items()
@@ -138,11 +151,34 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
138151
return None;
139152
}
140153
let range = module.syntax().text_range();
141-
let module = sema.to_def(&module)?;
154+
let module_def = sema.to_def(&module)?;
155+
156+
let path = module_def
157+
.path_to_root(sema.db)
158+
.into_iter()
159+
.rev()
160+
.filter_map(|it| it.name(sema.db))
161+
.join("::");
162+
163+
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
164+
let features_needed = get_features_needed(attrs);
142165

143-
let path =
144-
module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
145-
Some(Runnable { range, kind: RunnableKind::TestMod { path } })
166+
Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed })
167+
}
168+
169+
fn get_features_needed(attrs: Attrs) -> Option<Vec<SmolStr>> {
170+
let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree));
171+
let features_needed = cfg_expr.fold(vec![], |mut acc, cfg| {
172+
if let Some(features_needed) = cfg.minimal_features_needed() {
173+
acc.extend(features_needed);
174+
}
175+
acc
176+
});
177+
if features_needed.is_empty() {
178+
None
179+
} else {
180+
Some(features_needed)
181+
}
146182
}
147183

148184
#[cfg(test)]
@@ -174,6 +210,7 @@ mod tests {
174210
Runnable {
175211
range: 1..21,
176212
kind: Bin,
213+
features_needed: None,
177214
},
178215
Runnable {
179216
range: 22..46,
@@ -185,6 +222,7 @@ mod tests {
185222
ignore: false,
186223
},
187224
},
225+
features_needed: None,
188226
},
189227
Runnable {
190228
range: 47..81,
@@ -196,6 +234,7 @@ mod tests {
196234
ignore: true,
197235
},
198236
},
237+
features_needed: None,
199238
},
200239
]
201240
"###
@@ -223,6 +262,7 @@ mod tests {
223262
Runnable {
224263
range: 1..21,
225264
kind: Bin,
265+
features_needed: None,
226266
},
227267
Runnable {
228268
range: 22..64,
@@ -231,6 +271,7 @@ mod tests {
231271
"foo",
232272
),
233273
},
274+
features_needed: None,
234275
},
235276
]
236277
"###
@@ -258,6 +299,7 @@ mod tests {
258299
kind: TestMod {
259300
path: "test_mod",
260301
},
302+
features_needed: None,
261303
},
262304
Runnable {
263305
range: 28..57,
@@ -269,6 +311,7 @@ mod tests {
269311
ignore: false,
270312
},
271313
},
314+
features_needed: None,
272315
},
273316
]
274317
"###
@@ -298,6 +341,7 @@ mod tests {
298341
kind: TestMod {
299342
path: "foo::test_mod",
300343
},
344+
features_needed: None,
301345
},
302346
Runnable {
303347
range: 46..79,
@@ -309,6 +353,7 @@ mod tests {
309353
ignore: false,
310354
},
311355
},
356+
features_needed: None,
312357
},
313358
]
314359
"###
@@ -340,6 +385,7 @@ mod tests {
340385
kind: TestMod {
341386
path: "foo::bar::test_mod",
342387
},
388+
features_needed: None,
343389
},
344390
Runnable {
345391
range: 68..105,
@@ -351,6 +397,80 @@ mod tests {
351397
ignore: false,
352398
},
353399
},
400+
features_needed: None,
401+
},
402+
]
403+
"###
404+
);
405+
}
406+
407+
#[test]
408+
fn test_runnables_with_feature() {
409+
let (analysis, pos) = analysis_and_position(
410+
r#"
411+
//- /lib.rs crate:foo cfg:feature=foo
412+
<|> //empty
413+
#[test]
414+
#[cfg(feature = "foo")]
415+
fn test_foo1() {}
416+
"#,
417+
);
418+
let runnables = analysis.runnables(pos.file_id).unwrap();
419+
assert_debug_snapshot!(&runnables,
420+
@r###"
421+
[
422+
Runnable {
423+
range: 1..58,
424+
kind: Test {
425+
test_id: Name(
426+
"test_foo1",
427+
),
428+
attr: TestAttr {
429+
ignore: false,
430+
},
431+
},
432+
features_needed: Some(
433+
[
434+
"foo",
435+
],
436+
),
437+
},
438+
]
439+
"###
440+
);
441+
}
442+
443+
#[test]
444+
fn test_runnables_with_features() {
445+
let (analysis, pos) = analysis_and_position(
446+
r#"
447+
//- /lib.rs crate:foo cfg:feature=foo,feature=bar
448+
<|> //empty
449+
#[test]
450+
#[cfg(all(feature = "foo", feature = "bar"))]
451+
fn test_foo1() {}
452+
"#,
453+
);
454+
let runnables = analysis.runnables(pos.file_id).unwrap();
455+
assert_debug_snapshot!(&runnables,
456+
@r###"
457+
[
458+
Runnable {
459+
range: 1..80,
460+
kind: Test {
461+
test_id: Name(
462+
"test_foo1",
463+
),
464+
attr: TestAttr {
465+
ignore: false,
466+
},
467+
},
468+
features_needed: Some(
469+
[
470+
"foo",
471+
"bar",
472+
],
473+
),
354474
},
355475
]
356476
"###

0 commit comments

Comments
 (0)