@@ -20,8 +20,9 @@ use rustc_ast_pretty::pprust;
2020use rustc_errors:: { Diag , EmissionGuarantee , FatalError , PResult , pluralize} ;
2121pub use rustc_lexer:: UNICODE_VERSION ;
2222use rustc_session:: parse:: ParseSess ;
23+ use rustc_span:: edit_distance:: find_best_match_for_name;
2324use rustc_span:: source_map:: SourceMap ;
24- use rustc_span:: { FileName , SourceFile , Span } ;
25+ use rustc_span:: { FileName , SourceFile , Span , Symbol } ;
2526
2627pub const MACRO_ARGUMENTS : Option < & str > = Some ( "macro arguments" ) ;
2728
@@ -105,6 +106,12 @@ pub fn new_parser_from_source_str(
105106/// dropped.
106107///
107108/// If a span is given, that is used on an error as the source of the problem.
109+ ///
110+ /// Error messages are tailored to the specific error kind:
111+ /// - `NotFound`: "couldn't find file `{path}`" with typo suggestions for similar filenames
112+ /// - `PermissionDenied`: "insufficient permissions when opening file `{path}`"
113+ /// - `IsADirectory`: "`{path}` is a directory"
114+ /// - Paths ending with a separator that don't exist: "`{path}` is a non-existent directory"
108115pub fn new_parser_from_file < ' a > (
109116 psess : & ' a ParseSess ,
110117 path : & Path ,
@@ -113,8 +120,52 @@ pub fn new_parser_from_file<'a>(
113120) -> Result < Parser < ' a > , Vec < Diag < ' a > > > {
114121 let sm = psess. source_map ( ) ;
115122 let source_file = sm. load_file ( path) . unwrap_or_else ( |e| {
116- let msg = format ! ( "couldn't read `{}`: {}" , path. display( ) , e) ;
123+ use std:: io:: ErrorKind ;
124+
125+ let mut msg = match e. kind ( ) {
126+ ErrorKind :: NotFound => format ! ( "couldn't find file `{}`" , path. display( ) ) ,
127+ ErrorKind :: PermissionDenied => {
128+ format ! ( "insufficient permissions when opening file `{}`" , path. display( ) )
129+ }
130+ ErrorKind :: IsADirectory => format ! ( "`{}` is a directory" , path. display( ) ) ,
131+ _ => format ! ( "couldn't read `{}`: {}" , path. display( ) , e) ,
132+ } ;
133+
134+ // Special case: path ends with a separator but doesn't exist → non-existent directory
135+ if let Some ( path_str) = path. as_os_str ( ) . to_str ( )
136+ && let Some ( last_char) = path_str. chars ( ) . last ( )
137+ && std:: path:: is_separator ( last_char)
138+ && !path. exists ( )
139+ {
140+ msg = format ! ( "`{}` is a non-existent directory" , path. display( ) ) ;
141+ }
142+
117143 let mut err = psess. dcx ( ) . struct_fatal ( msg) ;
144+
145+ if e. kind ( ) == ErrorKind :: NotFound {
146+ if let Some ( file_name) = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
147+ let parent =
148+ path. parent ( ) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) . unwrap_or ( Path :: new ( "." ) ) ;
149+ if let Ok ( entries) = std:: fs:: read_dir ( parent) {
150+ let candidates: Vec < Symbol > = entries
151+ . flatten ( )
152+ . filter_map ( |entry| entry. file_name ( ) . to_str ( ) . map ( Symbol :: intern) )
153+ . collect ( ) ;
154+ let lookup = Symbol :: intern ( file_name) ;
155+ if let Some ( suggestion) = find_best_match_for_name ( & candidates, lookup, None ) {
156+ let suggested_path = if parent == Path :: new ( "." ) {
157+ suggestion. as_str ( ) . to_string ( )
158+ } else {
159+ parent. join ( suggestion. as_str ( ) ) . display ( ) . to_string ( )
160+ } ;
161+ err. help ( format ! (
162+ "you might have meant to open `{}`: `rustc {}`" ,
163+ suggested_path, suggested_path,
164+ ) ) ;
165+ }
166+ }
167+ }
168+ }
118169 if let Ok ( contents) = std:: fs:: read ( path)
119170 && let Err ( utf8err) = std:: str:: from_utf8 ( & contents)
120171 {
0 commit comments