Skip to content

Commit 1595163

Browse files
committed
Add suggestion for duplicated import.
This commit adds a suggestion when a import is duplicated (ie. the same name is used twice trying to import the same thing) to remove the second import.
1 parent a21bd75 commit 1595163

18 files changed

+570
-111
lines changed

src/librustc_resolve/build_reduced_graph.rs

+9
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,14 @@ impl<'a> Resolver<'a> {
238238
macro_ns: Cell::new(None),
239239
},
240240
type_ns_only,
241+
nested,
241242
};
242243
self.add_import_directive(
243244
module_path,
244245
subclass,
245246
use_tree.span,
246247
id,
248+
item,
247249
root_span,
248250
item.id,
249251
vis,
@@ -260,6 +262,7 @@ impl<'a> Resolver<'a> {
260262
subclass,
261263
use_tree.span,
262264
id,
265+
item,
263266
root_span,
264267
item.id,
265268
vis,
@@ -379,6 +382,9 @@ impl<'a> Resolver<'a> {
379382
source: orig_name,
380383
target: ident,
381384
},
385+
has_attributes: !item.attrs.is_empty(),
386+
use_span_with_attributes: item.span_with_attributes(),
387+
use_span: item.span,
382388
root_span: item.span,
383389
span: item.span,
384390
module_path: Vec::new(),
@@ -824,6 +830,9 @@ impl<'a> Resolver<'a> {
824830
parent_scope: parent_scope.clone(),
825831
imported_module: Cell::new(Some(ModuleOrUniformRoot::Module(module))),
826832
subclass: ImportDirectiveSubclass::MacroUse,
833+
use_span_with_attributes: item.span_with_attributes(),
834+
has_attributes: !item.attrs.is_empty(),
835+
use_span: item.span,
827836
root_span: span,
828837
span,
829838
module_path: Vec::new(),

src/librustc_resolve/lib.rs

+228-47
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use syntax::ast::{Label, Local, Mutability, Pat, PatKind, Path};
6363
use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
6464
use syntax::ptr::P;
6565

66-
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
66+
use syntax_pos::{BytePos, Span, DUMMY_SP, MultiSpan};
6767
use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
6868

6969
use std::cell::{Cell, RefCell};
@@ -1228,6 +1228,16 @@ enum NameBindingKind<'a> {
12281228
},
12291229
}
12301230

1231+
impl<'a> NameBindingKind<'a> {
1232+
/// Is this a name binding of a import?
1233+
fn is_import(&self) -> bool {
1234+
match *self {
1235+
NameBindingKind::Import { .. } => true,
1236+
_ => false,
1237+
}
1238+
}
1239+
}
1240+
12311241
struct PrivacyError<'a>(Span, Ident, &'a NameBinding<'a>);
12321242

12331243
struct UseError<'a> {
@@ -5134,64 +5144,235 @@ impl<'a> Resolver<'a> {
51345144
);
51355145

51365146
// See https://github.com/rust-lang/rust/issues/32354
5147+
use NameBindingKind::Import;
51375148
let directive = match (&new_binding.kind, &old_binding.kind) {
5138-
(NameBindingKind::Import { directive, .. }, _) if !new_binding.span.is_dummy() =>
5139-
Some((directive, new_binding.span)),
5140-
(_, NameBindingKind::Import { directive, .. }) if !old_binding.span.is_dummy() =>
5141-
Some((directive, old_binding.span)),
5149+
// If there are two imports where one or both have attributes then prefer removing the
5150+
// import without attributes.
5151+
(Import { directive: new, .. }, Import { directive: old, .. }) if {
5152+
!new_binding.span.is_dummy() && !old_binding.span.is_dummy() &&
5153+
(new.has_attributes || old.has_attributes)
5154+
} => {
5155+
if old.has_attributes {
5156+
Some((new, new_binding.span, true))
5157+
} else {
5158+
Some((old, old_binding.span, true))
5159+
}
5160+
},
5161+
// Otherwise prioritize the new binding.
5162+
(Import { directive, .. }, other) if !new_binding.span.is_dummy() =>
5163+
Some((directive, new_binding.span, other.is_import())),
5164+
(other, Import { directive, .. }) if !old_binding.span.is_dummy() =>
5165+
Some((directive, old_binding.span, other.is_import())),
51425166
_ => None,
51435167
};
5144-
if let Some((directive, binding_span)) = directive {
5145-
let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() {
5146-
format!("Other{}", name)
5147-
} else {
5148-
format!("other_{}", name)
5149-
};
51505168

5151-
let mut suggestion = None;
5152-
match directive.subclass {
5153-
ImportDirectiveSubclass::SingleImport { type_ns_only: true, .. } =>
5154-
suggestion = Some(format!("self as {}", suggested_name)),
5155-
ImportDirectiveSubclass::SingleImport { source, .. } => {
5156-
if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0)
5157-
.map(|pos| pos as usize) {
5158-
if let Ok(snippet) = self.session.source_map()
5159-
.span_to_snippet(binding_span) {
5160-
if pos <= snippet.len() {
5161-
suggestion = Some(format!(
5162-
"{} as {}{}",
5163-
&snippet[..pos],
5164-
suggested_name,
5165-
if snippet.ends_with(";") { ";" } else { "" }
5166-
))
5167-
}
5169+
// Check if the target of the use for both bindings is the same.
5170+
let duplicate = new_binding.def().opt_def_id() == old_binding.def().opt_def_id();
5171+
let has_dummy_span = new_binding.span.is_dummy() || old_binding.span.is_dummy();
5172+
let from_item = self.extern_prelude.get(&ident)
5173+
.map(|entry| entry.introduced_by_item)
5174+
.unwrap_or(true);
5175+
// Only suggest removing an import if both bindings are to the same def, if both spans
5176+
// aren't dummy spans. Further, if both bindings are imports, then the ident must have
5177+
// been introduced by a item.
5178+
let should_remove_import = duplicate && !has_dummy_span &&
5179+
((new_binding.is_extern_crate() || old_binding.is_extern_crate()) || from_item);
5180+
5181+
match directive {
5182+
Some((directive, span, true)) if should_remove_import && directive.is_nested() =>
5183+
self.add_suggestion_for_duplicate_nested_use(&mut err, directive, span),
5184+
Some((directive, _, true)) if should_remove_import && !directive.is_glob() => {
5185+
// Simple case - remove the entire import. Due to the above match arm, this can
5186+
// only be a single use so just remove it entirely.
5187+
err.span_suggestion(
5188+
directive.use_span_with_attributes,
5189+
"remove unnecessary import",
5190+
String::new(),
5191+
Applicability::MaybeIncorrect,
5192+
);
5193+
},
5194+
Some((directive, span, _)) =>
5195+
self.add_suggestion_for_rename_of_use(&mut err, name, directive, span),
5196+
_ => {},
5197+
}
5198+
5199+
err.emit();
5200+
self.name_already_seen.insert(name, span);
5201+
}
5202+
5203+
/// This function adds a suggestion to change the binding name of a new import that conflicts
5204+
/// with an existing import.
5205+
///
5206+
/// ```ignore (diagnostic)
5207+
/// help: you can use `as` to change the binding name of the import
5208+
/// |
5209+
/// LL | use foo::bar as other_bar;
5210+
/// | ^^^^^^^^^^^^^^^^^^^^^
5211+
/// ```
5212+
fn add_suggestion_for_rename_of_use(
5213+
&self,
5214+
err: &mut DiagnosticBuilder<'_>,
5215+
name: Symbol,
5216+
directive: &ImportDirective<'_>,
5217+
binding_span: Span,
5218+
) {
5219+
let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() {
5220+
format!("Other{}", name)
5221+
} else {
5222+
format!("other_{}", name)
5223+
};
5224+
5225+
let mut suggestion = None;
5226+
match directive.subclass {
5227+
ImportDirectiveSubclass::SingleImport { type_ns_only: true, .. } =>
5228+
suggestion = Some(format!("self as {}", suggested_name)),
5229+
ImportDirectiveSubclass::SingleImport { source, .. } => {
5230+
if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0)
5231+
.map(|pos| pos as usize) {
5232+
if let Ok(snippet) = self.session.source_map()
5233+
.span_to_snippet(binding_span) {
5234+
if pos <= snippet.len() {
5235+
suggestion = Some(format!(
5236+
"{} as {}{}",
5237+
&snippet[..pos],
5238+
suggested_name,
5239+
if snippet.ends_with(";") { ";" } else { "" }
5240+
))
51685241
}
51695242
}
51705243
}
5171-
ImportDirectiveSubclass::ExternCrate { source, target, .. } =>
5172-
suggestion = Some(format!(
5173-
"extern crate {} as {};",
5174-
source.unwrap_or(target.name),
5175-
suggested_name,
5176-
)),
5177-
_ => unreachable!(),
51785244
}
5245+
ImportDirectiveSubclass::ExternCrate { source, target, .. } =>
5246+
suggestion = Some(format!(
5247+
"extern crate {} as {};",
5248+
source.unwrap_or(target.name),
5249+
suggested_name,
5250+
)),
5251+
_ => unreachable!(),
5252+
}
5253+
5254+
let rename_msg = "you can use `as` to change the binding name of the import";
5255+
if let Some(suggestion) = suggestion {
5256+
err.span_suggestion(
5257+
binding_span,
5258+
rename_msg,
5259+
suggestion,
5260+
Applicability::MaybeIncorrect,
5261+
);
5262+
} else {
5263+
err.span_label(binding_span, rename_msg);
5264+
}
5265+
}
51795266

5180-
let rename_msg = "you can use `as` to change the binding name of the import";
5181-
if let Some(suggestion) = suggestion {
5182-
err.span_suggestion(
5183-
binding_span,
5184-
rename_msg,
5185-
suggestion,
5186-
Applicability::MaybeIncorrect,
5187-
);
5188-
} else {
5189-
err.span_label(binding_span, rename_msg);
5267+
/// This function adds a suggestion to remove a unnecessary binding from an import that is
5268+
/// nested. In the following example, this function will be invoked to remove the `a` binding
5269+
/// in the second use statement:
5270+
///
5271+
/// ```ignore (diagnostic)
5272+
/// use issue_52891::a;
5273+
/// use issue_52891::{d, a, e};
5274+
/// ```
5275+
///
5276+
/// The following suggestion will be added:
5277+
///
5278+
/// ```ignore (diagnostic)
5279+
/// use issue_52891::{d, a, e};
5280+
/// ^-- help: remove unnecessary import
5281+
/// ```
5282+
///
5283+
/// If the nested use contains only one import then the suggestion will remove the entire
5284+
/// line.
5285+
///
5286+
/// It is expected that the directive provided is a nested import - this isn't checked by the
5287+
/// function. If this invariant is not upheld, this function's behaviour will be unexpected
5288+
/// as characters expected by span manipulations won't be present.
5289+
fn add_suggestion_for_duplicate_nested_use(
5290+
&self,
5291+
err: &mut DiagnosticBuilder<'_>,
5292+
directive: &ImportDirective<'_>,
5293+
binding_span: Span,
5294+
) {
5295+
assert!(directive.is_nested());
5296+
let message = "remove unnecessary import";
5297+
let source_map = self.session.source_map();
5298+
5299+
// Two examples will be used to illustrate the span manipulations we're doing:
5300+
//
5301+
// - Given `use issue_52891::{d, a, e};` where `a` is a duplicate then `binding_span` is
5302+
// `a` and `directive.use_span` is `issue_52891::{d, a, e};`.
5303+
// - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is
5304+
// `a` and `directive.use_span` is `issue_52891::{d, e, a};`.
5305+
5306+
// Find the span of everything after the binding.
5307+
// ie. `a, e};` or `a};`
5308+
let binding_until_end = binding_span.with_hi(directive.use_span.hi());
5309+
5310+
// Find everything after the binding but not including the binding.
5311+
// ie. `, e};` or `};`
5312+
let after_binding_until_end = binding_until_end.with_lo(binding_span.hi());
5313+
5314+
// Keep characters in the span until we encounter something that isn't a comma or
5315+
// whitespace.
5316+
// ie. `, ` or ``.
5317+
//
5318+
// Also note whether a closing brace character was encountered. If there
5319+
// was, then later go backwards to remove any trailing commas that are left.
5320+
let mut found_closing_brace = false;
5321+
let after_binding_until_next_binding = source_map.span_take_while(
5322+
after_binding_until_end,
5323+
|&ch| {
5324+
if ch == '}' { found_closing_brace = true; }
5325+
ch == ' ' || ch == ','
5326+
}
5327+
);
5328+
5329+
// Combine the two spans.
5330+
// ie. `a, ` or `a`.
5331+
//
5332+
// Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };`
5333+
let span = binding_span.with_hi(after_binding_until_next_binding.hi());
5334+
5335+
// If there was a closing brace then identify the span to remove any trailing commas from
5336+
// previous imports.
5337+
if found_closing_brace {
5338+
if let Ok(prev_source) = source_map.span_to_prev_source(span) {
5339+
// `prev_source` will contain all of the source that came before the span.
5340+
// Then split based on a command and take the first (ie. closest to our span)
5341+
// snippet. In the example, this is a space.
5342+
let prev_comma = prev_source.rsplit(',').collect::<Vec<_>>();
5343+
let prev_starting_brace = prev_source.rsplit('{').collect::<Vec<_>>();
5344+
if prev_comma.len() > 1 && prev_starting_brace.len() > 1 {
5345+
let prev_comma = prev_comma.first().unwrap();
5346+
let prev_starting_brace = prev_starting_brace.first().unwrap();
5347+
5348+
// If the amount of source code before the comma is greater than
5349+
// the amount of source code before the starting brace then we've only
5350+
// got one item in the nested item (eg. `issue_52891::{self}`).
5351+
if prev_comma.len() > prev_starting_brace.len() {
5352+
// So just remove the entire line...
5353+
err.span_suggestion(
5354+
directive.use_span_with_attributes,
5355+
message,
5356+
String::new(),
5357+
Applicability::MaybeIncorrect,
5358+
);
5359+
return;
5360+
}
5361+
5362+
let span = span.with_lo(BytePos(
5363+
// Take away the number of bytes for the characters we've found and an
5364+
// extra for the comma.
5365+
span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1
5366+
));
5367+
err.span_suggestion(
5368+
span, message, String::new(), Applicability::MaybeIncorrect,
5369+
);
5370+
return;
5371+
}
51905372
}
51915373
}
51925374

5193-
err.emit();
5194-
self.name_already_seen.insert(name, span);
5375+
err.span_suggestion(span, message, String::new(), Applicability::MachineApplicable);
51955376
}
51965377

51975378
fn extern_prelude_get(&mut self, ident: Ident, speculative: bool)

0 commit comments

Comments
 (0)