blob: 4ef1daf7475b78644b1262972373b813efdc3e36 [file] [log] [blame]
use rustc_parse::{stream_to_parser_with_base_dir, Directory};
use rustc_session::parse::ParseSess;
use rustc_span::{symbol::kw, Symbol};
use syntax::ast;
use syntax::token::{DelimToken, TokenKind};
use syntax::visit::Visitor;
use crate::attr::MetaVisitor;
pub(crate) struct ModItem {
pub(crate) item: ast::Item,
}
/// Traverse `cfg_if!` macro and fetch modules.
pub(crate) struct CfgIfVisitor<'a> {
parse_sess: &'a ParseSess,
mods: Vec<ModItem>,
base_dir: Directory<'a>,
}
impl<'a> CfgIfVisitor<'a> {
pub(crate) fn new(parse_sess: &'a ParseSess, base_dir: Directory<'a>) -> CfgIfVisitor<'a> {
CfgIfVisitor {
mods: vec![],
parse_sess,
base_dir,
}
}
pub(crate) fn mods(self) -> Vec<ModItem> {
self.mods
}
}
impl<'a, 'ast: 'a> Visitor<'ast> for CfgIfVisitor<'a> {
fn visit_mac(&mut self, mac: &'ast ast::Mac) {
match self.visit_mac_inner(mac) {
Ok(()) => (),
Err(e) => debug!("{}", e),
}
}
}
impl<'a, 'ast: 'a> CfgIfVisitor<'a> {
fn visit_mac_inner(&mut self, mac: &'ast ast::Mac) -> Result<(), &'static str> {
// Support both:
// ```
// extern crate cfg_if;
// cfg_if::cfg_if! {..}
// ```
// And:
// ```
// #[macro_use]
// extern crate cfg_if;
// cfg_if! {..}
// ```
match mac.path.segments.first() {
Some(first_segment) => {
if first_segment.ident.name != Symbol::intern("cfg_if") {
return Err("Expected cfg_if");
}
}
None => {
return Err("Expected cfg_if");
}
};
let ts = mac.args.inner_tokens();
let mut parser =
stream_to_parser_with_base_dir(self.parse_sess, ts.clone(), self.base_dir.clone());
parser.cfg_mods = false;
let mut process_if_cfg = true;
while parser.token.kind != TokenKind::Eof {
if process_if_cfg {
if !parser.eat_keyword(kw::If) {
return Err("Expected `if`");
}
parser
.parse_attribute(false)
.map_err(|_| "Failed to parse attributes")?;
}
if !parser.eat(&TokenKind::OpenDelim(DelimToken::Brace)) {
return Err("Expected an opening brace");
}
while parser.token != TokenKind::CloseDelim(DelimToken::Brace)
&& parser.token.kind != TokenKind::Eof
{
let item = match parser.parse_item() {
Ok(Some(item_ptr)) => item_ptr.into_inner(),
Ok(None) => continue,
Err(mut err) => {
err.cancel();
parser.sess.span_diagnostic.reset_err_count();
return Err(
"Expected item inside cfg_if block, but failed to parse it as an item",
);
}
};
if let ast::ItemKind::Mod(..) = item.kind {
self.mods.push(ModItem { item });
}
}
if !parser.eat(&TokenKind::CloseDelim(DelimToken::Brace)) {
return Err("Expected a closing brace");
}
if parser.eat(&TokenKind::Eof) {
break;
}
if !parser.eat_keyword(kw::Else) {
return Err("Expected `else`");
}
process_if_cfg = parser.token.is_keyword(kw::If);
}
Ok(())
}
}
/// Extracts `path = "foo.rs"` from attributes.
#[derive(Default)]
pub(crate) struct PathVisitor {
/// A list of path defined in attributes.
paths: Vec<String>,
}
impl PathVisitor {
pub(crate) fn paths(self) -> Vec<String> {
self.paths
}
}
impl<'ast> MetaVisitor<'ast> for PathVisitor {
fn visit_meta_name_value(&mut self, meta_item: &'ast ast::MetaItem, lit: &'ast ast::Lit) {
if meta_item.check_name(Symbol::intern("path")) && lit.kind.is_str() {
self.paths.push(lit_to_str(lit));
}
}
}
#[cfg(not(windows))]
fn lit_to_str(lit: &ast::Lit) -> String {
match lit.kind {
ast::LitKind::Str(symbol, ..) => symbol.to_string(),
_ => unreachable!(),
}
}
#[cfg(windows)]
fn lit_to_str(lit: &ast::Lit) -> String {
match lit.kind {
ast::LitKind::Str(symbol, ..) => symbol.as_str().replace("/", "\\"),
_ => unreachable!(),
}
}