1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::path::{Path, PathBuf};
4
5use rustc_ast::ast;
6use rustc_ast::visit::Visitor;
7use rustc_span::Span;
8use rustc_span::symbol::{self, Symbol, sym};
9use thin_vec::ThinVec;
10use thiserror::Error;
11
12use crate::attr::MetaVisitor;
13use crate::config::FileName;
14use crate::items::is_mod_decl;
15use crate::parse::parser::{
16 Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError,
17};
18use crate::parse::session::ParseSess;
19use crate::utils::{contains_custom_attributes, contains_skip, mk_sp};
20
21mod visitor;
22
23type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
24
25#[derive(Debug, Clone)]
27pub(crate) struct Module<'a> {
28 ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
29 pub(crate) items: Cow<'a, ThinVec<Box<ast::Item>>>,
30 inner_attr: ast::AttrVec,
31 pub(crate) span: Span,
32}
33
34impl<'a> Module<'a> {
35 pub(crate) fn new(
36 mod_span: Span,
37 ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
38 mod_items: Cow<'a, ThinVec<Box<ast::Item>>>,
39 mod_attrs: Cow<'a, ast::AttrVec>,
40 ) -> Self {
41 let inner_attr = mod_attrs
42 .iter()
43 .filter(|attr| attr.style == ast::AttrStyle::Inner)
44 .cloned()
45 .collect();
46 Module {
47 items: mod_items,
48 inner_attr,
49 span: mod_span,
50 ast_mod_kind,
51 }
52 }
53
54 pub(crate) fn attrs(&self) -> &[ast::Attribute] {
55 &self.inner_attr
56 }
57}
58
59pub(crate) struct ModResolver<'ast, 'psess> {
61 psess: &'psess ParseSess,
62 directory: Directory,
63 file_map: FileModMap<'ast>,
64 recursive: bool,
65}
66
67#[derive(Debug, Error)]
69#[error("failed to resolve mod `{module}`: {kind}")]
70pub struct ModuleResolutionError {
71 pub(crate) module: String,
72 pub(crate) kind: ModuleResolutionErrorKind,
73}
74
75#[derive(Debug, Error)]
77pub(crate) enum ModuleResolutionErrorKind {
78 #[error("cannot parse {file}")]
80 ParseError { file: PathBuf },
81 #[error("{file} does not exist")]
83 NotFound { file: PathBuf },
84 #[error("file for module found at both {default_path:?} and {secondary_path:?}")]
86 MultipleCandidates {
87 default_path: PathBuf,
88 secondary_path: PathBuf,
89 },
90}
91
92#[derive(Clone)]
93enum SubModKind<'a, 'ast> {
94 External(PathBuf, DirectoryOwnership, Module<'ast>),
96 MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
98 Internal(&'a ast::Item),
100}
101
102impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> {
103 pub(crate) fn new(
105 psess: &'psess ParseSess,
106 directory_ownership: DirectoryOwnership,
107 recursive: bool,
108 ) -> Self {
109 ModResolver {
110 directory: Directory {
111 path: PathBuf::new(),
112 ownership: directory_ownership,
113 },
114 file_map: BTreeMap::new(),
115 psess,
116 recursive,
117 }
118 }
119
120 pub(crate) fn visit_crate(
122 mut self,
123 krate: &'ast ast::Crate,
124 ) -> Result<FileModMap<'ast>, ModuleResolutionError> {
125 let root_filename = self.psess.span_to_filename(krate.spans.inner_span);
126 self.directory.path = match root_filename {
127 FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
128 _ => PathBuf::new(),
129 };
130
131 if self.recursive {
133 self.visit_mod_from_ast(&krate.items)?;
134 }
135
136 let snippet_provider = self.psess.snippet_provider(krate.spans.inner_span);
137
138 self.file_map.insert(
139 root_filename,
140 Module::new(
141 mk_sp(snippet_provider.start_pos(), snippet_provider.end_pos()),
142 None,
143 Cow::Borrowed(&krate.items),
144 Cow::Borrowed(&krate.attrs),
145 ),
146 );
147 Ok(self.file_map)
148 }
149
150 fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> {
152 let mut visitor = visitor::CfgIfVisitor::new(self.psess);
153 visitor.visit_item(&item);
154 for module_item in visitor.mods() {
155 if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = module_item.item.kind {
156 self.visit_sub_mod(
157 &module_item.item,
158 Module::new(
159 module_item.item.span,
160 Some(Cow::Owned(sub_mod_kind.clone())),
161 Cow::Owned(ThinVec::new()),
162 Cow::Owned(ast::AttrVec::new()),
163 ),
164 )?;
165 }
166 }
167 Ok(())
168 }
169
170 fn visit_cfg_select(
171 &mut self,
172 item: Cow<'ast, ast::Item>,
173 ) -> Result<(), ModuleResolutionError> {
174 let mut visitor = visitor::CfgSelectVisitor::new(self.psess);
175 visitor.visit_item(&item);
176 for module_item in visitor.mods() {
177 if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = module_item.item.kind {
178 self.visit_sub_mod(
179 &module_item.item,
180 Module::new(
181 module_item.item.span,
182 Some(Cow::Owned(sub_mod_kind.clone())),
183 Cow::Owned(ThinVec::new()),
184 Cow::Owned(ast::AttrVec::new()),
185 ),
186 )?;
187 }
188 }
189 Ok(())
190 }
191
192 fn visit_mod_outside_ast(
194 &mut self,
195 items: ThinVec<Box<ast::Item>>,
196 ) -> Result<(), ModuleResolutionError> {
197 for item in items {
198 if is_cfg_if(&item) {
199 self.visit_cfg_if(Cow::Owned(*item))?;
200 continue;
201 }
202
203 if is_cfg_select(&item) {
204 self.visit_cfg_select(Cow::Owned(*item))?;
205 continue;
206 }
207
208 if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = item.kind {
209 let span = item.span;
210 self.visit_sub_mod(
211 &item,
212 Module::new(
213 span,
214 Some(Cow::Owned(sub_mod_kind.clone())),
215 Cow::Owned(ThinVec::new()),
216 Cow::Owned(ast::AttrVec::new()),
217 ),
218 )?;
219 }
220 }
221 Ok(())
222 }
223
224 fn visit_mod_from_ast(
226 &mut self,
227 items: &'ast [Box<ast::Item>],
228 ) -> Result<(), ModuleResolutionError> {
229 for item in items {
230 if is_cfg_if(item) {
231 self.visit_cfg_if(Cow::Borrowed(item))?;
232 }
233
234 if is_cfg_select(item) {
235 self.visit_cfg_select(Cow::Borrowed(item))?;
236 }
237
238 if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = item.kind {
239 let span = item.span;
240 self.visit_sub_mod(
241 item,
242 Module::new(
243 span,
244 Some(Cow::Borrowed(sub_mod_kind)),
245 Cow::Owned(ThinVec::new()),
246 Cow::Borrowed(&item.attrs),
247 ),
248 )?;
249 }
250 }
251 Ok(())
252 }
253
254 fn visit_sub_mod(
255 &mut self,
256 item: &'c ast::Item,
257 sub_mod: Module<'ast>,
258 ) -> Result<(), ModuleResolutionError> {
259 let old_directory = self.directory.clone();
260 let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
261 if let Some(sub_mod_kind) = sub_mod_kind {
262 self.insert_sub_mod(sub_mod_kind.clone())?;
263 self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
264 }
265 self.directory = old_directory;
266 Ok(())
267 }
268
269 fn peek_sub_mod(
271 &self,
272 item: &'c ast::Item,
273 sub_mod: &Module<'ast>,
274 ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
275 if contains_skip(&item.attrs) {
276 return Ok(None);
277 }
278
279 if is_mod_decl(item) {
280 self.find_external_module(item.kind.ident().unwrap(), &item.attrs, sub_mod)
283 } else {
284 Ok(Some(SubModKind::Internal(item)))
286 }
287 }
288
289 fn insert_sub_mod(
290 &mut self,
291 sub_mod_kind: SubModKind<'c, 'ast>,
292 ) -> Result<(), ModuleResolutionError> {
293 match sub_mod_kind {
294 SubModKind::External(mod_path, _, sub_mod) => {
295 self.file_map
296 .entry(FileName::Real(mod_path))
297 .or_insert(sub_mod);
298 }
299 SubModKind::MultiExternal(mods) => {
300 for (mod_path, _, sub_mod) in mods {
301 self.file_map
302 .entry(FileName::Real(mod_path))
303 .or_insert(sub_mod);
304 }
305 }
306 _ => (),
307 }
308 Ok(())
309 }
310
311 fn visit_sub_mod_inner(
312 &mut self,
313 sub_mod: Module<'ast>,
314 sub_mod_kind: SubModKind<'c, 'ast>,
315 ) -> Result<(), ModuleResolutionError> {
316 match sub_mod_kind {
317 SubModKind::External(mod_path, directory_ownership, sub_mod) => {
318 let directory = Directory {
319 path: mod_path.parent().unwrap().to_path_buf(),
320 ownership: directory_ownership,
321 };
322 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
323 }
324 SubModKind::Internal(item) => {
325 self.push_inline_mod_directory(item.kind.ident().unwrap(), &item.attrs);
326 self.visit_sub_mod_after_directory_update(sub_mod, None)
327 }
328 SubModKind::MultiExternal(mods) => {
329 for (mod_path, directory_ownership, sub_mod) in mods {
330 let directory = Directory {
331 path: mod_path.parent().unwrap().to_path_buf(),
332 ownership: directory_ownership,
333 };
334 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
335 }
336 Ok(())
337 }
338 }
339 }
340
341 fn visit_sub_mod_after_directory_update(
342 &mut self,
343 sub_mod: Module<'ast>,
344 directory: Option<Directory>,
345 ) -> Result<(), ModuleResolutionError> {
346 if let Some(directory) = directory {
347 self.directory = directory;
348 }
349 match (sub_mod.ast_mod_kind, sub_mod.items) {
350 (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _))), _) => {
351 self.visit_mod_from_ast(items)
352 }
353 (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
354 self.visit_mod_outside_ast(items)
355 }
356 (_, _) => Ok(()),
357 }
358 }
359
360 fn find_external_module(
362 &self,
363 mod_name: symbol::Ident,
364 attrs: &[ast::Attribute],
365 sub_mod: &Module<'ast>,
366 ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
367 let relative = match self.directory.ownership {
368 DirectoryOwnership::Owned { relative } => relative,
369 DirectoryOwnership::UnownedViaBlock => None,
370 };
371 if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
372 if self.psess.is_file_parsed(&path) {
373 return Ok(None);
374 }
375 return match Parser::parse_file_as_module(self.psess, &path, sub_mod.span) {
376 Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
377 Ok((attrs, items, span)) => Ok(Some(SubModKind::External(
378 path,
379 DirectoryOwnership::Owned { relative: None },
380 Module::new(
381 span,
382 Some(Cow::Owned(ast::ModKind::Unloaded)),
383 Cow::Owned(items),
384 Cow::Owned(attrs),
385 ),
386 ))),
387 Err(ParserError::ParseError) => Err(ModuleResolutionError {
388 module: mod_name.to_string(),
389 kind: ModuleResolutionErrorKind::ParseError { file: path },
390 }),
391 Err(..) => Err(ModuleResolutionError {
392 module: mod_name.to_string(),
393 kind: ModuleResolutionErrorKind::NotFound { file: path },
394 }),
395 };
396 }
397
398 let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod);
400
401 match self
402 .psess
403 .default_submod_path(mod_name, relative, &self.directory.path)
404 {
405 Ok(ModulePathSuccess {
406 file_path,
407 dir_ownership,
408 ..
409 }) => {
410 let outside_mods_empty = mods_outside_ast.is_empty();
411 let should_insert = !mods_outside_ast
412 .iter()
413 .any(|(outside_path, _, _)| outside_path == &file_path);
414 if self.psess.is_file_parsed(&file_path) {
415 if outside_mods_empty {
416 return Ok(None);
417 } else {
418 if should_insert {
419 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
420 }
421 return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
422 }
423 }
424 match Parser::parse_file_as_module(self.psess, &file_path, sub_mod.span) {
425 Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
426 Ok((attrs, items, span)) if outside_mods_empty => {
427 Ok(Some(SubModKind::External(
428 file_path,
429 dir_ownership,
430 Module::new(
431 span,
432 Some(Cow::Owned(ast::ModKind::Unloaded)),
433 Cow::Owned(items),
434 Cow::Owned(attrs),
435 ),
436 )))
437 }
438 Ok((attrs, items, span)) => {
439 mods_outside_ast.push((
440 file_path.clone(),
441 dir_ownership,
442 Module::new(
443 span,
444 Some(Cow::Owned(ast::ModKind::Unloaded)),
445 Cow::Owned(items),
446 Cow::Owned(attrs),
447 ),
448 ));
449 if should_insert {
450 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
451 }
452 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
453 }
454 Err(ParserError::ParseError) => Err(ModuleResolutionError {
455 module: mod_name.to_string(),
456 kind: ModuleResolutionErrorKind::ParseError { file: file_path },
457 }),
458 Err(..) if outside_mods_empty => Err(ModuleResolutionError {
459 module: mod_name.to_string(),
460 kind: ModuleResolutionErrorKind::NotFound { file: file_path },
461 }),
462 Err(..) => {
463 if should_insert {
464 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
465 }
466 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
467 }
468 }
469 }
470 Err(mod_err) if !mods_outside_ast.is_empty() => {
471 if let ModError::ParserError(e) = mod_err {
472 e.cancel();
473 }
474 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
475 }
476 Err(e) => match e {
477 ModError::FileNotFound(_, default_path, _secondary_path) => {
478 if contains_custom_attributes(attrs) {
479 tracing::warn!("Couldn't find file for mod {};`", mod_name.to_string());
485 return Ok(None);
486 }
487
488 Err(ModuleResolutionError {
489 module: mod_name.to_string(),
490 kind: ModuleResolutionErrorKind::NotFound { file: default_path },
491 })
492 }
493 ModError::MultipleCandidates(_, default_path, secondary_path) => {
494 Err(ModuleResolutionError {
495 module: mod_name.to_string(),
496 kind: ModuleResolutionErrorKind::MultipleCandidates {
497 default_path,
498 secondary_path,
499 },
500 })
501 }
502 ModError::ParserError(_)
503 | ModError::CircularInclusion(_)
504 | ModError::ModInBlock(_) => Err(ModuleResolutionError {
505 module: mod_name.to_string(),
506 kind: ModuleResolutionErrorKind::ParseError {
507 file: self.directory.path.clone(),
508 },
509 }),
510 },
511 }
512 }
513
514 fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) {
515 if let Some(path) = find_path_value(attrs) {
516 self.directory.path.push(path.as_str());
517 self.directory.ownership = DirectoryOwnership::Owned { relative: None };
518 } else {
519 let id = id.as_str();
520 if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
527 if let Some(ident) = relative.take() {
528 self.directory.path.push(ident.as_str());
530
531 if self.directory.path.exists() && !self.directory.path.join(id).exists() {
534 return;
535 }
536 }
537 }
538 self.directory.path.push(id);
539 }
540 }
541
542 fn find_mods_outside_of_ast(
543 &self,
544 attrs: &[ast::Attribute],
545 sub_mod: &Module<'ast>,
546 ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> {
547 let mut path_visitor = visitor::PathVisitor::default();
549 for attr in attrs.iter() {
550 if let Some(meta) = attr.meta() {
551 path_visitor.visit_meta_item(&meta)
552 }
553 }
554 let mut result = vec![];
555 for path in path_visitor.paths() {
556 let mut actual_path = self.directory.path.clone();
557 actual_path.push(&path);
558 if !actual_path.exists() {
559 continue;
560 }
561 if self.psess.is_file_parsed(&actual_path) {
562 result.push((
564 actual_path,
565 DirectoryOwnership::Owned { relative: None },
566 sub_mod.clone(),
567 ));
568 continue;
569 }
570 let (attrs, items, span) =
571 match Parser::parse_file_as_module(self.psess, &actual_path, sub_mod.span) {
572 Ok((ref attrs, _, _)) if contains_skip(attrs) => continue,
573 Ok(m) => m,
574 Err(..) => continue,
575 };
576
577 result.push((
578 actual_path,
579 DirectoryOwnership::Owned { relative: None },
580 Module::new(
581 span,
582 Some(Cow::Owned(ast::ModKind::Unloaded)),
583 Cow::Owned(items),
584 Cow::Owned(attrs),
585 ),
586 ));
587 }
588 result
589 }
590}
591
592fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
593 if attr.has_name(sym::path) {
594 attr.value_str()
595 } else {
596 None
597 }
598}
599
600fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
604 attrs.iter().flat_map(path_value).next()
605}
606
607fn is_cfg_if(item: &ast::Item) -> bool {
608 match item.kind {
609 ast::ItemKind::MacCall(ref mac) => {
610 if let Some(first_segment) = mac.path.segments.first() {
611 if first_segment.ident.name == Symbol::intern("cfg_if") {
612 return true;
613 }
614 }
615 false
616 }
617 _ => false,
618 }
619}
620
621fn is_cfg_select(item: &ast::Item) -> bool {
622 match item.kind {
623 ast::ItemKind::MacCall(ref mac) => {
624 if let Some(last_segment) = mac.path.segments.last() {
625 if last_segment.ident.name == Symbol::intern("cfg_select") {
626 return true;
627 }
628 }
629 false
630 }
631 _ => false,
632 }
633}