@@ -63,7 +63,7 @@ use syntax::ast::{Label, Local, Mutability, Pat, PatKind, Path};
63
63
use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
64
64
use syntax::ptr::P;
65
65
66
- use syntax_pos::{Span, DUMMY_SP, MultiSpan};
66
+ use syntax_pos::{BytePos, Span, DUMMY_SP, MultiSpan};
67
67
use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
68
68
69
69
use std::cell::{Cell, RefCell};
@@ -1228,6 +1228,16 @@ enum NameBindingKind<'a> {
1228
1228
},
1229
1229
}
1230
1230
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
+
1231
1241
struct PrivacyError<'a>(Span, Ident, &'a NameBinding<'a>);
1232
1242
1233
1243
struct UseError<'a> {
@@ -5134,64 +5144,235 @@ impl<'a> Resolver<'a> {
5134
5144
);
5135
5145
5136
5146
// See https://github.com/rust-lang/rust/issues/32354
5147
+ use NameBindingKind::Import;
5137
5148
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())),
5142
5166
_ => None,
5143
5167
};
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
- };
5150
5168
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
+ ))
5168
5241
}
5169
5242
}
5170
5243
}
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!(),
5178
5244
}
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
+ }
5179
5266
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
+ }
5190
5372
}
5191
5373
}
5192
5374
5193
- err.emit();
5194
- self.name_already_seen.insert(name, span);
5375
+ err.span_suggestion(span, message, String::new(), Applicability::MachineApplicable);
5195
5376
}
5196
5377
5197
5378
fn extern_prelude_get(&mut self, ident: Ident, speculative: bool)
0 commit comments