Skip to content

Commit 18c0369

Browse files
ytmimicalebcartwright
authored andcommitted
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.
1 parent 58de414 commit 18c0369

File tree

10 files changed

+88
-6
lines changed

10 files changed

+88
-6
lines changed

src/modules.rs

+32-6
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub struct ModuleResolutionError {
8181
pub(crate) kind: ModuleResolutionErrorKind,
8282
}
8383

84+
/// Defines variants similar to those of [rustc_expand::module::ModError]
8485
#[derive(Debug, Error)]
8586
pub(crate) enum ModuleResolutionErrorKind {
8687
/// Find a file that cannot be parsed.
@@ -89,6 +90,12 @@ pub(crate) enum ModuleResolutionErrorKind {
8990
/// File cannot be found.
9091
#[error("{file} does not exist")]
9192
NotFound { file: PathBuf },
93+
/// File a.rs and a/mod.rs both exist
94+
#[error("file for module found at both {default_path:?} and {secondary_path:?}")]
95+
MultipleCandidates {
96+
default_path: PathBuf,
97+
secondary_path: PathBuf,
98+
},
9299
}
93100

94101
#[derive(Clone)]
@@ -444,12 +451,31 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
444451
}
445452
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
446453
}
447-
Err(_) => Err(ModuleResolutionError {
448-
module: mod_name.to_string(),
449-
kind: ModuleResolutionErrorKind::NotFound {
450-
file: self.directory.path.clone(),
451-
},
452-
}),
454+
Err(e) => match e {
455+
ModError::FileNotFound(_, default_path, _secondary_path) => {
456+
Err(ModuleResolutionError {
457+
module: mod_name.to_string(),
458+
kind: ModuleResolutionErrorKind::NotFound { file: default_path },
459+
})
460+
}
461+
ModError::MultipleCandidates(_, default_path, secondary_path) => {
462+
Err(ModuleResolutionError {
463+
module: mod_name.to_string(),
464+
kind: ModuleResolutionErrorKind::MultipleCandidates {
465+
default_path,
466+
secondary_path,
467+
},
468+
})
469+
}
470+
ModError::ParserError(_)
471+
| ModError::CircularInclusion(_)
472+
| ModError::ModInBlock(_) => Err(ModuleResolutionError {
473+
module: mod_name.to_string(),
474+
kind: ModuleResolutionErrorKind::ParseError {
475+
file: self.directory.path.clone(),
476+
},
477+
}),
478+
},
453479
}
454480
}
455481

src/parse/session.rs

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

tests/mod-resolver/issue-5167/src/a.rs

Whitespace-only changes.

tests/mod-resolver/issue-5167/src/a/mod.rs

Whitespace-only changes.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod a;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// module resolution fails because the path does not exist.
2+
#[path = "path/to/does_not_exist.rs"]
3+
mod a;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// module resolution fails because `./a/b.rs` does not exist
2+
mod b;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod a;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// module resolution fails because `./a.rs` does not exist
2+
mod a;

tests/rustfmt/main.rs

+44
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,47 @@ fn rustfmt_usage_text() {
113113
let (stdout, _) = rustfmt(&args);
114114
assert!(stdout.contains("Format Rust code\n\nusage: rustfmt [options] <file>..."));
115115
}
116+
117+
#[test]
118+
fn mod_resolution_error_multiple_candidate_files() {
119+
// See also https://github.com/rust-lang/rustfmt/issues/5167
120+
let default_path = Path::new("tests/mod-resolver/issue-5167/src/a.rs");
121+
let secondary_path = Path::new("tests/mod-resolver/issue-5167/src/a/mod.rs");
122+
let error_message = format!(
123+
"file for module found at both {:?} and {:?}",
124+
default_path.canonicalize().unwrap(),
125+
secondary_path.canonicalize().unwrap(),
126+
);
127+
128+
let args = ["tests/mod-resolver/issue-5167/src/lib.rs"];
129+
let (_stdout, stderr) = rustfmt(&args);
130+
assert!(stderr.contains(&error_message))
131+
}
132+
133+
#[test]
134+
fn mod_resolution_error_sibling_module_not_found() {
135+
let args = ["tests/mod-resolver/module-not-found/sibling_module/lib.rs"];
136+
let (_stdout, stderr) = rustfmt(&args);
137+
// Module resolution fails because we're unable to find `a.rs` in the same directory as lib.rs
138+
assert!(stderr.contains("a.rs does not exist"))
139+
}
140+
141+
#[test]
142+
fn mod_resolution_error_relative_module_not_found() {
143+
let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"];
144+
let (_stdout, stderr) = rustfmt(&args);
145+
// The file `./a.rs` and directory `./a` both exist.
146+
// Module resolution fails becuase we're unable to find `./a/b.rs`
147+
#[cfg(not(windows))]
148+
assert!(stderr.contains("a/b.rs does not exist"));
149+
#[cfg(windows)]
150+
assert!(stderr.contains("a\\b.rs does not exist"));
151+
}
152+
153+
#[test]
154+
fn mod_resolution_error_path_attribute_does_not_exist() {
155+
let args = ["tests/mod-resolver/module-not-found/bad_path_attribute/lib.rs"];
156+
let (_stdout, stderr) = rustfmt(&args);
157+
// The path attribute points to a file that does not exist
158+
assert!(stderr.contains("does_not_exist.rs does not exist"));
159+
}

0 commit comments

Comments
 (0)