@@ -13,7 +13,7 @@ use syntax_pos::{SourceFile, Span, MultiSpan};
13
13
14
14
use crate :: {
15
15
Level , CodeSuggestion , Diagnostic , SubDiagnostic ,
16
- SuggestionStyle , SourceMapperDyn , DiagnosticId ,
16
+ SuggestionStyle , SourceMapper , SourceMapperDyn , DiagnosticId ,
17
17
} ;
18
18
use crate :: Level :: Error ;
19
19
use crate :: snippet:: { Annotation , AnnotationType , Line , MultilineAnnotation , StyledString , Style } ;
@@ -192,6 +192,8 @@ pub trait Emitter {
192
192
true
193
193
}
194
194
195
+ fn source_map ( & self ) -> Option < & Lrc < SourceMapperDyn > > ;
196
+
195
197
/// Formats the substitutions of the primary_span
196
198
///
197
199
/// The are a lot of conditions to this method, but in short:
@@ -204,7 +206,7 @@ pub trait Emitter {
204
206
/// we return the original `primary_span` and the original suggestions.
205
207
fn primary_span_formatted < ' a > (
206
208
& mut self ,
207
- db : & ' a Diagnostic
209
+ db : & ' a Diagnostic ,
208
210
) -> ( MultiSpan , & ' a [ CodeSuggestion ] ) {
209
211
let mut primary_span = db. span . clone ( ) ;
210
212
if let Some ( ( sugg, rest) ) = db. suggestions . split_first ( ) {
@@ -234,7 +236,20 @@ pub trait Emitter {
234
236
format ! ( "help: {}" , sugg. msg)
235
237
} else {
236
238
// 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
+ )
238
253
} ;
239
254
primary_span. push_span_label ( sugg. substitutions [ 0 ] . parts [ 0 ] . span , msg) ;
240
255
@@ -382,6 +397,10 @@ pub trait Emitter {
382
397
}
383
398
384
399
impl Emitter for EmitterWriter {
400
+ fn source_map ( & self ) -> Option < & Lrc < SourceMapperDyn > > {
401
+ self . sm . as_ref ( )
402
+ }
403
+
385
404
fn emit_diagnostic ( & mut self , db : & Diagnostic ) {
386
405
let mut children = db. children . clone ( ) ;
387
406
let ( mut primary_span, suggestions) = self . primary_span_formatted ( & db) ;
@@ -1461,7 +1480,9 @@ impl EmitterWriter {
1461
1480
let suggestions = suggestion. splice_lines ( & * * sm) ;
1462
1481
1463
1482
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;
1465
1486
// Only show underline if the suggestion spans a single line and doesn't cover the
1466
1487
// entirety of the code output. If you have multiple replacements in the same line
1467
1488
// of code, show the underline.
@@ -1552,7 +1573,10 @@ impl EmitterWriter {
1552
1573
}
1553
1574
if suggestions. len ( ) > MAX_SUGGESTIONS {
1554
1575
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 ) ;
1556
1580
}
1557
1581
emit_to_destination ( & buffer. render ( ) , level, & mut self . dst , self . short_message ) ?;
1558
1582
Ok ( ( ) )
@@ -2034,3 +2058,18 @@ impl<'a> Drop for WritableDst<'a> {
2034
2058
}
2035
2059
}
2036
2060
}
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