Skip to content

Commit 812f4b2

Browse files
ytmimiPSeitz
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 812f4b2

File tree

14 files changed

+141
-14
lines changed

14 files changed

+141
-14
lines changed

src/comment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ impl<'a> CommentRewrite<'a> {
796796
// 1) wrap_comments = true is configured
797797
// 2) The comment is not the start of a markdown header doc comment
798798
// 3) The comment width exceeds the shape's width
799-
// 4) No URLS were found in the commnet
799+
// 4) No URLS were found in the comment
800800
let should_wrap_comment = self.fmt.config.wrap_comments()
801801
&& !is_markdown_header_doc_comment
802802
&& unicode_str_width(line) > self.fmt.shape.width

src/modules.rs

Lines changed: 32 additions & 6 deletions
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

Lines changed: 3 additions & 0 deletions
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
}

src/string.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,27 +315,45 @@ fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]
315315
// Found a whitespace and what is on its left side is big enough.
316316
Some(index) if index >= MIN_STRING => break_at(index),
317317
// No whitespace found, try looking for a punctuation instead
318-
_ => match input[0..max_width_index_in_input]
319-
.iter()
320-
.rposition(|grapheme| is_punctuation(grapheme))
318+
_ => match (0..max_width_index_in_input)
319+
.rev()
320+
.skip_while(|pos| !is_valid_linebreak(input, *pos))
321+
.next()
321322
{
322323
// Found a punctuation and what is on its left side is big enough.
323324
Some(index) if index >= MIN_STRING => break_at(index),
324325
// Either no boundary character was found to the left of `input[max_chars]`, or the line
325326
// got too small. We try searching for a boundary character to the right.
326-
_ => match input[max_width_index_in_input..]
327-
.iter()
328-
.position(|grapheme| is_whitespace(grapheme) || is_punctuation(grapheme))
327+
_ => match (max_width_index_in_input..input.len())
328+
.skip_while(|pos| !is_valid_linebreak(input, *pos))
329+
.next()
329330
{
330331
// A boundary was found after the line limit
331-
Some(index) => break_at(max_width_index_in_input + index),
332+
Some(index) => break_at(index),
332333
// No boundary to the right, the input cannot be broken
333334
None => SnippetState::EndOfInput(input.concat()),
334335
},
335336
},
336337
}
337338
}
338339

340+
fn is_valid_linebreak(input: &[&str], pos: usize) -> bool {
341+
let is_whitespace = is_whitespace(input[pos]);
342+
if is_whitespace {
343+
return true;
344+
}
345+
let is_punctuation = is_punctuation(input[pos]);
346+
if is_punctuation && !is_part_of_type(input, pos) {
347+
return true;
348+
}
349+
false
350+
}
351+
352+
fn is_part_of_type(input: &[&str], pos: usize) -> bool {
353+
input.get(pos..=pos + 1) == Some(&[":", ":"])
354+
|| input.get(pos.saturating_sub(1)..=pos) == Some(&[":", ":"])
355+
}
356+
339357
fn is_new_line(grapheme: &str) -> bool {
340358
let bytes = grapheme.as_bytes();
341359
bytes.starts_with(b"\n") || bytes.starts_with(b"\r\n")
@@ -369,6 +387,19 @@ mod test {
369387
rewrite_string("eq_", &fmt, 2);
370388
}
371389

390+
#[test]
391+
fn line_break_at_valid_points_test() {
392+
let string = "[TheName](Dont::break::my::type::That::would::be::very::nice) break here";
393+
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
394+
assert_eq!(
395+
break_string(20, false, "", &graphemes[..]),
396+
SnippetState::LineEnd(
397+
"[TheName](Dont::break::my::type::That::would::be::very::nice) ".to_string(),
398+
62
399+
)
400+
);
401+
}
402+
372403
#[test]
373404
fn should_break_on_whitespace() {
374405
let string = "Placerat felis. Mauris porta ante sagittis purus.";

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

Whitespace-only changes.

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

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod a;
Lines changed: 3 additions & 0 deletions
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;
Lines changed: 2 additions & 0 deletions
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;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod a;

0 commit comments

Comments
 (0)