@@ -13,7 +13,7 @@ use syntax_pos::{SourceFile, Span, MultiSpan};
1313
1414use crate :: {
1515 Level , CodeSuggestion , Diagnostic , SubDiagnostic ,
16- SuggestionStyle , SourceMapperDyn , DiagnosticId ,
16+ SuggestionStyle , SourceMapper , SourceMapperDyn , DiagnosticId ,
1717} ;
1818use crate :: Level :: Error ;
1919use crate :: snippet:: { Annotation , AnnotationType , Line , MultilineAnnotation , StyledString , Style } ;
@@ -192,6 +192,8 @@ pub trait Emitter {
192192 true
193193 }
194194
195+ fn source_map ( & self ) -> Option < & Lrc < SourceMapperDyn > > ;
196+
195197 /// Formats the substitutions of the primary_span
196198 ///
197199 /// The are a lot of conditions to this method, but in short:
@@ -204,7 +206,7 @@ pub trait Emitter {
204206 /// we return the original `primary_span` and the original suggestions.
205207 fn primary_span_formatted < ' a > (
206208 & mut self ,
207- db : & ' a Diagnostic
209+ db : & ' a Diagnostic ,
208210 ) -> ( MultiSpan , & ' a [ CodeSuggestion ] ) {
209211 let mut primary_span = db. span . clone ( ) ;
210212 if let Some ( ( sugg, rest) ) = db. suggestions . split_first ( ) {
@@ -234,7 +236,20 @@ pub trait Emitter {
234236 format ! ( "help: {}" , sugg. msg)
235237 } else {
236238 // Show the default suggestion text with the substitution
237- format ! ( "help: {}: `{}`" , sugg. msg, substitution)
239+ format ! (
240+ "help: {}{}: `{}`" ,
241+ sugg. msg,
242+ if self . source_map( ) . map( |sm| is_case_difference(
243+ & * * sm,
244+ substitution,
245+ sugg. substitutions[ 0 ] . parts[ 0 ] . span,
246+ ) ) . unwrap_or( false ) {
247+ " (notice the capitalization)"
248+ } else {
249+ ""
250+ } ,
251+ substitution,
252+ )
238253 } ;
239254 primary_span. push_span_label ( sugg. substitutions [ 0 ] . parts [ 0 ] . span , msg) ;
240255
@@ -382,6 +397,10 @@ pub trait Emitter {
382397}
383398
384399impl Emitter for EmitterWriter {
400+ fn source_map ( & self ) -> Option < & Lrc < SourceMapperDyn > > {
401+ self . sm . as_ref ( )
402+ }
403+
385404 fn emit_diagnostic ( & mut self , db : & Diagnostic ) {
386405 let mut children = db. children . clone ( ) ;
387406 let ( mut primary_span, suggestions) = self . primary_span_formatted ( & db) ;
@@ -1461,7 +1480,9 @@ impl EmitterWriter {
14611480 let suggestions = suggestion. splice_lines ( & * * sm) ;
14621481
14631482 let mut row_num = 2 ;
1464- for & ( ref complete, ref parts) in suggestions. iter ( ) . take ( MAX_SUGGESTIONS ) {
1483+ let mut notice_capitalization = false ;
1484+ for ( complete, parts, only_capitalization) in suggestions. iter ( ) . take ( MAX_SUGGESTIONS ) {
1485+ notice_capitalization |= only_capitalization;
14651486 // Only show underline if the suggestion spans a single line and doesn't cover the
14661487 // entirety of the code output. If you have multiple replacements in the same line
14671488 // of code, show the underline.
@@ -1552,7 +1573,10 @@ impl EmitterWriter {
15521573 }
15531574 if suggestions. len ( ) > MAX_SUGGESTIONS {
15541575 let msg = format ! ( "and {} other candidates" , suggestions. len( ) - MAX_SUGGESTIONS ) ;
1555- buffer. puts ( row_num, 0 , & msg, Style :: NoStyle ) ;
1576+ buffer. puts ( row_num, max_line_num_len + 3 , & msg, Style :: NoStyle ) ;
1577+ } else if notice_capitalization {
1578+ let msg = "notice the capitalization difference" ;
1579+ buffer. puts ( row_num, max_line_num_len + 3 , & msg, Style :: NoStyle ) ;
15561580 }
15571581 emit_to_destination ( & buffer. render ( ) , level, & mut self . dst , self . short_message ) ?;
15581582 Ok ( ( ) )
@@ -2034,3 +2058,18 @@ impl<'a> Drop for WritableDst<'a> {
20342058 }
20352059 }
20362060}
2061+
2062+ /// Whether the original and suggested code are visually similar enough to warrant extra wording.
2063+ pub fn is_case_difference ( sm : & dyn SourceMapper , suggested : & str , sp : Span ) -> bool {
2064+ // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
2065+ let found = sm. span_to_snippet ( sp) . unwrap ( ) ;
2066+ let ascii_confusables = & [ 'c' , 'f' , 'i' , 'k' , 'o' , 's' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' ] ;
2067+ // All the chars that differ in capitalization are confusable (above):
2068+ let confusable = found. chars ( ) . zip ( suggested. chars ( ) ) . filter ( |( f, s) | f != s) . all ( |( f, s) | {
2069+ ( ascii_confusables. contains ( & f) || ascii_confusables. contains ( & s) )
2070+ } ) ;
2071+ confusable && found. to_lowercase ( ) == suggested. to_lowercase ( )
2072+ // FIXME: We sometimes suggest the same thing we already have, which is a
2073+ // bug, but be defensive against that here.
2074+ && found != suggested
2075+ }
0 commit comments