1 use crate::base
::ModuleData
;
3 ModuleCircular
, ModuleFileNotFound
, ModuleInBlock
, ModuleInBlockName
, ModuleMultipleCandidates
,
6 use rustc_ast
::{token, AttrVec, Attribute, Inline, Item, ModSpans}
;
7 use rustc_errors
::{DiagnosticBuilder, ErrorGuaranteed}
;
8 use rustc_parse
::new_parser_from_file
;
9 use rustc_parse
::validate_attr
;
10 use rustc_session
::parse
::ParseSess
;
11 use rustc_session
::Session
;
12 use rustc_span
::symbol
::{sym, Ident}
;
16 use std
::path
::{self, Path, PathBuf}
;
18 #[derive(Copy, Clone)]
19 pub enum DirOwnership
{
21 // None if `mod.rs`, `Some("foo")` if we're in `foo.rs`.
22 relative
: Option
<Ident
>,
27 // Public for rustfmt usage.
28 pub struct ModulePathSuccess
{
29 pub file_path
: PathBuf
,
30 pub dir_ownership
: DirOwnership
,
33 pub(crate) struct ParsedExternalMod
{
34 pub items
: Vec
<P
<Item
>>,
36 pub file_path
: PathBuf
,
37 pub dir_path
: PathBuf
,
38 pub dir_ownership
: DirOwnership
,
41 pub enum ModError
<'a
> {
42 CircularInclusion(Vec
<PathBuf
>),
43 ModInBlock(Option
<Ident
>),
44 FileNotFound(Ident
, PathBuf
, PathBuf
),
45 MultipleCandidates(Ident
, PathBuf
, PathBuf
),
46 ParserError(DiagnosticBuilder
<'a
, ErrorGuaranteed
>),
49 pub(crate) fn parse_external_mod(
52 span
: Span
, // The span to blame on errors.
54 mut dir_ownership
: DirOwnership
,
56 ) -> ParsedExternalMod
{
57 // We bail on the first error, but that error does not cause a fatal error... (1)
58 let result
: Result
<_
, ModError
<'_
>> = try
{
59 // Extract the file path and the new ownership.
60 let mp
= mod_file_path(sess
, ident
, &attrs
, &module
.dir_path
, dir_ownership
)?
;
61 dir_ownership
= mp
.dir_ownership
;
63 // Ensure file paths are acyclic.
64 if let Some(pos
) = module
.file_path_stack
.iter().position(|p
| p
== &mp
.file_path
) {
65 Err(ModError
::CircularInclusion(module
.file_path_stack
[pos
..].to_vec()))?
;
68 // Actually parse the external file as a module.
69 let mut parser
= new_parser_from_file(&sess
.parse_sess
, &mp
.file_path
, Some(span
));
70 let (inner_attrs
, items
, inner_span
) =
71 parser
.parse_mod(&token
::Eof
).map_err(|err
| ModError
::ParserError(err
))?
;
72 attrs
.extend(inner_attrs
);
73 (items
, inner_span
, mp
.file_path
)
75 // (1) ...instead, we return a dummy module.
76 let (items
, spans
, file_path
) =
77 result
.map_err(|err
| err
.report(sess
, span
)).unwrap_or_default();
79 // Extract the directory path for submodules of the module.
80 let dir_path
= file_path
.parent().unwrap_or(&file_path
).to_owned();
82 ParsedExternalMod { items, spans, file_path, dir_path, dir_ownership }
85 pub(crate) fn mod_dir_path(
90 mut dir_ownership
: DirOwnership
,
92 ) -> (PathBuf
, DirOwnership
) {
94 Inline
::Yes
if let Some(file_path
) = mod_file_path_from_attr(sess
, attrs
, &module
.dir_path
) => {
95 // For inline modules file path from `#[path]` is actually the directory path
96 // for historical reasons, so we don't pop the last segment here.
97 (file_path
, DirOwnership
::Owned { relative: None }
)
100 // We have to push on the current module name in the case of relative
101 // paths in order to ensure that any additional module paths from inline
102 // `mod x { ... }` come after the relative extension.
104 // For example, a `mod z { ... }` inside `x/y.rs` should set the current
105 // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
106 let mut dir_path
= module
.dir_path
.clone();
107 if let DirOwnership
::Owned { relative }
= &mut dir_ownership
{
108 if let Some(ident
) = relative
.take() {
109 // Remove the relative offset.
110 dir_path
.push(ident
.as_str());
113 dir_path
.push(ident
.as_str());
115 (dir_path
, dir_ownership
)
118 // FIXME: This is a subset of `parse_external_mod` without actual parsing,
119 // check whether the logic for unloaded, loaded and inline modules can be unified.
120 let file_path
= mod_file_path(sess
, ident
, &attrs
, &module
.dir_path
, dir_ownership
)
122 dir_ownership
= mp
.dir_ownership
;
125 .unwrap_or_default();
127 // Extract the directory path for submodules of the module.
128 let dir_path
= file_path
.parent().unwrap_or(&file_path
).to_owned();
130 (dir_path
, dir_ownership
)
135 fn mod_file_path
<'a
>(
140 dir_ownership
: DirOwnership
,
141 ) -> Result
<ModulePathSuccess
, ModError
<'a
>> {
142 if let Some(file_path
) = mod_file_path_from_attr(sess
, attrs
, dir_path
) {
143 // All `#[path]` files are treated as though they are a `mod.rs` file.
144 // This means that `mod foo;` declarations inside `#[path]`-included
145 // files are siblings,
147 // Note that this will produce weirdness when a file named `foo.rs` is
148 // `#[path]` included and contains a `mod foo;` declaration.
149 // If you encounter this, it's your own darn fault :P
150 let dir_ownership
= DirOwnership
::Owned { relative: None }
;
151 return Ok(ModulePathSuccess { file_path, dir_ownership }
);
154 let relative
= match dir_ownership
{
155 DirOwnership
::Owned { relative }
=> relative
,
156 DirOwnership
::UnownedViaBlock
=> None
,
158 let result
= default_submod_path(&sess
.parse_sess
, ident
, relative
, dir_path
);
159 match dir_ownership
{
160 DirOwnership
::Owned { .. }
=> result
,
161 DirOwnership
::UnownedViaBlock
=> Err(ModError
::ModInBlock(match result
{
162 Ok(_
) | Err(ModError
::MultipleCandidates(..)) => Some(ident
),
168 /// Derive a submodule path from the first found `#[path = "path_string"]`.
169 /// The provided `dir_path` is joined with the `path_string`.
170 fn mod_file_path_from_attr(
174 ) -> Option
<PathBuf
> {
175 // Extract path string from first `#[path = "path_string"]` attribute.
176 let first_path
= attrs
.iter().find(|at
| at
.has_name(sym
::path
))?
;
177 let Some(path_sym
) = first_path
.value_str() else {
178 // This check is here mainly to catch attempting to use a macro,
179 // such as #[path = concat!(...)]. This isn't currently supported
180 // because otherwise the InvocationCollector would need to defer
181 // loading a module until the #[path] attribute was expanded, and
182 // it doesn't support that (and would likely add a bit of
183 // complexity). Usually bad forms are checked in AstValidator (via
184 // `check_builtin_attribute`), but by the time that runs the macro
185 // is expanded, and it doesn't give an error.
186 validate_attr
::emit_fatal_malformed_builtin_attribute(
193 let path_str
= path_sym
.as_str();
195 // On windows, the base path might have the form
196 // `\\?\foo\bar` in which case it does not tolerate
197 // mixed `/` and `\` separators, so canonicalize
200 let path_str
= path_str
.replace("/", "\\");
202 Some(dir_path
.join(path_str
))
205 /// Returns a path to a module.
206 // Public for rustfmt usage.
207 pub fn default_submod_path
<'a
>(
210 relative
: Option
<Ident
>,
212 ) -> Result
<ModulePathSuccess
, ModError
<'a
>> {
213 // If we're in a foo.rs file instead of a mod.rs file,
214 // we need to look for submodules in
215 // `./foo/<ident>.rs` and `./foo/<ident>/mod.rs` rather than
216 // `./<ident>.rs` and `./<ident>/mod.rs`.
217 let relative_prefix_string
;
218 let relative_prefix
= if let Some(ident
) = relative
{
219 relative_prefix_string
= format
!("{}{}", ident
.name
, path
::MAIN_SEPARATOR
);
220 &relative_prefix_string
225 let default_path_str
= format
!("{}{}.rs", relative_prefix
, ident
.name
);
226 let secondary_path_str
=
227 format
!("{}{}{}mod.rs", relative_prefix
, ident
.name
, path
::MAIN_SEPARATOR
);
228 let default_path
= dir_path
.join(&default_path_str
);
229 let secondary_path
= dir_path
.join(&secondary_path_str
);
230 let default_exists
= sess
.source_map().file_exists(&default_path
);
231 let secondary_exists
= sess
.source_map().file_exists(&secondary_path
);
233 match (default_exists
, secondary_exists
) {
234 (true, false) => Ok(ModulePathSuccess
{
235 file_path
: default_path
,
236 dir_ownership
: DirOwnership
::Owned { relative: Some(ident) }
,
238 (false, true) => Ok(ModulePathSuccess
{
239 file_path
: secondary_path
,
240 dir_ownership
: DirOwnership
::Owned { relative: None }
,
242 (false, false) => Err(ModError
::FileNotFound(ident
, default_path
, secondary_path
)),
243 (true, true) => Err(ModError
::MultipleCandidates(ident
, default_path
, secondary_path
)),
248 fn report(self, sess
: &Session
, span
: Span
) -> ErrorGuaranteed
{
250 ModError
::CircularInclusion(file_paths
) => {
251 let path_to_string
= |path
: &PathBuf
| path
.display().to_string();
253 let paths
= file_paths
256 .chain(once(path_to_string(&file_paths
[0])))
257 .collect
::<Vec
<_
>>();
259 let modules
= paths
.join(" -> ");
261 sess
.emit_err(ModuleCircular { span, modules }
)
263 ModError
::ModInBlock(ident
) => sess
.emit_err(ModuleInBlock
{
265 name
: ident
.map(|name
| ModuleInBlockName { span, name }
),
267 ModError
::FileNotFound(name
, default_path
, secondary_path
) => {
268 sess
.emit_err(ModuleFileNotFound
{
271 default_path
: default_path
.display().to_string(),
272 secondary_path
: secondary_path
.display().to_string(),
275 ModError
::MultipleCandidates(name
, default_path
, secondary_path
) => {
276 sess
.emit_err(ModuleMultipleCandidates
{
279 default_path
: default_path
.display().to_string(),
280 secondary_path
: secondary_path
.display().to_string(),
283 ModError
::ParserError(mut err
) => err
.emit(),