1 use crate::base
::ModuleData
;
3 use rustc_ast
::{token, Attribute, Inline, Item}
;
4 use rustc_errors
::{struct_span_err, DiagnosticBuilder}
;
5 use rustc_parse
::new_parser_from_file
;
6 use rustc_session
::parse
::ParseSess
;
7 use rustc_session
::Session
;
8 use rustc_span
::symbol
::{sym, Ident}
;
11 use std
::path
::{self, Path, PathBuf}
;
13 #[derive(Copy, Clone)]
14 pub enum DirOwnership
{
16 // None if `mod.rs`, `Some("foo")` if we're in `foo.rs`.
17 relative
: Option
<Ident
>,
22 // Public for rustfmt usage.
23 pub struct ModulePathSuccess
{
24 pub file_path
: PathBuf
,
25 pub dir_ownership
: DirOwnership
,
28 crate struct ParsedExternalMod
{
29 pub items
: Vec
<P
<Item
>>,
31 pub file_path
: PathBuf
,
32 pub dir_path
: PathBuf
,
33 pub dir_ownership
: DirOwnership
,
36 pub enum ModError
<'a
> {
37 CircularInclusion(Vec
<PathBuf
>),
38 ModInBlock(Option
<Ident
>),
39 FileNotFound(Ident
, PathBuf
),
40 MultipleCandidates(Ident
, String
, String
),
41 ParserError(DiagnosticBuilder
<'a
>),
44 crate fn parse_external_mod(
47 span
: Span
, // The span to blame on errors.
49 mut dir_ownership
: DirOwnership
,
50 attrs
: &mut Vec
<Attribute
>,
51 ) -> ParsedExternalMod
{
52 // We bail on the first error, but that error does not cause a fatal error... (1)
53 let result
: Result
<_
, ModError
<'_
>> = try
{
54 // Extract the file path and the new ownership.
55 let mp
= mod_file_path(sess
, ident
, &attrs
, &module
.dir_path
, dir_ownership
)?
;
56 dir_ownership
= mp
.dir_ownership
;
58 // Ensure file paths are acyclic.
59 if let Some(pos
) = module
.file_path_stack
.iter().position(|p
| p
== &mp
.file_path
) {
60 Err(ModError
::CircularInclusion(module
.file_path_stack
[pos
..].to_vec()))?
;
63 // Actually parse the external file as a module.
64 let mut parser
= new_parser_from_file(&sess
.parse_sess
, &mp
.file_path
, Some(span
));
65 let (mut inner_attrs
, items
, inner_span
) =
66 parser
.parse_mod(&token
::Eof
).map_err(|err
| ModError
::ParserError(err
))?
;
67 attrs
.append(&mut inner_attrs
);
68 (items
, inner_span
, mp
.file_path
)
70 // (1) ...instead, we return a dummy module.
71 let (items
, inner_span
, file_path
) =
72 result
.map_err(|err
| err
.report(sess
, span
)).unwrap_or_default();
74 // Extract the directory path for submodules of the module.
75 let dir_path
= file_path
.parent().unwrap_or(&file_path
).to_owned();
77 ParsedExternalMod { items, inner_span, file_path, dir_path, dir_ownership }
80 crate fn mod_dir_path(
85 mut dir_ownership
: DirOwnership
,
87 ) -> (PathBuf
, DirOwnership
) {
90 if let Some(file_path
) = mod_file_path_from_attr(sess
, attrs
, &module
.dir_path
) {
91 // For inline modules file path from `#[path]` is actually the directory path
92 // for historical reasons, so we don't pop the last segment here.
93 return (file_path
, DirOwnership
::Owned { relative: None }
);
96 // We have to push on the current module name in the case of relative
97 // paths in order to ensure that any additional module paths from inline
98 // `mod x { ... }` come after the relative extension.
100 // For example, a `mod z { ... }` inside `x/y.rs` should set the current
101 // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
102 let mut dir_path
= module
.dir_path
.clone();
103 if let DirOwnership
::Owned { relative }
= &mut dir_ownership
{
104 if let Some(ident
) = relative
.take() {
105 // Remove the relative offset.
106 dir_path
.push(&*ident
.as_str());
109 dir_path
.push(&*ident
.as_str());
111 (dir_path
, dir_ownership
)
114 // FIXME: This is a subset of `parse_external_mod` without actual parsing,
115 // check whether the logic for unloaded, loaded and inline modules can be unified.
116 let file_path
= mod_file_path(sess
, ident
, &attrs
, &module
.dir_path
, dir_ownership
)
118 dir_ownership
= mp
.dir_ownership
;
121 .unwrap_or_default();
123 // Extract the directory path for submodules of the module.
124 let dir_path
= file_path
.parent().unwrap_or(&file_path
).to_owned();
126 (dir_path
, dir_ownership
)
131 fn mod_file_path
<'a
>(
136 dir_ownership
: DirOwnership
,
137 ) -> Result
<ModulePathSuccess
, ModError
<'a
>> {
138 if let Some(file_path
) = mod_file_path_from_attr(sess
, attrs
, dir_path
) {
139 // All `#[path]` files are treated as though they are a `mod.rs` file.
140 // This means that `mod foo;` declarations inside `#[path]`-included
141 // files are siblings,
143 // Note that this will produce weirdness when a file named `foo.rs` is
144 // `#[path]` included and contains a `mod foo;` declaration.
145 // If you encounter this, it's your own darn fault :P
146 let dir_ownership
= DirOwnership
::Owned { relative: None }
;
147 return Ok(ModulePathSuccess { file_path, dir_ownership }
);
150 let relative
= match dir_ownership
{
151 DirOwnership
::Owned { relative }
=> relative
,
152 DirOwnership
::UnownedViaBlock
=> None
,
154 let result
= default_submod_path(&sess
.parse_sess
, ident
, relative
, dir_path
);
155 match dir_ownership
{
156 DirOwnership
::Owned { .. }
=> result
,
157 DirOwnership
::UnownedViaBlock
=> Err(ModError
::ModInBlock(match result
{
158 Ok(_
) | Err(ModError
::MultipleCandidates(..)) => Some(ident
),
164 /// Derive a submodule path from the first found `#[path = "path_string"]`.
165 /// The provided `dir_path` is joined with the `path_string`.
166 fn mod_file_path_from_attr(
170 ) -> Option
<PathBuf
> {
171 // Extract path string from first `#[path = "path_string"]` attribute.
172 let path_string
= sess
.first_attr_value_str_by_name(attrs
, sym
::path
)?
.as_str();
174 // On windows, the base path might have the form
175 // `\\?\foo\bar` in which case it does not tolerate
176 // mixed `/` and `\` separators, so canonicalize
179 let path_string
= path_string
.replace("/", "\\");
181 Some(dir_path
.join(&*path_string
))
184 /// Returns a path to a module.
185 // Public for rustfmt usage.
186 pub fn default_submod_path
<'a
>(
189 relative
: Option
<Ident
>,
191 ) -> Result
<ModulePathSuccess
, ModError
<'a
>> {
192 // If we're in a foo.rs file instead of a mod.rs file,
193 // we need to look for submodules in
194 // `./foo/<ident>.rs` and `./foo/<ident>/mod.rs` rather than
195 // `./<ident>.rs` and `./<ident>/mod.rs`.
196 let relative_prefix_string
;
197 let relative_prefix
= if let Some(ident
) = relative
{
198 relative_prefix_string
= format
!("{}{}", ident
.name
, path
::MAIN_SEPARATOR
);
199 &relative_prefix_string
204 let mod_name
= ident
.name
.to_string();
205 let default_path_str
= format
!("{}{}.rs", relative_prefix
, mod_name
);
206 let secondary_path_str
=
207 format
!("{}{}{}mod.rs", relative_prefix
, mod_name
, path
::MAIN_SEPARATOR
);
208 let default_path
= dir_path
.join(&default_path_str
);
209 let secondary_path
= dir_path
.join(&secondary_path_str
);
210 let default_exists
= sess
.source_map().file_exists(&default_path
);
211 let secondary_exists
= sess
.source_map().file_exists(&secondary_path
);
213 match (default_exists
, secondary_exists
) {
214 (true, false) => Ok(ModulePathSuccess
{
215 file_path
: default_path
,
216 dir_ownership
: DirOwnership
::Owned { relative: Some(ident) }
,
218 (false, true) => Ok(ModulePathSuccess
{
219 file_path
: secondary_path
,
220 dir_ownership
: DirOwnership
::Owned { relative: None }
,
222 (false, false) => Err(ModError
::FileNotFound(ident
, default_path
)),
224 Err(ModError
::MultipleCandidates(ident
, default_path_str
, secondary_path_str
))
230 fn report(self, sess
: &Session
, span
: Span
) {
231 let diag
= &sess
.parse_sess
.span_diagnostic
;
233 ModError
::CircularInclusion(file_paths
) => {
234 let mut msg
= String
::from("circular modules: ");
235 for file_path
in &file_paths
{
236 msg
.push_str(&file_path
.display().to_string());
237 msg
.push_str(" -> ");
239 msg
.push_str(&file_paths
[0].display().to_string());
240 diag
.struct_span_err(span
, &msg
)
242 ModError
::ModInBlock(ident
) => {
243 let msg
= "cannot declare a non-inline module inside a block unless it has a path attribute";
244 let mut err
= diag
.struct_span_err(span
, msg
);
245 if let Some(ident
) = ident
{
247 format
!("maybe `use` the module `{}` instead of redeclaring it", ident
);
248 err
.span_note(span
, ¬e
);
252 ModError
::FileNotFound(ident
, default_path
) => {
253 let mut err
= struct_span_err
!(
257 "file not found for module `{}`",
261 "to create the module `{}`, create file \"{}\"",
263 default_path
.display(),
267 ModError
::MultipleCandidates(ident
, default_path_short
, secondary_path_short
) => {
268 let mut err
= struct_span_err
!(
272 "file for module `{}` found at both {} and {}",
275 secondary_path_short
,
277 err
.help("delete or rename one of them to remove the ambiguity");
280 ModError
::ParserError(err
) => err
,