Cleanup CFG API

This commit is contained in:
Aleksey Kladov 2020-07-23 16:22:17 +02:00
parent 0cf8ee2dc2
commit 38e38d9b29
6 changed files with 44 additions and 63 deletions

View file

@ -5,7 +5,6 @@
use std::slice::Iter as SliceIter;
use ra_syntax::SmolStr;
use tt::{Leaf, Subtree, TokenTree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfgExpr {
@ -18,6 +17,9 @@ pub enum CfgExpr {
}
impl CfgExpr {
pub fn parse(tt: &tt::Subtree) -> CfgExpr {
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
}
/// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
match self {
@ -35,22 +37,18 @@ impl CfgExpr {
}
}
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
}
fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
let name = match it.next() {
None => return None,
Some(TokenTree::Leaf(Leaf::Ident(ident))) => ident.text.clone(),
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
Some(_) => return Some(CfgExpr::Invalid),
};
// Peek
let ret = match it.as_slice().first() {
Some(TokenTree::Leaf(Leaf::Punct(punct))) if punct.char == '=' => {
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
match it.as_slice().get(1) {
Some(TokenTree::Leaf(Leaf::Literal(literal))) => {
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
it.next();
it.next();
// FIXME: escape? raw string?
@ -61,7 +59,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
_ => return Some(CfgExpr::Invalid),
}
}
Some(TokenTree::Subtree(subtree)) => {
Some(tt::TokenTree::Subtree(subtree)) => {
it.next();
let mut sub_it = subtree.token_trees.iter();
let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect();
@ -76,7 +74,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
};
// Eat comma separator
if let Some(TokenTree::Leaf(Leaf::Punct(punct))) = it.as_slice().first() {
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
if punct.char == ',' {
it.next();
}
@ -99,7 +97,8 @@ mod tests {
fn assert_parse_result(input: &str, expected: CfgExpr) {
let (tt, _) = get_token_tree_generated(input);
assert_eq!(parse_cfg(&tt), expected);
let cfg = CfgExpr::parse(&tt);
assert_eq!(cfg, expected);
}
#[test]

View file

@ -5,7 +5,7 @@ mod cfg_expr;
use ra_syntax::SmolStr;
use rustc_hash::FxHashSet;
pub use cfg_expr::{parse_cfg, CfgExpr};
pub use cfg_expr::CfgExpr;
/// Configuration options used for conditional compilition on items with `cfg` attributes.
/// We have two kind of options in different namespaces: atomic options like `unix`, and
@ -31,10 +31,6 @@ impl CfgOptions {
})
}
pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option<bool> {
self.check(&parse_cfg(attr))
}
pub fn insert_atom(&mut self, key: SmolStr) {
self.atoms.insert(key);
}

View file

@ -5,7 +5,7 @@ use std::{ops, sync::Arc};
use either::Either;
use hir_expand::{hygiene::Hygiene, AstId, InFile};
use mbe::ast_to_token_tree;
use ra_cfg::CfgOptions;
use ra_cfg::{CfgExpr, CfgOptions};
use ra_syntax::{
ast::{self, AstNode, AttrsOwner},
SmolStr,
@ -125,9 +125,12 @@ impl Attrs {
AttrQuery { attrs: self, key }
}
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ {
// FIXME: handle cfg_attr :-)
self.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false))
self.by_key("cfg").tt_values().map(CfgExpr::parse)
}
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
self.cfg().all(|cfg| cfg_options.check(&cfg) != Some(false))
}
}

View file

@ -168,8 +168,7 @@ fn runnable_fn(
};
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
let cfg_exprs =
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
let cfg_exprs = attrs.cfg().collect();
let nav = if let RunnableKind::DocTest { .. } = kind {
NavigationTarget::from_doc_commented(
@ -242,9 +241,7 @@ fn runnable_mod(
.join("::");
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
let cfg_exprs =
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
let cfg_exprs = attrs.cfg().collect();
let nav = module_def.to_nav(sema.db);
Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
}

View file

@ -16,6 +16,7 @@ use paths::{AbsPath, AbsPathBuf};
use ra_cfg::CfgOptions;
use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
use rustc_hash::{FxHashMap, FxHashSet};
use stdx::split_delim;
pub use crate::{
cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
@ -544,11 +545,10 @@ fn get_rustc_cfg_options(target: Option<&str>) -> CfgOptions {
match rustc_cfgs {
Ok(rustc_cfgs) => {
for line in rustc_cfgs.lines() {
match line.find('=') {
match split_delim(line, '=') {
None => cfg_options.insert_atom(line.into()),
Some(pos) => {
let key = &line[..pos];
let value = line[pos + 1..].trim_matches('"');
Some((key, value)) => {
let value = value.trim_matches('"');
cfg_options.insert_key_value(key.into(), value.into());
}
}

View file

@ -177,49 +177,35 @@ fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
mod tests {
use super::*;
use mbe::{ast_to_token_tree, TokenMap};
use ra_cfg::parse_cfg;
use mbe::ast_to_token_tree;
use ra_cfg::CfgExpr;
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();
fn check(cfg: &str, expected_features: &[&str]) {
let cfg_expr = {
let source_file = ast::SourceFile::parse(cfg).ok().unwrap();
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
ast_to_token_tree(&tt).unwrap()
let (tt, _) = ast_to_token_tree(&tt).unwrap();
CfgExpr::parse(&tt)
};
let mut features = vec![];
required_features(&cfg_expr, &mut features);
let expected_features =
expected_features.iter().map(|&it| SmolStr::new(it)).collect::<Vec<_>>();
assert_eq!(features, expected_features);
}
#[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![];
required_features(&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![];
required_features(&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![];
required_features(&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![];
required_features(&cfg_expr, &mut min_features);
assert!(min_features.is_empty());
check(r#"#![cfg(feature = "baz")]"#, &["baz"]);
check(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#, &["baz", "foo"]);
check(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#, &["baz"]);
check(r#"#![cfg(foo)]"#, &[]);
}
}