@@ -19,8 +19,9 @@ use rustc_errors::{
1919    Subdiagnostic , 
2020} ; 
2121use  rustc_session:: errors:: ExprParenthesesNeeded ; 
22+ use  rustc_span:: edit_distance:: find_best_match_for_name; 
2223use  rustc_span:: source_map:: Spanned ; 
23- use  rustc_span:: symbol:: { kw,  sym,  Ident } ; 
24+ use  rustc_span:: symbol:: { kw,  sym,  AllKeywords ,   Ident } ; 
2425use  rustc_span:: { BytePos ,  Span ,  SpanSnippetError ,  Symbol ,  DUMMY_SP } ; 
2526use  thin_vec:: { thin_vec,  ThinVec } ; 
2627use  tracing:: { debug,  trace} ; 
@@ -203,6 +204,37 @@ impl std::fmt::Display for UnaryFixity {
203204    } 
204205} 
205206
207+ #[ derive( Debug ,  rustc_macros:: Subdiagnostic ) ]  
208+ #[ suggestion(  
209+     parse_misspelled_kw,  
210+     applicability = "machine-applicable" ,  
211+     code = "{similar_kw}" ,  
212+     style = "verbose"  
213+ ) ] 
214+ struct  MisspelledKw  { 
215+     similar_kw :  String , 
216+     #[ primary_span]  
217+     span :  Span , 
218+     is_incorrect_case :  bool , 
219+ } 
220+ 
221+ /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. 
222+ fn  find_similar_kw ( lookup :  Ident ,  candidates :  & [ Symbol ] )  -> Option < MisspelledKw >  { 
223+     let  lowercase = lookup. name . as_str ( ) . to_lowercase ( ) ; 
224+     let  lowercase_sym = Symbol :: intern ( & lowercase) ; 
225+     if  candidates. contains ( & lowercase_sym)  { 
226+         Some ( MisspelledKw  {  similar_kw :  lowercase,  span :  lookup. span ,  is_incorrect_case :  true  } ) 
227+     }  else  if  let  Some ( similar_sym)  = find_best_match_for_name ( candidates,  lookup. name ,  None )  { 
228+         Some ( MisspelledKw  { 
229+             similar_kw :  similar_sym. to_string ( ) , 
230+             span :  lookup. span , 
231+             is_incorrect_case :  false , 
232+         } ) 
233+     }  else  { 
234+         None 
235+     } 
236+ } 
237+ 
206238struct  MultiSugg  { 
207239    msg :  String , 
208240    patches :  Vec < ( Span ,  String ) > , 
@@ -638,9 +670,9 @@ impl<'a> Parser<'a> {
638670            let  concat = Symbol :: intern ( & format ! ( "{prev}{cur}" ) ) ; 
639671            let  ident = Ident :: new ( concat,  DUMMY_SP ) ; 
640672            if  ident. is_used_keyword ( )  || ident. is_reserved ( )  || ident. is_raw_guess ( )  { 
641-                 let  span  = self . prev_token . span . to ( self . token . span ) ; 
673+                 let  concat_span  = self . prev_token . span . to ( self . token . span ) ; 
642674                err. span_suggestion_verbose ( 
643-                     span , 
675+                     concat_span , 
644676                    format ! ( "consider removing the space to spell keyword `{concat}`" ) , 
645677                    concat, 
646678                    Applicability :: MachineApplicable , 
@@ -741,9 +773,55 @@ impl<'a> Parser<'a> {
741773            err. span_label ( sp,  label_exp) ; 
742774            err. span_label ( self . token . span ,  "unexpected token" ) ; 
743775        } 
776+ 
777+         // Check for misspelled keywords if there are no suggestions added to the diagnostic. 
778+         if  err. suggestions . as_ref ( ) . is_ok_and ( |code_suggestions| code_suggestions. is_empty ( ) )  { 
779+             self . check_for_misspelled_kw ( & mut  err,  & expected) ; 
780+         } 
744781        Err ( err) 
745782    } 
746783
784+     /// Checks if the current token or the previous token are misspelled keywords 
785+      /// and adds a helpful suggestion. 
786+      fn  check_for_misspelled_kw ( & self ,  err :  & mut  Diag < ' _ > ,  expected :  & [ TokenType ] )  { 
787+         let  Some ( ( curr_ident,  _) )  = self . token . ident ( )  else  { 
788+             return ; 
789+         } ; 
790+         let  expected_tokens:  & [ TokenType ]  =
791+             expected. len ( ) . checked_sub ( 10 ) . map_or ( & expected,  |index| & expected[ index..] ) ; 
792+         let  expected_keywords:  Vec < Symbol >  = expected_tokens
793+             . iter ( ) 
794+             . filter_map ( |token| if  let  TokenType :: Keyword ( kw)  = token {  Some ( * kw)  }  else  {  None  } ) 
795+             . collect ( ) ; 
796+ 
797+         // When there a few keywords in the last ten elements of `self.expected_tokens` and the current 
798+         // token is an identifier, it's probably a misspelled keyword. 
799+         // This handles code like `async Move {}`, misspelled `if` in match guard, misspelled `else` in `if`-`else` 
800+         // and mispelled `where` in a where clause. 
801+         if  !expected_keywords. is_empty ( ) 
802+             && !curr_ident. is_used_keyword ( ) 
803+             && let  Some ( misspelled_kw)  = find_similar_kw ( curr_ident,  & expected_keywords) 
804+         { 
805+             err. subdiagnostic ( misspelled_kw) ; 
806+         }  else  if  let  Some ( ( prev_ident,  _) )  = self . prev_token . ident ( ) 
807+             && !prev_ident. is_used_keyword ( ) 
808+         { 
809+             // We generate a list of all keywords at runtime rather than at compile time 
810+             // so that it gets generated only when the diagnostic needs it. 
811+             // Also, it is unlikely that this list is generated multiple times because the 
812+             // parser halts after execution hits this path. 
813+             let  all_keywords = AllKeywords :: new ( ) . collect_used ( || prev_ident. span . edition ( ) ) ; 
814+ 
815+             // Otherwise, check the previous token with all the keywords as possible candidates. 
816+             // This handles code like `Struct Human;` and `While a < b {}`. 
817+             // We check the previous token only when the current token is an identifier to avoid false 
818+             // positives like suggesting keyword `for` for `extern crate foo {}`. 
819+             if  let  Some ( misspelled_kw)  = find_similar_kw ( prev_ident,  & all_keywords)  { 
820+                 err. subdiagnostic ( misspelled_kw) ; 
821+             } 
822+         } 
823+     } 
824+ 
747825    /// The user has written `#[attr] expr` which is unsupported. (#106020) 
748826     pub ( super )  fn  attr_on_non_tail_expr ( & self ,  expr :  & Expr )  -> ErrorGuaranteed  { 
749827        // Missing semicolon typo error. 
@@ -846,6 +924,7 @@ impl<'a> Parser<'a> {
846924                ) ; 
847925            } 
848926        } 
927+ 
849928        err. emit ( ) 
850929    } 
851930
0 commit comments