diff options
author | Benjamin Coenen <[email protected]> | 2020-05-21 09:48:42 +0100 |
---|---|---|
committer | Benjamin Coenen <[email protected]> | 2020-05-21 09:48:42 +0100 |
commit | c6143742bd4e625d391ac3ea860be7578ab9f53f (patch) | |
tree | 43cce6b63b38ab0497c54df8850d7c23afd70e0a /crates | |
parent | ebaa05a4478096aaf3bc2a48d0d171a287422c7c (diff) |
add support of feature flag for runnables #4464
Signed-off-by: Benjamin Coenen <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_cfg/src/cfg_expr.rs | 68 | ||||
-rw-r--r-- | crates/ra_hir/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_def/src/attr.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_def/src/body.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/runnables.rs | 146 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cargo_target_spec.rs | 9 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop/handlers.rs | 3 |
7 files changed, 212 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 | ||
38 | pub fn parse_cfg(tt: &Subtree) -> CfgExpr { | 68 | pub fn parse_cfg(tt: &Subtree) -> CfgExpr { |
@@ -88,13 +118,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> { | |||
88 | mod tests { | 118 | mod 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 | ||
63 | pub use hir_def::{ | 63 | pub 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. |
34 | pub(crate) struct CfgExpander { | 34 | pub(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 fa8a9d92c..4f7eb2c5b 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 | ||
3 | use hir::Semantics; | 3 | use hir::{Attrs, HirFileId, InFile, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use 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 | ||
11 | use crate::FileId; | 11 | use crate::FileId; |
@@ -16,6 +16,7 @@ use std::fmt::Display; | |||
16 | pub struct Runnable { | 16 | pub 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 { | |||
45 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 46 | pub(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 | ||
51 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { | 52 | fn 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 | ||
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> { | ||
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" { |
@@ -89,7 +94,11 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
89 | return None; | 94 | return None; |
90 | } | 95 | } |
91 | }; | 96 | }; |
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 }) | ||
93 | } | 102 | } |
94 | 103 | ||
95 | #[derive(Debug)] | 104 | #[derive(Debug)] |
@@ -125,7 +134,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool { | |||
125 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) | 134 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) |
126 | } | 135 | } |
127 | 136 | ||
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> { | ||
129 | let has_test_function = module | 142 | let has_test_function = module |
130 | .item_list()? | 143 | .item_list()? |
131 | .items() | 144 | .items() |
@@ -138,11 +151,34 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R | |||
138 | return None; | 151 | return None; |
139 | } | 152 | } |
140 | let range = module.syntax().text_range(); | 153 | 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); | ||
142 | 165 | ||
143 | let path = | 166 | Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed }) |
144 | module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); | 167 | } |
145 | Some(Runnable { range, kind: RunnableKind::TestMod { path } }) | 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 | } | ||
146 | } | 182 | } |
147 | 183 | ||
148 | #[cfg(test)] | 184 | #[cfg(test)] |
@@ -174,6 +210,7 @@ mod tests { | |||
174 | Runnable { | 210 | Runnable { |
175 | range: 1..21, | 211 | range: 1..21, |
176 | kind: Bin, | 212 | kind: Bin, |
213 | features_needed: None, | ||
177 | }, | 214 | }, |
178 | Runnable { | 215 | Runnable { |
179 | range: 22..46, | 216 | range: 22..46, |
@@ -185,6 +222,7 @@ mod tests { | |||
185 | ignore: false, | 222 | ignore: false, |
186 | }, | 223 | }, |
187 | }, | 224 | }, |
225 | features_needed: None, | ||
188 | }, | 226 | }, |
189 | Runnable { | 227 | Runnable { |
190 | range: 47..81, | 228 | range: 47..81, |
@@ -196,6 +234,7 @@ mod tests { | |||
196 | ignore: true, | 234 | ignore: true, |
197 | }, | 235 | }, |
198 | }, | 236 | }, |
237 | features_needed: None, | ||
199 | }, | 238 | }, |
200 | ] | 239 | ] |
201 | "### | 240 | "### |
@@ -223,6 +262,7 @@ mod tests { | |||
223 | Runnable { | 262 | Runnable { |
224 | range: 1..21, | 263 | range: 1..21, |
225 | kind: Bin, | 264 | kind: Bin, |
265 | features_needed: None, | ||
226 | }, | 266 | }, |
227 | Runnable { | 267 | Runnable { |
228 | range: 22..64, | 268 | range: 22..64, |
@@ -231,6 +271,7 @@ mod tests { | |||
231 | "foo", | 271 | "foo", |
232 | ), | 272 | ), |
233 | }, | 273 | }, |
274 | features_needed: None, | ||
234 | }, | 275 | }, |
235 | ] | 276 | ] |
236 | "### | 277 | "### |
@@ -258,6 +299,7 @@ mod tests { | |||
258 | kind: TestMod { | 299 | kind: TestMod { |
259 | path: "test_mod", | 300 | path: "test_mod", |
260 | }, | 301 | }, |
302 | features_needed: None, | ||
261 | }, | 303 | }, |
262 | Runnable { | 304 | Runnable { |
263 | range: 28..57, | 305 | range: 28..57, |
@@ -269,6 +311,7 @@ mod tests { | |||
269 | ignore: false, | 311 | ignore: false, |
270 | }, | 312 | }, |
271 | }, | 313 | }, |
314 | features_needed: None, | ||
272 | }, | 315 | }, |
273 | ] | 316 | ] |
274 | "### | 317 | "### |
@@ -298,6 +341,7 @@ mod tests { | |||
298 | kind: TestMod { | 341 | kind: TestMod { |
299 | path: "foo::test_mod", | 342 | path: "foo::test_mod", |
300 | }, | 343 | }, |
344 | features_needed: None, | ||
301 | }, | 345 | }, |
302 | Runnable { | 346 | Runnable { |
303 | range: 46..79, | 347 | range: 46..79, |
@@ -309,6 +353,7 @@ mod tests { | |||
309 | ignore: false, | 353 | ignore: false, |
310 | }, | 354 | }, |
311 | }, | 355 | }, |
356 | features_needed: None, | ||
312 | }, | 357 | }, |
313 | ] | 358 | ] |
314 | "### | 359 | "### |
@@ -340,6 +385,7 @@ mod tests { | |||
340 | kind: TestMod { | 385 | kind: TestMod { |
341 | path: "foo::bar::test_mod", | 386 | path: "foo::bar::test_mod", |
342 | }, | 387 | }, |
388 | features_needed: None, | ||
343 | }, | 389 | }, |
344 | Runnable { | 390 | Runnable { |
345 | range: 68..105, | 391 | range: 68..105, |
@@ -351,6 +397,80 @@ mod tests { | |||
351 | ignore: false, | 397 | ignore: false, |
352 | }, | 398 | }, |
353 | }, | 399 | }, |
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 | ), | ||
354 | }, | 474 | }, |
355 | ] | 475 | ] |
356 | "### | 476 | "### |
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}; | |||
4 | use ra_project_model::{self, ProjectWorkspace, TargetKind}; | 4 | use ra_project_model::{self, ProjectWorkspace, TargetKind}; |
5 | 5 | ||
6 | use crate::{world::WorldSnapshot, Result}; | 6 | use crate::{world::WorldSnapshot, Result}; |
7 | use 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 6b14830b6..0232cc6f0 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -1012,7 +1012,8 @@ fn to_lsp_runnable( | |||
1012 | ) -> Result<lsp_ext::Runnable> { | 1012 | ) -> Result<lsp_ext::Runnable> { |
1013 | let spec = CargoTargetSpec::for_file(world, file_id)?; | 1013 | let spec = CargoTargetSpec::for_file(world, file_id)?; |
1014 | let target = spec.as_ref().map(|s| s.target.clone()); | 1014 | let target = spec.as_ref().map(|s| s.target.clone()); |
1015 | let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; | 1015 | let (args, extra_args) = |
1016 | CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?; | ||
1016 | let line_index = world.analysis().file_line_index(file_id)?; | 1017 | let line_index = world.analysis().file_line_index(file_id)?; |
1017 | let label = match &runnable.kind { | 1018 | let label = match &runnable.kind { |
1018 | RunnableKind::Test { test_id, .. } => format!("test {}", test_id), | 1019 | RunnableKind::Test { test_id, .. } => format!("test {}", test_id), |