aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2020-05-23 19:59:18 +0100
committerBenjamin Coenen <[email protected]>2020-05-23 19:59:18 +0100
commit48d7c61e26398fa33b94e0e4bd0d2d1697ed4921 (patch)
tree7d22388871a7f4cb261cf37148c31fea91772d63
parent43339058e32e8bb0d218390b9df5b5a68fe57ca7 (diff)
add support of feature flag for runnables #4464
Signed-off-by: Benjamin Coenen <[email protected]>
-rw-r--r--Cargo.lock3
-rw-r--r--crates/ra_cfg/src/cfg_expr.rs55
-rw-r--r--crates/ra_ide/src/runnables.rs76
-rw-r--r--crates/rust-analyzer/Cargo.toml3
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs13
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs94
6 files changed, 145 insertions, 99 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c06236692..b406522d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1387,17 +1387,20 @@ dependencies = [
1387 "lsp-types", 1387 "lsp-types",
1388 "parking_lot", 1388 "parking_lot",
1389 "pico-args", 1389 "pico-args",
1390 "ra_cfg",
1390 "ra_db", 1391 "ra_db",
1391 "ra_flycheck", 1392 "ra_flycheck",
1392 "ra_hir", 1393 "ra_hir",
1393 "ra_hir_def", 1394 "ra_hir_def",
1394 "ra_hir_ty", 1395 "ra_hir_ty",
1395 "ra_ide", 1396 "ra_ide",
1397 "ra_mbe",
1396 "ra_proc_macro_srv", 1398 "ra_proc_macro_srv",
1397 "ra_prof", 1399 "ra_prof",
1398 "ra_project_model", 1400 "ra_project_model",
1399 "ra_syntax", 1401 "ra_syntax",
1400 "ra_text_edit", 1402 "ra_text_edit",
1403 "ra_tt",
1401 "ra_vfs", 1404 "ra_vfs",
1402 "rand", 1405 "rand",
1403 "relative-path", 1406 "relative-path",
diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
index 98f44f56d..85b100c6a 100644
--- a/crates/ra_cfg/src/cfg_expr.rs
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -33,33 +33,6 @@ 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) -> Vec<SmolStr> {
39 let mut features = vec![];
40 self.collect_minimal_features_needed(&mut features);
41
42 features
43 }
44
45 fn collect_minimal_features_needed(&self, features: &mut Vec<SmolStr>) {
46 match self {
47 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
48 CfgExpr::All(preds) => {
49 preds.iter().for_each(|cfg| cfg.collect_minimal_features_needed(features));
50 }
51 CfgExpr::Any(preds) => {
52 for cfg in preds {
53 let len_features = features.len();
54 cfg.collect_minimal_features_needed(features);
55 if len_features != features.len() {
56 break;
57 }
58 }
59 }
60 _ => {}
61 }
62 }
63} 36}
64 37
65pub fn parse_cfg(tt: &Subtree) -> CfgExpr { 38pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
@@ -160,32 +133,4 @@ mod tests {
160 ]), 133 ]),
161 ); 134 );
162 } 135 }
163
164 #[test]
165 fn test_cfg_expr_minimal_features_needed() {
166 let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
167 let cfg_expr = parse_cfg(&subtree);
168
169 assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]);
170
171 let (subtree, _) =
172 get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
173 let cfg_expr = parse_cfg(&subtree);
174
175 assert_eq!(
176 cfg_expr.minimal_features_needed(),
177 vec![SmolStr::new("baz"), SmolStr::new("foo")]
178 );
179
180 let (subtree, _) =
181 get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
182 let cfg_expr = parse_cfg(&subtree);
183
184 assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]);
185
186 let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
187 let cfg_expr = parse_cfg(&subtree);
188
189 assert!(cfg_expr.minimal_features_needed().is_empty());
190 }
191} 136}
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index a460370c5..a96c5f157 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -10,13 +10,14 @@ use ra_syntax::{
10 10
11use crate::FileId; 11use crate::FileId;
12use ast::DocCommentsOwner; 12use ast::DocCommentsOwner;
13use ra_cfg::CfgExpr;
13use std::fmt::Display; 14use std::fmt::Display;
14 15
15#[derive(Debug)] 16#[derive(Debug)]
16pub struct Runnable { 17pub struct Runnable {
17 pub range: TextRange, 18 pub range: TextRange,
18 pub kind: RunnableKind, 19 pub kind: RunnableKind,
19 pub features_needed: Option<Vec<SmolStr>>, 20 pub cfg_exprs: Vec<CfgExpr>,
20} 21}
21 22
22#[derive(Debug)] 23#[derive(Debug)]
@@ -118,9 +119,10 @@ fn runnable_fn(
118 }; 119 };
119 120
120 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); 121 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 let cfg_exprs =
123 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
122 124
123 Some(Runnable { range: fn_def.syntax().text_range(), kind, features_needed }) 125 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs })
124} 126}
125 127
126#[derive(Debug)] 128#[derive(Debug)]
@@ -183,15 +185,10 @@ fn runnable_mod(
183 .join("::"); 185 .join("::");
184 186
185 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); 187 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
186 let features_needed = get_features_needed(attrs); 188 let cfg_exprs =
189 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
187 190
188 Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed }) 191 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
189}
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.map(|cfg| cfg.minimal_features_needed()).flatten().collect();
194 Some(features_needed).filter(|it: &Vec<SmolStr>| !it.is_empty())
195} 192}
196 193
197#[cfg(test)] 194#[cfg(test)]
@@ -223,7 +220,7 @@ mod tests {
223 Runnable { 220 Runnable {
224 range: 1..21, 221 range: 1..21,
225 kind: Bin, 222 kind: Bin,
226 features_needed: None, 223 cfg_exprs: [],
227 }, 224 },
228 Runnable { 225 Runnable {
229 range: 22..46, 226 range: 22..46,
@@ -235,7 +232,7 @@ mod tests {
235 ignore: false, 232 ignore: false,
236 }, 233 },
237 }, 234 },
238 features_needed: None, 235 cfg_exprs: [],
239 }, 236 },
240 Runnable { 237 Runnable {
241 range: 47..81, 238 range: 47..81,
@@ -247,7 +244,7 @@ mod tests {
247 ignore: true, 244 ignore: true,
248 }, 245 },
249 }, 246 },
250 features_needed: None, 247 cfg_exprs: [],
251 }, 248 },
252 ] 249 ]
253 "### 250 "###
@@ -275,7 +272,7 @@ mod tests {
275 Runnable { 272 Runnable {
276 range: 1..21, 273 range: 1..21,
277 kind: Bin, 274 kind: Bin,
278 features_needed: None, 275 cfg_exprs: [],
279 }, 276 },
280 Runnable { 277 Runnable {
281 range: 22..64, 278 range: 22..64,
@@ -284,7 +281,7 @@ mod tests {
284 "foo", 281 "foo",
285 ), 282 ),
286 }, 283 },
287 features_needed: None, 284 cfg_exprs: [],
288 }, 285 },
289 ] 286 ]
290 "### 287 "###
@@ -315,7 +312,7 @@ mod tests {
315 Runnable { 312 Runnable {
316 range: 1..21, 313 range: 1..21,
317 kind: Bin, 314 kind: Bin,
318 features_needed: None, 315 cfg_exprs: [],
319 }, 316 },
320 Runnable { 317 Runnable {
321 range: 51..105, 318 range: 51..105,
@@ -324,7 +321,7 @@ mod tests {
324 "Data::foo", 321 "Data::foo",
325 ), 322 ),
326 }, 323 },
327 features_needed: None, 324 cfg_exprs: [],
328 }, 325 },
329 ] 326 ]
330 "### 327 "###
@@ -352,7 +349,7 @@ mod tests {
352 kind: TestMod { 349 kind: TestMod {
353 path: "test_mod", 350 path: "test_mod",
354 }, 351 },
355 features_needed: None, 352 cfg_exprs: [],
356 }, 353 },
357 Runnable { 354 Runnable {
358 range: 28..57, 355 range: 28..57,
@@ -364,7 +361,7 @@ mod tests {
364 ignore: false, 361 ignore: false,
365 }, 362 },
366 }, 363 },
367 features_needed: None, 364 cfg_exprs: [],
368 }, 365 },
369 ] 366 ]
370 "### 367 "###
@@ -394,7 +391,7 @@ mod tests {
394 kind: TestMod { 391 kind: TestMod {
395 path: "foo::test_mod", 392 path: "foo::test_mod",
396 }, 393 },
397 features_needed: None, 394 cfg_exprs: [],
398 }, 395 },
399 Runnable { 396 Runnable {
400 range: 46..79, 397 range: 46..79,
@@ -406,7 +403,7 @@ mod tests {
406 ignore: false, 403 ignore: false,
407 }, 404 },
408 }, 405 },
409 features_needed: None, 406 cfg_exprs: [],
410 }, 407 },
411 ] 408 ]
412 "### 409 "###
@@ -438,7 +435,7 @@ mod tests {
438 kind: TestMod { 435 kind: TestMod {
439 path: "foo::bar::test_mod", 436 path: "foo::bar::test_mod",
440 }, 437 },
441 features_needed: None, 438 cfg_exprs: [],
442 }, 439 },
443 Runnable { 440 Runnable {
444 range: 68..105, 441 range: 68..105,
@@ -450,7 +447,7 @@ mod tests {
450 ignore: false, 447 ignore: false,
451 }, 448 },
452 }, 449 },
453 features_needed: None, 450 cfg_exprs: [],
454 }, 451 },
455 ] 452 ]
456 "### 453 "###
@@ -482,11 +479,12 @@ mod tests {
482 ignore: false, 479 ignore: false,
483 }, 480 },
484 }, 481 },
485 features_needed: Some( 482 cfg_exprs: [
486 [ 483 KeyValue {
487 "foo", 484 key: "feature",
488 ], 485 value: "foo",
489 ), 486 },
487 ],
490 }, 488 },
491 ] 489 ]
492 "### 490 "###
@@ -518,12 +516,20 @@ mod tests {
518 ignore: false, 516 ignore: false,
519 }, 517 },
520 }, 518 },
521 features_needed: Some( 519 cfg_exprs: [
522 [ 520 All(
523 "foo", 521 [
524 "bar", 522 KeyValue {
525 ], 523 key: "feature",
526 ), 524 value: "foo",
525 },
526 KeyValue {
527 key: "feature",
528 value: "bar",
529 },
530 ],
531 ),
532 ],
527 }, 533 },
528 ] 534 ]
529 "### 535 "###
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 9b2d29b1d..65b487db3 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -40,6 +40,7 @@ ra_project_model = { path = "../ra_project_model" }
40ra_syntax = { path = "../ra_syntax" } 40ra_syntax = { path = "../ra_syntax" }
41ra_text_edit = { path = "../ra_text_edit" } 41ra_text_edit = { path = "../ra_text_edit" }
42ra_vfs = "0.6.0" 42ra_vfs = "0.6.0"
43ra_cfg = { path = "../ra_cfg"}
43 44
44# This should only be used in CLI 45# This should only be used in CLI
45ra_db = { path = "../ra_db" } 46ra_db = { path = "../ra_db" }
@@ -55,6 +56,8 @@ winapi = "0.3.8"
55tempfile = "3.1.0" 56tempfile = "3.1.0"
56insta = "0.16.0" 57insta = "0.16.0"
57test_utils = { path = "../test_utils" } 58test_utils = { path = "../test_utils" }
59mbe = { path = "../ra_mbe", package = "ra_mbe" }
60tt = { path = "../ra_tt", package = "ra_tt" }
58 61
59[features] 62[features]
60jemalloc = [ "ra_prof/jemalloc" ] 63jemalloc = [ "ra_prof/jemalloc" ]
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index a2f85060b..441fb61df 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -21,7 +21,7 @@ impl CargoTargetSpec {
21 pub(crate) fn runnable_args( 21 pub(crate) fn runnable_args(
22 spec: Option<CargoTargetSpec>, 22 spec: Option<CargoTargetSpec>,
23 kind: &RunnableKind, 23 kind: &RunnableKind,
24 features_needed: &Option<Vec<SmolStr>>, 24 features_needed: &Vec<SmolStr>,
25 ) -> Result<(Vec<String>, Vec<String>)> { 25 ) -> Result<(Vec<String>, Vec<String>)> {
26 let mut args = Vec::new(); 26 let mut args = Vec::new();
27 let mut extra_args = Vec::new(); 27 let mut extra_args = Vec::new();
@@ -76,12 +76,11 @@ impl CargoTargetSpec {
76 } 76 }
77 } 77 }
78 78
79 if let Some(features_needed) = features_needed { 79 features_needed.iter().for_each(|feature| {
80 features_needed.iter().for_each(|feature| { 80 args.push("--features".to_string());
81 args.push("--features".to_string()); 81 args.push(feature.to_string());
82 args.push(feature.to_string()); 82 });
83 }); 83
84 }
85 Ok((args, extra_args)) 84 Ok((args, extra_args))
86 } 85 }
87 86
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index cc9abd162..5f6e845a8 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -17,12 +17,13 @@ use lsp_types::{
17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, 17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit, 18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit,
19}; 19};
20use ra_cfg::CfgExpr;
20use ra_ide::{ 21use ra_ide::{
21 Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, 22 Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
22}; 23};
23use ra_prof::profile; 24use ra_prof::profile;
24use ra_project_model::TargetKind; 25use ra_project_model::TargetKind;
25use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize}; 26use ra_syntax::{AstNode, SmolStr, SyntaxKind, TextRange, TextSize};
26use rustc_hash::FxHashMap; 27use rustc_hash::FxHashMap;
27use serde::{Deserialize, Serialize}; 28use serde::{Deserialize, Serialize};
28use serde_json::to_value; 29use serde_json::to_value;
@@ -38,6 +39,7 @@ use crate::{
38 world::WorldSnapshot, 39 world::WorldSnapshot,
39 LspError, Result, 40 LspError, Result,
40}; 41};
42use hir::Attrs;
41 43
42pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { 44pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
43 let _p = profile("handle_analyzer_status"); 45 let _p = profile("handle_analyzer_status");
@@ -1006,8 +1008,12 @@ fn to_lsp_runnable(
1006) -> Result<lsp_ext::Runnable> { 1008) -> Result<lsp_ext::Runnable> {
1007 let spec = CargoTargetSpec::for_file(world, file_id)?; 1009 let spec = CargoTargetSpec::for_file(world, file_id)?;
1008 let target = spec.as_ref().map(|s| s.target.clone()); 1010 let target = spec.as_ref().map(|s| s.target.clone());
1011 let mut features_needed = vec![];
1012 for cfg_expr in &runnable.cfg_exprs {
1013 collect_minimal_features_needed(cfg_expr, &mut features_needed);
1014 }
1009 let (args, extra_args) = 1015 let (args, extra_args) =
1010 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?; 1016 CargoTargetSpec::runnable_args(spec, &runnable.kind, &features_needed)?;
1011 let line_index = world.analysis().file_line_index(file_id)?; 1017 let line_index = world.analysis().file_line_index(file_id)?;
1012 let label = match &runnable.kind { 1018 let label = match &runnable.kind {
1013 RunnableKind::Test { test_id, .. } => format!("test {}", test_id), 1019 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
@@ -1033,6 +1039,39 @@ fn to_lsp_runnable(
1033 }) 1039 })
1034} 1040}
1035 1041
1042fn get_features_needed(attrs: Attrs) -> Option<Vec<SmolStr>> {
1043 let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree));
1044 let features_needed = cfg_expr
1045 .map(|cfg| {
1046 let mut min_features = vec![];
1047 collect_minimal_features_needed(&cfg, &mut min_features);
1048 min_features
1049 })
1050 .flatten()
1051 .collect();
1052 Some(features_needed).filter(|it: &Vec<SmolStr>| !it.is_empty())
1053}
1054
1055/// Fill minimal features needed
1056fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec<SmolStr>) {
1057 match cfg_expr {
1058 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
1059 CfgExpr::All(preds) => {
1060 preds.iter().for_each(|cfg| collect_minimal_features_needed(cfg, features));
1061 }
1062 CfgExpr::Any(preds) => {
1063 for cfg in preds {
1064 let len_features = features.len();
1065 collect_minimal_features_needed(cfg, features);
1066 if len_features != features.len() {
1067 break;
1068 }
1069 }
1070 }
1071 _ => {}
1072 }
1073}
1074
1036pub fn handle_inlay_hints( 1075pub fn handle_inlay_hints(
1037 world: WorldSnapshot, 1076 world: WorldSnapshot,
1038 params: InlayHintsParams, 1077 params: InlayHintsParams,
@@ -1169,3 +1208,54 @@ pub fn handle_semantic_tokens_range(
1169 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1208 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1170 Ok(Some(semantic_tokens.into())) 1209 Ok(Some(semantic_tokens.into()))
1171} 1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215
1216 use mbe::{ast_to_token_tree, TokenMap};
1217 use ra_cfg::parse_cfg;
1218 use ra_syntax::{
1219 ast::{self, AstNode},
1220 SmolStr,
1221 };
1222
1223 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
1224 let source_file = ast::SourceFile::parse(input).ok().unwrap();
1225 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
1226 ast_to_token_tree(&tt).unwrap()
1227 }
1228
1229 #[test]
1230 fn test_cfg_expr_minimal_features_needed() {
1231 let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
1232 let cfg_expr = parse_cfg(&subtree);
1233 let mut min_features = vec![];
1234 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1235
1236 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1237
1238 let (subtree, _) =
1239 get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
1240 let cfg_expr = parse_cfg(&subtree);
1241
1242 let mut min_features = vec![];
1243 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1244 assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]);
1245
1246 let (subtree, _) =
1247 get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
1248 let cfg_expr = parse_cfg(&subtree);
1249
1250 let mut min_features = vec![];
1251 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1252 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1253
1254 let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
1255 let cfg_expr = parse_cfg(&subtree);
1256
1257 let mut min_features = vec![];
1258 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1259 assert!(min_features.is_empty());
1260 }
1261}