aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_cfg/src/cfg_expr.rs68
-rw-r--r--crates/ra_hir/src/lib.rs1
-rw-r--r--crates/ra_hir_def/src/attr.rs2
-rw-r--r--crates/ra_hir_def/src/body.rs2
-rw-r--r--crates/ra_ide/src/runnables.rs148
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs9
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs3
7 files changed, 214 insertions, 19 deletions
diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
index 39d71851c..a4b201e0e 100644
--- a/crates/ra_cfg/src/cfg_expr.rs
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -33,6 +33,36 @@ impl CfgExpr {
33 CfgExpr::Not(pred) => pred.fold(query).map(|s| !s), 33 CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
34 } 34 }
35 } 35 }
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 }
36} 66}
37 67
38pub fn parse_cfg(tt: &Subtree) -> CfgExpr { 68pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
@@ -88,13 +118,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
88mod tests { 118mod tests {
89 use super::*; 119 use super::*;
90 120
91 use mbe::ast_to_token_tree; 121 use mbe::{ast_to_token_tree, TokenMap};
92 use ra_syntax::ast::{self, AstNode}; 122 use ra_syntax::ast::{self, AstNode};
93 123
94 fn assert_parse_result(input: &str, expected: CfgExpr) { 124 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
95 let source_file = ast::SourceFile::parse(input).ok().unwrap(); 125 let source_file = ast::SourceFile::parse(input).ok().unwrap();
96 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); 126 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);
98 assert_eq!(parse_cfg(&tt), expected); 132 assert_eq!(parse_cfg(&tt), expected);
99 } 133 }
100 134
@@ -129,4 +163,32 @@ mod tests {
129 ]), 163 ]),
130 ); 164 );
131 } 165 }
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 }
132} 194}
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index c5df4ac24..3364a822f 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -62,6 +62,7 @@ pub use crate::{
62 62
63pub use hir_def::{ 63pub use hir_def::{
64 adt::StructKind, 64 adt::StructKind,
65 attr::Attrs,
65 body::scope::ExprScopes, 66 body::scope::ExprScopes,
66 builtin_type::BuiltinType, 67 builtin_type::BuiltinType,
67 docs::Documentation, 68 docs::Documentation,
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 576cd0c65..8b6c0bede 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -81,7 +81,7 @@ impl Attrs {
81 } 81 }
82 } 82 }
83 83
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 {
85 let hygiene = Hygiene::new(db.upcast(), owner.file_id); 85 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
86 Attrs::new(owner.value, &hygiene) 86 Attrs::new(owner.value, &hygiene)
87 } 87 }
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index f5a7305dc..273036cee 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -29,7 +29,7 @@ use crate::{
29 AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, 29 AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
30}; 30};
31 31
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
33/// avoid cyclic queries in crate def map during enum processing. 33/// avoid cyclic queries in crate def map during enum processing.
34pub(crate) struct CfgExpander { 34pub(crate) struct CfgExpander {
35 cfg_options: CfgOptions, 35 cfg_options: CfgOptions,
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 131b8f307..3a3d0b0ac 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,11 +1,11 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, 7 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
8 match_ast, SyntaxNode, TextRange, 8 match_ast, SmolStr, SyntaxNode, TextRange,
9}; 9};
10 10
11use crate::FileId; 11use crate::FileId;
@@ -16,6 +16,7 @@ use std::fmt::Display;
16pub struct Runnable { 16pub struct Runnable {
17 pub range: TextRange, 17 pub range: TextRange,
18 pub kind: RunnableKind, 18 pub kind: RunnableKind,
19 pub features_needed: Option<Vec<SmolStr>>,
19} 20}
20 21
21#[derive(Debug)] 22#[derive(Debug)]
@@ -45,20 +46,24 @@ pub enum RunnableKind {
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 46pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
46 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
47 let source_file = sema.parse(file_id); 48 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()
49} 50}
50 51
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 52fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
52 match_ast! { 53 match_ast! {
53 match item { 54 match item {
54 ast::FnDef(it) => runnable_fn(sema, it), 55 ast::FnDef(it) => runnable_fn(sema, it, file_id),
55 ast::Module(it) => runnable_mod(sema, it), 56 ast::Module(it) => runnable_mod(sema, it, file_id),
56 _ => None, 57 _ => None,
57 } 58 }
58 } 59 }
59} 60}
60 61
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 62fn runnable_fn(
63 sema: &Semantics<RootDatabase>,
64 fn_def: ast::FnDef,
65 file_id: FileId,
66) -> Option<Runnable> {
62 let name_string = fn_def.name()?.text().to_string(); 67 let name_string = fn_def.name()?.text().to_string();
63 68
64 let kind = if name_string == "main" { 69 let kind = if name_string == "main" {
@@ -111,7 +116,11 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
111 return None; 116 return None;
112 } 117 }
113 }; 118 };
114 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 119
120 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
121 let features_needed = get_features_needed(attrs);
122
123 Some(Runnable { range: fn_def.syntax().text_range(), kind, features_needed })
115} 124}
116 125
117#[derive(Debug)] 126#[derive(Debug)]
@@ -147,7 +156,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
147 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) 156 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
148} 157}
149 158
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 159fn runnable_mod(
160 sema: &Semantics<RootDatabase>,
161 module: ast::Module,
162 file_id: FileId,
163) -> Option<Runnable> {
151 let has_test_function = module 164 let has_test_function = module
152 .item_list()? 165 .item_list()?
153 .items() 166 .items()
@@ -160,11 +173,34 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
160 return None; 173 return None;
161 } 174 }
162 let range = module.syntax().text_range(); 175 let range = module.syntax().text_range();
163 let module = sema.to_def(&module)?; 176 let module_def = sema.to_def(&module)?;
177
178 let path = module_def
179 .path_to_root(sema.db)
180 .into_iter()
181 .rev()
182 .filter_map(|it| it.name(sema.db))
183 .join("::");
184
185 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
186 let features_needed = get_features_needed(attrs);
164 187
165 let path = 188 Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed })
166 module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); 189}
167 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 190
191fn get_features_needed(attrs: Attrs) -> Option<Vec<SmolStr>> {
192 let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree));
193 let features_needed = cfg_expr.fold(vec![], |mut acc, cfg| {
194 if let Some(features_needed) = cfg.minimal_features_needed() {
195 acc.extend(features_needed);
196 }
197 acc
198 });
199 if features_needed.is_empty() {
200 None
201 } else {
202 Some(features_needed)
203 }
168} 204}
169 205
170#[cfg(test)] 206#[cfg(test)]
@@ -196,6 +232,7 @@ mod tests {
196 Runnable { 232 Runnable {
197 range: 1..21, 233 range: 1..21,
198 kind: Bin, 234 kind: Bin,
235 features_needed: None,
199 }, 236 },
200 Runnable { 237 Runnable {
201 range: 22..46, 238 range: 22..46,
@@ -207,6 +244,7 @@ mod tests {
207 ignore: false, 244 ignore: false,
208 }, 245 },
209 }, 246 },
247 features_needed: None,
210 }, 248 },
211 Runnable { 249 Runnable {
212 range: 47..81, 250 range: 47..81,
@@ -218,6 +256,7 @@ mod tests {
218 ignore: true, 256 ignore: true,
219 }, 257 },
220 }, 258 },
259 features_needed: None,
221 }, 260 },
222 ] 261 ]
223 "### 262 "###
@@ -245,6 +284,7 @@ mod tests {
245 Runnable { 284 Runnable {
246 range: 1..21, 285 range: 1..21,
247 kind: Bin, 286 kind: Bin,
287 features_needed: None,
248 }, 288 },
249 Runnable { 289 Runnable {
250 range: 22..64, 290 range: 22..64,
@@ -253,6 +293,7 @@ mod tests {
253 "foo", 293 "foo",
254 ), 294 ),
255 }, 295 },
296 features_needed: None,
256 }, 297 },
257 ] 298 ]
258 "### 299 "###
@@ -283,6 +324,7 @@ mod tests {
283 Runnable { 324 Runnable {
284 range: 1..21, 325 range: 1..21,
285 kind: Bin, 326 kind: Bin,
327 features_needed: None,
286 }, 328 },
287 Runnable { 329 Runnable {
288 range: 51..105, 330 range: 51..105,
@@ -291,6 +333,7 @@ mod tests {
291 "Data::foo", 333 "Data::foo",
292 ), 334 ),
293 }, 335 },
336 features_needed: None,
294 }, 337 },
295 ] 338 ]
296 "### 339 "###
@@ -318,6 +361,7 @@ mod tests {
318 kind: TestMod { 361 kind: TestMod {
319 path: "test_mod", 362 path: "test_mod",
320 }, 363 },
364 features_needed: None,
321 }, 365 },
322 Runnable { 366 Runnable {
323 range: 28..57, 367 range: 28..57,
@@ -329,6 +373,7 @@ mod tests {
329 ignore: false, 373 ignore: false,
330 }, 374 },
331 }, 375 },
376 features_needed: None,
332 }, 377 },
333 ] 378 ]
334 "### 379 "###
@@ -358,6 +403,7 @@ mod tests {
358 kind: TestMod { 403 kind: TestMod {
359 path: "foo::test_mod", 404 path: "foo::test_mod",
360 }, 405 },
406 features_needed: None,
361 }, 407 },
362 Runnable { 408 Runnable {
363 range: 46..79, 409 range: 46..79,
@@ -369,6 +415,7 @@ mod tests {
369 ignore: false, 415 ignore: false,
370 }, 416 },
371 }, 417 },
418 features_needed: None,
372 }, 419 },
373 ] 420 ]
374 "### 421 "###
@@ -400,6 +447,7 @@ mod tests {
400 kind: TestMod { 447 kind: TestMod {
401 path: "foo::bar::test_mod", 448 path: "foo::bar::test_mod",
402 }, 449 },
450 features_needed: None,
403 }, 451 },
404 Runnable { 452 Runnable {
405 range: 68..105, 453 range: 68..105,
@@ -411,6 +459,80 @@ mod tests {
411 ignore: false, 459 ignore: false,
412 }, 460 },
413 }, 461 },
462 features_needed: None,
463 },
464 ]
465 "###
466 );
467 }
468
469 #[test]
470 fn test_runnables_with_feature() {
471 let (analysis, pos) = analysis_and_position(
472 r#"
473 //- /lib.rs crate:foo cfg:feature=foo
474 <|> //empty
475 #[test]
476 #[cfg(feature = "foo")]
477 fn test_foo1() {}
478 "#,
479 );
480 let runnables = analysis.runnables(pos.file_id).unwrap();
481 assert_debug_snapshot!(&runnables,
482 @r###"
483 [
484 Runnable {
485 range: 1..58,
486 kind: Test {
487 test_id: Name(
488 "test_foo1",
489 ),
490 attr: TestAttr {
491 ignore: false,
492 },
493 },
494 features_needed: Some(
495 [
496 "foo",
497 ],
498 ),
499 },
500 ]
501 "###
502 );
503 }
504
505 #[test]
506 fn test_runnables_with_features() {
507 let (analysis, pos) = analysis_and_position(
508 r#"
509 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
510 <|> //empty
511 #[test]
512 #[cfg(all(feature = "foo", feature = "bar"))]
513 fn test_foo1() {}
514 "#,
515 );
516 let runnables = analysis.runnables(pos.file_id).unwrap();
517 assert_debug_snapshot!(&runnables,
518 @r###"
519 [
520 Runnable {
521 range: 1..80,
522 kind: Test {
523 test_id: Name(
524 "test_foo1",
525 ),
526 attr: TestAttr {
527 ignore: false,
528 },
529 },
530 features_needed: Some(
531 [
532 "foo",
533 "bar",
534 ],
535 ),
414 }, 536 },
415 ] 537 ]
416 "### 538 "###
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 5e5a17943..a2f85060b 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -4,6 +4,7 @@ use ra_ide::{FileId, RunnableKind, TestId};
4use ra_project_model::{self, ProjectWorkspace, TargetKind}; 4use ra_project_model::{self, ProjectWorkspace, TargetKind};
5 5
6use crate::{world::WorldSnapshot, Result}; 6use crate::{world::WorldSnapshot, Result};
7use ra_syntax::SmolStr;
7 8
8/// Abstract representation of Cargo target. 9/// Abstract representation of Cargo target.
9/// 10///
@@ -20,6 +21,7 @@ impl CargoTargetSpec {
20 pub(crate) fn runnable_args( 21 pub(crate) fn runnable_args(
21 spec: Option<CargoTargetSpec>, 22 spec: Option<CargoTargetSpec>,
22 kind: &RunnableKind, 23 kind: &RunnableKind,
24 features_needed: &Option<Vec<SmolStr>>,
23 ) -> Result<(Vec<String>, Vec<String>)> { 25 ) -> Result<(Vec<String>, Vec<String>)> {
24 let mut args = Vec::new(); 26 let mut args = Vec::new();
25 let mut extra_args = Vec::new(); 27 let mut extra_args = Vec::new();
@@ -73,6 +75,13 @@ impl CargoTargetSpec {
73 } 75 }
74 } 76 }
75 } 77 }
78
79 if let Some(features_needed) = features_needed {
80 features_needed.iter().for_each(|feature| {
81 args.push("--features".to_string());
82 args.push(feature.to_string());
83 });
84 }
76 Ok((args, extra_args)) 85 Ok((args, extra_args))
77 } 86 }
78 87
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index be6a0aece..cc9abd162 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -1006,7 +1006,8 @@ fn to_lsp_runnable(
1006) -> Result<lsp_ext::Runnable> { 1006) -> Result<lsp_ext::Runnable> {
1007 let spec = CargoTargetSpec::for_file(world, file_id)?; 1007 let spec = CargoTargetSpec::for_file(world, file_id)?;
1008 let target = spec.as_ref().map(|s| s.target.clone()); 1008 let target = spec.as_ref().map(|s| s.target.clone());
1009 let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; 1009 let (args, extra_args) =
1010 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?;
1010 let line_index = world.analysis().file_line_index(file_id)?; 1011 let line_index = world.analysis().file_line_index(file_id)?;
1011 let label = match &runnable.kind { 1012 let label = match &runnable.kind {
1012 RunnableKind::Test { test_id, .. } => format!("test {}", test_id), 1013 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),