Skip to content

Commit 88af8fa

Browse files
committed
Show candidates for names not in scope
This commit adds functionality that allows the name resolution pass to search for entities (traits/types/enums/structs) by name, in order to show recommendations along with the errors. For now, only E0405 and E0412 have suggestions attached, as per the request in bug #21221, but it's likely other errors can also benefit from the ability to generate suggestions.
1 parent e18f7a1 commit 88af8fa

32 files changed

+521
-85
lines changed

src/librustc_resolve/diagnostics.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,12 @@ impl Foo for Bar { // ok!
461461
"##,
462462

463463
E0405: r##"
464-
An unknown trait was implemented. Example of erroneous code:
464+
The code refers to a trait that is not in scope. Example of erroneous code:
465465
466466
```compile_fail
467467
struct Foo;
468468
469-
impl SomeTrait for Foo {} // error: use of undeclared trait name `SomeTrait`
469+
impl SomeTrait for Foo {} // error: trait `SomeTrait` is not in scope
470470
```
471471
472472
Please verify that the name of the trait wasn't misspelled and ensure that it
@@ -599,20 +599,20 @@ trait Baz : Foo + Foo2 {
599599
"##,
600600

601601
E0412: r##"
602-
An undeclared type name was used. Example of erroneous codes:
602+
The type name used is not in scope. Example of erroneous codes:
603603
604604
```compile_fail
605-
impl Something {} // error: use of undeclared type name `Something`
605+
impl Something {} // error: type name `Something` is not in scope
606606
607607
// or:
608608
609609
trait Foo {
610-
fn bar(N); // error: use of undeclared type name `N`
610+
fn bar(N); // error: type name `N` is not in scope
611611
}
612612
613613
// or:
614614
615-
fn foo(x: T) {} // error: use of undeclared type name `T`
615+
fn foo(x: T) {} // type name `T` is not in scope
616616
```
617617
618618
To fix this error, please verify you didn't misspell the type name, you did

src/librustc_resolve/lib.rs

+231-26
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ use rustc_front::hir::{ItemFn, ItemForeignMod, ItemImpl, ItemMod, ItemStatic, It
8181
use rustc_front::hir::{ItemStruct, ItemTrait, ItemTy, ItemUse};
8282
use rustc_front::hir::Local;
8383
use rustc_front::hir::{Pat, PatKind, Path, PrimTy};
84+
use rustc_front::hir::{PathSegment, PathParameters};
85+
use rustc_front::hir::HirVec;
8486
use rustc_front::hir::{TraitRef, Ty, TyBool, TyChar, TyFloat, TyInt};
8587
use rustc_front::hir::{TyRptr, TyStr, TyUint, TyPath, TyPtr};
8688
use rustc_front::util::walk_pat;
@@ -117,6 +119,12 @@ enum SuggestionType {
117119
NotFound,
118120
}
119121

122+
/// Candidates for a name resolution failure
123+
pub struct SuggestedCandidates {
124+
name: String,
125+
candidates: Vec<Path>,
126+
}
127+
120128
pub enum ResolutionError<'a> {
121129
/// error E0401: can't use type parameters from outer function
122130
TypeParametersFromOuterFunction,
@@ -127,7 +135,7 @@ pub enum ResolutionError<'a> {
127135
/// error E0404: is not a trait
128136
IsNotATrait(&'a str),
129137
/// error E0405: use of undeclared trait name
130-
UndeclaredTraitName(&'a str),
138+
UndeclaredTraitName(&'a str, SuggestedCandidates),
131139
/// error E0406: undeclared associated type
132140
UndeclaredAssociatedType,
133141
/// error E0407: method is not a member of trait
@@ -145,7 +153,7 @@ pub enum ResolutionError<'a> {
145153
/// error E0411: use of `Self` outside of an impl or trait
146154
SelfUsedOutsideImplOrTrait,
147155
/// error E0412: use of undeclared
148-
UseOfUndeclared(&'a str, &'a str),
156+
UseOfUndeclared(&'a str, &'a str, SuggestedCandidates),
149157
/// error E0413: declaration shadows an enum variant or unit-like struct in scope
150158
DeclarationShadowsEnumVariantOrUnitLikeStruct(Name),
151159
/// error E0414: only irrefutable patterns allowed here
@@ -248,12 +256,14 @@ fn resolve_struct_error<'b, 'a: 'b, 'tcx: 'a>(resolver: &'b Resolver<'a, 'tcx>,
248256
ResolutionError::IsNotATrait(name) => {
249257
struct_span_err!(resolver.session, span, E0404, "`{}` is not a trait", name)
250258
}
251-
ResolutionError::UndeclaredTraitName(name) => {
252-
struct_span_err!(resolver.session,
253-
span,
254-
E0405,
255-
"use of undeclared trait name `{}`",
256-
name)
259+
ResolutionError::UndeclaredTraitName(name, candidates) => {
260+
let mut err = struct_span_err!(resolver.session,
261+
span,
262+
E0405,
263+
"trait `{}` is not in scope",
264+
name);
265+
show_candidates(&mut err, span, &candidates);
266+
err
257267
}
258268
ResolutionError::UndeclaredAssociatedType => {
259269
struct_span_err!(resolver.session, span, E0406, "undeclared associated type")
@@ -313,13 +323,15 @@ fn resolve_struct_error<'b, 'a: 'b, 'tcx: 'a>(resolver: &'b Resolver<'a, 'tcx>,
313323
E0411,
314324
"use of `Self` outside of an impl or trait")
315325
}
316-
ResolutionError::UseOfUndeclared(kind, name) => {
317-
struct_span_err!(resolver.session,
318-
span,
319-
E0412,
320-
"use of undeclared {} `{}`",
321-
kind,
322-
name)
326+
ResolutionError::UseOfUndeclared(kind, name, candidates) => {
327+
let mut err = struct_span_err!(resolver.session,
328+
span,
329+
E0412,
330+
"{} `{}` is undefined or not in scope",
331+
kind,
332+
name);
333+
show_candidates(&mut err, span, &candidates);
334+
err
323335
}
324336
ResolutionError::DeclarationShadowsEnumVariantOrUnitLikeStruct(name) => {
325337
struct_span_err!(resolver.session,
@@ -839,6 +851,7 @@ pub struct ModuleS<'a> {
839851
pub type Module<'a> = &'a ModuleS<'a>;
840852

841853
impl<'a> ModuleS<'a> {
854+
842855
fn new(parent_link: ParentLink<'a>, def: Option<Def>, external: bool, is_public: bool) -> Self {
843856
ModuleS {
844857
parent_link: parent_link,
@@ -1969,10 +1982,28 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
19691982
Err(())
19701983
}
19711984
} else {
1972-
resolve_error(self,
1973-
trait_path.span,
1974-
ResolutionError::UndeclaredTraitName(&path_names_to_string(trait_path,
1975-
path_depth)));
1985+
1986+
// find possible candidates
1987+
let trait_name = trait_path.segments.last().unwrap().identifier.name;
1988+
let candidates =
1989+
self.lookup_candidates(
1990+
trait_name,
1991+
TypeNS,
1992+
|def| match def {
1993+
Def::Trait(_) => true,
1994+
_ => false,
1995+
},
1996+
);
1997+
1998+
// create error object
1999+
let name = &path_names_to_string(trait_path, path_depth);
2000+
let error =
2001+
ResolutionError::UndeclaredTraitName(
2002+
name,
2003+
candidates,
2004+
);
2005+
2006+
resolve_error(self, trait_path.span, error);
19762007
Err(())
19772008
}
19782009
}
@@ -2296,13 +2327,33 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
22962327
ty.span,
22972328
ResolutionError::SelfUsedOutsideImplOrTrait);
22982329
} else {
2299-
resolve_error(self,
2300-
ty.span,
2301-
ResolutionError::UseOfUndeclared(
2302-
kind,
2303-
&path_names_to_string(path,
2304-
0))
2305-
);
2330+
let segment = path.segments.last();
2331+
let segment = segment.expect("missing name in path");
2332+
let type_name = segment.identifier.name;
2333+
2334+
let candidates =
2335+
self.lookup_candidates(
2336+
type_name,
2337+
TypeNS,
2338+
|def| match def {
2339+
Def::Trait(_) |
2340+
Def::Enum(_) |
2341+
Def::Struct(_) |
2342+
Def::TyAlias(_) => true,
2343+
_ => false,
2344+
},
2345+
);
2346+
2347+
// create error object
2348+
let name = &path_names_to_string(path, 0);
2349+
let error =
2350+
ResolutionError::UseOfUndeclared(
2351+
kind,
2352+
name,
2353+
candidates,
2354+
);
2355+
2356+
resolve_error(self, ty.span, error);
23062357
}
23072358
}
23082359
}
@@ -3457,6 +3508,99 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
34573508
found_traits
34583509
}
34593510

3511+
/// When name resolution fails, this method can be used to look up candidate
3512+
/// entities with the expected name. It allows filtering them using the
3513+
/// supplied predicate (which should be used to only accept the types of
3514+
/// definitions expected e.g. traits). The lookup spans across all crates.
3515+
///
3516+
/// NOTE: The method does not look into imports, but this is not a problem,
3517+
/// since we report the definitions (thus, the de-aliased imports).
3518+
fn lookup_candidates<FilterFn>(&mut self,
3519+
lookup_name: Name,
3520+
namespace: Namespace,
3521+
filter_fn: FilterFn) -> SuggestedCandidates
3522+
where FilterFn: Fn(Def) -> bool {
3523+
3524+
let mut lookup_results = Vec::new();
3525+
let mut worklist = Vec::new();
3526+
worklist.push((self.graph_root, Vec::new(), false));
3527+
3528+
while let Some((in_module,
3529+
path_segments,
3530+
in_module_is_extern)) = worklist.pop() {
3531+
build_reduced_graph::populate_module_if_necessary(self, &in_module);
3532+
3533+
in_module.for_each_child(|name, ns, name_binding| {
3534+
3535+
// avoid imports entirely
3536+
if name_binding.is_import() { return; }
3537+
3538+
// collect results based on the filter function
3539+
if let Some(def) = name_binding.def() {
3540+
if name == lookup_name && ns == namespace && filter_fn(def) {
3541+
// create the path
3542+
let ident = hir::Ident::from_name(name);
3543+
let params = PathParameters::none();
3544+
let segment = PathSegment {
3545+
identifier: ident,
3546+
parameters: params,
3547+
};
3548+
let span = name_binding.span.unwrap_or(syntax::codemap::DUMMY_SP);
3549+
let mut segms = path_segments.clone();
3550+
segms.push(segment);
3551+
let segms = HirVec::from_vec(segms);
3552+
let path = Path {
3553+
span: span,
3554+
global: true,
3555+
segments: segms,
3556+
};
3557+
// the entity is accessible in the following cases:
3558+
// 1. if it's defined in the same crate, it's always
3559+
// accessible (since private entities can be made public)
3560+
// 2. if it's defined in another crate, it's accessible
3561+
// only if both the module is public and the entity is
3562+
// declared as public (due to pruning, we don't explore
3563+
// outside crate private modules => no need to check this)
3564+
if !in_module_is_extern || name_binding.is_public() {
3565+
lookup_results.push(path);
3566+
}
3567+
}
3568+
}
3569+
3570+
// collect submodules to explore
3571+
if let Some(module) = name_binding.module() {
3572+
// form the path
3573+
let path_segments = match module.parent_link {
3574+
NoParentLink => path_segments.clone(),
3575+
ModuleParentLink(_, name) => {
3576+
let mut paths = path_segments.clone();
3577+
let ident = hir::Ident::from_name(name);
3578+
let params = PathParameters::none();
3579+
let segm = PathSegment {
3580+
identifier: ident,
3581+
parameters: params,
3582+
};
3583+
paths.push(segm);
3584+
paths
3585+
}
3586+
_ => unreachable!(),
3587+
};
3588+
3589+
if !in_module_is_extern || name_binding.is_public() {
3590+
// add the module to the lookup
3591+
let is_extern = in_module_is_extern || module.is_extern_crate;
3592+
worklist.push((module, path_segments, is_extern));
3593+
}
3594+
}
3595+
})
3596+
}
3597+
3598+
SuggestedCandidates {
3599+
name: lookup_name.as_str().to_string(),
3600+
candidates: lookup_results,
3601+
}
3602+
}
3603+
34603604
fn record_def(&mut self, node_id: NodeId, resolution: PathResolution) {
34613605
debug!("(recording def) recording {:?} for {}", resolution, node_id);
34623606
assert!(match resolution.last_private {
@@ -3512,6 +3656,67 @@ fn path_names_to_string(path: &Path, depth: usize) -> String {
35123656
names_to_string(&names[..])
35133657
}
35143658

3659+
/// When an entity with a given name is not available in scope, we search for
3660+
/// entities with that name in all crates. This method allows outputting the
3661+
/// results of this search in a programmer-friendly way
3662+
fn show_candidates(session: &mut DiagnosticBuilder,
3663+
span: syntax::codemap::Span,
3664+
candidates: &SuggestedCandidates) {
3665+
3666+
let paths = &candidates.candidates;
3667+
3668+
if paths.len() > 0 {
3669+
// don't show more than MAX_CANDIDATES results, so
3670+
// we're consistent with the trait suggestions
3671+
const MAX_CANDIDATES: usize = 5;
3672+
3673+
// we want consistent results across executions, but candidates are produced
3674+
// by iterating through a hash map, so make sure they are ordered:
3675+
let mut path_strings: Vec<_> = paths.into_iter()
3676+
.map(|p| path_names_to_string(&p, 0))
3677+
.collect();
3678+
path_strings.sort();
3679+
3680+
// behave differently based on how many candidates we have:
3681+
if !paths.is_empty() {
3682+
if paths.len() == 1 {
3683+
session.fileline_help(
3684+
span,
3685+
&format!("you can to import it into scope: `use {};`.",
3686+
&path_strings[0]),
3687+
);
3688+
} else {
3689+
session.fileline_help(span, "you can import several candidates \
3690+
into scope (`use ...;`):");
3691+
let count = path_strings.len() as isize - MAX_CANDIDATES as isize + 1;
3692+
3693+
for (idx, path_string) in path_strings.iter().enumerate() {
3694+
if idx == MAX_CANDIDATES - 1 && count > 1 {
3695+
session.fileline_help(
3696+
span,
3697+
&format!(" and {} other candidates", count).to_string(),
3698+
);
3699+
break;
3700+
} else {
3701+
session.fileline_help(
3702+
span,
3703+
&format!(" `{}`", path_string).to_string(),
3704+
);
3705+
}
3706+
}
3707+
}
3708+
}
3709+
} else {
3710+
// nothing found:
3711+
session.fileline_help(
3712+
span,
3713+
&format!("no candidates by the name of `{}` found in your \
3714+
project; maybe you misspelled the name or forgot to import \
3715+
an external crate?", candidates.name.to_string()),
3716+
);
3717+
};
3718+
}
3719+
35153720
/// A somewhat inefficient routine to obtain the name of a module.
35163721
fn module_to_string<'a>(module: Module<'a>) -> String {
35173722
let mut names = Vec::new();

0 commit comments

Comments
 (0)