]> git.proxmox.com Git - rustc.git/blame - src/tools/rustfmt/src/modules.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / src / tools / rustfmt / src / modules.rs
CommitLineData
f20569fa
XL
1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::path::{Path, PathBuf};
4
5use rustc_ast::ast;
f20569fa
XL
6use rustc_ast::visit::Visitor;
7use rustc_span::symbol::{self, sym, Symbol};
cdc7bbd5 8use rustc_span::Span;
f20569fa
XL
9use thiserror::Error;
10
11use crate::attr::MetaVisitor;
12use crate::config::FileName;
13use crate::items::is_mod_decl;
a2a8927a 14use crate::parse::parser::{
cdc7bbd5 15 Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError,
f20569fa 16};
a2a8927a 17use crate::parse::session::ParseSess;
3c0e092e 18use crate::utils::{contains_skip, mk_sp};
f20569fa
XL
19
20mod visitor;
21
22type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
23
24/// Represents module with its inner attributes.
25#[derive(Debug, Clone)]
26pub(crate) struct Module<'a> {
cdc7bbd5
XL
27 ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
28 pub(crate) items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
f2b60f7d 29 inner_attr: ast::AttrVec,
cdc7bbd5 30 pub(crate) span: Span,
f20569fa
XL
31}
32
33impl<'a> Module<'a> {
cdc7bbd5
XL
34 pub(crate) fn new(
35 mod_span: Span,
36 ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
37 mod_items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
f2b60f7d 38 mod_attrs: Cow<'a, ast::AttrVec>,
cdc7bbd5
XL
39 ) -> Self {
40 let inner_attr = mod_attrs
f20569fa
XL
41 .iter()
42 .filter(|attr| attr.style == ast::AttrStyle::Inner)
43 .cloned()
44 .collect();
45 Module {
cdc7bbd5 46 items: mod_items,
f20569fa 47 inner_attr,
cdc7bbd5
XL
48 span: mod_span,
49 ast_mod_kind,
f20569fa
XL
50 }
51 }
f20569fa 52
04454e1e 53 pub(crate) fn attrs(&self) -> &[ast::Attribute] {
f20569fa
XL
54 &self.inner_attr
55 }
f20569fa
XL
56}
57
58/// Maps each module to the corresponding file.
59pub(crate) struct ModResolver<'ast, 'sess> {
60 parse_sess: &'sess ParseSess,
61 directory: Directory,
62 file_map: FileModMap<'ast>,
63 recursive: bool,
64}
65
66/// Represents errors while trying to resolve modules.
f20569fa 67#[derive(Debug, Error)]
cdc7bbd5 68#[error("failed to resolve mod `{module}`: {kind}")]
f20569fa
XL
69pub struct ModuleResolutionError {
70 pub(crate) module: String,
71 pub(crate) kind: ModuleResolutionErrorKind,
72}
73
5e7ed085 74/// Defines variants similar to those of [rustc_expand::module::ModError]
f20569fa
XL
75#[derive(Debug, Error)]
76pub(crate) enum ModuleResolutionErrorKind {
77 /// Find a file that cannot be parsed.
78 #[error("cannot parse {file}")]
79 ParseError { file: PathBuf },
80 /// File cannot be found.
81 #[error("{file} does not exist")]
82 NotFound { file: PathBuf },
5e7ed085
FG
83 /// File a.rs and a/mod.rs both exist
84 #[error("file for module found at both {default_path:?} and {secondary_path:?}")]
85 MultipleCandidates {
86 default_path: PathBuf,
87 secondary_path: PathBuf,
88 },
f20569fa
XL
89}
90
91#[derive(Clone)]
92enum SubModKind<'a, 'ast> {
93 /// `mod foo;`
94 External(PathBuf, DirectoryOwnership, Module<'ast>),
95 /// `mod foo;` with multiple sources.
96 MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
97 /// `mod foo {}`
98 Internal(&'a ast::Item),
99}
100
101impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
102 /// Creates a new `ModResolver`.
103 pub(crate) fn new(
104 parse_sess: &'sess ParseSess,
105 directory_ownership: DirectoryOwnership,
106 recursive: bool,
107 ) -> Self {
108 ModResolver {
109 directory: Directory {
110 path: PathBuf::new(),
111 ownership: directory_ownership,
112 },
113 file_map: BTreeMap::new(),
114 parse_sess,
115 recursive,
116 }
117 }
118
119 /// Creates a map that maps a file name to the module in AST.
120 pub(crate) fn visit_crate(
121 mut self,
122 krate: &'ast ast::Crate,
123 ) -> Result<FileModMap<'ast>, ModuleResolutionError> {
5e7ed085 124 let root_filename = self.parse_sess.span_to_filename(krate.spans.inner_span);
f20569fa
XL
125 self.directory.path = match root_filename {
126 FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
127 _ => PathBuf::new(),
128 };
129
130 // Skip visiting sub modules when the input is from stdin.
131 if self.recursive {
cdc7bbd5 132 self.visit_mod_from_ast(&krate.items)?;
f20569fa
XL
133 }
134
5e7ed085 135 let snippet_provider = self.parse_sess.snippet_provider(krate.spans.inner_span);
3c0e092e 136
f20569fa
XL
137 self.file_map.insert(
138 root_filename,
cdc7bbd5 139 Module::new(
3c0e092e 140 mk_sp(snippet_provider.start_pos(), snippet_provider.end_pos()),
cdc7bbd5
XL
141 None,
142 Cow::Borrowed(&krate.items),
143 Cow::Borrowed(&krate.attrs),
144 ),
f20569fa
XL
145 );
146 Ok(self.file_map)
147 }
148
149 /// Visit `cfg_if` macro and look for module declarations.
150 fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> {
151 let mut visitor = visitor::CfgIfVisitor::new(self.parse_sess);
152 visitor.visit_item(&item);
153 for module_item in visitor.mods() {
cdc7bbd5 154 if let ast::ItemKind::Mod(_, ref sub_mod_kind) = module_item.item.kind {
f20569fa
XL
155 self.visit_sub_mod(
156 &module_item.item,
cdc7bbd5
XL
157 Module::new(
158 module_item.item.span,
159 Some(Cow::Owned(sub_mod_kind.clone())),
160 Cow::Owned(vec![]),
f2b60f7d 161 Cow::Owned(ast::AttrVec::new()),
cdc7bbd5 162 ),
f20569fa
XL
163 )?;
164 }
165 }
166 Ok(())
167 }
168
169 /// Visit modules defined inside macro calls.
cdc7bbd5
XL
170 fn visit_mod_outside_ast(
171 &mut self,
172 items: Vec<rustc_ast::ptr::P<ast::Item>>,
173 ) -> Result<(), ModuleResolutionError> {
174 for item in items {
f20569fa
XL
175 if is_cfg_if(&item) {
176 self.visit_cfg_if(Cow::Owned(item.into_inner()))?;
177 continue;
178 }
179
cdc7bbd5
XL
180 if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
181 let span = item.span;
182 self.visit_sub_mod(
183 &item,
184 Module::new(
185 span,
186 Some(Cow::Owned(sub_mod_kind.clone())),
187 Cow::Owned(vec![]),
f2b60f7d 188 Cow::Owned(ast::AttrVec::new()),
cdc7bbd5
XL
189 ),
190 )?;
f20569fa
XL
191 }
192 }
193 Ok(())
194 }
195
196 /// Visit modules from AST.
cdc7bbd5
XL
197 fn visit_mod_from_ast(
198 &mut self,
3c0e092e 199 items: &'ast [rustc_ast::ptr::P<ast::Item>],
cdc7bbd5
XL
200 ) -> Result<(), ModuleResolutionError> {
201 for item in items {
f20569fa
XL
202 if is_cfg_if(item) {
203 self.visit_cfg_if(Cow::Borrowed(item))?;
204 }
205
cdc7bbd5
XL
206 if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
207 let span = item.span;
208 self.visit_sub_mod(
209 item,
210 Module::new(
211 span,
212 Some(Cow::Borrowed(sub_mod_kind)),
213 Cow::Owned(vec![]),
214 Cow::Borrowed(&item.attrs),
215 ),
216 )?;
f20569fa
XL
217 }
218 }
219 Ok(())
220 }
221
222 fn visit_sub_mod(
223 &mut self,
224 item: &'c ast::Item,
225 sub_mod: Module<'ast>,
226 ) -> Result<(), ModuleResolutionError> {
227 let old_directory = self.directory.clone();
228 let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
229 if let Some(sub_mod_kind) = sub_mod_kind {
230 self.insert_sub_mod(sub_mod_kind.clone())?;
231 self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
232 }
233 self.directory = old_directory;
234 Ok(())
235 }
236
237 /// Inspect the given sub-module which we are about to visit and returns its kind.
238 fn peek_sub_mod(
239 &self,
240 item: &'c ast::Item,
241 sub_mod: &Module<'ast>,
242 ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
243 if contains_skip(&item.attrs) {
244 return Ok(None);
245 }
246
247 if is_mod_decl(item) {
248 // mod foo;
249 // Look for an extern file.
250 self.find_external_module(item.ident, &item.attrs, sub_mod)
251 } else {
252 // An internal module (`mod foo { /* ... */ }`);
253 Ok(Some(SubModKind::Internal(item)))
254 }
255 }
256
257 fn insert_sub_mod(
258 &mut self,
259 sub_mod_kind: SubModKind<'c, 'ast>,
260 ) -> Result<(), ModuleResolutionError> {
261 match sub_mod_kind {
262 SubModKind::External(mod_path, _, sub_mod) => {
263 self.file_map
264 .entry(FileName::Real(mod_path))
265 .or_insert(sub_mod);
266 }
267 SubModKind::MultiExternal(mods) => {
268 for (mod_path, _, sub_mod) in mods {
269 self.file_map
270 .entry(FileName::Real(mod_path))
271 .or_insert(sub_mod);
272 }
273 }
274 _ => (),
275 }
276 Ok(())
277 }
278
279 fn visit_sub_mod_inner(
280 &mut self,
281 sub_mod: Module<'ast>,
282 sub_mod_kind: SubModKind<'c, 'ast>,
283 ) -> Result<(), ModuleResolutionError> {
284 match sub_mod_kind {
285 SubModKind::External(mod_path, directory_ownership, sub_mod) => {
286 let directory = Directory {
287 path: mod_path.parent().unwrap().to_path_buf(),
288 ownership: directory_ownership,
289 };
290 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
291 }
3c0e092e 292 SubModKind::Internal(item) => {
f20569fa
XL
293 self.push_inline_mod_directory(item.ident, &item.attrs);
294 self.visit_sub_mod_after_directory_update(sub_mod, None)
295 }
296 SubModKind::MultiExternal(mods) => {
297 for (mod_path, directory_ownership, sub_mod) in mods {
298 let directory = Directory {
299 path: mod_path.parent().unwrap().to_path_buf(),
300 ownership: directory_ownership,
301 };
302 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
303 }
304 Ok(())
305 }
306 }
307 }
308
309 fn visit_sub_mod_after_directory_update(
310 &mut self,
311 sub_mod: Module<'ast>,
312 directory: Option<Directory>,
313 ) -> Result<(), ModuleResolutionError> {
314 if let Some(directory) = directory {
315 self.directory = directory;
316 }
cdc7bbd5 317 match (sub_mod.ast_mod_kind, sub_mod.items) {
17df50a5 318 (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _))), _) => {
3c0e092e
XL
319 self.visit_mod_from_ast(items)
320 }
321 (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
322 self.visit_mod_outside_ast(items)
cdc7bbd5 323 }
cdc7bbd5 324 (_, _) => Ok(()),
f20569fa
XL
325 }
326 }
327
328 /// Find a file path in the filesystem which corresponds to the given module.
329 fn find_external_module(
330 &self,
331 mod_name: symbol::Ident,
332 attrs: &[ast::Attribute],
333 sub_mod: &Module<'ast>,
334 ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
335 let relative = match self.directory.ownership {
336 DirectoryOwnership::Owned { relative } => relative,
cdc7bbd5 337 DirectoryOwnership::UnownedViaBlock => None,
f20569fa
XL
338 };
339 if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
340 if self.parse_sess.is_file_parsed(&path) {
341 return Ok(None);
342 }
cdc7bbd5
XL
343 return match Parser::parse_file_as_module(self.parse_sess, &path, sub_mod.span) {
344 Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
345 Ok((attrs, items, span)) => Ok(Some(SubModKind::External(
f20569fa
XL
346 path,
347 DirectoryOwnership::Owned { relative: None },
cdc7bbd5
XL
348 Module::new(
349 span,
350 Some(Cow::Owned(ast::ModKind::Unloaded)),
351 Cow::Owned(items),
352 Cow::Owned(attrs),
353 ),
f20569fa
XL
354 ))),
355 Err(ParserError::ParseError) => Err(ModuleResolutionError {
356 module: mod_name.to_string(),
357 kind: ModuleResolutionErrorKind::ParseError { file: path },
358 }),
359 Err(..) => Err(ModuleResolutionError {
360 module: mod_name.to_string(),
361 kind: ModuleResolutionErrorKind::NotFound { file: path },
362 }),
363 };
364 }
365
366 // Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
367 let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod);
368
369 match self
370 .parse_sess
371 .default_submod_path(mod_name, relative, &self.directory.path)
f20569fa
XL
372 {
373 Ok(ModulePathSuccess {
cdc7bbd5
XL
374 file_path,
375 dir_ownership,
376 ..
f20569fa
XL
377 }) => {
378 let outside_mods_empty = mods_outside_ast.is_empty();
379 let should_insert = !mods_outside_ast
380 .iter()
cdc7bbd5
XL
381 .any(|(outside_path, _, _)| outside_path == &file_path);
382 if self.parse_sess.is_file_parsed(&file_path) {
f20569fa
XL
383 if outside_mods_empty {
384 return Ok(None);
385 } else {
386 if should_insert {
cdc7bbd5 387 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
f20569fa
XL
388 }
389 return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
390 }
391 }
cdc7bbd5
XL
392 match Parser::parse_file_as_module(self.parse_sess, &file_path, sub_mod.span) {
393 Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
394 Ok((attrs, items, span)) if outside_mods_empty => {
395 Ok(Some(SubModKind::External(
396 file_path,
397 dir_ownership,
398 Module::new(
399 span,
400 Some(Cow::Owned(ast::ModKind::Unloaded)),
401 Cow::Owned(items),
402 Cow::Owned(attrs),
403 ),
404 )))
405 }
406 Ok((attrs, items, span)) => {
f20569fa 407 mods_outside_ast.push((
cdc7bbd5
XL
408 file_path.clone(),
409 dir_ownership,
410 Module::new(
411 span,
412 Some(Cow::Owned(ast::ModKind::Unloaded)),
413 Cow::Owned(items),
414 Cow::Owned(attrs),
415 ),
f20569fa
XL
416 ));
417 if should_insert {
cdc7bbd5 418 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
f20569fa
XL
419 }
420 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
421 }
422 Err(ParserError::ParseError) => Err(ModuleResolutionError {
423 module: mod_name.to_string(),
cdc7bbd5 424 kind: ModuleResolutionErrorKind::ParseError { file: file_path },
f20569fa
XL
425 }),
426 Err(..) if outside_mods_empty => Err(ModuleResolutionError {
427 module: mod_name.to_string(),
cdc7bbd5 428 kind: ModuleResolutionErrorKind::NotFound { file: file_path },
f20569fa
XL
429 }),
430 Err(..) => {
431 if should_insert {
cdc7bbd5 432 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
f20569fa
XL
433 }
434 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
435 }
436 }
437 }
cdc7bbd5 438 Err(mod_err) if !mods_outside_ast.is_empty() => {
5e7ed085 439 if let ModError::ParserError(e) = mod_err {
cdc7bbd5
XL
440 e.cancel();
441 }
f20569fa
XL
442 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
443 }
5e7ed085
FG
444 Err(e) => match e {
445 ModError::FileNotFound(_, default_path, _secondary_path) => {
446 Err(ModuleResolutionError {
447 module: mod_name.to_string(),
448 kind: ModuleResolutionErrorKind::NotFound { file: default_path },
449 })
450 }
451 ModError::MultipleCandidates(_, default_path, secondary_path) => {
452 Err(ModuleResolutionError {
453 module: mod_name.to_string(),
454 kind: ModuleResolutionErrorKind::MultipleCandidates {
455 default_path,
456 secondary_path,
457 },
458 })
459 }
460 ModError::ParserError(_)
461 | ModError::CircularInclusion(_)
462 | ModError::ModInBlock(_) => Err(ModuleResolutionError {
463 module: mod_name.to_string(),
464 kind: ModuleResolutionErrorKind::ParseError {
465 file: self.directory.path.clone(),
466 },
467 }),
468 },
f20569fa
XL
469 }
470 }
471
472 fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) {
473 if let Some(path) = find_path_value(attrs) {
a2a8927a 474 self.directory.path.push(path.as_str());
f20569fa
XL
475 self.directory.ownership = DirectoryOwnership::Owned { relative: None };
476 } else {
5e7ed085 477 let id = id.as_str();
f20569fa
XL
478 // We have to push on the current module name in the case of relative
479 // paths in order to ensure that any additional module paths from inline
480 // `mod x { ... }` come after the relative extension.
481 //
482 // For example, a `mod z { ... }` inside `x/y.rs` should set the current
483 // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
484 if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
485 if let Some(ident) = relative.take() {
486 // remove the relative offset
a2a8927a 487 self.directory.path.push(ident.as_str());
5e7ed085
FG
488
489 // In the case where there is an x.rs and an ./x directory we want
490 // to prevent adding x twice. For example, ./x/x
491 if self.directory.path.exists() && !self.directory.path.join(id).exists() {
492 return;
493 }
f20569fa
XL
494 }
495 }
5e7ed085 496 self.directory.path.push(id);
f20569fa
XL
497 }
498 }
499
500 fn find_mods_outside_of_ast(
501 &self,
502 attrs: &[ast::Attribute],
503 sub_mod: &Module<'ast>,
504 ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> {
505 // Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
506 let mut path_visitor = visitor::PathVisitor::default();
507 for attr in attrs.iter() {
508 if let Some(meta) = attr.meta() {
509 path_visitor.visit_meta_item(&meta)
510 }
511 }
512 let mut result = vec![];
513 for path in path_visitor.paths() {
514 let mut actual_path = self.directory.path.clone();
515 actual_path.push(&path);
516 if !actual_path.exists() {
517 continue;
518 }
519 if self.parse_sess.is_file_parsed(&actual_path) {
520 // If the specified file is already parsed, then we just use that.
521 result.push((
522 actual_path,
523 DirectoryOwnership::Owned { relative: None },
524 sub_mod.clone(),
525 ));
526 continue;
527 }
cdc7bbd5
XL
528 let (attrs, items, span) =
529 match Parser::parse_file_as_module(self.parse_sess, &actual_path, sub_mod.span) {
530 Ok((ref attrs, _, _)) if contains_skip(attrs) => continue,
531 Ok(m) => m,
532 Err(..) => continue,
533 };
f20569fa
XL
534
535 result.push((
536 actual_path,
537 DirectoryOwnership::Owned { relative: None },
cdc7bbd5
XL
538 Module::new(
539 span,
540 Some(Cow::Owned(ast::ModKind::Unloaded)),
541 Cow::Owned(items),
542 Cow::Owned(attrs),
543 ),
f20569fa
XL
544 ))
545 }
546 result
547 }
548}
549
550fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
551 if attr.has_name(sym::path) {
552 attr.value_str()
553 } else {
554 None
555 }
556}
557
558// N.B., even when there are multiple `#[path = ...]` attributes, we just need to
559// examine the first one, since rustc ignores the second and the subsequent ones
560// as unused attributes.
561fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
562 attrs.iter().flat_map(path_value).next()
563}
564
565fn is_cfg_if(item: &ast::Item) -> bool {
566 match item.kind {
567 ast::ItemKind::MacCall(ref mac) => {
568 if let Some(first_segment) = mac.path.segments.first() {
569 if first_segment.ident.name == Symbol::intern("cfg_if") {
570 return true;
571 }
572 }
573 false
574 }
575 _ => false,
576 }
577}