]> git.lizzy.rs Git - rust.git/commitdiff
Improve E0433, so that it suggests missing imports
authorPatryk Wychowaniec <wychowaniec.patryk@gmail.com>
Tue, 2 Jun 2020 18:16:23 +0000 (20:16 +0200)
committerPatryk Wychowaniec <wychowaniec.patryk@gmail.com>
Tue, 2 Jun 2020 18:41:25 +0000 (20:41 +0200)
src/librustc_resolve/diagnostics.rs
src/librustc_resolve/late.rs
src/librustc_resolve/lib.rs
src/test/ui/derived-errors/issue-31997-1.stderr
src/test/ui/error-codes/E0433.rs
src/test/ui/error-codes/E0433.stderr
src/test/ui/hygiene/no_implicit_prelude.stderr
src/test/ui/resolve/use_suggestion.rs [new file with mode: 0644]
src/test/ui/resolve/use_suggestion.stderr [new file with mode: 0644]

index ea237f1a04f992d78ebfe7a6a4c14d08b202fe09..cbb2878011c5f2741e4fc206c421d6ea4cba901d 100644 (file)
@@ -1475,7 +1475,7 @@ fn find_span_immediately_after_crate_name(
     // This is `None` if all placement locations are inside expansions
     use_placement_span: Option<Span>,
     candidates: &[ImportSuggestion],
-    better: bool,
+    instead: bool,
     found_use: bool,
 ) {
     if candidates.is_empty() {
@@ -1486,6 +1486,7 @@ fn find_span_immediately_after_crate_name(
     // by iterating through a hash map, so make sure they are ordered:
     let mut path_strings: Vec<_> =
         candidates.iter().map(|c| path_names_to_string(&c.path)).collect();
+
     path_strings.sort();
     path_strings.dedup();
 
@@ -1494,8 +1495,9 @@ fn find_span_immediately_after_crate_name(
     } else {
         ("one of these", "items")
     };
-    let instead = if better { " instead" } else { "" };
-    let msg = format!("consider importing {} {}{}", determiner, kind, instead);
+
+    let instead = if instead { " instead" } else { "" };
+    let mut msg = format!("consider importing {} {}{}", determiner, kind, instead);
 
     if let Some(span) = use_placement_span {
         for candidate in &mut path_strings {
@@ -1507,12 +1509,13 @@ fn find_span_immediately_after_crate_name(
 
         err.span_suggestions(span, &msg, path_strings.into_iter(), Applicability::Unspecified);
     } else {
-        let mut msg = msg;
         msg.push(':');
+
         for candidate in path_strings {
             msg.push('\n');
             msg.push_str(&candidate);
         }
+
         err.note(&msg);
     }
 }
index 3b49b3b6ff7d274274594292bfb568f56b99785f..f6728b758db650106bc37021bfbaf3d7b49f7616 100644 (file)
@@ -29,8 +29,9 @@
 use smallvec::{smallvec, SmallVec};
 
 use log::debug;
+use rustc_span::source_map::{respan, Spanned};
 use std::collections::BTreeSet;
-use std::mem::replace;
+use std::mem::{replace, take};
 
 mod diagnostics;
 crate mod lifetimes;
@@ -234,6 +235,13 @@ fn descr_expected(self) -> &'static str {
         }
     }
 
+    fn is_call(self) -> bool {
+        match self {
+            PathSource::Expr(Some(&Expr { kind: ExprKind::Call(..), .. })) => true,
+            _ => false,
+        }
+    }
+
     crate fn is_expected(self, res: Res) -> bool {
         match self {
             PathSource::Type => match res {
@@ -1620,14 +1628,83 @@ fn smart_resolve_path_fragment(
 
         let report_errors = |this: &mut Self, res: Option<Res>| {
             let (err, candidates) = this.smart_resolve_report_errors(path, span, source, res);
+
             let def_id = this.parent_scope.module.normal_ancestor_id;
-            let better = res.is_some();
+            let instead = res.is_some();
             let suggestion =
                 if res.is_none() { this.report_missing_type_error(path) } else { None };
-            this.r.use_injections.push(UseError { err, candidates, def_id, better, suggestion });
+
+            this.r.use_injections.push(UseError { err, candidates, def_id, instead, suggestion });
+
             PartialRes::new(Res::Err)
         };
 
+        // For paths originating from calls (like in `HashMap::new()`), tries
+        // to enrich the plain `failed to resolve: ...` message with hints
+        // about possible missing imports.
+        //
+        // Similar thing, for types, happens in `report_errors` above.
+        let report_errors_for_call = |this: &mut Self, parent_err: Spanned<ResolutionError<'a>>| {
+            if !source.is_call() {
+                return Some(parent_err);
+            }
+
+            // Before we start looking for candidates, we have to get our hands
+            // on the type user is trying to perform invocation on; basically:
+            // we're transforming `HashMap::new` into just `HashMap`
+            let path = if let Some((_, path)) = path.split_last() {
+                path
+            } else {
+                return Some(parent_err);
+            };
+
+            let (mut err, candidates) =
+                this.smart_resolve_report_errors(path, span, PathSource::Type, None);
+
+            if candidates.is_empty() {
+                err.cancel();
+                return Some(parent_err);
+            }
+
+            // There are two different error messages user might receive at
+            // this point:
+            // - E0412 cannot find type `{}` in this scope
+            // - E0433 failed to resolve: use of undeclared type or module `{}`
+            //
+            // The first one is emitted for paths in type-position, and the
+            // latter one - for paths in expression-position.
+            //
+            // Thus (since we're in expression-position at this point), not to
+            // confuse the user, we want to keep the *message* from E04322 (so
+            // `parent_err`), but we want *hints* from E0412 (so `err`).
+            //
+            // And that's what happens below - we're just mixing both messages
+            // into a single one.
+            let mut parent_err = this.r.into_struct_error(parent_err.span, parent_err.node);
+
+            parent_err.cancel();
+
+            err.message = take(&mut parent_err.message);
+            err.code = take(&mut parent_err.code);
+            err.children = take(&mut parent_err.children);
+
+            drop(parent_err);
+
+            let def_id = this.parent_scope.module.normal_ancestor_id;
+
+            this.r.use_injections.push(UseError {
+                err,
+                candidates,
+                def_id,
+                instead: false,
+                suggestion: None,
+            });
+
+            // We don't return `Some(parent_err)` here, because the error will
+            // be already printed as part of the `use` injections
+            None
+        };
+
         let partial_res = match self.resolve_qpath_anywhere(
             id,
             qself,
@@ -1637,14 +1714,15 @@ fn smart_resolve_path_fragment(
             source.defer_to_typeck(),
             crate_lint,
         ) {
-            Some(partial_res) if partial_res.unresolved_segments() == 0 => {
+            Ok(Some(partial_res)) if partial_res.unresolved_segments() == 0 => {
                 if is_expected(partial_res.base_res()) || partial_res.base_res() == Res::Err {
                     partial_res
                 } else {
                     report_errors(self, Some(partial_res.base_res()))
                 }
             }
-            Some(partial_res) if source.defer_to_typeck() => {
+
+            Ok(Some(partial_res)) if source.defer_to_typeck() => {
                 // Not fully resolved associated item `T::A::B` or `<T as Tr>::A::B`
                 // or `<T>::A::B`. If `B` should be resolved in value namespace then
                 // it needs to be added to the trait map.
@@ -1655,25 +1733,34 @@ fn smart_resolve_path_fragment(
                 }
 
                 let mut std_path = vec![Segment::from_ident(Ident::with_dummy_span(sym::std))];
+
                 std_path.extend(path);
+
                 if self.r.primitive_type_table.primitive_types.contains_key(&path[0].ident.name) {
-                    let cl = CrateLint::No;
-                    let ns = Some(ns);
                     if let PathResult::Module(_) | PathResult::NonModule(_) =
-                        self.resolve_path(&std_path, ns, false, span, cl)
+                        self.resolve_path(&std_path, Some(ns), false, span, CrateLint::No)
                     {
-                        // check if we wrote `str::from_utf8` instead of `std::str::from_utf8`
+                        // Check if we wrote `str::from_utf8` instead of `std::str::from_utf8`
                         let item_span =
                             path.iter().last().map(|segment| segment.ident.span).unwrap_or(span);
-                        debug!("accessed item from `std` submodule as a bare type {:?}", std_path);
+
                         let mut hm = self.r.session.confused_type_with_std_module.borrow_mut();
                         hm.insert(item_span, span);
-                        // In some places (E0223) we only have access to the full path
                         hm.insert(span, span);
                     }
                 }
+
                 partial_res
             }
+
+            Err(err) => {
+                if let Some(err) = report_errors_for_call(self, err) {
+                    self.r.report_error(err.span, err.node);
+                }
+
+                PartialRes::new(Res::Err)
+            }
+
             _ => report_errors(self, None),
         };
 
@@ -1682,6 +1769,7 @@ fn smart_resolve_path_fragment(
             // Avoid recording definition of `A::B` in `<T as A>::B::C`.
             self.r.record_partial_res(id, partial_res);
         }
+
         partial_res
     }
 
@@ -1711,17 +1799,16 @@ fn resolve_qpath_anywhere(
         span: Span,
         defer_to_typeck: bool,
         crate_lint: CrateLint,
-    ) -> Option<PartialRes> {
+    ) -> Result<Option<PartialRes>, Spanned<ResolutionError<'a>>> {
         let mut fin_res = None;
+
         for (i, ns) in [primary_ns, TypeNS, ValueNS].iter().cloned().enumerate() {
             if i == 0 || ns != primary_ns {
-                match self.resolve_qpath(id, qself, path, ns, span, crate_lint) {
-                    // If defer_to_typeck, then resolution > no resolution,
-                    // otherwise full resolution > partial resolution > no resolution.
+                match self.resolve_qpath(id, qself, path, ns, span, crate_lint)? {
                     Some(partial_res)
                         if partial_res.unresolved_segments() == 0 || defer_to_typeck =>
                     {
-                        return Some(partial_res);
+                        return Ok(Some(partial_res));
                     }
                     partial_res => {
                         if fin_res.is_none() {
@@ -1732,19 +1819,19 @@ fn resolve_qpath_anywhere(
             }
         }
 
-        // `MacroNS`
         assert!(primary_ns != MacroNS);
+
         if qself.is_none() {
             let path_seg = |seg: &Segment| PathSegment::from_ident(seg.ident);
             let path = Path { segments: path.iter().map(path_seg).collect(), span };
             if let Ok((_, res)) =
                 self.r.resolve_macro_path(&path, None, &self.parent_scope, false, false)
             {
-                return Some(PartialRes::new(res));
+                return Ok(Some(PartialRes::new(res)));
             }
         }
 
-        fin_res
+        Ok(fin_res)
     }
 
     /// Handles paths that may refer to associated items.
@@ -1756,7 +1843,7 @@ fn resolve_qpath(
         ns: Namespace,
         span: Span,
         crate_lint: CrateLint,
-    ) -> Option<PartialRes> {
+    ) -> Result<Option<PartialRes>, Spanned<ResolutionError<'a>>> {
         debug!(
             "resolve_qpath(id={:?}, qself={:?}, path={:?}, ns={:?}, span={:?})",
             id, qself, path, ns, span,
@@ -1767,10 +1854,10 @@ fn resolve_qpath(
                 // This is a case like `<T>::B`, where there is no
                 // trait to resolve.  In that case, we leave the `B`
                 // segment to be resolved by type-check.
-                return Some(PartialRes::with_unresolved_segments(
+                return Ok(Some(PartialRes::with_unresolved_segments(
                     Res::Def(DefKind::Mod, DefId::local(CRATE_DEF_INDEX)),
                     path.len(),
-                ));
+                )));
             }
 
             // Make sure `A::B` in `<T as A::B>::C` is a trait item.
@@ -1800,10 +1887,10 @@ fn resolve_qpath(
             // The remaining segments (the `C` in our example) will
             // have to be resolved by type-check, since that requires doing
             // trait resolution.
-            return Some(PartialRes::with_unresolved_segments(
+            return Ok(Some(PartialRes::with_unresolved_segments(
                 partial_res.base_res(),
                 partial_res.unresolved_segments() + path.len() - qself.position - 1,
-            ));
+            )));
         }
 
         let result = match self.resolve_path(&path, Some(ns), true, span, crate_lint) {
@@ -1838,11 +1925,10 @@ fn resolve_qpath(
                 PartialRes::new(module.res().unwrap())
             }
             PathResult::Failed { is_error_from_last_segment: false, span, label, suggestion } => {
-                self.r.report_error(span, ResolutionError::FailedToResolve { label, suggestion });
-                PartialRes::new(Res::Err)
+                return Err(respan(span, ResolutionError::FailedToResolve { label, suggestion }));
             }
-            PathResult::Module(..) | PathResult::Failed { .. } => return None,
-            PathResult::Indeterminate => bug!("indetermined path result in resolve_qpath"),
+            PathResult::Module(..) | PathResult::Failed { .. } => return Ok(None),
+            PathResult::Indeterminate => bug!("indeterminate path result in resolve_qpath"),
         };
 
         if path.len() > 1
@@ -1862,7 +1948,7 @@ fn resolve_qpath(
                     PathResult::Module(ModuleOrUniformRoot::Module(module)) => {
                         module.res().unwrap()
                     }
-                    _ => return Some(result),
+                    _ => return Ok(Some(result)),
                 }
             };
             if result.base_res() == unqualified_result {
@@ -1871,7 +1957,7 @@ fn resolve_qpath(
             }
         }
 
-        Some(result)
+        Ok(Some(result))
     }
 
     fn with_resolved_label(&mut self, label: Option<Label>, id: NodeId, f: impl FnOnce(&mut Self)) {
index e5fc23e14d2ab729c423824fa97e0ab7b736fd05..c7f10fac6bc0826b9556a63b8bd54da5400fea7a 100644 (file)
@@ -618,13 +618,13 @@ struct PrivacyError<'a> {
 
 struct UseError<'a> {
     err: DiagnosticBuilder<'a>,
-    /// Attach `use` statements for these candidates.
+    /// Candidates which user could `use` to access the missing type.
     candidates: Vec<ImportSuggestion>,
-    /// The `NodeId` of the module to place the use-statements in.
+    /// The `DefId` of the module to place the use-statements in.
     def_id: DefId,
-    /// Whether the diagnostic should state that it's "better".
-    better: bool,
-    /// Extra free form suggestion. Currently used to suggest new type parameter.
+    /// Whether the diagnostic should say "instead" (as in `consider importing ... instead`).
+    instead: bool,
+    /// Extra free-form suggestion.
     suggestion: Option<(Span, &'static str, String, Applicability)>,
 }
 
@@ -2577,12 +2577,12 @@ fn report_errors(&mut self, krate: &Crate) {
     }
 
     fn report_with_use_injections(&mut self, krate: &Crate) {
-        for UseError { mut err, candidates, def_id, better, suggestion } in
+        for UseError { mut err, candidates, def_id, instead, suggestion } in
             self.use_injections.drain(..)
         {
             let (span, found_use) = UsePlacementFinder::check(&self.definitions, krate, def_id);
             if !candidates.is_empty() {
-                diagnostics::show_candidates(&mut err, span, &candidates, better, found_use);
+                diagnostics::show_candidates(&mut err, span, &candidates, instead, found_use);
             } else if let Some((span, msg, sugg, appl)) = suggestion {
                 err.span_suggestion(span, msg, sugg, appl);
             }
index 6df748122a2ebc2cf9d1d4ed27584518cd54c1b8..a4daf86cc8a211145cf2daf26a2fbb3e0513b58d 100644 (file)
@@ -2,7 +2,14 @@ error[E0433]: failed to resolve: use of undeclared type or module `HashMap`
   --> $DIR/issue-31997-1.rs:20:19
    |
 LL |     let mut map = HashMap::new();
-   |                   ^^^^^^^ use of undeclared type or module `HashMap`
+   |                   ^^^^^^^ not found in this scope
+   |
+help: consider importing one of these items
+   |
+LL | use std::collections::HashMap;
+   |
+LL | use std::collections::hash_map::HashMap;
+   |
 
 error: aborting due to previous error
 
index 9b54ec8c5cfde40f586dca8390ab21075113ea39..d555e6542632bcdc3c86940ef570b748b754ac60 100644 (file)
@@ -1,3 +1,3 @@
 fn main () {
-    let map = HashMap::new(); //~ ERROR E0433
+    let map = NonExistingMap::new(); //~ ERROR E0433
 }
index d852e18838442d0f0c6e6e62dbcc90739c17f53d..d9555e1fcf7a8718101600602f1565cf3dd03558 100644 (file)
@@ -1,8 +1,8 @@
-error[E0433]: failed to resolve: use of undeclared type or module `HashMap`
+error[E0433]: failed to resolve: use of undeclared type or module `NonExistingMap`
   --> $DIR/E0433.rs:2:15
    |
-LL |     let map = HashMap::new();
-   |               ^^^^^^^ use of undeclared type or module `HashMap`
+LL |     let map = NonExistingMap::new();
+   |               ^^^^^^^^^^^^^^ use of undeclared type or module `NonExistingMap`
 
 error: aborting due to previous error
 
index 986671c7810e70cc02ff6fd5e1dbbe15469f28a3..c0539434d02073e43920490c63404573a6a50122 100644 (file)
@@ -13,9 +13,15 @@ LL |     fn f() { ::bar::m!(); }
    |              ------------ in this macro invocation
 ...
 LL |         Vec::new();
-   |         ^^^ use of undeclared type or module `Vec`
+   |         ^^^ not found in this scope
    |
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+help: consider importing one of these items
+   |
+LL |     use std::prelude::v1::Vec;
+   |
+LL |     use std::vec::Vec;
+   |
 
 error[E0599]: no method named `clone` found for unit type `()` in the current scope
   --> $DIR/no_implicit_prelude.rs:12:12
diff --git a/src/test/ui/resolve/use_suggestion.rs b/src/test/ui/resolve/use_suggestion.rs
new file mode 100644 (file)
index 0000000..8c9bc6d
--- /dev/null
@@ -0,0 +1,7 @@
+fn main() {
+    let x1 = HashMap::new(); //~ ERROR failed to resolve
+    let x2 = GooMap::new(); //~ ERROR failed to resolve
+
+    let y1: HashMap; //~ ERROR cannot find type
+    let y2: GooMap; //~ ERROR cannot find type
+}
diff --git a/src/test/ui/resolve/use_suggestion.stderr b/src/test/ui/resolve/use_suggestion.stderr
new file mode 100644 (file)
index 0000000..2fd3d5d
--- /dev/null
@@ -0,0 +1,42 @@
+error[E0433]: failed to resolve: use of undeclared type or module `GooMap`
+  --> $DIR/use_suggestion.rs:3:14
+   |
+LL |     let x2 = GooMap::new();
+   |              ^^^^^^ use of undeclared type or module `GooMap`
+
+error[E0433]: failed to resolve: use of undeclared type or module `HashMap`
+  --> $DIR/use_suggestion.rs:2:14
+   |
+LL |     let x1 = HashMap::new();
+   |              ^^^^^^^ not found in this scope
+   |
+help: consider importing one of these items
+   |
+LL | use std::collections::HashMap;
+   |
+LL | use std::collections::hash_map::HashMap;
+   |
+
+error[E0412]: cannot find type `HashMap` in this scope
+  --> $DIR/use_suggestion.rs:5:13
+   |
+LL |     let y1: HashMap;
+   |             ^^^^^^^ not found in this scope
+   |
+help: consider importing one of these items
+   |
+LL | use std::collections::HashMap;
+   |
+LL | use std::collections::hash_map::HashMap;
+   |
+
+error[E0412]: cannot find type `GooMap` in this scope
+  --> $DIR/use_suggestion.rs:6:13
+   |
+LL |     let y2: GooMap;
+   |             ^^^^^^ not found in this scope
+
+error: aborting due to 4 previous errors
+
+Some errors have detailed explanations: E0412, E0433.
+For more information about an error, try `rustc --explain E0412`.