Skip to content

Commit af09d24

Browse files
authored
Rollup merge of #91412 - compiler-errors:issue-86035, r=oli-obk
Improve suggestions for importing out-of-scope traits reexported as `_` 1. Fix up the `parent_map` query to prefer visible parents that _don't_ export items with the name `_`. * I'm not sure if I modified this query properly. Not sure if we want to check for other idents than `kw::Underscore`. * This also has the side-effect of not doing BFS on any modules re-exported as `_`, but I think that's desirable here too (or else we get suggestions for paths like `a::_::b` like in [this doctest example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d9505ea45bb80adf40bb991298f952be)). 2. Bail in `try_print_visible_def_path` if the `def_id` is re-exported as `_`, which will fall back to printing the def-id's real (definition) path. * Side-effect of this is that we print paths that are not actually public, but it seems we already sometimes suggest `use`ing paths that are private anyways. See [this doctest example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bad513ed3241f8ff87579eed8046ad10) for demonstration of current behavior. 3. Suggest a glob-import (for example `my_library::prelude::*`) if the trait in question is only pub-exported as `_`, as a fallback. ``` use foo::bar::prelude::*; // trait MyTrait ``` * I think this is good fallback behavior to suggest instead of doing nothing. Thanks to the original issue filer for suggesting this. I was somewhat opinionated about behaviors in this PR, and I'm totally open to limiting the impact of my changes or only landing parts of this. Happy to receive feedback if there are better ways to the same end. Fixes #86035
2 parents 3009dd7 + 682a342 commit af09d24

File tree

9 files changed

+212
-27
lines changed

9 files changed

+212
-27
lines changed

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_session::utils::NativeLibKind;
1717
use rustc_session::{Session, StableCrateId};
1818
use rustc_span::hygiene::{ExpnHash, ExpnId};
1919
use rustc_span::source_map::{Span, Spanned};
20-
use rustc_span::symbol::Symbol;
20+
use rustc_span::symbol::{kw, Symbol};
2121

2222
use rustc_data_structures::sync::Lrc;
2323
use smallvec::SmallVec;
@@ -295,6 +295,10 @@ pub fn provide(providers: &mut Providers) {
295295
use std::collections::vec_deque::VecDeque;
296296

297297
let mut visible_parent_map: DefIdMap<DefId> = Default::default();
298+
// This is a secondary visible_parent_map, storing the DefId of parents that re-export
299+
// the child as `_`. Since we prefer parents that don't do this, merge this map at the
300+
// end, only if we're missing any keys from the former.
301+
let mut fallback_map: DefIdMap<DefId> = Default::default();
298302

299303
// Issue 46112: We want the map to prefer the shortest
300304
// paths when reporting the path to an item. Therefore we
@@ -317,12 +321,17 @@ pub fn provide(providers: &mut Providers) {
317321
bfs_queue.push_back(DefId { krate: cnum, index: CRATE_DEF_INDEX });
318322
}
319323

320-
let mut add_child = |bfs_queue: &mut VecDeque<_>, child: &Export, parent: DefId| {
321-
if !child.vis.is_public() {
324+
let mut add_child = |bfs_queue: &mut VecDeque<_>, export: &Export, parent: DefId| {
325+
if !export.vis.is_public() {
322326
return;
323327
}
324328

325-
if let Some(child) = child.res.opt_def_id() {
329+
if let Some(child) = export.res.opt_def_id() {
330+
if export.ident.name == kw::Underscore {
331+
fallback_map.insert(child, parent);
332+
return;
333+
}
334+
326335
match visible_parent_map.entry(child) {
327336
Entry::Occupied(mut entry) => {
328337
// If `child` is defined in crate `cnum`, ensure
@@ -345,6 +354,12 @@ pub fn provide(providers: &mut Providers) {
345354
}
346355
}
347356

357+
// Fill in any missing entries with the (less preferable) path ending in `::_`.
358+
// We still use this path in a diagnostic that suggests importing `::*`.
359+
for (child, parent) in fallback_map {
360+
visible_parent_map.entry(child).or_insert(parent);
361+
}
362+
348363
visible_parent_map
349364
},
350365

compiler/rustc_middle/src/ty/print/pretty.rs

+28-16
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ pub trait PrettyPrinter<'tcx>:
319319
///
320320
/// `callers` is a chain of visible_parent's leading to `def_id`,
321321
/// to support cycle detection during recursion.
322+
///
323+
/// This method returns false if we can't print the visible path, so
324+
/// `print_def_path` can fall back on the item's real definition path.
322325
fn try_print_visible_def_path_recur(
323326
mut self,
324327
def_id: DefId,
@@ -405,19 +408,7 @@ pub trait PrettyPrinter<'tcx>:
405408
Some(parent) => parent,
406409
None => return Ok((self, false)),
407410
};
408-
if callers.contains(&visible_parent) {
409-
return Ok((self, false));
410-
}
411-
callers.push(visible_parent);
412-
// HACK(eddyb) this bypasses `path_append`'s prefix printing to avoid
413-
// knowing ahead of time whether the entire path will succeed or not.
414-
// To support printers that do not implement `PrettyPrinter`, a `Vec` or
415-
// linked list on the stack would need to be built, before any printing.
416-
match self.try_print_visible_def_path_recur(visible_parent, callers)? {
417-
(cx, false) => return Ok((cx, false)),
418-
(cx, true) => self = cx,
419-
}
420-
callers.pop();
411+
421412
let actual_parent = self.tcx().parent(def_id);
422413
debug!(
423414
"try_print_visible_def_path: visible_parent={:?} actual_parent={:?}",
@@ -463,14 +454,21 @@ pub trait PrettyPrinter<'tcx>:
463454
// `visible_parent_map`), looking for the specific child we currently have and then
464455
// have access to the re-exported name.
465456
DefPathData::TypeNs(ref mut name) if Some(visible_parent) != actual_parent => {
457+
// Item might be re-exported several times, but filter for the one
458+
// that's public and whose identifier isn't `_`.
466459
let reexport = self
467460
.tcx()
468461
.item_children(visible_parent)
469462
.iter()
470-
.find(|child| child.res.opt_def_id() == Some(def_id))
463+
.filter(|child| child.res.opt_def_id() == Some(def_id))
464+
.find(|child| child.vis.is_public() && child.ident.name != kw::Underscore)
471465
.map(|child| child.ident.name);
472-
if let Some(reexport) = reexport {
473-
*name = reexport;
466+
467+
if let Some(new_name) = reexport {
468+
*name = new_name;
469+
} else {
470+
// There is no name that is public and isn't `_`, so bail.
471+
return Ok((self, false));
474472
}
475473
}
476474
// Re-exported `extern crate` (#43189).
@@ -481,6 +479,20 @@ pub trait PrettyPrinter<'tcx>:
481479
}
482480
debug!("try_print_visible_def_path: data={:?}", data);
483481

482+
if callers.contains(&visible_parent) {
483+
return Ok((self, false));
484+
}
485+
callers.push(visible_parent);
486+
// HACK(eddyb) this bypasses `path_append`'s prefix printing to avoid
487+
// knowing ahead of time whether the entire path will succeed or not.
488+
// To support printers that do not implement `PrettyPrinter`, a `Vec` or
489+
// linked list on the stack would need to be built, before any printing.
490+
match self.try_print_visible_def_path_recur(visible_parent, callers)? {
491+
(cx, false) => return Ok((cx, false)),
492+
(cx, true) => self = cx,
493+
}
494+
callers.pop();
495+
484496
Ok((self.path_append(Ok, &DisambiguatedDefPathData { data, disambiguator: 0 })?, true))
485497
}
486498

compiler/rustc_typeck/src/check/method/suggest.rs

+68-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_hir::{ExprKind, Node, QPath};
1212
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1313
use rustc_middle::ty::fast_reject::{simplify_type, SimplifyParams, StripReferences};
1414
use rustc_middle::ty::print::with_crate_prefix;
15-
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeFoldable};
15+
use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable};
1616
use rustc_span::lev_distance;
1717
use rustc_span::symbol::{kw, sym, Ident};
1818
use rustc_span::{source_map, FileName, MultiSpan, Span, Symbol};
@@ -1310,25 +1310,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13101310
mut msg: String,
13111311
candidates: Vec<DefId>,
13121312
) {
1313+
let parent_map = self.tcx.visible_parent_map(());
1314+
1315+
// Separate out candidates that must be imported with a glob, because they are named `_`
1316+
// and cannot be referred with their identifier.
1317+
let (candidates, globs): (Vec<_>, Vec<_>) = candidates.into_iter().partition(|trait_did| {
1318+
if let Some(parent_did) = parent_map.get(trait_did) {
1319+
// If the item is re-exported as `_`, we should suggest a glob-import instead.
1320+
if Some(*parent_did) != self.tcx.parent(*trait_did)
1321+
&& self
1322+
.tcx
1323+
.item_children(*parent_did)
1324+
.iter()
1325+
.filter(|child| child.res.opt_def_id() == Some(*trait_did))
1326+
.all(|child| child.ident.name == kw::Underscore)
1327+
{
1328+
return false;
1329+
}
1330+
}
1331+
1332+
true
1333+
});
1334+
13131335
let module_did = self.tcx.parent_module(self.body_id);
13141336
let (span, found_use) = find_use_placement(self.tcx, module_did);
13151337
if let Some(span) = span {
1316-
let path_strings = candidates.iter().map(|did| {
1338+
let path_strings = candidates.iter().map(|trait_did| {
13171339
// Produce an additional newline to separate the new use statement
13181340
// from the directly following item.
13191341
let additional_newline = if found_use { "" } else { "\n" };
13201342
format!(
13211343
"use {};\n{}",
1322-
with_crate_prefix(|| self.tcx.def_path_str(*did)),
1344+
with_crate_prefix(|| self.tcx.def_path_str(*trait_did)),
13231345
additional_newline
13241346
)
13251347
});
13261348

1327-
err.span_suggestions(span, &msg, path_strings, Applicability::MaybeIncorrect);
1349+
let glob_path_strings = globs.iter().map(|trait_did| {
1350+
let parent_did = parent_map.get(trait_did).unwrap();
1351+
1352+
// Produce an additional newline to separate the new use statement
1353+
// from the directly following item.
1354+
let additional_newline = if found_use { "" } else { "\n" };
1355+
format!(
1356+
"use {}::*; // trait {}\n{}",
1357+
with_crate_prefix(|| self.tcx.def_path_str(*parent_did)),
1358+
self.tcx.item_name(*trait_did),
1359+
additional_newline
1360+
)
1361+
});
1362+
1363+
err.span_suggestions(
1364+
span,
1365+
&msg,
1366+
path_strings.chain(glob_path_strings),
1367+
Applicability::MaybeIncorrect,
1368+
);
13281369
} else {
1329-
let limit = if candidates.len() == 5 { 5 } else { 4 };
1370+
let limit = if candidates.len() + globs.len() == 5 { 5 } else { 4 };
13301371
for (i, trait_did) in candidates.iter().take(limit).enumerate() {
1331-
if candidates.len() > 1 {
1372+
if candidates.len() + globs.len() > 1 {
13321373
msg.push_str(&format!(
13331374
"\ncandidate #{}: `use {};`",
13341375
i + 1,
@@ -1341,8 +1382,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13411382
));
13421383
}
13431384
}
1385+
for (i, trait_did) in
1386+
globs.iter().take(limit.saturating_sub(candidates.len())).enumerate()
1387+
{
1388+
let parent_did = parent_map.get(trait_did).unwrap();
1389+
1390+
if candidates.len() + globs.len() > 1 {
1391+
msg.push_str(&format!(
1392+
"\ncandidate #{}: `use {}::*; // trait {}`",
1393+
candidates.len() + i + 1,
1394+
with_crate_prefix(|| self.tcx.def_path_str(*parent_did)),
1395+
self.tcx.item_name(*trait_did),
1396+
));
1397+
} else {
1398+
msg.push_str(&format!(
1399+
"\n`use {}::*; // trait {}`",
1400+
with_crate_prefix(|| self.tcx.def_path_str(*parent_did)),
1401+
self.tcx.item_name(*trait_did),
1402+
));
1403+
}
1404+
}
13441405
if candidates.len() > limit {
1345-
msg.push_str(&format!("\nand {} others", candidates.len() - limit));
1406+
msg.push_str(&format!("\nand {} others", candidates.len() + globs.len() - limit));
13461407
}
13471408
err.note(&msg);
13481409
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* This crate declares an item as both `prelude::*` and `m::Tr`.
2+
* The compiler should always suggest `m::Tr`. */
3+
4+
pub struct S;
5+
6+
pub mod prelude {
7+
pub use crate::m::Tr as _;
8+
}
9+
10+
pub mod m {
11+
pub trait Tr { fn method(&self); }
12+
impl Tr for crate::S { fn method(&self) {} }
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* This crate declares an item that is unnamed.
2+
* Its only public path is through `prelude::*`. */
3+
4+
pub struct S;
5+
6+
mod m {
7+
pub trait Tr { fn method(&self); }
8+
impl Tr for crate::S { fn method(&self) {} }
9+
}
10+
11+
pub mod prelude {
12+
pub use crate::m::Tr as _;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// aux-build:overlapping_pub_trait_source.rs
2+
3+
/*
4+
* This crate declares two public paths, `m::Tr` and `prelude::_`. Make sure we prefer the former.
5+
*/
6+
extern crate overlapping_pub_trait_source;
7+
8+
fn main() {
9+
//~^ HELP the following trait is implemented but not in scope; perhaps add a `use` for it:
10+
//~| SUGGESTION overlapping_pub_trait_source::m::Tr
11+
use overlapping_pub_trait_source::S;
12+
S.method();
13+
//~^ ERROR no method named `method` found for struct `S` in the current scope [E0599]
14+
//~| HELP items from traits can only be used if the trait is in scope
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0599]: no method named `method` found for struct `S` in the current scope
2+
--> $DIR/overlapping_pub_trait.rs:12:7
3+
|
4+
LL | S.method();
5+
| ^^^^^^ method not found in `S`
6+
|
7+
::: $DIR/auxiliary/overlapping_pub_trait_source.rs:11:23
8+
|
9+
LL | pub trait Tr { fn method(&self); }
10+
| ------ the method is available for `S` here
11+
|
12+
= help: items from traits can only be used if the trait is in scope
13+
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
14+
|
15+
LL | use overlapping_pub_trait_source::m::Tr;
16+
|
17+
18+
error: aborting due to previous error
19+
20+
For more information about this error, try `rustc --explain E0599`.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// aux-build:unnamed_pub_trait_source.rs
2+
3+
/*
4+
* This crate declares an unnameable public path for our item. Make sure we don't suggest
5+
* importing it by name, and instead we suggest importing it by glob.
6+
*/
7+
extern crate unnamed_pub_trait_source;
8+
9+
fn main() {
10+
//~^ HELP the following trait is implemented but not in scope; perhaps add a `use` for it:
11+
//~| SUGGESTION unnamed_pub_trait_source::prelude::*; // trait Tr
12+
use unnamed_pub_trait_source::S;
13+
S.method();
14+
//~^ ERROR no method named `method` found for struct `S` in the current scope [E0599]
15+
//~| HELP items from traits can only be used if the trait is in scope
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0599]: no method named `method` found for struct `S` in the current scope
2+
--> $DIR/unnamed_pub_trait.rs:13:7
3+
|
4+
LL | S.method();
5+
| ^^^^^^ method not found in `S`
6+
|
7+
::: $DIR/auxiliary/unnamed_pub_trait_source.rs:7:23
8+
|
9+
LL | pub trait Tr { fn method(&self); }
10+
| ------ the method is available for `S` here
11+
|
12+
= help: items from traits can only be used if the trait is in scope
13+
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
14+
|
15+
LL | use unnamed_pub_trait_source::prelude::*; // trait Tr
16+
|
17+
18+
error: aborting due to previous error
19+
20+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)