Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions compiler/rustc_parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use rustc_ast_pretty::pprust;
use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize};
pub use rustc_lexer::UNICODE_VERSION;
use rustc_session::parse::ParseSess;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::source_map::SourceMap;
use rustc_span::{FileName, SourceFile, Span};
use rustc_span::{FileName, SourceFile, Span, Symbol};

pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");

Expand Down Expand Up @@ -105,6 +106,8 @@ pub fn new_parser_from_source_str(
/// dropped.
///
/// If a span is given, that is used on an error as the source of the problem.
///
/// Error messages are tailored to the specific error kind.
pub fn new_parser_from_file<'a>(
psess: &'a ParseSess,
path: &Path,
Expand All @@ -113,8 +116,42 @@ pub fn new_parser_from_file<'a>(
) -> Result<Parser<'a>, Vec<Diag<'a>>> {
let sm = psess.source_map();
let source_file = sm.load_file(path).unwrap_or_else(|e| {
let msg = format!("couldn't read `{}`: {}", path.display(), e);
use std::io::ErrorKind;

let msg = match e.kind() {
ErrorKind::NotFound => format!("couldn't find file `{}`", path.display()),
ErrorKind::PermissionDenied => {
format!("permission denied when opening file `{}`", path.display())
}
ErrorKind::IsADirectory => format!("`{}` is a directory", path.display()),
_ => format!("couldn't read `{}`: {}", path.display(), e),
};

let mut err = psess.dcx().struct_fatal(msg);

if e.kind() == ErrorKind::NotFound {
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
let parent = match path.parent() {
Some(p) if !p.as_os_str().is_empty() => p,
_ => Path::new("."),
};
if let Ok(entries) = std::fs::read_dir(parent) {
let candidates: Vec<Symbol> = entries
.flatten()
.filter_map(|entry| entry.file_name().to_str().map(Symbol::intern))
.collect();
let lookup = Symbol::intern(file_name);
if let Some(suggestion) = find_best_match_for_name(&candidates, lookup, None) {
let suggested_path = if parent == Path::new(".") {
suggestion.as_str().to_string()
} else {
parent.join(suggestion.as_str()).display().to_string()
};
err.help(format!("you might have meant to open `{}`", suggested_path));
}
}
}
}
if let Ok(contents) = std::fs::read(path)
&& let Err(utf8err) = std::str::from_utf8(&contents)
{
Expand Down
63 changes: 63 additions & 0 deletions tests/run-make/input-file-errors/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Tests that rustc produces helpful error messages when the input file
// cannot be opened, including specific messages for different error kinds
// and typo suggestions for NotFound errors.
//
// The permission-denied test requires Unix file mode bits and is skipped
// on Windows. It is also skipped on riscv64/arm because those CI runners
// run as root, which bypasses permission restrictions.

//@ ignore-riscv64
//@ ignore-arm
//@ ignore-windows
//@ needs-target-std

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

use run_make_support::{rfs, run_in_tmpdir, rustc};

fn main() {
// 1. NotFound — basic case: no "os error 2" in the output
run_in_tmpdir(|| {
rustc()
.input("fo.rs")
.run_fail()
.assert_stderr_contains("couldn't find file `fo.rs`")
.assert_stderr_not_contains("os error 2");
});

// 2. NotFound with typo suggestion: foo.rs exists, compiling fo.rs should suggest foo.rs
run_in_tmpdir(|| {
rfs::write("foo.rs", b"fn main() {}");
rustc()
.input("fo.rs")
.run_fail()
.assert_stderr_contains("couldn't find file `fo.rs`")
.assert_stderr_contains("you might have meant to open `foo.rs`");
});

// 3. PermissionDenied — file exists but is unreadable
run_in_tmpdir(|| {
rfs::write("secret.rs", b"fn main() {}");

let mut perms = rfs::metadata("secret.rs").permissions();
perms.set_mode(0o000); // no read, write, or execute
rfs::set_permissions("secret.rs", perms);

// Run rustc before restoring permissions, store the result
let output = rustc().input("secret.rs").run_fail();

// Restore permissions so the tmpdir cleanup can delete the file
let mut perms = rfs::metadata("secret.rs").permissions();
perms.set_mode(0o644);
rfs::set_permissions("secret.rs", perms);

output.assert_stderr_contains("permission denied when opening file");
});

// 4. IsADirectory — path points to a directory, not a file
run_in_tmpdir(|| {
rfs::create_dir("mydir.rs");
rustc().input("mydir.rs").run_fail().assert_stderr_contains("is a directory");
});
}
2 changes: 1 addition & 1 deletion tests/ui/modules/path-no-file-name.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: couldn't read `$DIR/.`: $ACCESS_DENIED_MSG (os error $ACCESS_DENIED_CODE)
error: permission denied when opening file `$DIR/.`
--> $DIR/path-no-file-name.rs:5:1
|
LL | mod m;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/parser/issues/issue-5806.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
//@ normalize-stderr: "os error \d+" -> "os error $$ACCESS_DENIED_CODE"

#[path = "../parser"]
mod foo; //~ ERROR couldn't read
mod foo; //~ ERROR couldn't find file `$DIR/../parser`

fn main() {}
2 changes: 1 addition & 1 deletion tests/ui/parser/issues/issue-5806.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: couldn't read `$DIR/../parser`: $ACCESS_DENIED_MSG (os error $ACCESS_DENIED_CODE)
error: couldn't find file `$DIR/../parser`
--> $DIR/issue-5806.rs:5:1
|
LL | mod foo;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/parser/mod_file_with_path_attr.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: couldn't read `$DIR/not_a_real_file.rs`: $FILE_NOT_FOUND_MSG (os error 2)
error: couldn't find file `$DIR/not_a_real_file.rs`
--> $DIR/mod_file_with_path_attr.rs:4:1
|
LL | mod m;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/unpretty/staged-api-invalid-path-108697.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
#![feature(staged_api)]
#[path = "lol"]
mod foo;
//~^ ERROR couldn't read `$DIR/lol`
//~^ ERROR couldn't find file `$DIR/lol`
2 changes: 1 addition & 1 deletion tests/ui/unpretty/staged-api-invalid-path-108697.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: couldn't read `$DIR/lol`: $FILE_NOT_FOUND_MSG (os error 2)
error: couldn't find file `$DIR/lol`
--> $DIR/staged-api-invalid-path-108697.rs:8:1
|
LL | mod foo;
Expand Down
Loading