Improve mod resolution error for mods with multiple candidate files

Fixes 5167

When ``a.rs`` and ``a/mod.rs`` are both present we would emit an error
message telling the user that the module couldn't be found. However,
this behavior is misleading because we're dealing with an ambiguous
module path, not a "file not found" error.

Is the file ``a.rs`` or is it ``a/mod.rs``? Rustfmt can't decide, and
the user needs to resolve this ambiguity themselves.

Now, the error message displayed to the user is in line with what they
would see if they went to compile their code with these conflicting
module names.
This commit is contained in:
Yacin Tmimi 2022-01-10 20:45:27 -05:00 committed by Caleb Cartwright
parent 58de4142c5
commit 18c0369688
10 changed files with 88 additions and 6 deletions

View File

@ -81,6 +81,7 @@ pub struct ModuleResolutionError {
pub(crate) kind: ModuleResolutionErrorKind,
}
/// Defines variants similar to those of [rustc_expand::module::ModError]
#[derive(Debug, Error)]
pub(crate) enum ModuleResolutionErrorKind {
/// Find a file that cannot be parsed.
@ -89,6 +90,12 @@ pub(crate) enum ModuleResolutionErrorKind {
/// File cannot be found.
#[error("{file} does not exist")]
NotFound { file: PathBuf },
/// File a.rs and a/mod.rs both exist
#[error("file for module found at both {default_path:?} and {secondary_path:?}")]
MultipleCandidates {
default_path: PathBuf,
secondary_path: PathBuf,
},
}
#[derive(Clone)]
@ -444,12 +451,31 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
}
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
}
Err(_) => Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::NotFound {
file: self.directory.path.clone(),
},
}),
Err(e) => match e {
ModError::FileNotFound(_, default_path, _secondary_path) => {
Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::NotFound { file: default_path },
})
}
ModError::MultipleCandidates(_, default_path, secondary_path) => {
Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::MultipleCandidates {
default_path,
secondary_path,
},
})
}
ModError::ParserError(_)
| ModError::CircularInclusion(_)
| ModError::ModInBlock(_) => Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::ParseError {
file: self.directory.path.clone(),
},
}),
},
}
}

View File

@ -163,8 +163,11 @@ impl ParseSess {
|e| {
// If resloving a module relative to {dir_path}/{symbol} fails because a file
// could not be found, then try to resolve the module relative to {dir_path}.
// If we still can't find the module after searching for it in {dir_path},
// surface the original error.
if matches!(e, ModError::FileNotFound(..)) && relative.is_some() {
rustc_expand::module::default_submod_path(&self.parse_sess, id, None, dir_path)
.map_err(|_| e)
} else {
Err(e)
}

View File

View File

@ -0,0 +1 @@
mod a;

View File

@ -0,0 +1,3 @@
// module resolution fails because the path does not exist.
#[path = "path/to/does_not_exist.rs"]
mod a;

View File

@ -0,0 +1,2 @@
// module resolution fails because `./a/b.rs` does not exist
mod b;

View File

@ -0,0 +1 @@
mod a;

View File

@ -0,0 +1,2 @@
// module resolution fails because `./a.rs` does not exist
mod a;

View File

@ -113,3 +113,47 @@ fn rustfmt_usage_text() {
let (stdout, _) = rustfmt(&args);
assert!(stdout.contains("Format Rust code\n\nusage: rustfmt [options] <file>..."));
}
#[test]
fn mod_resolution_error_multiple_candidate_files() {
// See also https://github.com/rust-lang/rustfmt/issues/5167
let default_path = Path::new("tests/mod-resolver/issue-5167/src/a.rs");
let secondary_path = Path::new("tests/mod-resolver/issue-5167/src/a/mod.rs");
let error_message = format!(
"file for module found at both {:?} and {:?}",
default_path.canonicalize().unwrap(),
secondary_path.canonicalize().unwrap(),
);
let args = ["tests/mod-resolver/issue-5167/src/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
assert!(stderr.contains(&error_message))
}
#[test]
fn mod_resolution_error_sibling_module_not_found() {
let args = ["tests/mod-resolver/module-not-found/sibling_module/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// Module resolution fails because we're unable to find `a.rs` in the same directory as lib.rs
assert!(stderr.contains("a.rs does not exist"))
}
#[test]
fn mod_resolution_error_relative_module_not_found() {
let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// The file `./a.rs` and directory `./a` both exist.
// Module resolution fails becuase we're unable to find `./a/b.rs`
#[cfg(not(windows))]
assert!(stderr.contains("a/b.rs does not exist"));
#[cfg(windows)]
assert!(stderr.contains("a\\b.rs does not exist"));
}
#[test]
fn mod_resolution_error_path_attribute_does_not_exist() {
let args = ["tests/mod-resolver/module-not-found/bad_path_attribute/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// The path attribute points to a file that does not exist
assert!(stderr.contains("does_not_exist.rs does not exist"));
}