Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit e71bffb

Browse files
authored
Format modules defined inside cfg_if macro calls (rust-lang#3600)
1 parent 87565c4 commit e71bffb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5589
-50
lines changed

src/formatting.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ fn format_project<T: FormatHandler>(
9999

100100
let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
101101
let files = modules::ModResolver::new(
102-
context.parse_session.source_map(),
103-
directory_ownership.unwrap_or(parse::DirectoryOwnership::UnownedViaMod(false)),
102+
&context.parse_session,
103+
directory_ownership.unwrap_or(parse::DirectoryOwnership::UnownedViaMod(true)),
104104
!(input_is_stdin || config.skip_children()),
105105
)
106106
.visit_crate(&krate)
@@ -112,7 +112,7 @@ fn format_project<T: FormatHandler>(
112112
}
113113
should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path));
114114
let is_root = path == main_file;
115-
context.format_file(path, module, is_root)?;
115+
context.format_file(path, &module, is_root)?;
116116
}
117117
timer = timer.done_formatting();
118118

src/modules.rs

Lines changed: 142 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1+
use std::borrow::Cow;
12
use std::collections::BTreeMap;
23
use std::path::{Path, PathBuf};
34

45
use syntax::ast;
5-
use syntax::parse::{parser, DirectoryOwnership};
6+
use syntax::parse::{parser, DirectoryOwnership, ParseSess};
67
use syntax::source_map;
78
use syntax::symbol::sym;
9+
use syntax::visit::Visitor;
810
use syntax_pos::symbol::Symbol;
911

1012
use crate::config::FileName;
1113
use crate::items::is_mod_decl;
1214
use crate::utils::contains_skip;
1315

14-
type FileModMap<'a> = BTreeMap<FileName, (&'a ast::Mod, String)>;
16+
mod visitor;
17+
18+
type FileModMap<'ast> = BTreeMap<FileName, (Cow<'ast, ast::Mod>, String)>;
1519

1620
/// Maps each module to the corresponding file.
17-
pub(crate) struct ModResolver<'a, 'b> {
18-
source_map: &'b source_map::SourceMap,
21+
pub(crate) struct ModResolver<'ast, 'sess> {
22+
parse_sess: &'sess ParseSess,
1923
directory: Directory,
20-
file_map: FileModMap<'a>,
24+
file_map: FileModMap<'ast>,
2125
recursive: bool,
2226
}
2327

@@ -27,10 +31,28 @@ struct Directory {
2731
ownership: DirectoryOwnership,
2832
}
2933

30-
impl<'a, 'b> ModResolver<'a, 'b> {
34+
impl<'a> Directory {
35+
fn to_syntax_directory(&'a self) -> syntax::parse::Directory<'a> {
36+
syntax::parse::Directory {
37+
path: Cow::Borrowed(&self.path),
38+
ownership: self.ownership.clone(),
39+
}
40+
}
41+
}
42+
43+
enum SubModKind {
44+
/// `mod foo;`
45+
External(PathBuf, DirectoryOwnership),
46+
/// `#[path = "..."] mod foo {}`
47+
InternalWithPath(PathBuf),
48+
/// `mod foo {}`
49+
Internal,
50+
}
51+
52+
impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
3153
/// Creates a new `ModResolver`.
3254
pub(crate) fn new(
33-
source_map: &'b source_map::SourceMap,
55+
parse_sess: &'sess ParseSess,
3456
directory_ownership: DirectoryOwnership,
3557
recursive: bool,
3658
) -> Self {
@@ -40,14 +62,17 @@ impl<'a, 'b> ModResolver<'a, 'b> {
4062
ownership: directory_ownership,
4163
},
4264
file_map: BTreeMap::new(),
43-
source_map,
65+
parse_sess,
4466
recursive,
4567
}
4668
}
4769

4870
/// Creates a map that maps a file name to the module in AST.
49-
pub(crate) fn visit_crate(mut self, krate: &'a ast::Crate) -> Result<FileModMap<'a>, String> {
50-
let root_filename = self.source_map.span_to_filename(krate.span);
71+
pub(crate) fn visit_crate(
72+
mut self,
73+
krate: &'ast ast::Crate,
74+
) -> Result<FileModMap<'ast>, String> {
75+
let root_filename = self.parse_sess.source_map().span_to_filename(krate.span);
5176
self.directory.path = match root_filename {
5277
source_map::FileName::Real(ref path) => path
5378
.parent()
@@ -58,54 +83,125 @@ impl<'a, 'b> ModResolver<'a, 'b> {
5883

5984
// Skip visiting sub modules when the input is from stdin.
6085
if self.recursive {
61-
self.visit_mod(&krate.module)?;
86+
self.visit_mod_from_ast(&krate.module)?;
6287
}
6388

64-
self.file_map
65-
.insert(root_filename.into(), (&krate.module, String::new()));
89+
self.file_map.insert(
90+
root_filename.into(),
91+
(Cow::Borrowed(&krate.module), String::new()),
92+
);
6693
Ok(self.file_map)
6794
}
6895

69-
fn visit_mod(&mut self, module: &'a ast::Mod) -> Result<(), String> {
96+
/// Visit macro calls and look for module declarations. Currently only supports `cfg_if` macro.
97+
fn visit_mac(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), String> {
98+
let mut visitor =
99+
visitor::CfgIfVisitor::new(self.parse_sess, self.directory.to_syntax_directory());
100+
visitor.visit_item(&item);
101+
for module_item in visitor.mods() {
102+
if let ast::ItemKind::Mod(ref sub_mod) = module_item.item.node {
103+
self.visit_mod_from_mac_inner(&item, Cow::Owned(sub_mod.clone()))?;
104+
}
105+
}
106+
Ok(())
107+
}
108+
109+
/// Visit modules defined inside macro calls.
110+
fn visit_mod_from_macro(&mut self, module: Cow<'ast, ast::Mod>) -> Result<(), String> {
70111
for item in &module.items {
112+
if let ast::ItemKind::Mac(..) = item.node {
113+
self.visit_mac(Cow::Owned(item.clone().into_inner()))?;
114+
}
115+
71116
if let ast::ItemKind::Mod(ref sub_mod) = item.node {
72-
if contains_skip(&item.attrs) {
73-
continue;
74-
}
117+
self.visit_mod_from_mac_inner(item, Cow::Owned(sub_mod.clone()))?;
118+
}
119+
}
120+
Ok(())
121+
}
75122

76-
let old_direcotry = self.directory.clone();
77-
if is_mod_decl(item) {
78-
// mod foo;
79-
// Look for an extern file.
80-
let (mod_path, directory_ownership) =
81-
self.find_external_module(item.ident, &item.attrs)?;
82-
self.file_map.insert(
83-
FileName::Real(mod_path.clone()),
84-
(sub_mod, item.ident.as_str().get().to_owned()),
85-
);
86-
self.directory = Directory {
87-
path: mod_path.parent().unwrap().to_path_buf(),
88-
ownership: directory_ownership,
89-
}
90-
} else {
91-
// An internal module (`mod foo { /* ... */ }`);
92-
if let Some(path) = find_path_value(&item.attrs) {
93-
// All `#[path]` files are treated as though they are a `mod.rs` file.
94-
self.directory = Directory {
95-
path: Path::new(&path.as_str()).to_path_buf(),
96-
ownership: DirectoryOwnership::Owned { relative: None },
97-
};
98-
} else {
99-
self.push_inline_mod_directory(item.ident, &item.attrs);
100-
}
101-
}
102-
self.visit_mod(sub_mod)?;
103-
self.directory = old_direcotry;
123+
fn visit_mod_from_mac_inner(
124+
&mut self,
125+
item: &'c ast::Item,
126+
sub_mod: Cow<'ast, ast::Mod>,
127+
) -> Result<(), String> {
128+
let old_directory = self.directory.clone();
129+
self.visit_sub_mod(item, &sub_mod)?;
130+
self.visit_mod_from_macro(sub_mod)?;
131+
self.directory = old_directory;
132+
Ok(())
133+
}
134+
135+
/// Visit modules from AST.
136+
fn visit_mod_from_ast(&mut self, module: &'ast ast::Mod) -> Result<(), String> {
137+
for item in &module.items {
138+
if let ast::ItemKind::Mac(..) = item.node {
139+
self.visit_mac(Cow::Borrowed(item))?;
140+
}
141+
142+
if let ast::ItemKind::Mod(ref sub_mod) = item.node {
143+
let old_directory = self.directory.clone();
144+
self.visit_sub_mod(item, &Cow::Borrowed(sub_mod))?;
145+
self.visit_mod_from_ast(sub_mod)?;
146+
self.directory = old_directory;
147+
}
148+
}
149+
Ok(())
150+
}
151+
152+
fn visit_sub_mod(
153+
&mut self,
154+
item: &'c ast::Item,
155+
sub_mod: &Cow<'ast, ast::Mod>,
156+
) -> Result<(), String> {
157+
match self.peek_sub_mod(item)? {
158+
Some(SubModKind::External(mod_path, directory_ownership)) => {
159+
self.file_map.insert(
160+
FileName::Real(mod_path.clone()),
161+
(sub_mod.clone(), item.ident.name.as_str().get().to_owned()),
162+
);
163+
self.directory = Directory {
164+
path: mod_path.parent().unwrap().to_path_buf(),
165+
ownership: directory_ownership,
166+
};
167+
}
168+
Some(SubModKind::InternalWithPath(mod_path)) => {
169+
// All `#[path]` files are treated as though they are a `mod.rs` file.
170+
self.directory = Directory {
171+
path: mod_path,
172+
ownership: DirectoryOwnership::Owned { relative: None },
173+
};
104174
}
175+
Some(SubModKind::Internal) => self.push_inline_mod_directory(item.ident, &item.attrs),
176+
None => (), // rustfmt::skip
105177
}
106178
Ok(())
107179
}
108180

181+
/// Inspect the given sub-module which we are about to visit and returns its kind.
182+
fn peek_sub_mod(&self, item: &'c ast::Item) -> Result<Option<SubModKind>, String> {
183+
if contains_skip(&item.attrs) {
184+
return Ok(None);
185+
}
186+
187+
if is_mod_decl(item) {
188+
// mod foo;
189+
// Look for an extern file.
190+
let (mod_path, directory_ownership) =
191+
self.find_external_module(item.ident, &item.attrs)?;
192+
Ok(Some(SubModKind::External(mod_path, directory_ownership)))
193+
} else {
194+
// An internal module (`mod foo { /* ... */ }`);
195+
if let Some(path) = find_path_value(&item.attrs) {
196+
let path = Path::new(&path.as_str()).to_path_buf();
197+
Ok(Some(SubModKind::InternalWithPath(path)))
198+
} else {
199+
Ok(Some(SubModKind::Internal))
200+
}
201+
}
202+
}
203+
204+
/// Find a file path in the filesystem which corresponds to the given module.
109205
fn find_external_module(
110206
&self,
111207
mod_name: ast::Ident,
@@ -123,7 +219,7 @@ impl<'a, 'b> ModResolver<'a, 'b> {
123219
mod_name,
124220
relative,
125221
&self.directory.path,
126-
self.source_map,
222+
self.parse_sess.source_map(),
127223
)
128224
.result
129225
{

src/modules/visitor.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
pub(crate) struct ModItem {
9+
pub(crate) item: ast::Item,
10+
}
11+
12+
/// Traverse `cfg_if!` macro and fetch modules.
13+
pub(crate) struct CfgIfVisitor<'a> {
14+
parse_sess: &'a ParseSess,
15+
mods: Vec<ModItem>,
16+
base_dir: Directory<'a>,
17+
}
18+
19+
impl<'a> CfgIfVisitor<'a> {
20+
pub(crate) fn new(parse_sess: &'a ParseSess, base_dir: Directory<'a>) -> CfgIfVisitor<'a> {
21+
CfgIfVisitor {
22+
mods: vec![],
23+
parse_sess,
24+
base_dir,
25+
}
26+
}
27+
28+
pub(crate) fn mods(self) -> Vec<ModItem> {
29+
self.mods
30+
}
31+
}
32+
33+
impl<'a, 'ast: 'a> Visitor<'ast> for CfgIfVisitor<'a> {
34+
fn visit_mac(&mut self, mac: &'ast ast::Mac) {
35+
match self.visit_mac_inner(mac) {
36+
Ok(()) => (),
37+
Err(e) => debug!("{}", e),
38+
}
39+
}
40+
}
41+
42+
impl<'a, 'ast: 'a> CfgIfVisitor<'a> {
43+
fn visit_mac_inner(&mut self, mac: &'ast ast::Mac) -> Result<(), &'static str> {
44+
if mac.node.path != Symbol::intern("cfg_if") {
45+
return Err("Expected cfg_if");
46+
}
47+
48+
let mut parser = stream_to_parser_with_base_dir(
49+
self.parse_sess,
50+
mac.node.tts.clone(),
51+
self.base_dir.clone(),
52+
);
53+
parser.cfg_mods = false;
54+
let mut process_if_cfg = true;
55+
56+
while parser.token != Token::Eof {
57+
if process_if_cfg {
58+
if !parser.eat_keyword(kw::If) {
59+
return Err("Expected `if`");
60+
}
61+
parser
62+
.parse_attribute(false)
63+
.map_err(|_| "Failed to parse attributes")?;
64+
}
65+
66+
if !parser.eat(&Token::OpenDelim(DelimToken::Brace)) {
67+
return Err("Expected an opening brace");
68+
}
69+
70+
while parser.token != Token::CloseDelim(DelimToken::Brace) && parser.token != Token::Eof
71+
{
72+
let item = match parser.parse_item() {
73+
Ok(Some(item_ptr)) => item_ptr.into_inner(),
74+
Ok(None) => continue,
75+
Err(mut err) => {
76+
err.cancel();
77+
parser.sess.span_diagnostic.reset_err_count();
78+
return Err(
79+
"Expected item inside cfg_if block, but failed to parse it as an item",
80+
);
81+
}
82+
};
83+
if let ast::ItemKind::Mod(..) = item.node {
84+
self.mods.push(ModItem { item });
85+
}
86+
}
87+
88+
if !parser.eat(&Token::CloseDelim(DelimToken::Brace)) {
89+
return Err("Expected a closing brace");
90+
}
91+
92+
if parser.eat(&Token::Eof) {
93+
break;
94+
}
95+
96+
if !parser.eat_keyword(kw::Else) {
97+
return Err("Expected `else`");
98+
}
99+
100+
process_if_cfg = parser.token.is_keyword(kw::If);
101+
}
102+
103+
Ok(())
104+
}
105+
}

0 commit comments

Comments
 (0)