Skip to content

Commit cfabd17

Browse files
committed
Auto merge of #31674 - VladUreche:issue/21221, r=nikomatsakis
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.
2 parents df128bd + 88af8fa commit cfabd17

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,
@@ -1970,10 +1983,28 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
19701983
Err(())
19711984
}
19721985
} else {
1973-
resolve_error(self,
1974-
trait_path.span,
1975-
ResolutionError::UndeclaredTraitName(&path_names_to_string(trait_path,
1976-
path_depth)));
1986+
1987+
// find possible candidates
1988+
let trait_name = trait_path.segments.last().unwrap().identifier.name;
1989+
let candidates =
1990+
self.lookup_candidates(
1991+
trait_name,
1992+
TypeNS,
1993+
|def| match def {
1994+
Def::Trait(_) => true,
1995+
_ => false,
1996+
},
1997+
);
1998+
1999+
// create error object
2000+
let name = &path_names_to_string(trait_path, path_depth);
2001+
let error =
2002+
ResolutionError::UndeclaredTraitName(
2003+
name,
2004+
candidates,
2005+
);
2006+
2007+
resolve_error(self, trait_path.span, error);
19772008
Err(())
19782009
}
19792010
}
@@ -2297,13 +2328,33 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
22972328
ty.span,
22982329
ResolutionError::SelfUsedOutsideImplOrTrait);
22992330
} else {
2300-
resolve_error(self,
2301-
ty.span,
2302-
ResolutionError::UseOfUndeclared(
2303-
kind,
2304-
&path_names_to_string(path,
2305-
0))
2306-
);
2331+
let segment = path.segments.last();
2332+
let segment = segment.expect("missing name in path");
2333+
let type_name = segment.identifier.name;
2334+
2335+
let candidates =
2336+
self.lookup_candidates(
2337+
type_name,
2338+
TypeNS,
2339+
|def| match def {
2340+
Def::Trait(_) |
2341+
Def::Enum(_) |
2342+
Def::Struct(_) |
2343+
Def::TyAlias(_) => true,
2344+
_ => false,
2345+
},
2346+
);
2347+
2348+
// create error object
2349+
let name = &path_names_to_string(path, 0);
2350+
let error =
2351+
ResolutionError::UseOfUndeclared(
2352+
kind,
2353+
name,
2354+
candidates,
2355+
);
2356+
2357+
resolve_error(self, ty.span, error);
23072358
}
23082359
}
23092360
}
@@ -3458,6 +3509,99 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
34583509
found_traits
34593510
}
34603511

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

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

0 commit comments

Comments
 (0)