Skip to content

Commit

Permalink
Add suggestions for unresolved imports.
Browse files Browse the repository at this point in the history
This commit adds suggestions for unresolved imports in the cases where
there could be a missing `crate::`, `super::`, `self::` or a missing
external crate name before an import.
  • Loading branch information
davidtwco committed Sep 20, 2018
1 parent 32dc5a0 commit 317163a
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 19 deletions.
162 changes: 162 additions & 0 deletions src/librustc_resolve/error_reporting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use {CrateLint, PathResult};

use syntax::ast::Ident;
use syntax::symbol::keywords;
use syntax_pos::Span;

use resolve_imports::ImportResolver;

impl<'a, 'b:'a, 'c: 'b> ImportResolver<'a, 'b, 'c> {
/// Add suggestions for a path that cannot be resolved.
pub(crate) fn make_path_suggestion(
&mut self,
span: Span,
path: Vec<Ident>
) -> Option<Vec<Ident>> {
debug!("make_path_suggestion: span={:?} path={:?}", span, path);
// If we don't have a path to suggest changes to, then return.
if path.is_empty() {
return None;
}

// Check whether a ident is a path segment that is not root.
let is_special = |ident: Ident| ident.is_path_segment_keyword() &&
ident.name != keywords::CrateRoot.name();

match (path.get(0), path.get(1)) {
// Make suggestions that require at least two non-special path segments.
(Some(fst), Some(snd)) if !is_special(*fst) && !is_special(*snd) => {
debug!("make_path_suggestion: fst={:?} snd={:?}", fst, snd);

self.make_missing_self_suggestion(span, path.clone())
.or_else(|| self.make_missing_crate_suggestion(span, path.clone()))
.or_else(|| self.make_missing_super_suggestion(span, path.clone()))
.or_else(|| self.make_external_crate_suggestion(span, path.clone()))
},
_ => None,
}
}

/// Suggest a missing `self::` if that resolves to an correct module.
///
/// ```
/// |
/// LL | use foo::Bar;
/// | ^^^ Did you mean `self::foo`?
/// ```
fn make_missing_self_suggestion(
&mut self,
span: Span,
mut path: Vec<Ident>
) -> Option<Vec<Ident>> {
// Replace first ident with `self` and check if that is valid.
path[0].name = keywords::SelfValue.name();
let result = self.resolve_path(None, &path, None, false, span, CrateLint::No);
debug!("make_missing_self_suggestion: path={:?} result={:?}", path, result);
if let PathResult::Module(..) = result {
Some(path)
} else {
None
}
}

/// Suggest a missing `crate::` if that resolves to an correct module.
///
/// ```
/// |
/// LL | use foo::Bar;
/// | ^^^ Did you mean `crate::foo`?
/// ```
fn make_missing_crate_suggestion(
&mut self,
span: Span,
mut path: Vec<Ident>
) -> Option<Vec<Ident>> {
// Replace first ident with `crate` and check if that is valid.
path[0].name = keywords::Crate.name();
let result = self.resolve_path(None, &path, None, false, span, CrateLint::No);
debug!("make_missing_crate_suggestion: path={:?} result={:?}", path, result);
if let PathResult::Module(..) = result {
Some(path)
} else {
None
}
}

/// Suggest a missing `super::` if that resolves to an correct module.
///
/// ```
/// |
/// LL | use foo::Bar;
/// | ^^^ Did you mean `super::foo`?
/// ```
fn make_missing_super_suggestion(
&mut self,
span: Span,
mut path: Vec<Ident>
) -> Option<Vec<Ident>> {
// Replace first ident with `crate` and check if that is valid.
path[0].name = keywords::Super.name();
let result = self.resolve_path(None, &path, None, false, span, CrateLint::No);
debug!("make_missing_super_suggestion: path={:?} result={:?}", path, result);
if let PathResult::Module(..) = result {
Some(path)
} else {
None
}
}

/// Suggest a missing external crate name if that resolves to an correct module.
///
/// ```
/// |
/// LL | use foobar::Baz;
/// | ^^^ Did you mean `baz::foobar`?
/// ```
///
/// Used when importing a submodule of an external crate but missing that crate's
/// name as the first part of path.
fn make_external_crate_suggestion(
&mut self,
span: Span,
mut path: Vec<Ident>
) -> Option<Vec<Ident>> {
// Need to clone else we can't call `resolve_path` without a borrow error.
let external_crate_names = self.extern_prelude.clone();

// Insert a new path segment that we can replace.
let new_path_segment = path[0].clone();
path.insert(1, new_path_segment);

for name in &external_crate_names {
// Don't suggest meta as it will error in `resolve_path`.
if name.as_str() == "meta" {
continue;
}

// Replace the first after root (a placeholder we inserted) with a crate name
// and check if that is valid.
path[1].name = *name;
let result = self.resolve_path(None, &path, None, false, span, CrateLint::No);
debug!("make_external_crate_suggestion: name={:?} path={:?} result={:?}",
name, path, result);
if let PathResult::Module(..) = result {
return Some(path)
}
}

// Remove our placeholder segment.
path.remove(1);
None
}
}
2 changes: 1 addition & 1 deletion src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ use macros::{InvocationData, LegacyBinding, ParentScope};
// NB: This module needs to be declared first so diagnostics are
// registered before they are used.
mod diagnostics;

mod error_reporting;
mod macros;
mod check_unused;
mod build_reduced_graph;
Expand Down
18 changes: 7 additions & 11 deletions src/librustc_resolve/resolve_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,17 +957,13 @@ impl<'a, 'b:'a, 'c: 'b> ImportResolver<'a, 'b, 'c> {
return None;
}
PathResult::Failed(span, msg, true) => {
let (mut self_path, mut self_result) = (module_path.clone(), None);
let is_special = |ident: Ident| ident.is_path_segment_keyword() &&
ident.name != keywords::CrateRoot.name();
if !self_path.is_empty() && !is_special(self_path[0]) &&
!(self_path.len() > 1 && is_special(self_path[1])) {
self_path[0].name = keywords::SelfValue.name();
self_result = Some(self.resolve_path(None, &self_path, None, false,
span, CrateLint::No));
}
return if let Some(PathResult::Module(..)) = self_result {
Some((span, format!("Did you mean `{}`?", names_to_string(&self_path[..]))))
return if let Some(suggested_path) = self.make_path_suggestion(
span, module_path.clone()
) {
Some((
span,
format!("Did you mean `{}`?", names_to_string(&suggested_path[..]))
))
} else {
Some((span, msg))
};
Expand Down
6 changes: 3 additions & 3 deletions src/test/ui/resolve_self_super_hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ mod a {
mod b {
use alloc::HashMap;
//~^ ERROR unresolved import `alloc` [E0432]
//~| Did you mean `a::alloc`?
//~| Did you mean `super::alloc`?
mod c {
use alloc::HashMap;
//~^ ERROR unresolved import `alloc` [E0432]
//~| Did you mean `a::alloc`?
//~| Did you mean `std::alloc`?
mod d {
use alloc::HashMap;
//~^ ERROR unresolved import `alloc` [E0432]
//~| Did you mean `a::alloc`?
//~| Did you mean `std::alloc`?
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/ui/resolve_self_super_hint.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ error[E0432]: unresolved import `alloc`
--> $DIR/resolve_self_super_hint.rs:20:13
|
LL | use alloc::HashMap;
| ^^^^^ Did you mean `a::alloc`?
| ^^^^^ Did you mean `super::alloc`?

error[E0432]: unresolved import `alloc`
--> $DIR/resolve_self_super_hint.rs:24:17
|
LL | use alloc::HashMap;
| ^^^^^ Did you mean `a::alloc`?
| ^^^^^ Did you mean `std::alloc`?

error[E0432]: unresolved import `alloc`
--> $DIR/resolve_self_super_hint.rs:28:21
|
LL | use alloc::HashMap;
| ^^^^^ Did you mean `a::alloc`?
| ^^^^^ Did you mean `std::alloc`?

error: aborting due to 4 previous errors

Expand Down
15 changes: 15 additions & 0 deletions src/test/ui/rust-2018/auxiliary/baz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This file is used as part of the local-path-suggestions.rs test.

pub mod foobar {
pub struct Baz;
}
2 changes: 1 addition & 1 deletion src/test/ui/rust-2018/issue-54006.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error[E0432]: unresolved import `alloc`
--> $DIR/issue-54006.rs:16:5
|
LL | use alloc::vec;
| ^^^^^ Could not find `alloc` in `{{root}}`
| ^^^^^ Did you mean `std::alloc`?

error: cannot determine resolution for the macro `vec`
--> $DIR/issue-54006.rs:20:18
Expand Down
31 changes: 31 additions & 0 deletions src/test/ui/rust-2018/local-path-suggestions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// aux-build:baz.rs
// compile-flags:--extern baz
// edition:2018

mod foo {
type Bar = u32;
}

mod baz {
use foo::Bar;

fn baz() {
let x: Bar = 22;
}
}

use foo::Bar;

use foobar::Baz;

fn main() { }
21 changes: 21 additions & 0 deletions src/test/ui/rust-2018/local-path-suggestions.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0432]: unresolved import `foo`
--> $DIR/local-path-suggestions.rs:20:9
|
LL | use foo::Bar;
| ^^^ Did you mean `crate::foo`?

error[E0432]: unresolved import `foo`
--> $DIR/local-path-suggestions.rs:27:5
|
LL | use foo::Bar;
| ^^^ Did you mean `self::foo`?

error[E0432]: unresolved import `foobar`
--> $DIR/local-path-suggestions.rs:29:5
|
LL | use foobar::Baz;
| ^^^^^^ Did you mean `baz::foobar`?

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0432`.

0 comments on commit 317163a

Please sign in to comment.