about summary refs log tree commit diff
path: root/src/librustc_errors
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-10-15 00:54:10 +0000
committerbors <bors@rust-lang.org>2019-10-15 00:54:10 +0000
commite369d87b015a84653343032833d65d0545fd3f26 (patch)
treed58c82d0b3d84633a9452fa78a4c3c136cfa229b /src/librustc_errors
parente413dc36a83a5aad3ab6270373000693a917e92b (diff)
parent9422feb43395a7a1df60d6e81b5ead87abc90b4b (diff)
downloadrust-e369d87b015a84653343032833d65d0545fd3f26.tar.gz
rust-e369d87b015a84653343032833d65d0545fd3f26.zip
Auto merge of #65422 - tmandry:rollup-r5u3mlc, r=tmandry
Rollup of 10 pull requests

Successful merges:

 - #65170 (rustc_metadata: Privatize private code and remove dead code)
 - #65260 (Optimize `LexicalResolve::expansion`.)
 - #65261 (Remove `Option` from `TokenStream`)
 - #65332 (std::fmt: reorder docs)
 - #65340 (Several changes to the codegen backend organization)
 - #65365 (Include const generic arguments in metadata)
 - #65398 (Bring attention to suggestions when the only difference is capitalization)
 - #65410 (syntax: add parser recovery for intersection- / and-patterns `p1 @ p2`)
 - #65415 (Remove an outdated test output file)
 - #65416 (Minor sync changes)

Failed merges:

r? @ghost
Diffstat (limited to 'src/librustc_errors')
-rw-r--r--src/librustc_errors/annotate_snippet_emitter_writer.rs4
-rw-r--r--src/librustc_errors/emitter.rs49
-rw-r--r--src/librustc_errors/lib.rs32
3 files changed, 68 insertions, 17 deletions
diff --git a/src/librustc_errors/annotate_snippet_emitter_writer.rs b/src/librustc_errors/annotate_snippet_emitter_writer.rs
index 0281d10fd93..16a1a28cd3a 100644
--- a/src/librustc_errors/annotate_snippet_emitter_writer.rs
+++ b/src/librustc_errors/annotate_snippet_emitter_writer.rs
@@ -49,6 +49,10 @@ impl Emitter for AnnotateSnippetEmitterWriter {
                                    &suggestions);
     }
 
+    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
+        self.source_map.as_ref()
+    }
+
     fn should_show_explain(&self) -> bool {
         !self.short_message
     }
diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs
index 68f933363da..84400091100 100644
--- a/src/librustc_errors/emitter.rs
+++ b/src/librustc_errors/emitter.rs
@@ -13,7 +13,7 @@ use syntax_pos::{SourceFile, Span, MultiSpan};
 
 use crate::{
     Level, CodeSuggestion, Diagnostic, SubDiagnostic,
-    SuggestionStyle, SourceMapperDyn, DiagnosticId,
+    SuggestionStyle, SourceMapper, SourceMapperDyn, DiagnosticId,
 };
 use crate::Level::Error;
 use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, StyledString, Style};
@@ -192,6 +192,8 @@ pub trait Emitter {
         true
     }
 
+    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>>;
+
     /// Formats the substitutions of the primary_span
     ///
     /// The are a lot of conditions to this method, but in short:
@@ -204,7 +206,7 @@ pub trait Emitter {
     ///   we return the original `primary_span` and the original suggestions.
     fn primary_span_formatted<'a>(
         &mut self,
-        db: &'a Diagnostic
+        db: &'a Diagnostic,
     ) -> (MultiSpan, &'a [CodeSuggestion]) {
         let mut primary_span = db.span.clone();
         if let Some((sugg, rest)) = db.suggestions.split_first() {
@@ -234,7 +236,20 @@ pub trait Emitter {
                     format!("help: {}", sugg.msg)
                 } else {
                     // Show the default suggestion text with the substitution
-                    format!("help: {}: `{}`", sugg.msg, substitution)
+                    format!(
+                        "help: {}{}: `{}`",
+                        sugg.msg,
+                        if self.source_map().map(|sm| is_case_difference(
+                            &**sm,
+                            substitution,
+                            sugg.substitutions[0].parts[0].span,
+                        )).unwrap_or(false) {
+                            " (notice the capitalization)"
+                        } else {
+                            ""
+                        },
+                        substitution,
+                    )
                 };
                 primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
 
@@ -382,6 +397,10 @@ pub trait Emitter {
 }
 
 impl Emitter for EmitterWriter {
+    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
+        self.sm.as_ref()
+    }
+
     fn emit_diagnostic(&mut self, db: &Diagnostic) {
         let mut children = db.children.clone();
         let (mut primary_span, suggestions) = self.primary_span_formatted(&db);
@@ -1461,7 +1480,9 @@ impl EmitterWriter {
         let suggestions = suggestion.splice_lines(&**sm);
 
         let mut row_num = 2;
-        for &(ref complete, ref parts) in suggestions.iter().take(MAX_SUGGESTIONS) {
+        let mut notice_capitalization = false;
+        for (complete, parts, only_capitalization) in suggestions.iter().take(MAX_SUGGESTIONS) {
+            notice_capitalization |= only_capitalization;
             // Only show underline if the suggestion spans a single line and doesn't cover the
             // entirety of the code output. If you have multiple replacements in the same line
             // of code, show the underline.
@@ -1552,7 +1573,10 @@ impl EmitterWriter {
         }
         if suggestions.len() > MAX_SUGGESTIONS {
             let msg = format!("and {} other candidates", suggestions.len() - MAX_SUGGESTIONS);
-            buffer.puts(row_num, 0, &msg, Style::NoStyle);
+            buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
+        } else if notice_capitalization {
+            let msg = "notice the capitalization difference";
+            buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
         }
         emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
         Ok(())
@@ -2034,3 +2058,18 @@ impl<'a> Drop for WritableDst<'a> {
         }
     }
 }
+
+/// Whether the original and suggested code are visually similar enough to warrant extra wording.
+pub fn is_case_difference(sm: &dyn SourceMapper, suggested: &str, sp: Span) -> bool {
+    // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
+    let found = sm.span_to_snippet(sp).unwrap();
+    let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
+    // All the chars that differ in capitalization are confusable (above):
+    let confusable = found.chars().zip(suggested.chars()).filter(|(f, s)| f != s).all(|(f, s)| {
+        (ascii_confusables.contains(&f) || ascii_confusables.contains(&s))
+    });
+    confusable && found.to_lowercase() == suggested.to_lowercase()
+            // FIXME: We sometimes suggest the same thing we already have, which is a
+            //        bug, but be defensive against that here.
+            && found != suggested
+}
diff --git a/src/librustc_errors/lib.rs b/src/librustc_errors/lib.rs
index 2fae584c153..63df052a225 100644
--- a/src/librustc_errors/lib.rs
+++ b/src/librustc_errors/lib.rs
@@ -13,7 +13,7 @@ pub use emitter::ColorConfig;
 
 use Level::*;
 
-use emitter::{Emitter, EmitterWriter};
+use emitter::{Emitter, EmitterWriter, is_case_difference};
 use registry::Registry;
 
 use rustc_data_structures::sync::{self, Lrc, Lock};
@@ -37,13 +37,16 @@ pub mod registry;
 mod styled_buffer;
 mod lock;
 
-use syntax_pos::{BytePos,
-                 Loc,
-                 FileLinesResult,
-                 SourceFile,
-                 FileName,
-                 MultiSpan,
-                 Span};
+use syntax_pos::{
+    BytePos,
+    FileLinesResult,
+    FileName,
+    Loc,
+    MultiSpan,
+    SourceFile,
+    Span,
+    SpanSnippetError,
+};
 
 /// Indicates the confidence in the correctness of a suggestion.
 ///
@@ -147,6 +150,7 @@ pub trait SourceMapper {
     fn lookup_char_pos(&self, pos: BytePos) -> Loc;
     fn span_to_lines(&self, sp: Span) -> FileLinesResult;
     fn span_to_string(&self, sp: Span) -> String;
+    fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError>;
     fn span_to_filename(&self, sp: Span) -> FileName;
     fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span>;
     fn call_span_if_macro(&self, sp: Span) -> Span;
@@ -155,9 +159,12 @@ pub trait SourceMapper {
 }
 
 impl CodeSuggestion {
-    /// Returns the assembled code suggestions and whether they should be shown with an underline.
-    pub fn splice_lines(&self, cm: &SourceMapperDyn)
-                        -> Vec<(String, Vec<SubstitutionPart>)> {
+    /// Returns the assembled code suggestions, whether they should be shown with an underline
+    /// and whether the substitution only differs in capitalization.
+    pub fn splice_lines(
+        &self,
+        cm: &SourceMapperDyn,
+    ) -> Vec<(String, Vec<SubstitutionPart>, bool)> {
         use syntax_pos::{CharPos, Pos};
 
         fn push_trailing(buf: &mut String,
@@ -232,6 +239,7 @@ impl CodeSuggestion {
                 prev_hi = cm.lookup_char_pos(part.span.hi());
                 prev_line = fm.get_line(prev_hi.line - 1);
             }
+            let only_capitalization = is_case_difference(cm, &buf, bounding_span);
             // if the replacement already ends with a newline, don't print the next line
             if !buf.ends_with('\n') {
                 push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
@@ -240,7 +248,7 @@ impl CodeSuggestion {
             while buf.ends_with('\n') {
                 buf.pop();
             }
-            (buf, substitution.parts)
+            (buf, substitution.parts, only_capitalization)
         }).collect()
     }
 }