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

View file

@ -5,7 +5,7 @@ mod cfg_expr;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
use rustc_hash::FxHashSet; 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. /// 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 /// 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) { pub fn insert_atom(&mut self, key: SmolStr) {
self.atoms.insert(key); self.atoms.insert(key);
} }

View file

@ -5,7 +5,7 @@ use std::{ops, sync::Arc};
use either::Either; use either::Either;
use hir_expand::{hygiene::Hygiene, AstId, InFile}; use hir_expand::{hygiene::Hygiene, AstId, InFile};
use mbe::ast_to_token_tree; use mbe::ast_to_token_tree;
use ra_cfg::CfgOptions; use ra_cfg::{CfgExpr, CfgOptions};
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode, AttrsOwner}, ast::{self, AstNode, AttrsOwner},
SmolStr, SmolStr,
@ -125,9 +125,12 @@ impl Attrs {
AttrQuery { attrs: self, key } 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 :-) // 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 attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
let cfg_exprs = let cfg_exprs = attrs.cfg().collect();
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
let nav = if let RunnableKind::DocTest { .. } = kind { let nav = if let RunnableKind::DocTest { .. } = kind {
NavigationTarget::from_doc_commented( NavigationTarget::from_doc_commented(
@ -242,9 +241,7 @@ fn runnable_mod(
.join("::"); .join("::");
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
let cfg_exprs = let cfg_exprs = attrs.cfg().collect();
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
let nav = module_def.to_nav(sema.db); let nav = module_def.to_nav(sema.db);
Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) 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_cfg::CfgOptions;
use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId}; use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use stdx::split_delim;
pub use crate::{ pub use crate::{
cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
@ -544,11 +545,10 @@ fn get_rustc_cfg_options(target: Option<&str>) -> CfgOptions {
match rustc_cfgs { match rustc_cfgs {
Ok(rustc_cfgs) => { Ok(rustc_cfgs) => {
for line in rustc_cfgs.lines() { for line in rustc_cfgs.lines() {
match line.find('=') { match split_delim(line, '=') {
None => cfg_options.insert_atom(line.into()), None => cfg_options.insert_atom(line.into()),
Some(pos) => { Some((key, value)) => {
let key = &line[..pos]; let value = value.trim_matches('"');
let value = line[pos + 1..].trim_matches('"');
cfg_options.insert_key_value(key.into(), value.into()); 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 { mod tests {
use super::*; use super::*;
use mbe::{ast_to_token_tree, TokenMap}; use mbe::ast_to_token_tree;
use ra_cfg::parse_cfg; use ra_cfg::CfgExpr;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
SmolStr, SmolStr,
}; };
fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { fn check(cfg: &str, expected_features: &[&str]) {
let source_file = ast::SourceFile::parse(input).ok().unwrap(); let cfg_expr = {
let source_file = ast::SourceFile::parse(cfg).ok().unwrap();
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).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] #[test]
fn test_cfg_expr_minimal_features_needed() { fn test_cfg_expr_minimal_features_needed() {
let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); check(r#"#![cfg(feature = "baz")]"#, &["baz"]);
let cfg_expr = parse_cfg(&subtree); check(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#, &["baz", "foo"]);
let mut min_features = vec![]; check(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#, &["baz"]);
required_features(&cfg_expr, &mut min_features); check(r#"#![cfg(foo)]"#, &[]);
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());
} }
} }