@@ -63,7 +63,7 @@ use syntax::ast::{Label, Local, Mutability, Pat, PatKind, Path};
6363use syntax:: ast:: { QSelf , TraitItemKind , TraitRef , Ty , TyKind } ;
6464use syntax:: ptr:: P ;
6565
66- use syntax_pos:: { Span , DUMMY_SP , MultiSpan } ;
66+ use syntax_pos:: { BytePos , Span , DUMMY_SP , MultiSpan } ;
6767use errors:: { Applicability , DiagnosticBuilder , DiagnosticId } ;
6868
6969use 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+
12311241struct PrivacyError < ' a > ( Span , Ident , & ' a NameBinding < ' a > ) ;
12321242
12331243struct 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