Skip to content

Commit

Permalink
Auto merge of #54391 - davidtwco:issue-54230, r=petrochenkov
Browse files Browse the repository at this point in the history
suggest `crate::...` for "local" paths in 2018

Fixes #54230.

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.

r? @nikomatsakis
  • Loading branch information
bors committed Oct 3, 2018
2 parents 5597ee8 + 5872d3e commit 4bf883b
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 19 deletions.
169 changes: 169 additions & 0 deletions src/librustc_resolve/error_reporting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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 std::collections::BTreeSet;

use syntax::ast::Ident;
use syntax::symbol::{keywords, Symbol};
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. We also store
// into a `BTreeMap` so we can get consistent ordering (and therefore the same diagnostic)
// each time.
let external_crate_names: BTreeSet<Symbol> = self.resolver.session.extern_prelude
.clone().drain().collect();

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

// Iterate in reverse so that we start with crates at the end of the alphabet. This means
// that we'll always get `std` before `core`.
for name in external_crate_names.iter().rev() {
let ident = Ident::with_empty_ctxt(*name);
// Calling `maybe_process_path_extern` ensures that we're only running `resolve_path`
// on a crate name that won't ICE.
if let Some(_) = self.crate_loader.maybe_process_path_extern(*name, ident.span) {
// 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 @@ -84,7 +84,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 @@ -958,17 +958,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
36 changes: 36 additions & 0 deletions src/test/ui/rust-2018/local-path-suggestions-2015.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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:2015

// This test exists to demonstrate the behaviour of the import suggestions
// from the `local-path-suggestions-2018.rs` test when not using the 2018 edition.

extern crate baz as aux_baz;

mod foo {
pub type Bar = u32;
}

mod baz {
use foo::Bar;

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

use foo::Bar;

use foobar::Baz;

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

error: aborting due to previous error

For more information about this error, try `rustc --explain E0432`.
31 changes: 31 additions & 0 deletions src/test/ui/rust-2018/local-path-suggestions-2018.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 {
pub 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-2018.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0432]: unresolved import `foo`
--> $DIR/local-path-suggestions-2018.rs:20:9
|
LL | use foo::Bar;
| ^^^ Did you mean `crate::foo`?

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

error[E0432]: unresolved import `foobar`
--> $DIR/local-path-suggestions-2018.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 4bf883b

Please sign in to comment.