Introduce ra_cfg to parse and evaluate CfgExpr

This commit is contained in:
uHOOCCOOHu 2019-09-30 06:52:15 +08:00
parent ffe179a736
commit b1ed887d81
No known key found for this signature in database
GPG key ID: CED392DE0C483D00
11 changed files with 315 additions and 27 deletions

14
Cargo.lock generated
View file

@ -922,6 +922,16 @@ dependencies = [
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ra_cfg"
version = "0.1.0"
dependencies = [
"ra_mbe 0.1.0",
"ra_syntax 0.1.0",
"ra_tt 0.1.0",
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ra_cli"
version = "0.1.0"
@ -941,6 +951,7 @@ dependencies = [
name = "ra_db"
version = "0.1.0"
dependencies = [
"ra_cfg 0.1.0",
"ra_prof 0.1.0",
"ra_syntax 0.1.0",
"relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -971,6 +982,7 @@ dependencies = [
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_arena 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"ra_mbe 0.1.0",
"ra_prof 0.1.0",
@ -993,6 +1005,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_assists 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"ra_fmt 0.1.0",
"ra_hir 0.1.0",
@ -1075,6 +1088,7 @@ dependencies = [
"cargo_metadata 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_arena 0.1.0",
"ra_cfg 0.1.0",
"ra_db 0.1.0",
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",

14
crates/ra_cfg/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
edition = "2018"
name = "ra_cfg"
version = "0.1.0"
authors = ["rust-analyzer developers"]
[dependencies]
rustc-hash = "1.0.1"
ra_syntax = { path = "../ra_syntax" }
tt = { path = "../ra_tt", package = "ra_tt" }
[dev-dependencies]
mbe = { path = "../ra_mbe", package = "ra_mbe" }

View file

@ -0,0 +1,128 @@
use std::slice::Iter as SliceIter;
use ra_syntax::SmolStr;
use tt::{Leaf, Subtree, TokenTree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfgExpr {
Invalid,
Atom(SmolStr),
KeyValue { key: SmolStr, value: SmolStr },
All(Vec<CfgExpr>),
Any(Vec<CfgExpr>),
Not(Box<CfgExpr>),
}
impl CfgExpr {
/// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
pub fn fold(&self, query: &impl Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
match self {
CfgExpr::Invalid => None,
CfgExpr::Atom(name) => Some(query(name, None)),
CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
CfgExpr::All(preds) => {
preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
}
CfgExpr::Any(preds) => {
preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
}
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
}
}
}
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(_) => return Some(CfgExpr::Invalid),
};
// Peek
let ret = match it.as_slice().first() {
Some(TokenTree::Leaf(Leaf::Punct(punct))) if punct.char == '=' => {
match it.as_slice().get(1) {
Some(TokenTree::Leaf(Leaf::Literal(literal))) => {
it.next();
it.next();
// FIXME: escape? raw string?
let value =
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
CfgExpr::KeyValue { key: name, value }
}
_ => return Some(CfgExpr::Invalid),
}
}
Some(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();
match name.as_str() {
"all" => CfgExpr::All(subs),
"any" => CfgExpr::Any(subs),
"not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))),
_ => CfgExpr::Invalid,
}
}
_ => CfgExpr::Atom(name),
};
// Eat comma separator
if let Some(TokenTree::Leaf(Leaf::Punct(punct))) = it.as_slice().first() {
if punct.char == ',' {
it.next();
}
}
Some(ret)
}
#[cfg(test)]
mod tests {
use super::*;
use mbe::ast_to_token_tree;
use ra_syntax::ast::{self, AstNode};
fn assert_parse_result(input: &str, expected: CfgExpr) {
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();
assert_eq!(parse_cfg(&tt), expected);
}
#[test]
fn test_cfg_expr_parser() {
assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
assert_parse_result(
"#![cfg(not(foo))]",
CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
);
assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
// Only take the first
assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
assert_parse_result(
r#"#![cfg(all(foo, bar = "baz"))]"#,
CfgExpr::All(vec![
CfgExpr::Atom("foo".into()),
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
]),
);
assert_parse_result(
r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
CfgExpr::Any(vec![
CfgExpr::Not(Box::new(CfgExpr::Invalid)),
CfgExpr::All(vec![]),
CfgExpr::Invalid,
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
]),
);
}
}

43
crates/ra_cfg/src/lib.rs Normal file
View file

@ -0,0 +1,43 @@
//! ra_cfg defines conditional compiling options, `cfg` attibute parser and evaluator
use ra_syntax::SmolStr;
use rustc_hash::{FxHashMap, FxHashSet};
mod cfg_expr;
pub use cfg_expr::{parse_cfg, CfgExpr};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CfgOptions {
atoms: FxHashSet<SmolStr>,
features: FxHashSet<SmolStr>,
options: FxHashMap<SmolStr, SmolStr>,
}
impl CfgOptions {
pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
cfg.fold(&|key, value| match value {
None => self.atoms.contains(key),
Some(value) if key == "feature" => self.features.contains(value),
Some(value) => self.options.get(key).map_or(false, |v| v == value),
})
}
pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option<bool> {
self.check(&parse_cfg(attr))
}
pub fn atom(mut self, name: SmolStr) -> CfgOptions {
self.atoms.insert(name);
self
}
pub fn feature(mut self, name: SmolStr) -> CfgOptions {
self.features.insert(name);
self
}
pub fn option(mut self, key: SmolStr, value: SmolStr) -> CfgOptions {
self.options.insert(key, value);
self
}
}

View file

@ -10,4 +10,5 @@ relative-path = "0.4.0"
rustc-hash = "1.0"
ra_syntax = { path = "../ra_syntax" }
ra_cfg = { path = "../ra_cfg" }
ra_prof = { path = "../ra_prof" }

View file

@ -9,6 +9,7 @@
use relative_path::{RelativePath, RelativePathBuf};
use rustc_hash::FxHashMap;
use ra_cfg::CfgOptions;
use ra_syntax::SmolStr;
use rustc_hash::FxHashSet;
@ -109,11 +110,13 @@ struct CrateData {
file_id: FileId,
edition: Edition,
dependencies: Vec<Dependency>,
cfg_options: CfgOptions,
}
impl CrateData {
fn new(file_id: FileId, edition: Edition) -> CrateData {
CrateData { file_id, edition, dependencies: Vec::new() }
// FIXME: cfg options
CrateData { file_id, edition, dependencies: Vec::new(), cfg_options: CfgOptions::default() }
}
fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) {
@ -141,6 +144,10 @@ impl CrateGraph {
crate_id
}
pub fn cfg_options(&self, crate_id: CrateId) -> &CfgOptions {
&self.arena[&crate_id].cfg_options
}
pub fn add_dep(
&mut self,
from: CrateId,

View file

@ -15,6 +15,7 @@ once_cell = "1.0.1"
ra_syntax = { path = "../ra_syntax" }
ra_arena = { path = "../ra_arena" }
ra_cfg = { path = "../ra_cfg" }
ra_db = { path = "../ra_db" }
mbe = { path = "../ra_mbe", package = "ra_mbe" }
tt = { path = "../ra_tt", package = "ra_tt" }

58
crates/ra_hir/src/attr.rs Normal file
View file

@ -0,0 +1,58 @@
use mbe::ast_to_token_tree;
use ra_syntax::{
ast::{self, AstNode},
SmolStr,
};
use tt::Subtree;
use crate::{db::AstDatabase, path::Path, Source};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Attr {
pub(crate) path: Path,
pub(crate) input: Option<AttrInput>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrInput {
Literal(SmolStr),
TokenTree(Subtree),
}
impl Attr {
pub(crate) fn from_src(
Source { file_id, ast }: Source<ast::Attr>,
db: &impl AstDatabase,
) -> Option<Attr> {
let path = Path::from_src(Source { file_id, ast: ast.path()? }, db)?;
let input = match ast.input() {
None => None,
Some(ast::AttrInput::Literal(lit)) => {
// FIXME: escape? raw string?
let value = lit.syntax().first_token()?.text().trim_matches('"').into();
Some(AttrInput::Literal(value))
}
Some(ast::AttrInput::TokenTree(tt)) => {
Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
}
};
Some(Attr { path, input })
}
pub(crate) fn is_simple_atom(&self, name: &str) -> bool {
// FIXME: Avoid cloning
self.path.as_ident().map_or(false, |s| s.to_string() == name)
}
pub(crate) fn as_cfg(&self) -> Option<&Subtree> {
if self.is_simple_atom("cfg") {
match &self.input {
Some(AttrInput::TokenTree(subtree)) => Some(subtree),
_ => None,
}
} else {
None
}
}
}

View file

@ -44,6 +44,7 @@ mod traits;
mod type_alias;
mod type_ref;
mod ty;
mod attr;
mod impl_block;
mod expr;
mod lang_item;

View file

@ -1,11 +1,13 @@
//! FIXME: write short doc here
use ra_cfg::CfgOptions;
use ra_db::FileId;
use ra_syntax::{ast, SmolStr};
use rustc_hash::FxHashMap;
use test_utils::tested_by;
use crate::{
attr::Attr,
db::DefDatabase,
ids::{AstItemDef, LocationCtx, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind},
name::MACRO_RULES,
@ -35,6 +37,9 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
}
}
let crate_graph = db.crate_graph();
let cfg_options = crate_graph.cfg_options(def_map.krate().crate_id());
let mut collector = DefCollector {
db,
def_map,
@ -42,6 +47,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
unresolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
macro_stack_monitor: MacroStackMonitor::default(),
cfg_options,
};
collector.collect();
collector.finish()
@ -76,8 +82,8 @@ impl MacroStackMonitor {
}
/// Walks the tree of module recursively
struct DefCollector<DB> {
db: DB,
struct DefCollector<'a, DB> {
db: &'a DB,
def_map: CrateDefMap,
glob_imports: FxHashMap<CrateModuleId, Vec<(CrateModuleId, raw::ImportId)>>,
unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>,
@ -86,9 +92,11 @@ struct DefCollector<DB> {
/// Some macro use `$tt:tt which mean we have to handle the macro perfectly
/// To prevent stack overflow, we add a deep counter here for prevent that.
macro_stack_monitor: MacroStackMonitor,
cfg_options: &'a CfgOptions,
}
impl<'a, DB> DefCollector<&'a DB>
impl<DB> DefCollector<'_, DB>
where
DB: DefDatabase,
{
@ -506,7 +514,7 @@ struct ModCollector<'a, D> {
parent_module: Option<ParentModule<'a>>,
}
impl<DB> ModCollector<'_, &'_ mut DefCollector<&'_ DB>>
impl<DB> ModCollector<'_, &'_ mut DefCollector<'_, DB>>
where
DB: DefDatabase,
{
@ -523,6 +531,7 @@ where
// `#[macro_use] extern crate` is hoisted to imports macros before collecting
// any other items.
for item in items {
if self.is_cfg_enabled(&item.attrs) {
if let raw::RawItemKind::Import(import_id) = item.kind {
let import = self.raw_items[import_id].clone();
if import.is_extern_crate && import.is_macro_use {
@ -530,8 +539,10 @@ where
}
}
}
}
for item in items {
if self.is_cfg_enabled(&item.attrs) {
match item.kind {
raw::RawItemKind::Module(m) => self.collect_module(&self.raw_items[m]),
raw::RawItemKind::Import(import_id) => self
@ -543,6 +554,7 @@ where
}
}
}
}
fn collect_module(&mut self, module: &raw::ModuleData) {
match module {
@ -702,6 +714,13 @@ where
self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_);
}
}
fn is_cfg_enabled(&self, attrs: &[Attr]) -> bool {
attrs
.iter()
.flat_map(|attr| attr.as_cfg())
.all(|cfg| self.def_collector.cfg_options.is_cfg_enabled(cfg).unwrap_or(true))
}
}
fn is_macro_rules(path: &Path) -> bool {
@ -729,6 +748,7 @@ mod tests {
unresolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
macro_stack_monitor: monitor,
cfg_options: &CfgOptions::default(),
};
collector.collect();
collector.finish()

View file

@ -2,7 +2,6 @@
use std::{ops::Index, sync::Arc};
use mbe::ast_to_token_tree;
use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
use ra_syntax::{
ast::{self, AttrsOwner, NameOwner},
@ -11,6 +10,7 @@ use ra_syntax::{
use test_utils::tested_by;
use crate::{
attr::Attr,
db::{AstDatabase, DefDatabase},
AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, Source,
};
@ -29,8 +29,6 @@ pub struct RawItems {
items: Vec<RawItem>,
}
type Attrs = Arc<[tt::Subtree]>;
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ImportSourceMap {
map: ArenaMap<ImportId, ImportSourcePtr>,
@ -124,7 +122,7 @@ impl Index<Macro> for RawItems {
#[derive(Debug, PartialEq, Eq, Clone)]
pub(super) struct RawItem {
pub(super) attrs: Attrs,
pub(super) attrs: Arc<[Attr]>,
pub(super) kind: RawItemKind,
}
@ -285,6 +283,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
let attrs = self.parse_attrs(&module);
let ast_id = self.source_ast_id_map.ast_id(&module);
// FIXME: cfg_attr
let is_macro_use = module.has_atom_attr("macro_use");
if module.has_semi() {
let attr_path = extract_mod_path_attribute(&module);
@ -315,6 +314,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
}
fn add_use_item(&mut self, current_module: Option<Module>, use_item: ast::UseItem) {
// FIXME: cfg_attr
let is_prelude = use_item.has_atom_attr("prelude_import");
let attrs = self.parse_attrs(&use_item);
@ -349,6 +349,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
let path = Path::from_name_ref(&name_ref);
let alias = extern_crate.alias().and_then(|a| a.name()).map(|it| it.as_name());
let attrs = self.parse_attrs(&extern_crate);
// FIXME: cfg_attr
let is_macro_use = extern_crate.has_atom_attr("macro_use");
let import_data = ImportData {
path,
@ -368,6 +369,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
}
fn add_macro(&mut self, current_module: Option<Module>, m: ast::MacroCall) {
let attrs = self.parse_attrs(&m);
let path = match m
.path()
.and_then(|path| Path::from_src(Source { ast: path, file_id: self.file_id }, self.db))
@ -378,6 +380,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
let name = m.name().map(|it| it.as_name());
let ast_id = self.source_ast_id_map.ast_id(&m);
// FIXME: cfg_attr
let export = m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "macro_export");
let m = self.raw_items.macros.alloc(MacroData { ast_id, path, name, export });
@ -387,7 +390,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
fn push_import(
&mut self,
current_module: Option<Module>,
attrs: Attrs,
attrs: Arc<[Attr]>,
data: ImportData,
source: ImportSourcePtr,
) {
@ -396,7 +399,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
self.push_item(current_module, attrs, RawItemKind::Import(import))
}
fn push_item(&mut self, current_module: Option<Module>, attrs: Attrs, kind: RawItemKind) {
fn push_item(&mut self, current_module: Option<Module>, attrs: Arc<[Attr]>, kind: RawItemKind) {
match current_module {
Some(module) => match &mut self.raw_items.modules[module] {
ModuleData::Definition { items, .. } => items,
@ -407,11 +410,9 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
.push(RawItem { attrs, kind })
}
fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs {
fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Arc<[Attr]> {
item.attrs()
.flat_map(|attr| attr.value())
.flat_map(|tt| ast_to_token_tree(&tt))
.map(|(tt, _)| tt)
.flat_map(|ast| Attr::from_src(Source { ast, file_id: self.file_id }, self.db))
.collect()
}
}