rustc_expand/
module.rs

1use std::iter::once;
2use std::path::{self, Path, PathBuf};
3
4use rustc_ast::ptr::P;
5use rustc_ast::{AttrVec, Attribute, Inline, Item, ModSpans};
6use rustc_errors::{Diag, ErrorGuaranteed};
7use rustc_parse::{exp, new_parser_from_file, unwrap_or_emit_fatal, validate_attr};
8use rustc_session::Session;
9use rustc_session::parse::ParseSess;
10use rustc_span::{Ident, Span, sym};
11use thin_vec::ThinVec;
12
13use crate::base::ModuleData;
14use crate::errors::{
15    ModuleCircular, ModuleFileNotFound, ModuleInBlock, ModuleInBlockName, ModuleMultipleCandidates,
16};
17
18#[derive(Copy, Clone)]
19pub enum DirOwnership {
20    Owned {
21        // None if `mod.rs`, `Some("foo")` if we're in `foo.rs`.
22        relative: Option<Ident>,
23    },
24    UnownedViaBlock,
25}
26
27// Public for rustfmt usage.
28pub struct ModulePathSuccess {
29    pub file_path: PathBuf,
30    pub dir_ownership: DirOwnership,
31}
32
33pub(crate) struct ParsedExternalMod {
34    pub items: ThinVec<P<Item>>,
35    pub spans: ModSpans,
36    pub file_path: PathBuf,
37    pub dir_path: PathBuf,
38    pub dir_ownership: DirOwnership,
39    pub had_parse_error: Result<(), ErrorGuaranteed>,
40}
41
42pub enum ModError<'a> {
43    CircularInclusion(Vec<PathBuf>),
44    ModInBlock(Option<Ident>),
45    FileNotFound(Ident, PathBuf, PathBuf),
46    MultipleCandidates(Ident, PathBuf, PathBuf),
47    ParserError(Diag<'a>),
48}
49
50pub(crate) fn parse_external_mod(
51    sess: &Session,
52    ident: Ident,
53    span: Span, // The span to blame on errors.
54    module: &ModuleData,
55    mut dir_ownership: DirOwnership,
56    attrs: &mut AttrVec,
57) -> ParsedExternalMod {
58    // We bail on the first error, but that error does not cause a fatal error... (1)
59    let result: Result<_, ModError<'_>> = try {
60        // Extract the file path and the new ownership.
61        let mp = mod_file_path(sess, ident, attrs, &module.dir_path, dir_ownership)?;
62        dir_ownership = mp.dir_ownership;
63
64        // Ensure file paths are acyclic.
65        if let Some(pos) = module.file_path_stack.iter().position(|p| p == &mp.file_path) {
66            do yeet ModError::CircularInclusion(module.file_path_stack[pos..].to_vec());
67        }
68
69        // Actually parse the external file as a module.
70        let mut parser =
71            unwrap_or_emit_fatal(new_parser_from_file(&sess.psess, &mp.file_path, Some(span)));
72        let (inner_attrs, items, inner_span) =
73            parser.parse_mod(exp!(Eof)).map_err(|err| ModError::ParserError(err))?;
74        attrs.extend(inner_attrs);
75        (items, inner_span, mp.file_path)
76    };
77
78    // (1) ...instead, we return a dummy module.
79    let ((items, spans, file_path), had_parse_error) = match result {
80        Err(err) => (Default::default(), Err(err.report(sess, span))),
81        Ok(result) => (result, Ok(())),
82    };
83
84    // Extract the directory path for submodules of the module.
85    let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
86
87    ParsedExternalMod { items, spans, file_path, dir_path, dir_ownership, had_parse_error }
88}
89
90pub(crate) fn mod_dir_path(
91    sess: &Session,
92    ident: Ident,
93    attrs: &[Attribute],
94    module: &ModuleData,
95    mut dir_ownership: DirOwnership,
96    inline: Inline,
97) -> (PathBuf, DirOwnership) {
98    match inline {
99        Inline::Yes
100            if let Some(file_path) = mod_file_path_from_attr(sess, attrs, &module.dir_path) =>
101        {
102            // For inline modules file path from `#[path]` is actually the directory path
103            // for historical reasons, so we don't pop the last segment here.
104            (file_path, DirOwnership::Owned { relative: None })
105        }
106        Inline::Yes => {
107            // We have to push on the current module name in the case of relative
108            // paths in order to ensure that any additional module paths from inline
109            // `mod x { ... }` come after the relative extension.
110            //
111            // For example, a `mod z { ... }` inside `x/y.rs` should set the current
112            // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
113            let mut dir_path = module.dir_path.clone();
114            if let DirOwnership::Owned { relative } = &mut dir_ownership {
115                if let Some(ident) = relative.take() {
116                    // Remove the relative offset.
117                    dir_path.push(ident.as_str());
118                }
119            }
120            dir_path.push(ident.as_str());
121
122            (dir_path, dir_ownership)
123        }
124        Inline::No => {
125            // FIXME: This is a subset of `parse_external_mod` without actual parsing,
126            // check whether the logic for unloaded, loaded and inline modules can be unified.
127            let file_path = mod_file_path(sess, ident, attrs, &module.dir_path, dir_ownership)
128                .map(|mp| {
129                    dir_ownership = mp.dir_ownership;
130                    mp.file_path
131                })
132                .unwrap_or_default();
133
134            // Extract the directory path for submodules of the module.
135            let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
136
137            (dir_path, dir_ownership)
138        }
139    }
140}
141
142fn mod_file_path<'a>(
143    sess: &'a Session,
144    ident: Ident,
145    attrs: &[Attribute],
146    dir_path: &Path,
147    dir_ownership: DirOwnership,
148) -> Result<ModulePathSuccess, ModError<'a>> {
149    if let Some(file_path) = mod_file_path_from_attr(sess, attrs, dir_path) {
150        // All `#[path]` files are treated as though they are a `mod.rs` file.
151        // This means that `mod foo;` declarations inside `#[path]`-included
152        // files are siblings,
153        //
154        // Note that this will produce weirdness when a file named `foo.rs` is
155        // `#[path]` included and contains a `mod foo;` declaration.
156        // If you encounter this, it's your own darn fault :P
157        let dir_ownership = DirOwnership::Owned { relative: None };
158        return Ok(ModulePathSuccess { file_path, dir_ownership });
159    }
160
161    let relative = match dir_ownership {
162        DirOwnership::Owned { relative } => relative,
163        DirOwnership::UnownedViaBlock => None,
164    };
165    let result = default_submod_path(&sess.psess, ident, relative, dir_path);
166    match dir_ownership {
167        DirOwnership::Owned { .. } => result,
168        DirOwnership::UnownedViaBlock => Err(ModError::ModInBlock(match result {
169            Ok(_) | Err(ModError::MultipleCandidates(..)) => Some(ident),
170            _ => None,
171        })),
172    }
173}
174
175/// Derive a submodule path from the first found `#[path = "path_string"]`.
176/// The provided `dir_path` is joined with the `path_string`.
177pub(crate) fn mod_file_path_from_attr(
178    sess: &Session,
179    attrs: &[Attribute],
180    dir_path: &Path,
181) -> Option<PathBuf> {
182    // Extract path string from first `#[path = "path_string"]` attribute.
183    let first_path = attrs.iter().find(|at| at.has_name(sym::path))?;
184    let Some(path_sym) = first_path.value_str() else {
185        // This check is here mainly to catch attempting to use a macro,
186        // such as `#[path = concat!(...)]`. This isn't supported because
187        // otherwise the `InvocationCollector` would need to defer loading
188        // a module until the `#[path]` attribute was expanded, and it
189        // doesn't support that (and would likely add a bit of complexity).
190        // Usually bad forms are checked during semantic analysis via
191        // `TyCtxt::check_mod_attrs`), but by the time that runs the macro
192        // is expanded, and it doesn't give an error.
193        validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, first_path, sym::path);
194    };
195
196    let path_str = path_sym.as_str();
197
198    // On windows, the base path might have the form
199    // `\\?\foo\bar` in which case it does not tolerate
200    // mixed `/` and `\` separators, so canonicalize
201    // `/` to `\`.
202    #[cfg(windows)]
203    let path_str = path_str.replace("/", "\\");
204
205    Some(dir_path.join(path_str))
206}
207
208/// Returns a path to a module.
209// Public for rustfmt usage.
210pub fn default_submod_path<'a>(
211    psess: &'a ParseSess,
212    ident: Ident,
213    relative: Option<Ident>,
214    dir_path: &Path,
215) -> Result<ModulePathSuccess, ModError<'a>> {
216    // If we're in a foo.rs file instead of a mod.rs file,
217    // we need to look for submodules in
218    // `./foo/<ident>.rs` and `./foo/<ident>/mod.rs` rather than
219    // `./<ident>.rs` and `./<ident>/mod.rs`.
220    let relative_prefix_string;
221    let relative_prefix = if let Some(ident) = relative {
222        relative_prefix_string = format!("{}{}", ident.name, path::MAIN_SEPARATOR);
223        &relative_prefix_string
224    } else {
225        ""
226    };
227
228    let default_path_str = format!("{}{}.rs", relative_prefix, ident.name);
229    let secondary_path_str =
230        format!("{}{}{}mod.rs", relative_prefix, ident.name, path::MAIN_SEPARATOR);
231    let default_path = dir_path.join(&default_path_str);
232    let secondary_path = dir_path.join(&secondary_path_str);
233    let default_exists = psess.source_map().file_exists(&default_path);
234    let secondary_exists = psess.source_map().file_exists(&secondary_path);
235
236    match (default_exists, secondary_exists) {
237        (true, false) => Ok(ModulePathSuccess {
238            file_path: default_path,
239            dir_ownership: DirOwnership::Owned { relative: Some(ident) },
240        }),
241        (false, true) => Ok(ModulePathSuccess {
242            file_path: secondary_path,
243            dir_ownership: DirOwnership::Owned { relative: None },
244        }),
245        (false, false) => Err(ModError::FileNotFound(ident, default_path, secondary_path)),
246        (true, true) => Err(ModError::MultipleCandidates(ident, default_path, secondary_path)),
247    }
248}
249
250impl ModError<'_> {
251    fn report(self, sess: &Session, span: Span) -> ErrorGuaranteed {
252        match self {
253            ModError::CircularInclusion(file_paths) => {
254                let path_to_string = |path: &PathBuf| path.display().to_string();
255
256                let paths = file_paths
257                    .iter()
258                    .map(path_to_string)
259                    .chain(once(path_to_string(&file_paths[0])))
260                    .collect::<Vec<_>>();
261
262                let modules = paths.join(" -> ");
263
264                sess.dcx().emit_err(ModuleCircular { span, modules })
265            }
266            ModError::ModInBlock(ident) => sess.dcx().emit_err(ModuleInBlock {
267                span,
268                name: ident.map(|name| ModuleInBlockName { span, name }),
269            }),
270            ModError::FileNotFound(name, default_path, secondary_path) => {
271                sess.dcx().emit_err(ModuleFileNotFound {
272                    span,
273                    name,
274                    default_path: default_path.display().to_string(),
275                    secondary_path: secondary_path.display().to_string(),
276                })
277            }
278            ModError::MultipleCandidates(name, default_path, secondary_path) => {
279                sess.dcx().emit_err(ModuleMultipleCandidates {
280                    span,
281                    name,
282                    default_path: default_path.display().to_string(),
283                    secondary_path: secondary_path.display().to_string(),
284                })
285            }
286            ModError::ParserError(err) => err.emit(),
287        }
288    }
289}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy