@@ -1697,6 +1697,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
16971697 struct_span_err ! ( self . tcx. sess, ident. span, E0603 , "{} `{}` is private" , descr, ident) ;
16981698 err. span_label ( ident. span , format ! ( "private {descr}" ) ) ;
16991699
1700+ let mut not_publicly_reexported = false ;
17001701 if let Some ( ( this_res, outer_ident) ) = outermost_res {
17011702 let import_suggestions = self . lookup_import_candidates (
17021703 outer_ident,
@@ -1717,6 +1718,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17171718 ) ;
17181719 // If we suggest importing a public re-export, don't point at the definition.
17191720 if point_to_def && ident. span != outer_ident. span {
1721+ not_publicly_reexported = true ;
17201722 err. span_label (
17211723 outer_ident. span ,
17221724 format ! ( "{} `{outer_ident}` is not publicly re-exported" , this_res. descr( ) ) ,
@@ -1749,10 +1751,51 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17491751 }
17501752 }
17511753
1754+ let mut sugg_paths = vec ! [ ] ;
1755+ if let Some ( mut def_id) = res. opt_def_id ( ) {
1756+ // We can't use `def_path_str` in resolve.
1757+ let mut path = vec ! [ def_id] ;
1758+ while let Some ( parent) = self . tcx . opt_parent ( def_id) {
1759+ def_id = parent;
1760+ if !def_id. is_top_level_module ( ) {
1761+ path. push ( def_id) ;
1762+ } else {
1763+ break ;
1764+ }
1765+ }
1766+ // We will only suggest importing directly if it is accessible through that path.
1767+ let path_names: Option < Vec < String > > = path
1768+ . iter ( )
1769+ . rev ( )
1770+ . map ( |def_id| {
1771+ self . tcx . opt_item_name ( * def_id) . map ( |n| {
1772+ if def_id. is_top_level_module ( ) {
1773+ "crate" . to_string ( )
1774+ } else {
1775+ n. to_string ( )
1776+ }
1777+ } )
1778+ } )
1779+ . collect ( ) ;
1780+ if let Some ( def_id) = path. get ( 0 )
1781+ && let Some ( path) = path_names
1782+ {
1783+ if let Some ( def_id) = def_id. as_local ( ) {
1784+ if self . effective_visibilities . is_directly_public ( def_id) {
1785+ sugg_paths. push ( ( path, false ) ) ;
1786+ }
1787+ } else if self . is_accessible_from ( self . tcx . visibility ( def_id) , parent_scope. module )
1788+ {
1789+ sugg_paths. push ( ( path, false ) ) ;
1790+ }
1791+ }
1792+ }
1793+
17521794 // Print the whole import chain to make it easier to see what happens.
17531795 let first_binding = binding;
17541796 let mut next_binding = Some ( binding) ;
17551797 let mut next_ident = ident;
1798+ let mut path = vec ! [ ] ;
17561799 while let Some ( binding) = next_binding {
17571800 let name = next_ident;
17581801 next_binding = match binding. kind {
@@ -1771,6 +1814,21 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17711814 _ => None ,
17721815 } ;
17731816
1817+ match binding. kind {
1818+ NameBindingKind :: Import { import, .. } => {
1819+ for segment in import. module_path . iter ( ) . skip ( 1 ) {
1820+ path. push ( segment. ident . to_string ( ) ) ;
1821+ }
1822+ sugg_paths. push ( (
1823+ path. iter ( )
1824+ . cloned ( )
1825+ . chain ( vec ! [ ident. to_string( ) ] . into_iter ( ) )
1826+ . collect :: < Vec < _ > > ( ) ,
1827+ true , // re-export
1828+ ) ) ;
1829+ }
1830+ NameBindingKind :: Res ( _) | NameBindingKind :: Module ( _) => { }
1831+ }
17741832 let first = binding == first_binding;
17751833 let msg = format ! (
17761834 "{and_refers_to}the {item} `{name}`{which} is defined here{dots}" ,
@@ -1782,7 +1840,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17821840 let def_span = self . tcx . sess . source_map ( ) . guess_head_span ( binding. span ) ;
17831841 let mut note_span = MultiSpan :: from_span ( def_span) ;
17841842 if !first && binding. vis . is_public ( ) {
1785- note_span. push_span_label ( def_span, "consider importing it directly" ) ;
1843+ let desc = match binding. kind {
1844+ NameBindingKind :: Import { .. } => "re-export" ,
1845+ _ => "directly" ,
1846+ } ;
1847+ note_span. push_span_label ( def_span, format ! ( "you could import this {desc}" ) ) ;
17861848 }
17871849 // Final step in the import chain, point out if the ADT is `non_exhaustive`
17881850 // which is probably why this privacy violation occurred.
@@ -1796,6 +1858,29 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
17961858 }
17971859 err. span_note ( note_span, msg) ;
17981860 }
1861+ // We prioritize shorter paths, non-core imports and direct imports over the alternatives.
1862+ sugg_paths. sort_by_key ( |( p, reexport) | ( p. len ( ) , p[ 0 ] == "core" , * reexport) ) ;
1863+ for ( sugg, reexport) in sugg_paths {
1864+ if not_publicly_reexported {
1865+ break ;
1866+ }
1867+ if sugg. len ( ) <= 1 {
1868+ // A single path segment suggestion is wrong. This happens on circular imports.
1869+ // `tests/ui/imports/issue-55884-2.rs`
1870+ continue ;
1871+ }
1872+ let path = sugg. join ( "::" ) ;
1873+ err. span_suggestion_verbose (
1874+ dedup_span,
1875+ format ! (
1876+ "import `{ident}` {}" ,
1877+ if reexport { "through the re-export" } else { "directly" }
1878+ ) ,
1879+ path,
1880+ Applicability :: MachineApplicable ,
1881+ ) ;
1882+ break ;
1883+ }
17991884
18001885 err. emit ( ) ;
18011886 }
0 commit comments