@@ -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