|
| 1 | +use super::{Parser, PResult}; |
| 2 | +use super::item::ItemInfo; |
| 3 | + |
| 4 | +use crate::attr; |
| 5 | +use crate::ast::{self, Ident, Attribute, ItemKind, Mod, Crate}; |
| 6 | +use crate::parse::{new_sub_parser_from_file, DirectoryOwnership}; |
| 7 | +use crate::parse::token::{self, TokenKind}; |
| 8 | +use crate::parse::diagnostics::{Error}; |
| 9 | +use crate::source_map::{SourceMap, Span, DUMMY_SP, FileName}; |
| 10 | +use crate::symbol::sym; |
| 11 | + |
| 12 | +use std::path::{self, Path, PathBuf}; |
| 13 | + |
| 14 | +/// Information about the path to a module. |
| 15 | +pub struct ModulePath { |
| 16 | + name: String, |
| 17 | + path_exists: bool, |
| 18 | + pub result: Result<ModulePathSuccess, Error>, |
| 19 | +} |
| 20 | + |
| 21 | +pub struct ModulePathSuccess { |
| 22 | + pub path: PathBuf, |
| 23 | + pub directory_ownership: DirectoryOwnership, |
| 24 | + warn: bool, |
| 25 | +} |
| 26 | + |
| 27 | +impl<'a> Parser<'a> { |
| 28 | + /// Parses a source module as a crate. This is the main entry point for the parser. |
| 29 | + pub fn parse_crate_mod(&mut self) -> PResult<'a, Crate> { |
| 30 | + let lo = self.token.span; |
| 31 | + let krate = Ok(ast::Crate { |
| 32 | + attrs: self.parse_inner_attributes()?, |
| 33 | + module: self.parse_mod_items(&token::Eof, lo)?, |
| 34 | + span: lo.to(self.token.span), |
| 35 | + }); |
| 36 | + krate |
| 37 | + } |
| 38 | + |
| 39 | + /// Parse a `mod <foo> { ... }` or `mod <foo>;` item |
| 40 | + pub(super) fn parse_item_mod(&mut self, outer_attrs: &[Attribute]) -> PResult<'a, ItemInfo> { |
| 41 | + let (in_cfg, outer_attrs) = { |
| 42 | + let mut strip_unconfigured = crate::config::StripUnconfigured { |
| 43 | + sess: self.sess, |
| 44 | + features: None, // don't perform gated feature checking |
| 45 | + }; |
| 46 | + let mut outer_attrs = outer_attrs.to_owned(); |
| 47 | + strip_unconfigured.process_cfg_attrs(&mut outer_attrs); |
| 48 | + (!self.cfg_mods || strip_unconfigured.in_cfg(&outer_attrs), outer_attrs) |
| 49 | + }; |
| 50 | + |
| 51 | + let id_span = self.token.span; |
| 52 | + let id = self.parse_ident()?; |
| 53 | + if self.eat(&token::Semi) { |
| 54 | + if in_cfg && self.recurse_into_file_modules { |
| 55 | + // This mod is in an external file. Let's go get it! |
| 56 | + let ModulePathSuccess { path, directory_ownership, warn } = |
| 57 | + self.submod_path(id, &outer_attrs, id_span)?; |
| 58 | + let (module, mut attrs) = |
| 59 | + self.eval_src_mod(path, directory_ownership, id.to_string(), id_span)?; |
| 60 | + // Record that we fetched the mod from an external file |
| 61 | + if warn { |
| 62 | + let attr = attr::mk_attr_outer( |
| 63 | + attr::mk_word_item(Ident::with_empty_ctxt(sym::warn_directory_ownership))); |
| 64 | + attr::mark_known(&attr); |
| 65 | + attrs.push(attr); |
| 66 | + } |
| 67 | + Ok((id, ItemKind::Mod(module), Some(attrs))) |
| 68 | + } else { |
| 69 | + let placeholder = ast::Mod { |
| 70 | + inner: DUMMY_SP, |
| 71 | + items: Vec::new(), |
| 72 | + inline: false |
| 73 | + }; |
| 74 | + Ok((id, ItemKind::Mod(placeholder), None)) |
| 75 | + } |
| 76 | + } else { |
| 77 | + let old_directory = self.directory.clone(); |
| 78 | + self.push_directory(id, &outer_attrs); |
| 79 | + |
| 80 | + self.expect(&token::OpenDelim(token::Brace))?; |
| 81 | + let mod_inner_lo = self.token.span; |
| 82 | + let attrs = self.parse_inner_attributes()?; |
| 83 | + let module = self.parse_mod_items(&token::CloseDelim(token::Brace), mod_inner_lo)?; |
| 84 | + |
| 85 | + self.directory = old_directory; |
| 86 | + Ok((id, ItemKind::Mod(module), Some(attrs))) |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + /// Given a termination token, parses all of the items in a module. |
| 91 | + fn parse_mod_items(&mut self, term: &TokenKind, inner_lo: Span) -> PResult<'a, Mod> { |
| 92 | + let mut items = vec![]; |
| 93 | + while let Some(item) = self.parse_item()? { |
| 94 | + items.push(item); |
| 95 | + self.maybe_consume_incorrect_semicolon(&items); |
| 96 | + } |
| 97 | + |
| 98 | + if !self.eat(term) { |
| 99 | + let token_str = self.this_token_descr(); |
| 100 | + if !self.maybe_consume_incorrect_semicolon(&items) { |
| 101 | + let mut err = self.fatal(&format!("expected item, found {}", token_str)); |
| 102 | + err.span_label(self.token.span, "expected item"); |
| 103 | + return Err(err); |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + let hi = if self.token.span.is_dummy() { |
| 108 | + inner_lo |
| 109 | + } else { |
| 110 | + self.prev_span |
| 111 | + }; |
| 112 | + |
| 113 | + Ok(Mod { |
| 114 | + inner: inner_lo.to(hi), |
| 115 | + items, |
| 116 | + inline: true |
| 117 | + }) |
| 118 | + } |
| 119 | + |
| 120 | + fn submod_path( |
| 121 | + &mut self, |
| 122 | + id: ast::Ident, |
| 123 | + outer_attrs: &[Attribute], |
| 124 | + id_sp: Span |
| 125 | + ) -> PResult<'a, ModulePathSuccess> { |
| 126 | + if let Some(path) = Parser::submod_path_from_attr(outer_attrs, &self.directory.path) { |
| 127 | + return Ok(ModulePathSuccess { |
| 128 | + directory_ownership: match path.file_name().and_then(|s| s.to_str()) { |
| 129 | + // All `#[path]` files are treated as though they are a `mod.rs` file. |
| 130 | + // This means that `mod foo;` declarations inside `#[path]`-included |
| 131 | + // files are siblings, |
| 132 | + // |
| 133 | + // Note that this will produce weirdness when a file named `foo.rs` is |
| 134 | + // `#[path]` included and contains a `mod foo;` declaration. |
| 135 | + // If you encounter this, it's your own darn fault :P |
| 136 | + Some(_) => DirectoryOwnership::Owned { relative: None }, |
| 137 | + _ => DirectoryOwnership::UnownedViaMod(true), |
| 138 | + }, |
| 139 | + path, |
| 140 | + warn: false, |
| 141 | + }); |
| 142 | + } |
| 143 | + |
| 144 | + let relative = match self.directory.ownership { |
| 145 | + DirectoryOwnership::Owned { relative } => relative, |
| 146 | + DirectoryOwnership::UnownedViaBlock | |
| 147 | + DirectoryOwnership::UnownedViaMod(_) => None, |
| 148 | + }; |
| 149 | + let paths = Parser::default_submod_path( |
| 150 | + id, relative, &self.directory.path, self.sess.source_map()); |
| 151 | + |
| 152 | + match self.directory.ownership { |
| 153 | + DirectoryOwnership::Owned { .. } => { |
| 154 | + paths.result.map_err(|err| self.span_fatal_err(id_sp, err)) |
| 155 | + }, |
| 156 | + DirectoryOwnership::UnownedViaBlock => { |
| 157 | + let msg = |
| 158 | + "Cannot declare a non-inline module inside a block \ |
| 159 | + unless it has a path attribute"; |
| 160 | + let mut err = self.diagnostic().struct_span_err(id_sp, msg); |
| 161 | + if paths.path_exists { |
| 162 | + let msg = format!("Maybe `use` the module `{}` instead of redeclaring it", |
| 163 | + paths.name); |
| 164 | + err.span_note(id_sp, &msg); |
| 165 | + } |
| 166 | + Err(err) |
| 167 | + } |
| 168 | + DirectoryOwnership::UnownedViaMod(warn) => { |
| 169 | + if warn { |
| 170 | + if let Ok(result) = paths.result { |
| 171 | + return Ok(ModulePathSuccess { warn: true, ..result }); |
| 172 | + } |
| 173 | + } |
| 174 | + let mut err = self.diagnostic().struct_span_err(id_sp, |
| 175 | + "cannot declare a new module at this location"); |
| 176 | + if !id_sp.is_dummy() { |
| 177 | + let src_path = self.sess.source_map().span_to_filename(id_sp); |
| 178 | + if let FileName::Real(src_path) = src_path { |
| 179 | + if let Some(stem) = src_path.file_stem() { |
| 180 | + let mut dest_path = src_path.clone(); |
| 181 | + dest_path.set_file_name(stem); |
| 182 | + dest_path.push("mod.rs"); |
| 183 | + err.span_note(id_sp, |
| 184 | + &format!("maybe move this module `{}` to its own \ |
| 185 | + directory via `{}`", src_path.display(), |
| 186 | + dest_path.display())); |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + if paths.path_exists { |
| 191 | + err.span_note(id_sp, |
| 192 | + &format!("... or maybe `use` the module `{}` instead \ |
| 193 | + of possibly redeclaring it", |
| 194 | + paths.name)); |
| 195 | + } |
| 196 | + Err(err) |
| 197 | + } |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + pub fn submod_path_from_attr(attrs: &[Attribute], dir_path: &Path) -> Option<PathBuf> { |
| 202 | + if let Some(s) = attr::first_attr_value_str_by_name(attrs, sym::path) { |
| 203 | + let s = s.as_str(); |
| 204 | + |
| 205 | + // On windows, the base path might have the form |
| 206 | + // `\\?\foo\bar` in which case it does not tolerate |
| 207 | + // mixed `/` and `\` separators, so canonicalize |
| 208 | + // `/` to `\`. |
| 209 | + #[cfg(windows)] |
| 210 | + let s = s.replace("/", "\\"); |
| 211 | + Some(dir_path.join(s)) |
| 212 | + } else { |
| 213 | + None |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + /// Returns a path to a module. |
| 218 | + pub fn default_submod_path( |
| 219 | + id: ast::Ident, |
| 220 | + relative: Option<ast::Ident>, |
| 221 | + dir_path: &Path, |
| 222 | + source_map: &SourceMap) -> ModulePath |
| 223 | + { |
| 224 | + // If we're in a foo.rs file instead of a mod.rs file, |
| 225 | + // we need to look for submodules in |
| 226 | + // `./foo/<id>.rs` and `./foo/<id>/mod.rs` rather than |
| 227 | + // `./<id>.rs` and `./<id>/mod.rs`. |
| 228 | + let relative_prefix_string; |
| 229 | + let relative_prefix = if let Some(ident) = relative { |
| 230 | + relative_prefix_string = format!("{}{}", ident.as_str(), path::MAIN_SEPARATOR); |
| 231 | + &relative_prefix_string |
| 232 | + } else { |
| 233 | + "" |
| 234 | + }; |
| 235 | + |
| 236 | + let mod_name = id.to_string(); |
| 237 | + let default_path_str = format!("{}{}.rs", relative_prefix, mod_name); |
| 238 | + let secondary_path_str = format!("{}{}{}mod.rs", |
| 239 | + relative_prefix, mod_name, path::MAIN_SEPARATOR); |
| 240 | + let default_path = dir_path.join(&default_path_str); |
| 241 | + let secondary_path = dir_path.join(&secondary_path_str); |
| 242 | + let default_exists = source_map.file_exists(&default_path); |
| 243 | + let secondary_exists = source_map.file_exists(&secondary_path); |
| 244 | + |
| 245 | + let result = match (default_exists, secondary_exists) { |
| 246 | + (true, false) => Ok(ModulePathSuccess { |
| 247 | + path: default_path, |
| 248 | + directory_ownership: DirectoryOwnership::Owned { |
| 249 | + relative: Some(id), |
| 250 | + }, |
| 251 | + warn: false, |
| 252 | + }), |
| 253 | + (false, true) => Ok(ModulePathSuccess { |
| 254 | + path: secondary_path, |
| 255 | + directory_ownership: DirectoryOwnership::Owned { |
| 256 | + relative: None, |
| 257 | + }, |
| 258 | + warn: false, |
| 259 | + }), |
| 260 | + (false, false) => Err(Error::FileNotFoundForModule { |
| 261 | + mod_name: mod_name.clone(), |
| 262 | + default_path: default_path_str, |
| 263 | + secondary_path: secondary_path_str, |
| 264 | + dir_path: dir_path.display().to_string(), |
| 265 | + }), |
| 266 | + (true, true) => Err(Error::DuplicatePaths { |
| 267 | + mod_name: mod_name.clone(), |
| 268 | + default_path: default_path_str, |
| 269 | + secondary_path: secondary_path_str, |
| 270 | + }), |
| 271 | + }; |
| 272 | + |
| 273 | + ModulePath { |
| 274 | + name: mod_name, |
| 275 | + path_exists: default_exists || secondary_exists, |
| 276 | + result, |
| 277 | + } |
| 278 | + } |
| 279 | + |
| 280 | + /// Reads a module from a source file. |
| 281 | + fn eval_src_mod( |
| 282 | + &mut self, |
| 283 | + path: PathBuf, |
| 284 | + directory_ownership: DirectoryOwnership, |
| 285 | + name: String, |
| 286 | + id_sp: Span, |
| 287 | + ) -> PResult<'a, (Mod, Vec<Attribute>)> { |
| 288 | + let mut included_mod_stack = self.sess.included_mod_stack.borrow_mut(); |
| 289 | + if let Some(i) = included_mod_stack.iter().position(|p| *p == path) { |
| 290 | + let mut err = String::from("circular modules: "); |
| 291 | + let len = included_mod_stack.len(); |
| 292 | + for p in &included_mod_stack[i.. len] { |
| 293 | + err.push_str(&p.to_string_lossy()); |
| 294 | + err.push_str(" -> "); |
| 295 | + } |
| 296 | + err.push_str(&path.to_string_lossy()); |
| 297 | + return Err(self.span_fatal(id_sp, &err[..])); |
| 298 | + } |
| 299 | + included_mod_stack.push(path.clone()); |
| 300 | + drop(included_mod_stack); |
| 301 | + |
| 302 | + let mut p0 = |
| 303 | + new_sub_parser_from_file(self.sess, &path, directory_ownership, Some(name), id_sp); |
| 304 | + p0.cfg_mods = self.cfg_mods; |
| 305 | + let mod_inner_lo = p0.token.span; |
| 306 | + let mod_attrs = p0.parse_inner_attributes()?; |
| 307 | + let mut m0 = p0.parse_mod_items(&token::Eof, mod_inner_lo)?; |
| 308 | + m0.inline = false; |
| 309 | + self.sess.included_mod_stack.borrow_mut().pop(); |
| 310 | + Ok((m0, mod_attrs)) |
| 311 | + } |
| 312 | + |
| 313 | + fn push_directory(&mut self, id: Ident, attrs: &[Attribute]) { |
| 314 | + if let Some(path) = attr::first_attr_value_str_by_name(attrs, sym::path) { |
| 315 | + self.directory.path.to_mut().push(&path.as_str()); |
| 316 | + self.directory.ownership = DirectoryOwnership::Owned { relative: None }; |
| 317 | + } else { |
| 318 | + // We have to push on the current module name in the case of relative |
| 319 | + // paths in order to ensure that any additional module paths from inline |
| 320 | + // `mod x { ... }` come after the relative extension. |
| 321 | + // |
| 322 | + // For example, a `mod z { ... }` inside `x/y.rs` should set the current |
| 323 | + // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`. |
| 324 | + if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership { |
| 325 | + if let Some(ident) = relative.take() { // remove the relative offset |
| 326 | + self.directory.path.to_mut().push(ident.as_str()); |
| 327 | + } |
| 328 | + } |
| 329 | + self.directory.path.to_mut().push(&id.as_str()); |
| 330 | + } |
| 331 | + } |
| 332 | +} |
0 commit comments