From c6143742bd4e625d391ac3ea860be7578ab9f53f Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Thu, 21 May 2020 10:48:42 +0200 Subject: [PATCH 1/4] add support of feature flag for runnables #4464 Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/ra_cfg/src/cfg_expr.rs | 68 +++++++- crates/ra_hir/src/lib.rs | 1 + crates/ra_hir_def/src/attr.rs | 2 +- crates/ra_hir_def/src/body.rs | 2 +- crates/ra_ide/src/runnables.rs | 146 ++++++++++++++++-- crates/rust-analyzer/src/cargo_target_spec.rs | 9 ++ .../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 39d71851ca7..a4b201e0e01 100644 --- a/crates/ra_cfg/src/cfg_expr.rs +++ b/crates/ra_cfg/src/cfg_expr.rs @@ -33,6 +33,36 @@ pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option CfgExpr::Not(pred) => pred.fold(query).map(|s| !s), } } + + /// Return minimal features needed + pub fn minimal_features_needed(&self) -> Option> { + let mut features = vec![]; + self.collect_minimal_features_needed(&mut features); + if features.is_empty() { + None + } else { + Some(features) + } + } + + fn collect_minimal_features_needed(&self, features: &mut Vec) { + match self { + CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()), + CfgExpr::All(preds) => { + preds.iter().for_each(|cfg| cfg.collect_minimal_features_needed(features)); + } + CfgExpr::Any(preds) => { + for cfg in preds { + let len_features = features.len(); + cfg.collect_minimal_features_needed(features); + if len_features != features.len() { + break; + } + } + } + _ => {} + } + } } pub fn parse_cfg(tt: &Subtree) -> CfgExpr { @@ -88,13 +118,17 @@ fn next_cfg_expr(it: &mut SliceIter) -> Option { mod tests { use super::*; - use mbe::ast_to_token_tree; + use mbe::{ast_to_token_tree, TokenMap}; use ra_syntax::ast::{self, AstNode}; - fn assert_parse_result(input: &str, expected: CfgExpr) { + fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let (tt, _) = ast_to_token_tree(&tt).unwrap(); + ast_to_token_tree(&tt).unwrap() + } + + fn assert_parse_result(input: &str, expected: CfgExpr) { + let (tt, _) = get_token_tree_generated(input); assert_eq!(parse_cfg(&tt), expected); } @@ -129,4 +163,32 @@ fn test_cfg_expr_parser() { ]), ); } + + #[test] + fn test_cfg_expr_minimal_features_needed() { + let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); + let cfg_expr = parse_cfg(&subtree); + + assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); + let cfg_expr = parse_cfg(&subtree); + + assert_eq!( + cfg_expr.minimal_features_needed().unwrap(), + vec![SmolStr::new("baz"), SmolStr::new("foo")] + ); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); + let cfg_expr = parse_cfg(&subtree); + + assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]); + + let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); + let cfg_expr = parse_cfg(&subtree); + + assert!(cfg_expr.minimal_features_needed().is_none()); + } } diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index c5df4ac24da..3364a822f43 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -62,6 +62,7 @@ fn from(it: $sv) -> $e { pub use hir_def::{ adt::StructKind, + attr::Attrs, body::scope::ExprScopes, builtin_type::BuiltinType, docs::Documentation, diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 576cd0c65ba..8b6c0bedee7 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -81,7 +81,7 @@ pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { } } - fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs { + pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs { let hygiene = Hygiene::new(db.upcast(), owner.file_id); Attrs::new(owner.value, &hygiene) } diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index f5a7305dc09..273036cee1c 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -29,7 +29,7 @@ AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, }; -/// A subset of Exander that only deals with cfg attributes. We only need it to +/// A subset of Expander that only deals with cfg attributes. We only need it to /// avoid cyclic queries in crate def map during enum processing. pub(crate) struct CfgExpander { cfg_options: CfgOptions, diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index fa8a9d92c99..4f7eb2c5b23 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -1,11 +1,11 @@ //! FIXME: write short doc here -use hir::Semantics; +use hir::{Attrs, HirFileId, InFile, Semantics}; use itertools::Itertools; use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - match_ast, SyntaxNode, TextRange, + match_ast, SmolStr, SyntaxNode, TextRange, }; use crate::FileId; @@ -16,6 +16,7 @@ pub struct Runnable { pub range: TextRange, pub kind: RunnableKind, + pub features_needed: Option>, } #[derive(Debug)] @@ -45,20 +46,24 @@ pub enum RunnableKind { pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { let sema = Semantics::new(db); let source_file = sema.parse(file_id); - source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() + source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() } -fn runnable(sema: &Semantics, item: SyntaxNode) -> Option { +fn runnable(sema: &Semantics, item: SyntaxNode, file_id: FileId) -> Option { match_ast! { match item { - ast::FnDef(it) => runnable_fn(sema, it), - ast::Module(it) => runnable_mod(sema, it), + ast::FnDef(it) => runnable_fn(sema, it, file_id), + ast::Module(it) => runnable_mod(sema, it, file_id), _ => None, } } } -fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option { +fn runnable_fn( + sema: &Semantics, + fn_def: ast::FnDef, + file_id: FileId, +) -> Option { let name_string = fn_def.name()?.text().to_string(); let kind = if name_string == "main" { @@ -89,7 +94,11 @@ fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option bool { fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) } -fn runnable_mod(sema: &Semantics, module: ast::Module) -> Option { +fn runnable_mod( + sema: &Semantics, + module: ast::Module, + file_id: FileId, +) -> Option { let has_test_function = module .item_list()? .items() @@ -138,11 +151,34 @@ fn runnable_mod(sema: &Semantics, module: ast::Module) -> Option Option> { + let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)); + let features_needed = cfg_expr.fold(vec![], |mut acc, cfg| { + if let Some(features_needed) = cfg.minimal_features_needed() { + acc.extend(features_needed); + } + acc + }); + if features_needed.is_empty() { + None + } else { + Some(features_needed) + } } #[cfg(test)] @@ -174,6 +210,7 @@ fn test_foo() {} Runnable { range: 1..21, kind: Bin, + features_needed: None, }, Runnable { range: 22..46, @@ -185,6 +222,7 @@ fn test_foo() {} ignore: false, }, }, + features_needed: None, }, Runnable { range: 47..81, @@ -196,6 +234,7 @@ fn test_foo() {} ignore: true, }, }, + features_needed: None, }, ] "### @@ -223,6 +262,7 @@ fn foo() {} Runnable { range: 1..21, kind: Bin, + features_needed: None, }, Runnable { range: 22..64, @@ -231,6 +271,7 @@ fn foo() {} "foo", ), }, + features_needed: None, }, ] "### @@ -258,6 +299,7 @@ fn test_foo1() {} kind: TestMod { path: "test_mod", }, + features_needed: None, }, Runnable { range: 28..57, @@ -269,6 +311,7 @@ fn test_foo1() {} ignore: false, }, }, + features_needed: None, }, ] "### @@ -298,6 +341,7 @@ fn test_foo1() {} kind: TestMod { path: "foo::test_mod", }, + features_needed: None, }, Runnable { range: 46..79, @@ -309,6 +353,7 @@ fn test_foo1() {} ignore: false, }, }, + features_needed: None, }, ] "### @@ -340,6 +385,7 @@ fn test_foo1() {} kind: TestMod { path: "foo::bar::test_mod", }, + features_needed: None, }, Runnable { range: 68..105, @@ -351,6 +397,80 @@ fn test_foo1() {} ignore: false, }, }, + features_needed: None, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_with_feature() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs crate:foo cfg:feature=foo + <|> //empty + #[test] + #[cfg(feature = "foo")] + fn test_foo1() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: 1..58, + kind: Test { + test_id: Name( + "test_foo1", + ), + attr: TestAttr { + ignore: false, + }, + }, + features_needed: Some( + [ + "foo", + ], + ), + }, + ] + "### + ); + } + + #[test] + fn test_runnables_with_features() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs crate:foo cfg:feature=foo,feature=bar + <|> //empty + #[test] + #[cfg(all(feature = "foo", feature = "bar"))] + fn test_foo1() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: 1..80, + kind: Test { + test_id: Name( + "test_foo1", + ), + attr: TestAttr { + ignore: false, + }, + }, + features_needed: Some( + [ + "foo", + "bar", + ], + ), }, ] "### diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 5e5a17943d2..a2f85060ba3 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_project_model::{self, ProjectWorkspace, TargetKind}; use crate::{world::WorldSnapshot, Result}; +use ra_syntax::SmolStr; /// Abstract representation of Cargo target. /// @@ -20,6 +21,7 @@ impl CargoTargetSpec { pub(crate) fn runnable_args( spec: Option, kind: &RunnableKind, + features_needed: &Option>, ) -> Result<(Vec, Vec)> { let mut args = Vec::new(); let mut extra_args = Vec::new(); @@ -73,6 +75,13 @@ pub(crate) fn runnable_args( } } } + + if let Some(features_needed) = features_needed { + features_needed.iter().for_each(|feature| { + args.push("--features".to_string()); + args.push(feature.to_string()); + }); + } Ok((args, extra_args)) } diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 6b14830b6df..0232cc6f0cc 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( ) -> Result { let spec = CargoTargetSpec::for_file(world, file_id)?; let target = spec.as_ref().map(|s| s.target.clone()); - let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; + let (args, extra_args) = + CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?; let line_index = world.analysis().file_line_index(file_id)?; let label = match &runnable.kind { RunnableKind::Test { test_id, .. } => format!("test {}", test_id), From 43339058e32e8bb0d218390b9df5b5a68fe57ca7 Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Fri, 22 May 2020 09:23:31 +0200 Subject: [PATCH 2/4] add support of feature flag for runnables #4464 Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/ra_cfg/src/cfg_expr.rs | 17 +++++++---------- crates/ra_ide/src/runnables.rs | 13 ++----------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs index a4b201e0e01..98f44f56d03 100644 --- a/crates/ra_cfg/src/cfg_expr.rs +++ b/crates/ra_cfg/src/cfg_expr.rs @@ -35,14 +35,11 @@ pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option } /// Return minimal features needed - pub fn minimal_features_needed(&self) -> Option> { + pub fn minimal_features_needed(&self) -> Vec { let mut features = vec![]; self.collect_minimal_features_needed(&mut features); - if features.is_empty() { - None - } else { - Some(features) - } + + features } fn collect_minimal_features_needed(&self, features: &mut Vec) { @@ -169,14 +166,14 @@ fn test_cfg_expr_minimal_features_needed() { let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); let cfg_expr = parse_cfg(&subtree); - assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]); + assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]); let (subtree, _) = get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); let cfg_expr = parse_cfg(&subtree); assert_eq!( - cfg_expr.minimal_features_needed().unwrap(), + cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz"), SmolStr::new("foo")] ); @@ -184,11 +181,11 @@ fn test_cfg_expr_minimal_features_needed() { get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); let cfg_expr = parse_cfg(&subtree); - assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]); + assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]); let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); let cfg_expr = parse_cfg(&subtree); - assert!(cfg_expr.minimal_features_needed().is_none()); + assert!(cfg_expr.minimal_features_needed().is_empty()); } } diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 3a3d0b0ac12..a460370c5c3 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -190,17 +190,8 @@ fn runnable_mod( fn get_features_needed(attrs: Attrs) -> Option> { let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)); - let features_needed = cfg_expr.fold(vec![], |mut acc, cfg| { - if let Some(features_needed) = cfg.minimal_features_needed() { - acc.extend(features_needed); - } - acc - }); - if features_needed.is_empty() { - None - } else { - Some(features_needed) - } + let features_needed = cfg_expr.map(|cfg| cfg.minimal_features_needed()).flatten().collect(); + Some(features_needed).filter(|it: &Vec| !it.is_empty()) } #[cfg(test)] From 48d7c61e26398fa33b94e0e4bd0d2d1697ed4921 Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Sat, 23 May 2020 20:59:18 +0200 Subject: [PATCH 3/4] add support of feature flag for runnables #4464 Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- Cargo.lock | 3 + crates/ra_cfg/src/cfg_expr.rs | 55 ----------- crates/ra_ide/src/runnables.rs | 76 ++++++++------- crates/rust-analyzer/Cargo.toml | 3 + crates/rust-analyzer/src/cargo_target_spec.rs | 13 ++- .../rust-analyzer/src/main_loop/handlers.rs | 94 ++++++++++++++++++- 6 files changed, 145 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c062366923d..b406522d240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1387,17 +1387,20 @@ dependencies = [ "lsp-types", "parking_lot", "pico-args", + "ra_cfg", "ra_db", "ra_flycheck", "ra_hir", "ra_hir_def", "ra_hir_ty", "ra_ide", + "ra_mbe", "ra_proc_macro_srv", "ra_prof", "ra_project_model", "ra_syntax", "ra_text_edit", + "ra_tt", "ra_vfs", "rand", "relative-path", diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs index 98f44f56d03..85b100c6adf 100644 --- a/crates/ra_cfg/src/cfg_expr.rs +++ b/crates/ra_cfg/src/cfg_expr.rs @@ -33,33 +33,6 @@ pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option CfgExpr::Not(pred) => pred.fold(query).map(|s| !s), } } - - /// Return minimal features needed - pub fn minimal_features_needed(&self) -> Vec { - let mut features = vec![]; - self.collect_minimal_features_needed(&mut features); - - features - } - - fn collect_minimal_features_needed(&self, features: &mut Vec) { - match self { - CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()), - CfgExpr::All(preds) => { - preds.iter().for_each(|cfg| cfg.collect_minimal_features_needed(features)); - } - CfgExpr::Any(preds) => { - for cfg in preds { - let len_features = features.len(); - cfg.collect_minimal_features_needed(features); - if len_features != features.len() { - break; - } - } - } - _ => {} - } - } } pub fn parse_cfg(tt: &Subtree) -> CfgExpr { @@ -160,32 +133,4 @@ fn test_cfg_expr_parser() { ]), ); } - - #[test] - fn test_cfg_expr_minimal_features_needed() { - let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); - let cfg_expr = parse_cfg(&subtree); - - assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]); - - let (subtree, _) = - get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); - let cfg_expr = parse_cfg(&subtree); - - assert_eq!( - cfg_expr.minimal_features_needed(), - vec![SmolStr::new("baz"), SmolStr::new("foo")] - ); - - let (subtree, _) = - get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); - let cfg_expr = parse_cfg(&subtree); - - assert_eq!(cfg_expr.minimal_features_needed(), vec![SmolStr::new("baz")]); - - let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); - let cfg_expr = parse_cfg(&subtree); - - assert!(cfg_expr.minimal_features_needed().is_empty()); - } } diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index a460370c5c3..a96c5f157ba 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -10,13 +10,14 @@ use crate::FileId; use ast::DocCommentsOwner; +use ra_cfg::CfgExpr; use std::fmt::Display; #[derive(Debug)] pub struct Runnable { pub range: TextRange, pub kind: RunnableKind, - pub features_needed: Option>, + pub cfg_exprs: Vec, } #[derive(Debug)] @@ -118,9 +119,10 @@ fn runnable_fn( }; let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); - let features_needed = get_features_needed(attrs); + let cfg_exprs = + attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); - Some(Runnable { range: fn_def.syntax().text_range(), kind, features_needed }) + Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs }) } #[derive(Debug)] @@ -183,15 +185,10 @@ fn runnable_mod( .join("::"); let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); - let features_needed = get_features_needed(attrs); + let cfg_exprs = + attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); - Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed }) -} - -fn get_features_needed(attrs: Attrs) -> Option> { - let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)); - let features_needed = cfg_expr.map(|cfg| cfg.minimal_features_needed()).flatten().collect(); - Some(features_needed).filter(|it: &Vec| !it.is_empty()) + Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs }) } #[cfg(test)] @@ -223,7 +220,7 @@ fn test_foo() {} Runnable { range: 1..21, kind: Bin, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 22..46, @@ -235,7 +232,7 @@ fn test_foo() {} ignore: false, }, }, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 47..81, @@ -247,7 +244,7 @@ fn test_foo() {} ignore: true, }, }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -275,7 +272,7 @@ fn foo() {} Runnable { range: 1..21, kind: Bin, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 22..64, @@ -284,7 +281,7 @@ fn foo() {} "foo", ), }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -315,7 +312,7 @@ fn foo() {} Runnable { range: 1..21, kind: Bin, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 51..105, @@ -324,7 +321,7 @@ fn foo() {} "Data::foo", ), }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -352,7 +349,7 @@ fn test_foo1() {} kind: TestMod { path: "test_mod", }, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 28..57, @@ -364,7 +361,7 @@ fn test_foo1() {} ignore: false, }, }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -394,7 +391,7 @@ fn test_foo1() {} kind: TestMod { path: "foo::test_mod", }, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 46..79, @@ -406,7 +403,7 @@ fn test_foo1() {} ignore: false, }, }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -438,7 +435,7 @@ fn test_foo1() {} kind: TestMod { path: "foo::bar::test_mod", }, - features_needed: None, + cfg_exprs: [], }, Runnable { range: 68..105, @@ -450,7 +447,7 @@ fn test_foo1() {} ignore: false, }, }, - features_needed: None, + cfg_exprs: [], }, ] "### @@ -482,11 +479,12 @@ fn test_foo1() {} ignore: false, }, }, - features_needed: Some( - [ - "foo", - ], - ), + cfg_exprs: [ + KeyValue { + key: "feature", + value: "foo", + }, + ], }, ] "### @@ -518,12 +516,20 @@ fn test_foo1() {} ignore: false, }, }, - features_needed: Some( - [ - "foo", - "bar", - ], - ), + cfg_exprs: [ + All( + [ + KeyValue { + key: "feature", + value: "foo", + }, + KeyValue { + key: "feature", + value: "bar", + }, + ], + ), + ], }, ] "### diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 9b2d29b1d57..65b487db3b9 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -40,6 +40,7 @@ ra_project_model = { path = "../ra_project_model" } ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } ra_vfs = "0.6.0" +ra_cfg = { path = "../ra_cfg"} # This should only be used in CLI ra_db = { path = "../ra_db" } @@ -55,6 +56,8 @@ winapi = "0.3.8" tempfile = "3.1.0" insta = "0.16.0" test_utils = { path = "../test_utils" } +mbe = { path = "../ra_mbe", package = "ra_mbe" } +tt = { path = "../ra_tt", package = "ra_tt" } [features] jemalloc = [ "ra_prof/jemalloc" ] diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index a2f85060ba3..441fb61df00 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 { pub(crate) fn runnable_args( spec: Option, kind: &RunnableKind, - features_needed: &Option>, + features_needed: &Vec, ) -> Result<(Vec, Vec)> { let mut args = Vec::new(); let mut extra_args = Vec::new(); @@ -76,12 +76,11 @@ pub(crate) fn runnable_args( } } - if let Some(features_needed) = features_needed { - features_needed.iter().for_each(|feature| { - args.push("--features".to_string()); - args.push(feature.to_string()); - }); - } + features_needed.iter().for_each(|feature| { + args.push("--features".to_string()); + args.push(feature.to_string()); + }); + Ok((args, extra_args)) } diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index cc9abd162bb..5f6e845a887 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -17,12 +17,13 @@ SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit, }; +use ra_cfg::CfgExpr; use ra_ide::{ Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, }; use ra_prof::profile; use ra_project_model::TargetKind; -use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize}; +use ra_syntax::{AstNode, SmolStr, SyntaxKind, TextRange, TextSize}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use serde_json::to_value; @@ -38,6 +39,7 @@ world::WorldSnapshot, LspError, Result, }; +use hir::Attrs; pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); @@ -1006,8 +1008,12 @@ fn to_lsp_runnable( ) -> Result { let spec = CargoTargetSpec::for_file(world, file_id)?; let target = spec.as_ref().map(|s| s.target.clone()); + let mut features_needed = vec![]; + for cfg_expr in &runnable.cfg_exprs { + collect_minimal_features_needed(cfg_expr, &mut features_needed); + } let (args, extra_args) = - CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?; + CargoTargetSpec::runnable_args(spec, &runnable.kind, &features_needed)?; let line_index = world.analysis().file_line_index(file_id)?; let label = match &runnable.kind { RunnableKind::Test { test_id, .. } => format!("test {}", test_id), @@ -1033,6 +1039,39 @@ fn to_lsp_runnable( }) } +fn get_features_needed(attrs: Attrs) -> Option> { + let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)); + let features_needed = cfg_expr + .map(|cfg| { + let mut min_features = vec![]; + collect_minimal_features_needed(&cfg, &mut min_features); + min_features + }) + .flatten() + .collect(); + Some(features_needed).filter(|it: &Vec| !it.is_empty()) +} + +/// Fill minimal features needed +fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec) { + match cfg_expr { + CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()), + CfgExpr::All(preds) => { + preds.iter().for_each(|cfg| collect_minimal_features_needed(cfg, features)); + } + CfgExpr::Any(preds) => { + for cfg in preds { + let len_features = features.len(); + collect_minimal_features_needed(cfg, features); + if len_features != features.len() { + break; + } + } + } + _ => {} + } +} + pub fn handle_inlay_hints( world: WorldSnapshot, params: InlayHintsParams, @@ -1169,3 +1208,54 @@ pub fn handle_semantic_tokens_range( let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } + +#[cfg(test)] +mod tests { + use super::*; + + use mbe::{ast_to_token_tree, TokenMap}; + use ra_cfg::parse_cfg; + use ra_syntax::{ + ast::{self, AstNode}, + SmolStr, + }; + + fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { + let source_file = ast::SourceFile::parse(input).ok().unwrap(); + let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + ast_to_token_tree(&tt).unwrap() + } + + #[test] + fn test_cfg_expr_minimal_features_needed() { + let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); + let cfg_expr = parse_cfg(&subtree); + let mut min_features = vec![]; + collect_minimal_features_needed(&cfg_expr, &mut min_features); + + assert_eq!(min_features, vec![SmolStr::new("baz")]); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + collect_minimal_features_needed(&cfg_expr, &mut min_features); + assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + collect_minimal_features_needed(&cfg_expr, &mut min_features); + assert_eq!(min_features, vec![SmolStr::new("baz")]); + + let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + collect_minimal_features_needed(&cfg_expr, &mut min_features); + assert!(min_features.is_empty()); + } +} From 27ed376bc4dfed39295af650effe63007e443b6f Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Sun, 24 May 2020 13:34:34 +0200 Subject: [PATCH 4/4] add support of feature flag for runnables #4464 Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/ra_ide/src/runnables.rs | 2 +- crates/rust-analyzer/src/main_loop/handlers.rs | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index a96c5f157ba..ed98e58e00c 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - match_ast, SmolStr, SyntaxNode, TextRange, + match_ast, SyntaxNode, TextRange, }; use crate::FileId; diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 5f6e845a887..c51e4346aa8 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -39,7 +39,6 @@ world::WorldSnapshot, LspError, Result, }; -use hir::Attrs; pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); @@ -1039,19 +1038,6 @@ fn to_lsp_runnable( }) } -fn get_features_needed(attrs: Attrs) -> Option> { - let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)); - let features_needed = cfg_expr - .map(|cfg| { - let mut min_features = vec![]; - collect_minimal_features_needed(&cfg, &mut min_features); - min_features - }) - .flatten() - .collect(); - Some(features_needed).filter(|it: &Vec| !it.is_empty()) -} - /// Fill minimal features needed fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec) { match cfg_expr {