]> git.lizzy.rs Git - rust.git/blob - src/modules/visitor.rs
Format modules defined in cfg_attr (#3604)
[rust.git] / src / modules / visitor.rs
1 use syntax::ast;
2 use syntax::parse::token::{DelimToken, Token};
3 use syntax::parse::{stream_to_parser_with_base_dir, Directory, ParseSess};
4 use syntax::symbol::kw;
5 use syntax::visit::Visitor;
6 use syntax_pos::Symbol;
7
8 use crate::attr::MetaVisitor;
9
10 pub(crate) struct ModItem {
11     pub(crate) item: ast::Item,
12 }
13
14 /// Traverse `cfg_if!` macro and fetch modules.
15 pub(crate) struct CfgIfVisitor<'a> {
16     parse_sess: &'a ParseSess,
17     mods: Vec<ModItem>,
18     base_dir: Directory<'a>,
19 }
20
21 impl<'a> CfgIfVisitor<'a> {
22     pub(crate) fn new(parse_sess: &'a ParseSess, base_dir: Directory<'a>) -> CfgIfVisitor<'a> {
23         CfgIfVisitor {
24             mods: vec![],
25             parse_sess,
26             base_dir,
27         }
28     }
29
30     pub(crate) fn mods(self) -> Vec<ModItem> {
31         self.mods
32     }
33 }
34
35 impl<'a, 'ast: 'a> Visitor<'ast> for CfgIfVisitor<'a> {
36     fn visit_mac(&mut self, mac: &'ast ast::Mac) {
37         match self.visit_mac_inner(mac) {
38             Ok(()) => (),
39             Err(e) => debug!("{}", e),
40         }
41     }
42 }
43
44 impl<'a, 'ast: 'a> CfgIfVisitor<'a> {
45     fn visit_mac_inner(&mut self, mac: &'ast ast::Mac) -> Result<(), &'static str> {
46         if mac.node.path != Symbol::intern("cfg_if") {
47             return Err("Expected cfg_if");
48         }
49
50         let mut parser = stream_to_parser_with_base_dir(
51             self.parse_sess,
52             mac.node.tts.clone(),
53             self.base_dir.clone(),
54         );
55         parser.cfg_mods = false;
56         let mut process_if_cfg = true;
57
58         while parser.token != Token::Eof {
59             if process_if_cfg {
60                 if !parser.eat_keyword(kw::If) {
61                     return Err("Expected `if`");
62                 }
63                 parser
64                     .parse_attribute(false)
65                     .map_err(|_| "Failed to parse attributes")?;
66             }
67
68             if !parser.eat(&Token::OpenDelim(DelimToken::Brace)) {
69                 return Err("Expected an opening brace");
70             }
71
72             while parser.token != Token::CloseDelim(DelimToken::Brace) && parser.token != Token::Eof
73             {
74                 let item = match parser.parse_item() {
75                     Ok(Some(item_ptr)) => item_ptr.into_inner(),
76                     Ok(None) => continue,
77                     Err(mut err) => {
78                         err.cancel();
79                         parser.sess.span_diagnostic.reset_err_count();
80                         return Err(
81                             "Expected item inside cfg_if block, but failed to parse it as an item",
82                         );
83                     }
84                 };
85                 if let ast::ItemKind::Mod(..) = item.node {
86                     self.mods.push(ModItem { item });
87                 }
88             }
89
90             if !parser.eat(&Token::CloseDelim(DelimToken::Brace)) {
91                 return Err("Expected a closing brace");
92             }
93
94             if parser.eat(&Token::Eof) {
95                 break;
96             }
97
98             if !parser.eat_keyword(kw::Else) {
99                 return Err("Expected `else`");
100             }
101
102             process_if_cfg = parser.token.is_keyword(kw::If);
103         }
104
105         Ok(())
106     }
107 }
108
109 /// Extracts `path = "foo.rs"` from attributes.
110 #[derive(Default)]
111 pub(crate) struct PathVisitor {
112     /// A list of path defined in attributes.
113     paths: Vec<String>,
114 }
115
116 impl PathVisitor {
117     pub(crate) fn paths(self) -> Vec<String> {
118         self.paths
119     }
120 }
121
122 impl<'ast> MetaVisitor<'ast> for PathVisitor {
123     fn visit_meta_name_value(&mut self, meta_item: &'ast ast::MetaItem, lit: &'ast ast::Lit) {
124         if meta_item.check_name(Symbol::intern("path")) && lit.node.is_str() {
125             self.paths.push(lit_to_str(lit));
126         }
127     }
128 }
129
130 #[cfg(not(windows))]
131 fn lit_to_str(lit: &ast::Lit) -> String {
132     match lit.node {
133         ast::LitKind::Str(symbol, ..) => symbol.to_string(),
134         _ => unreachable!(),
135     }
136 }
137
138 #[cfg(windows)]
139 fn lit_to_str(lit: &ast::Lit) -> String {
140     match lit.node {
141         ast::LitKind::Str(symbol, ..) => symbol.as_str().replace("/", "\\"),
142         _ => unreachable!(),
143     }
144 }