add support of feature flag for runnables #4464

Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
Benjamin Coenen 2020-05-23 20:59:18 +02:00
parent 43339058e3
commit 48d7c61e26
6 changed files with 145 additions and 99 deletions

3
Cargo.lock generated
View file

@ -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",

View file

@ -33,33 +33,6 @@ impl CfgExpr {
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
}
}
/// Return minimal features needed
pub fn minimal_features_needed(&self) -> Vec<SmolStr> {
let mut features = vec![];
self.collect_minimal_features_needed(&mut features);
features
}
fn collect_minimal_features_needed(&self, features: &mut Vec<SmolStr>) {
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 @@ mod tests {
]),
);
}
#[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());
}
}

View file

@ -10,13 +10,14 @@ use ra_syntax::{
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<Vec<SmolStr>>,
pub cfg_exprs: Vec<CfgExpr>,
}
#[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<Vec<SmolStr>> {
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<SmolStr>| !it.is_empty())
Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
}
#[cfg(test)]
@ -223,7 +220,7 @@ mod tests {
Runnable {
range: 1..21,
kind: Bin,
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 22..46,
@ -235,7 +232,7 @@ mod tests {
ignore: false,
},
},
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 47..81,
@ -247,7 +244,7 @@ mod tests {
ignore: true,
},
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -275,7 +272,7 @@ mod tests {
Runnable {
range: 1..21,
kind: Bin,
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 22..64,
@ -284,7 +281,7 @@ mod tests {
"foo",
),
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -315,7 +312,7 @@ mod tests {
Runnable {
range: 1..21,
kind: Bin,
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 51..105,
@ -324,7 +321,7 @@ mod tests {
"Data::foo",
),
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -352,7 +349,7 @@ mod tests {
kind: TestMod {
path: "test_mod",
},
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 28..57,
@ -364,7 +361,7 @@ mod tests {
ignore: false,
},
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -394,7 +391,7 @@ mod tests {
kind: TestMod {
path: "foo::test_mod",
},
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 46..79,
@ -406,7 +403,7 @@ mod tests {
ignore: false,
},
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -438,7 +435,7 @@ mod tests {
kind: TestMod {
path: "foo::bar::test_mod",
},
features_needed: None,
cfg_exprs: [],
},
Runnable {
range: 68..105,
@ -450,7 +447,7 @@ mod tests {
ignore: false,
},
},
features_needed: None,
cfg_exprs: [],
},
]
"###
@ -482,11 +479,12 @@ mod tests {
ignore: false,
},
},
features_needed: Some(
[
"foo",
],
),
cfg_exprs: [
KeyValue {
key: "feature",
value: "foo",
},
],
},
]
"###
@ -518,12 +516,20 @@ mod tests {
ignore: false,
},
},
features_needed: Some(
[
"foo",
"bar",
],
),
cfg_exprs: [
All(
[
KeyValue {
key: "feature",
value: "foo",
},
KeyValue {
key: "feature",
value: "bar",
},
],
),
],
},
]
"###

View file

@ -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" ]

View file

@ -21,7 +21,7 @@ impl CargoTargetSpec {
pub(crate) fn runnable_args(
spec: Option<CargoTargetSpec>,
kind: &RunnableKind,
features_needed: &Option<Vec<SmolStr>>,
features_needed: &Vec<SmolStr>,
) -> Result<(Vec<String>, Vec<String>)> {
let mut args = Vec::new();
let mut extra_args = Vec::new();
@ -76,12 +76,11 @@ impl CargoTargetSpec {
}
}
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))
}

View file

@ -17,12 +17,13 @@ use lsp_types::{
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 @@ use crate::{
world::WorldSnapshot,
LspError, Result,
};
use hir::Attrs;
pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
let _p = profile("handle_analyzer_status");
@ -1006,8 +1008,12 @@ fn to_lsp_runnable(
) -> Result<lsp_ext::Runnable> {
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<Vec<SmolStr>> {
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<SmolStr>| !it.is_empty())
}
/// Fill minimal features needed
fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec<SmolStr>) {
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());
}
}