Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions compiler/rustc_resolve/src/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use thin_vec::ThinVec;
use tracing::debug;

use crate::Namespace::{MacroNS, TypeNS, ValueNS};
use crate::check_unused::nested_item_removal_span;
use crate::def_collector::DefCollector;
use crate::diagnostics::impls::{OnUnknownData, StructCtor};
use crate::imports::{ImportData, ImportKind, NameResolution, NameResolutionRef};
Expand All @@ -39,8 +40,8 @@ use crate::ref_mut::CmCell;
use crate::{
BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, ExternModule,
ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, ModuleOrUniformRoot,
ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used, VisResolutionError,
diagnostics,
NestedImportRemoval, ParentScope, PathResult, Res, ResolutionTable, Resolver, Segment, Used,
VisResolutionError, diagnostics,
};

impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
Expand Down Expand Up @@ -548,6 +549,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
root_span: Span,
root_id: NodeId,
vis: Visibility,
nested: Option<NestedImportRemoval>,
) {
let current_module = self.parent_scope.module.expect_local();
let import = self.r.arenas.alloc_import(ImportData {
Expand All @@ -564,6 +566,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
vis,
vis_span: item.vis.span,
on_unknown_attr: OnUnknownData::from_attrs(self.r, &item.attrs),
nested,
});

self.r.indeterminate_imports.push((import, None, 0));
Expand Down Expand Up @@ -595,20 +598,20 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
use_tree: &ast::UseTree,
id: NodeId,
parent_prefix: &[Segment],
nested: bool,
nested: Option<NestedImportRemoval>,
list_stem: bool,
vis: Visibility,
root_span: Span,
feed: TyCtxtFeed<'tcx, LocalDefId>,
) {
debug!(
"build_reduced_graph_for_use_tree(parent_prefix={:?}, use_tree={:?}, nested={})",
"build_reduced_graph_for_use_tree(parent_prefix={:?}, use_tree={:?}, nested={:?})",
parent_prefix, use_tree, nested
);

// Top level use tree reuses the item's id and list stems reuse their parent
// use tree's ids, so in both cases their visibilities are already filled.
if nested && !list_stem {
if nested.is_some() && !list_stem {
self.r.feed_visibility(feed, vis);
}

Expand Down Expand Up @@ -733,18 +736,36 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
source: source.ident,
target: ident,
decls: Default::default(),
nested,
nested: nested.is_some(),
id,
def_id: feed.def_id(),
};

self.add_import(module_path, kind, use_tree.span(), item, root_span, item.id, vis);
self.add_import(
module_path,
kind,
use_tree.span(),
item,
root_span,
item.id,
vis,
nested,
);
}
ast::UseTreeKind::Glob(_) => {
if !ast::attr::contains_name(&item.attrs, sym::prelude_import) {
let kind =
ImportKind::Glob { max_vis: CmCell::new(None), id, def_id: feed.def_id() };
self.add_import(prefix, kind, use_tree.span(), item, root_span, item.id, vis);
self.add_import(
prefix,
kind,
use_tree.span(),
item,
root_span,
item.id,
vis,
nested,
);
} else {
// Resolve the prelude import early.
let path_res =
Expand All @@ -757,15 +778,26 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
}
}
ast::UseTreeKind::Nested { ref items, .. } => {
for tree in items {
let can_extract = item.attrs.is_empty()
&& matches!(item.vis.kind, ast::VisibilityKind::Inherited);
let use_span = can_extract.then_some(item.span);
for (pos, tree) in items.iter().enumerate() {
let id = tree.id;
let feed = self.create_def(id, None, DefKind::Use, use_tree.span());
let item_nested = if items.len() > 1 {
Some(NestedImportRemoval::Remove {
removal_span: nested_item_removal_span(items, pos, pos > 0),
use_span,
})
} else {
nested.or(Some(NestedImportRemoval::ReplaceRoot(root_span)))
};
self.build_reduced_graph_for_use_tree(
item,
&tree.inner,
id,
&prefix,
true,
item_nested,
false,
vis,
root_span,
Expand All @@ -789,7 +821,15 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
self.parent_scope.module.nearest_parent_mod().expect_local(),
);
self.build_reduced_graph_for_use_tree(
item, &tree, id, &prefix, true, true, vis, root_span, feed,
item,
&tree,
id,
&prefix,
nested.or(Some(NestedImportRemoval::ReplaceRoot(root_span))),
true,
vis,
root_span,
feed,
);
}
}
Expand Down Expand Up @@ -840,7 +880,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
use_tree,
item.id,
&[],
false,
None,
false,
vis,
use_tree.span(),
Expand Down Expand Up @@ -1053,6 +1093,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
vis,
vis_span: item.vis.span,
on_unknown_attr: OnUnknownData::from_attrs(self.r, &item.attrs),
nested: None,
});
if used {
self.r.import_use_map.insert(import, Used::Other);
Expand Down Expand Up @@ -1185,6 +1226,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
vis: Visibility::Restricted(CRATE_MOD_ID),
vis_span: item.vis.span,
on_unknown_attr: OnUnknownData::from_attrs(this.r, &item.attrs),
nested: None,
})
};

Expand Down Expand Up @@ -1368,6 +1410,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
vis,
vis_span: item.vis.span,
on_unknown_attr: OnUnknownData::from_attrs(self.r, &item.attrs),
nested: None,
});
self.r.import_use_map.insert(import, Used::Other);
let import_decl = self.r.new_import_decl(decl, import);
Expand Down
32 changes: 25 additions & 7 deletions compiler/rustc_resolve/src/check_unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,27 @@ enum UnusedSpanResult {
PartialUnused { spans: Vec<Span>, remove: Vec<Span> },
}

/// Derives the span to remove an item from a multi-item nested `UseTree` group
/// using only AST spans, avoiding source-text scanning and byte arithmetic.
pub(crate) fn nested_item_removal_span(
items: &[ast::UseTreeAndId],
pos: usize,
with_preceding_comma: bool,
) -> Span {
debug_assert!(items.len() >= 2);
debug_assert!(pos < items.len());
let item = &items[pos].inner;
if with_preceding_comma {
debug_assert!(pos > 0);
let prev = &items[pos - 1].inner;
prev.hi_span().shrink_to_hi().to(item.hi_span())
} else {
debug_assert!(pos + 1 < items.len());
let next = &items[pos + 1].inner;
item.prefix.span.to(next.prefix.span.shrink_to_lo())
}
}

fn calc_unused_spans(
unused_import: &UnusedImport,
use_tree: &ast::UseTree,
Expand Down Expand Up @@ -352,14 +373,11 @@ fn calc_unused_spans(
if let Some(remove) = remove {
let remove_span = if nested.len() == 1 {
remove
} else if pos == nested.len() - 1 || used_children > 0 {
// Delete everything from the end of the last import, to delete the
// previous comma
nested[pos - 1].inner.hi_span().shrink_to_hi().to(use_tree.inner.hi_span())
} else {
// Delete everything until the next import, to delete the trailing commas
let inner = &nested[pos + 1].inner;
use_tree.inner.prefix.span.to(inner.prefix.span.shrink_to_lo())
// Delete previous comma when earlier items are kept or at the last position;
// otherwise delete trailing comma.
let with_preceding_comma = pos == nested.len() - 1 || used_children > 0;
nested_item_removal_span(nested, pos, with_preceding_comma)
};

// Try to collapse adjacent spans into a single one. This prevents all cases of
Expand Down
90 changes: 70 additions & 20 deletions compiler/rustc_resolve/src/diagnostics/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ use crate::late::{ConstantRequiresType, DiagMetadata, PatternSource, Rib};
use crate::{
AmbiguityError, AmbiguityKind, AmbiguityWarning, BindingError, BindingKey, Decl, DeclKind,
DelayedVisResolutionError, Finalize, ForwardGenericParamBanReason, HasGenericParams, IdentKey,
LateDecl, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot, ParentScope, PathResult,
PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet, Segment, UseError, Used,
VisResolutionError, path_names_to_string,
LateDecl, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot, NestedImportRemoval,
ParentScope, PathResult, PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet,
Segment, UseError, Used, VisResolutionError, path_names_to_string,
};

/// A vector of spans and replacements, a message and applicability.
Expand Down Expand Up @@ -125,6 +125,13 @@ pub(crate) struct ImportSuggestion {
pub is_stable: bool,
}

/// Diagnostic suggestion target: in-place replacement or grouped import extraction.
#[derive(Copy, Clone, Debug)]
enum SuggTarget {
Replace(Span),
Extract { use_span: Span, removal_span: Span },
}

/// Adjust the impl span so that just the `impl` keyword is taken by removing
/// everything after `<` (`"impl<T> Iterator for A<T> {}" -> "impl"`) and
/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`).
Expand Down Expand Up @@ -2727,7 +2734,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
decl,
outermost_res,
parent_scope,
single_nested,
nested,
dedup_span,
ref source,
} = *privacy_error;
Expand Down Expand Up @@ -2765,7 +2772,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
&import_suggestions,
Instead::Yes,
FoundUse::Yes,
DiagMode::Import { append: single_nested, unresolved_import: false },
DiagMode::Import { append: nested.is_some(), unresolved_import: false },
vec![],
"",
);
Expand Down Expand Up @@ -2953,17 +2960,31 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
};
err.subdiagnostic(note);
}
// The suggestion replaces `dedup_span` with a path reaching the failing ident.
// That's valid only when
// 1) the failing ident is the imported leaf, otherwise `as` renames and trailing segments
// get dropped, and
// 2) the use isn't nested, otherwise `dedup_span` is one ident in `{...}`.
//
// See issue #156060.
let can_replace_use = !shown_candidates
&& !single_nested
&& !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span);
if can_replace_use {
let is_leaf = !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span);
// Modifying grouped imports drops renames (`A as Renamed`) and globs (`m::*`).
let is_renamed_or_glob = dedup_span.hi() != ident.span.hi();
// The suggestion replaces the root tree for non-grouped or single-item imports,
// or extracts the failing item into its own `use` statement for multi-item groups.
let sugg_target = match nested {
None => Some(SuggTarget::Replace(dedup_span)),
Some(_) if is_renamed_or_glob => None,
Some(NestedImportRemoval::ReplaceRoot(root_span))
if root_span.can_be_used_for_suggestions() =>
{
Some(SuggTarget::Replace(root_span))
}
Some(NestedImportRemoval::Remove { removal_span, use_span: Some(use_span) })
if use_span.can_be_used_for_suggestions()
&& removal_span.can_be_used_for_suggestions() =>
{
Some(SuggTarget::Extract { use_span, removal_span })
}
Some(_) => None,
};
if !shown_candidates
&& is_leaf
&& let Some(sugg_target) = sugg_target
{
// We prioritize shorter paths, non-core imports and direct imports over the
// alternatives.
sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0].name == sym::core, *reexport));
Expand All @@ -2976,10 +2997,39 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
// The suggested path may use a different name than the private alias.
let ident = *sugg.last().expect("at least one segment");
let path = join_path_idents(sugg);
let sugg = if reexport {
diagnostics::ImportIdent::ThroughReExport { span: dedup_span, ident, path }
} else {
diagnostics::ImportIdent::Directly { span: dedup_span, ident, path }
let sugg = match sugg_target {
SuggTarget::Replace(span) => {
if reexport {
diagnostics::ImportIdent::ThroughReExport { span, ident, path }
} else {
diagnostics::ImportIdent::Directly { span, ident, path }
}
}
SuggTarget::Extract { use_span, removal_span } => {
let indent = self
.tcx
.sess
.source_map()
.indentation_before(use_span)
.unwrap_or_default();
let use_stmt = format!("use {path};\n{indent}");
let insert_span = use_span.shrink_to_lo();
if reexport {
diagnostics::ImportIdent::ExtractThroughReExport {
insert_span,
removal_span,
ident,
use_stmt,
}
} else {
diagnostics::ImportIdent::ExtractDirectly {
insert_span,
removal_span,
ident,
use_stmt,
}
}
}
};
err.subdiagnostic(sugg);
break;
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_resolve/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,32 @@ pub(crate) enum ImportIdent {
ident: Ident,
path: String,
},
#[multipart_suggestion(
"import `{$ident}` through the re-export",
applicability = "machine-applicable",
style = "verbose"
)]
ExtractThroughReExport {
#[suggestion_part(code = "{use_stmt}")]
insert_span: Span,
#[suggestion_part(code = "")]
removal_span: Span,
ident: Ident,
use_stmt: String,
},
#[multipart_suggestion(
"import `{$ident}` directly",
applicability = "machine-applicable",
style = "verbose"
)]
ExtractDirectly {
#[suggestion_part(code = "{use_stmt}")]
insert_span: Span,
#[suggestion_part(code = "")]
removal_span: Span,
ident: Ident,
use_stmt: String,
},
}

#[derive(Subdiagnostic)]
Expand Down
Loading
Loading